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サーバーを構築できるようになります。

