GoのHTTPサーバーをゼロから理解する:net/httpパッケージの完全ガイド

GoのHTTPサーバーをゼロから理解する:net/httpパッケージの完全ガイド | mohablog

Goはシンプルで高速なWebサーバーを構築するために設計された言語です。標準ライブラリのnet/httpパッケージを使えば、複雑なフレームワークに依存することなく、わずか数行のコードで本格的なHTTPサーバーを立ち上げることができます。この記事では、Goでのサーバー開発の基本から応用まで、段階的に学んでいきましょう。

目次

Goでサーバーを選ぶ理由

Goがサーバーサイド開発に適している理由は、その言語設計にあります。以下の特徴が挙げられます:

  • 軽量で高速:コンパイル型言語で実行速度が速く、メモリ効率も優れている
  • 並行処理が得意:ゴルーチンにより数千の同時接続を効率的に処理
  • 標準ライブラリが充実:net/httpだけで本格的なサーバーが構築可能
  • デプロイが簡単:単一のバイナリファイルとしてコンパイルされる

PythonのFlaskやNode.jsのExpressも優秀ですが、Goは性能と開発効率のバランスに優れています。

最小限のHTTPサーバーを構築する

まず、Goでの最もシンプルなサーバー実装から始めましょう。

package main

import (
    "fmt"
    "net/http"
)

func helloHandler(w http.ResponseWriter, r *http.Request) {
    fmt.Fprintf(w, "Hello, World!")
}

func main() {
    http.HandleFunc("/", helloHandler)
    http.ListenAndServe(":8080", nil)
}

このコードの流れを説明します:

  • helloHandler:クライアントからのリクエストに対応するハンドラー関数
  • http.HandleFunc():指定したパスとハンドラーを紐付ける
  • http.ListenAndServe():8080番ポートでサーバーを起動

実行するには go run main.go を実行し、ブラウザで http://localhost:8080 にアクセスすれば「Hello, World!」が表示されます。

複数のエンドポイントを処理する

実践的なサーバーでは、複数のパスに対応する必要があります。以下の例を見てください:

package main

import (
    "fmt"
    "net/http"
)

func homeHandler(w http.ResponseWriter, r *http.Request) {
    fmt.Fprintf(w, "ホームページです")
}

func aboutHandler(w http.ResponseWriter, r *http.Request) {
    fmt.Fprintf(w, "このサイトについて")
}

func apiHandler(w http.ResponseWriter, r *http.Request) {
    w.Header().Set("Content-Type", "application/json")
    fmt.Fprintf(w, `{"message": "API response"}`)
}

func main() {
    http.HandleFunc("/", homeHandler)
    http.HandleFunc("/about", aboutHandler)
    http.HandleFunc("/api/data", apiHandler)
    http.ListenAndServe(":8080", nil)
}

複数のエンドポイントを登録することで、それぞれのパスに異なるレスポンスを返せます。またJSONレスポンスの場合は、Content-Typeヘッダーを適切に設定することが重要です。

HTTPメソッドの判定

RESTful APIを構築する際は、GET、POST、PUT、DELETEなどのメソッドを区別する必要があります。

package main

import (
    "fmt"
    "net/http"
)

func dataHandler(w http.ResponseWriter, r *http.Request) {
    switch r.Method {
    case "GET":
        fmt.Fprintf(w, "データを取得しました")
    case "POST":
        fmt.Fprintf(w, "データを保存しました")
    case "PUT":
        fmt.Fprintf(w, "データを更新しました")
    case "DELETE":
        fmt.Fprintf(w, "データを削除しました")
    default:
        http.Error(w, "メソッドが許可されていません", http.StatusMethodNotAllowed)
    }
}

func main() {
    http.HandleFunc("/data", dataHandler)
    http.ListenAndServe(":8080", nil)
}

リクエストのr.MethodプロパティでHTTPメソッドを判定し、適切なレスポンスを返します。また無効なメソッドに対してはhttp.StatusMethodNotAllowed(405エラー)を返すのがベストプラクティスです。

クエリパラメータとボディの処理

実践的なサーバーでは、クライアントから送られてくるデータを処理する必要があります。

package main

import (
    "fmt"
    "net/http"
)

func searchHandler(w http.ResponseWriter, r *http.Request) {
    // クエリパラメータを取得
    query := r.URL.Query().Get("q")
    if query == "" {
        http.Error(w, "クエリパラメータが必要です", http.StatusBadRequest)
        return
    }
    fmt.Fprintf(w, "検索キーワード: %s", query)
}

func formHandler(w http.ResponseWriter, r *http.Request) {
    // フォームデータを解析
    r.ParseForm()
    name := r.FormValue("name")
    email := r.FormValue("email")
    fmt.Fprintf(w, "名前: %s, メール: %s", name, email)
}

func main() {
    http.HandleFunc("/search", searchHandler)
    http.HandleFunc("/form", formHandler)
    http.ListenAndServe(":8080", nil)
}

r.URL.Query().Get()でクエリパラメータを、r.FormValue()でPOSTフォームデータを取得できます。常にバリデーションを行い、必要なデータが存在するか確認しましょう。

静的ファイルの配信

HTMLやCSSなどの静的ファイルをサーブする場合は、http.FileServer()を使用します。

package main

import (
    "net/http"
)

func main() {
    // 静的ファイルの配信
    http.Handle("/public/", http.StripPrefix("/public/", http.FileServer(http.Dir("./public"))))
    
    // 動的コンテンツ
    http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
        w.Header().Set("Content-Type", "text/html; charset=utf-8")
        http.ServeFile(w, r, "./index.html")
    })
    
    http.ListenAndServe(":8080", nil)
}

http.StripPrefix()を使うことで、URLパスから接頭辞を削除し、正しいファイルパスにマッピングできます。これにより /public/style.css./public/style.css から読み込まれます。

エラーハンドリングとステータスコード

適切なHTTPステータスコードを返すことは、APIの信頼性に関わります。

package main

import (
    "encoding/json"
    "net/http"
)

type User struct {
    ID   int    `json:"id"`
    Name string `json:"name"`
}

func getUserHandler(w http.ResponseWriter, r *http.Request) {
    w.Header().Set("Content-Type", "application/json")
    
    // 例: IDが見つからない場合
    id := r.URL.Query().Get("id")
    if id == "" {
        w.WriteHeader(http.StatusBadRequest)
        json.NewEncoder(w).Encode(map[string]string{"error": "IDが必要です"})
        return
    }
    
    // 成功時
    w.WriteHeader(http.StatusOK)
    user := User{ID: 1, Name: "Taro"}
    json.NewEncoder(w).Encode(user)
}

func main() {
    http.HandleFunc("/user", getUserHandler)
    http.ListenAndServe(":8080", nil)
}

w.WriteHeader()でステータスコードを明示的に設定できます。一般的なコードは以下の通りです:

  • 200 OK:成功
  • 400 Bad Request:不正なリクエスト
  • 404 Not Found:リソースが見つからない
  • 500 Internal Server Error:サーバーエラー

ロギングとデバッグ

本番環境ではリクエストログを記録することが重要です。以下はシンプルなロギング機能の例です:

package main

import (
    "fmt"
    "log"
    "net/http"
    "time"
)

func loggingMiddleware(next http.HandlerFunc) http.HandlerFunc {
    return func(w http.ResponseWriter, r *http.Request) {
        start := time.Now()
        next(w, r)
        elapsed := time.Since(start)
        log.Printf("%s %s %s (%.3fs)", r.Method, r.URL.Path, r.RemoteAddr, elapsed.Seconds())
    }
}

func helloHandler(w http.ResponseWriter, r *http.Request) {
    fmt.Fprintf(w, "Hello!")
}

func main() {
    http.HandleFunc("/", loggingMiddleware(helloHandler))
    log.Println("サーバーが起動しました: http://localhost:8080")
    http.ListenAndServe(":8080", nil)
}

このアプローチでは、ミドルウェアを使用してすべてのリクエストをログに記録し、実行時間も測定しています。デバッグや性能改善に非常に役立ちます。

まとめ

GoのHTTPサーバー開発について、ゼロから実装できるレベルまで学習しました。以下が要点です:

  • net/httpパッケージを使えば、複雑なフレームワークなしでサーバーを構築可能
  • http.HandleFunc()でエンドポイントを定義し、複数のパスに対応できる
  • HTTPメソッドの判定でRESTful APIを実装
  • クエリパラメータとフォームデータの処理はGoの標準機能で十分
  • 静的ファイルの配信はhttp.FileServer()を活用
  • 適切なステータスコードを返し、エラーハンドリングを意識する
  • ロギング機能でデバッグと性能監視を実現

Goでのサーバー開発は習得が容易でありながら、スケーラビリティにも優れています。これらの基本を習得すれば、本番レベルのWebサーバーを構築できるようになります。

よかったらシェアしてね!
  • URLをコピーしました!
  • URLをコピーしました!
目次