100日チャレンジ day50 (自作プロキシサーバとMITMによるキャッシュ)

昨日
100日チャレンジに感化されたので、アレンジして自分でもやってみます。
やりたいこと
- 世の中のさまざまなドメインの簡易実装をつくり、バックエンドの実装に慣れる(dbスキーマ設計や、関数の分割、使いやすいインターフェイスの切り方に慣れる
- 設計力(これはシステムのオーバービューを先に自分で作ってaiに依頼できるようにする
- 生成aiをつかったバイブコーティングになれる
- 実際にやったことはzennのスクラップにまとめ、成果はzennのブログにまとめる(アプリ自体の公開は必須ではないかコードはgithubにおく)
できたもの

プロキシ を作る

Go言語での高機能HTTP/HTTPSフォワードプロキシ開発、素晴らしいですね!Goはネットワークプログラミングに適しており、パフォーマンスも期待できるため、非常に良い選択だと思います。
Day50のプロジェクトとして、以下の仕様で進めるのはいかがでしょうか。
Day50 プロジェクト: 高機能 HTTP/HTTPS フォワードプロキシ (Go言語)
目標: 基本的なフォワードプロキシ機能に加え、1つまたは複数の高度な機能を備えたプロキシサーバーをGo言語で実装する。
基本機能:
-
HTTPリクエスト転送:
- クライアントからのHTTPリクエストを受け取り、指定されたターゲットサーバーに転送する。
- ターゲットサーバーからのレスポンスをクライアントに返す。
-
HTTPS (CONNECTメソッド) 対応:
- クライアントからの
CONNECT
リクエストを受け取り、ターゲットサーバーとの間にTCPトンネルを確立する。 - その後はクライアントとターゲットサーバー間の暗号化された通信を透過的に中継する。
- クライアントからの
中核となる挑戦的機能 (以下から1つ以上選択、または組み合わせ):
-
A. 動的なリクエスト/レスポンス改変:
-
A-1. ヘッダー操作: 設定ファイルやAPI経由で指定されたルールに基づき、リクエスト/レスポンスのHTTPヘッダーを追加・削除・変更する。
- 例: 特定のドメインへのリクエストに
X-Forwarded-For
ヘッダーを付与する。特定のレスポンスからServer
ヘッダーを削除する。
- 例: 特定のドメインへのリクエストに
-
A-2. ボディ書き換え (HTTPのみ): (HTTPSのMITMは今回はスコープ外とします)
- HTTPレスポンスボディの内容をリアルタイムで書き換える。設定ファイルで正規表現と置換文字列を指定できるようにする。
- 例: HTML内の特定の文字列を別の文字列に置換する。
-
A-1. ヘッダー操作: 設定ファイルやAPI経由で指定されたルールに基づき、リクエスト/レスポンスのHTTPヘッダーを追加・削除・変更する。
-
B. インテリジェント・キャッシング:
- HTTPレスポンスをSQLiteにキャッシュする。
-
Cache-Control
,Expires
,ETag
,Last-Modified
ヘッダーを考慮してキャッシュの鮮度を管理する。 - キャッシュヒット時には効率的にレスポンスを返す。
- SQLiteのテーブル設計とクエリを工夫する。
-
C. 高度なアクセス制御とフィルタリング:
- 設定ファイル (例: YAMLやJSON) で定義されたルールに基づいてアクセスを制御する。
- ルールは送信元IP、リクエスト先ドメイン/IP、ポート、HTTPメソッド、User-Agentなどで指定可能にする。
- 許可/拒否のポリシーを設定できるようにする。
-
D. 詳細なトラフィックロギング:
- 通過するHTTP/HTTPSリクエストのメタデータ (タイムスタンプ, 送信元IP, リクエストメソッド, URL, ステータスコード, User-Agent, レスポンスサイズなど) をSQLiteに構造化して記録する。
- (オプション) ログを閲覧・検索するためのシンプルなWeb UIをGoの
net/http
パッケージで提供する。
技術スタック:
- 言語: Go
-
主要パッケージ:
-
net/http
(HTTPサーバー、クライアント、プロキシ機能の中核) -
net
(TCP接続処理) -
database/sql
,github.com/mattn/go-sqlite3
(SQLiteへのアクセス) -
encoding/json
orgopkg.in/yaml.v3
(設定ファイルのパース) - (UIを作る場合)
html/template
-
- 設定管理: JSONまたはYAMLファイル
-
データベース: SQLite (
proxy_data.db
のようなファイル名)
ディレクトリ構成案 (ワークスペースルートからの相対):
/
├── day50_go_proxy/
│ ├── main.go # プロキシサーバーのエントリーポイント
│ ├── proxy/ # プロキシのコアロジック
│ │ ├── http_handler.go # HTTPリクエスト処理
│ │ ├── https_handler.go # HTTPS (CONNECT) 処理
│ │ ├── modifier.go # (もしAを実装する場合) リクエスト/レスポンス改変
│ │ ├── cache.go # (もしBを実装する場合) キャッシュロジック
│ │ ├── acl.go # (もしCを実装する場合) アクセス制御ロジック
│ │ └── logger.go # (もしDを実装する場合) ロギングロジック
│ ├── config/
│ │ ├── config.go # 設定ファイルの読み込み・管理
│ │ └── config.example.yml # 設定ファイル例
│ ├── db/
│ │ ├── schema.sql # (もしBまたはDを実装する場合) DBスキーマ
│ │ └── database.go # (もしBまたはDを実装する場合) DB初期化・操作
│ ├── ui/ # (もしWeb UIを実装する場合)
│ │ ├── static/ # CSS, JSファイル
│ │ └── templates/ # HTMLテンプレート
│ ├── go.mod
│ ├── go.sum
│ └── README.md
├── template/
│ └── ... (既存のtemplateディレクトリ内容)
└── ... (他のdayXXディレクトリ)
テスト方法:
- プロキシサーバーを指定のポート (例:
localhost:8080
) で起動する。 - ターミナルから
curl
コマンドでプロキシ経由のアクセスを試す。- HTTP:
curl -x http://localhost:8080 http://example.com
- HTTPS:
curl -x http://localhost:8080 -p https://google.com
- HTTP:
- 選択した挑戦的機能が正しく動作することを確認する。
- ヘッダー改変なら、
curl -v
でヘッダーを確認。 - キャッシュなら、2回目以降のアクセスでレスポンスが速いか、またはログでキャッシュヒットを確認。
- アクセス制御なら、拒否されるべきリクエストが拒否されるか確認。
- ロギングなら、SQLiteのDBファイルに記録が残っているか確認。
- ヘッダー改変なら、
実装ステップ案:
-
プロジェクト初期化:
-
day50_go_proxy
ディレクトリ作成 (templateからのコピーは不要、Goプロジェクトとして新規作成)。 -
go mod init github.com/lirlia/100day_challenge_backend/day50_go_proxy
を実行。 -
README.md
にプロジェクト概要を記述。 - 基本的なHTTPフォワードプロキシ機能を実装 (
http_handler.go
)。 -
curl -x http://localhost:8080 http://example.com
で動作確認。
-
-
HTTPS (CONNECT) 対応:
-
CONNECT
メソッドを処理し、TCPトンネルを確立する機能を実装 (https_handler.go
)。 -
curl -x http://localhost:8080 -p https://google.com
で動作確認。
-
-
挑戦的機能の実装 (1つ目):
- 上記 A, B, C, D から1つ選択し、設計・実装を行う。
- 関連する設定ファイル (
config.go
,config.example.yml
) やDB周り (db/
) も整備する。 - 単体テストや
curl
で機能確認。
-
(オプション) 挑戦的機能の実装 (2つ目以降):
- さらに機能を追加したい場合は、同様に実装とテストを行う。
-
(オプション) Web UI 実装:
- ロギング機能Dを実装した場合など、必要に応じてシンプルなWeb UIを実装する。
-
最終テストとドキュメント整備:
- 全体の動作確認。
-
README.md
に使い方、設定方法、ビルド方法などを追記。 -
PROGRESS.md
の作成 (手動で作成し、進捗を記述)。
デザインについて:
今回はGo言語でのバックエンド実装がメインなので、UIはもし作成するとしても非常にシンプルなもので良いでしょう。Tailwind CSS 等は必須ではありません。
まず、どの挑戦的機能に最も興味がありますか?
それによって、最初の詳細設計や実装の優先順位が変わってきます。
例えば、「D. 詳細なトラフィックロギング」と「C. 高度なアクセス制御」を組み合わせ、管理画面でログを見ながらフィルタリングルールを調整できる、といった形も面白いかもしれません。
ご意見をお聞かせください!了解しました。Go言語で高機能HTTP/HTTPSフォワードプロキシを開発しましょう。
まずはプロジェクトの初期セットアップと、基本的なHTTP/HTTPSフォワードプロキシ機能の実装から始めます。
作業計画:
-
プロジェクト初期化:
-
day50_go_proxy
ディレクトリを作成します。 -
go mod init github.com/lirlia/100day_challenge_backend/day50_go_proxy
を実行します。 -
README.md
とPROGRESS.md
を作成します。 -
.gitignore
を作成します。
-
-
設定ファイルの準備:
- プロキシサーバーの待ち受けポートなどを設定ファイル (
config/config.yml
) で管理できるようにします。 - 設定読み込み用のGoの構造体と関数 (
config/config.go
) を作成します。
- プロキシサーバーの待ち受けポートなどを設定ファイル (
-
基本的なHTTPフォワードプロキシ機能の実装:
-
main.go
にHTTPサーバーを起動する処理を記述します。 -
proxy/http_handler.go
にHTTPリクエストをターゲットサーバーに転送するロジックを実装します。
-
-
HTTPS (CONNECTメソッド) 対応:
-
proxy/https_handler.go
にCONNECT
メソッドを処理し、TCPトンネルを確立するロジックを実装します。
-
-
テスト:
curl -x http://localhost:[設定ポート] http://example.com
-
curl -x http://localhost:[設定ポート] -p https://google.com
で動作確認を行います。

HTTPSもプロキシしてキャッシュしてほしいので MITM でいい感じにやる

学び
- http はシンプルにリクエストを別に作ってその結果をコピーしてるだけ
- https は
http.Hijacker
ってのを使って CONNECT 時の tcp connection を取得し、以後はそれを使い回してプロキシを行う
ネスペや旧セキスペで学んだり、実際にHTTPS Proxyを使っていたけど実際に実装してみるとこうなってるのか〜という驚きがあった。