ホーム › HTTP サーバー自作 › H-01: 最小 HTTP サーバー(GET のみ)
H-01: 最小 HTTP サーバー(GET のみ)
Java 標準ライブラリの ServerSocket と Socket だけを使って、HTTP サーバーを0から実装します。 リクエスト行のパース・パスによるルーティング・レスポンスの組み立てという HTTP の基本構造を 手を動かして学べます。
説明・ユースケース
HTTP サーバーは、クライアント(ブラウザや curl)からのリクエストを受け取り、 適切なレスポンスを返すプログラムです。通常は Spring Boot や Tomcat などのフレームワークを使いますが、 このサンプルでは Socket 通信だけで実装することで、HTTP プロトコルの仕組みを深く理解できます。
このサンプルで学べること
- HTTP/1.1 のリクエスト行(GET /path HTTP/1.1)の構造と解析方法
- レスポンスヘッダー(Content-Type・Content-Length)の組み立て方
- パスによるルーティング(/ → トップ、/hello → テキスト、それ以外 → 404)
- ExecutorService(Java 8/17)と Virtual Thread(Java 21)を使ったマルチスレッド処理
| クラス / メソッド | 役割 |
|---|---|
| ServerSocket(port) | 指定ポートで接続待ち受けを開始する |
| serverSocket.accept() | クライアントからの接続を待ち、Socket を返す |
| socket.getInputStream() | クライアントが送ったデータ(リクエスト)を受け取る |
| socket.getOutputStream() | クライアントへデータ(レスポンス)を送る |
| ExecutorService | スレッドプールでリクエストを並行処理する(Java 8/17) |
| Thread.ofVirtual() | Virtual Thread でリクエストごとに軽量スレッドを生成(Java 21) |
サンプルコード
Java 8 版では ExecutorService のスレッドプールでマルチスレッド処理を行います。 Java 17 版では var による変数宣言の簡潔化と テキストブロックによる HTML の可読性向上が追加されます。 Java 21 版では Thread.ofVirtual() による Virtual Thread と switch 式によるルーティングの簡潔化を使います。
よくあるミス・注意点
ヘッダーと空行を正しく読み飛ばさないとボディが読めない
HTTP リクエストは「リクエスト行 → ヘッダー行 → 空行(CRLF のみ)→ ボディ」という順序で構成されています。 ヘッダーを読み飛ばさずにボディを読もうとすると、ヘッダーの内容がボディとして混入します。 空行(line.isEmpty())が来るまでループするのが正しい実装です。
Content-Length は必ず送る
レスポンスに Content-Length を含めないと、 一部のブラウザや HTTP クライアントがレスポンスの終端を判断できず、ハングアップすることがあります。 ボディのバイト数(文字列長ではなく UTF-8 でエンコードした後のバイト数)を正確に計算して送信しましょう。
Java バージョンごとの違い
Java 8 ではスレッドプール(ExecutorService)でリクエストごとにスレッドを割り当てます。 スレッド数の上限(newFixedThreadPool(10))を超えると 待ち行列が発生します。Java 21 の Virtual Thread(仮想スレッド)はこの制約を大幅に緩和し、 OS スレッドを使わずに何万もの同時接続を効率的に処理できます。
テストする観点
- GET / へのリクエストで 200 OK と HTML ボディが返るか
- GET /hello へのリクエストで 200 OK と「Hello, World!」が返るか
- 存在しないパス(/unknown)に GET したとき 404 Not Found が返るか
- POST メソッドを送ったとき 405 Method Not Allowed が返るか
- レスポンスの Content-Length がボディのバイト数と一致するか
- 日本語を含む HTML ボディを返したとき文字化けしないか(UTF-8 エンコード)
- parseRequestLine にnullや空文字列を渡したときクラッシュせずデフォルト値を返すか