Go の net/http でシンプルな HTTP サーバーを立てる

Go の標準ライブラリ net/http を使えば、外部パッケージなしで HTTP サーバーを立ち上げられます。フレームワークに頼る前に、まずは標準ライブラリだけでサーバーを動かしてみましょう。

最小構成のサーバー

もっとも短いコードは以下のようになります。

package main

import (
	"fmt"
	"net/http"
)

func main() {
	http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
		fmt.Fprintln(w, "Hello, World!")
	})

	http.ListenAndServe(":8080", nil)
}

http.HandleFunc でパスとハンドラ関数を登録し、http.ListenAndServe でサーバーを起動しています。第 2 引数の nil は、デフォルトのマルチプレクサ(http.DefaultServeMux)を使うという意味です。

このコードを main.go として保存し、go run main.go を実行すると、http://localhost:8080 にアクセスできるようになります。ブラウザで開けば「Hello, World!」と表示されるはずです。

複数のルーティングを追加する

実用的なサーバーでは、パスに応じて異なるレスポンスを返す必要があります。HandleFunc を複数回呼べば、ルーティングを増やせます。

package main

import (
	"fmt"
	"net/http"
)

func main() {
	http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
		fmt.Fprintln(w, "トップページ")
	})

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

	http.HandleFunc("/health", func(w http.ResponseWriter, r *http.Request) {
		w.WriteHeader(http.StatusOK)
		fmt.Fprintln(w, "OK")
	})

	fmt.Println("サーバー起動: http://localhost:8080")
	http.ListenAndServe(":8080", nil)
}

/health エンドポイントのように、w.WriteHeader でステータスコードを明示的に設定することも可能です。何も設定しなければ 200 OK がデフォルトで返ります。

ただし注意点があります。Go の DefaultServeMux はパスマッチングが単純で、/ は全パスにマッチしてしまいます。/about/health に一致しないリクエストはすべて / のハンドラに流れる仕組みです。

リクエストメソッドで処理を分ける

REST API を作るなら、GET や POST などのメソッドごとに処理を分岐させたいところです。net/http ではメソッドのルーティングが組み込まれていないため、ハンドラ内で自分で分岐させます。

http.HandleFunc("/users", func(w http.ResponseWriter, r *http.Request) {
	switch r.Method {
	case http.MethodGet:
		fmt.Fprintln(w, "ユーザー一覧を取得")
	case http.MethodPost:
		fmt.Fprintln(w, "ユーザーを新規作成")
	default:
		http.Error(w, "Method Not Allowed", http.StatusMethodNotAllowed)
	}
})

r.Method でリクエストメソッドを取得し、switch で分岐するのが定番パターンになります。http.Error を使えば、エラーメッセージとステータスコードをまとめて返せます。

net/http 標準

メソッドの分岐はハンドラ内で手動。自由度は高いが、ルーティングが増えると冗長になりやすい。

chi や gin などのフレームワーク

r.Get("/users", handler) のようにメソッドごとにルートを定義できる。大規模 API には向いている。

小規模なツールやプロトタイプであれば標準ライブラリで十分ですが、エンドポイントが 10 を超えてくるとフレームワークの導入を検討したほうがよいでしょう。

ListenAndServe のエラーハンドリング

http.ListenAndServe はエラーを返す関数です。本番コードではエラーを無視せず、適切にハンドリングしましょう。

func main() {
	http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
		fmt.Fprintln(w, "Hello!")
	})

	fmt.Println("サーバー起動: http://localhost:8080")
	if err := http.ListenAndServe(":8080", nil); err != nil {
		fmt.Println("サーバーエラー:", err)
	}
}

よくあるのは「ポートがすでに使われている」というエラーです。別のプロセスが同じポートを占有していると起動に失敗します。その場合は lsof -i :8080 でプロセスを確認するか、ポート番号を変えて試してみてください。

log.Fatal を使うと、エラーメッセージを出力してプログラムを終了してくれます。サーバーの起動失敗は致命的なので、log.Fatal を使うのが一般的です。

log.Fatal(http.ListenAndServe(":8080", nil))

標準ライブラリだけでもこれだけのことができます。まずはこの最小構成を動かしてみて、Go の HTTP サーバーの基本的な流れをつかんでみてください。