Go言語の標準パッケージ net/http を使って、Webサーバを立ててみよう!
WebアプリケーションやAPIを開発する上で、HTTPサーバの構築は基本的なステップです。Go言語には標準で強力な net/http パッケージが用意されており、驚くほど少ないコードでHTTPサーバを立ち上げることができます。
このステップでは、net/http パッケージの基本的な使い方を学び、簡単なHTTPサーバを構築する方法を解説します。
最もシンプルなHTTPサーバ
まずは、定番の “Hello, World!” をブラウザに表示するだけのシンプルなHTTPサーバを作成してみましょう。
package main
import (
"fmt"
"net/http"
)
// handler はリクエストを受け取り、レスポンスを書き込む関数です。
func handler(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, "Hello, World! ") // レスポンスに文字列を書き込む
}
func main() {
// ルートパス ("/") へのリクエストを handler 関数で処理するように登録します。
http.HandleFunc("/", handler)
// 8080番ポートでHTTPサーバを開始します。
// 第2引数に nil を指定すると、デフォルトのServeMux (ルーター) が使用されます。
fmt.Println("Server starting on port 8080...")
err := http.ListenAndServe(":8080", nil)
if err != nil {
// サーバ起動時にエラーが発生した場合 (例: ポートが既に使用されている)
fmt.Println("Error starting server:", err)
}
}
このコードを main.go として保存し、ターミナルで以下のコマンドを実行します。
go run main.goターミナルに “Server starting on port 8080…” と表示されたら、Webブラウザを開き、http://localhost:8080 にアクセスしてください。”Hello, World! ” と表示されれば成功です!
net/httpパッケージをインポートします。- リクエストを処理する関数(ハンドラ関数)を定義します。この関数は
http.ResponseWriterと*http.Requestを引数に取ります。 http.HandleFuncで、特定のパス(ここでは “/”)とハンドラ関数を結びつけます。http.ListenAndServeで、指定したポート(ここでは “:8080″)でサーバを起動します。
ハンドラ関数を理解する
先ほどの例で登場した handler 関数についてもう少し詳しく見ていきましょう。
func handler(w http.ResponseWriter, r *http.Request) {
// ...処理...
}この関数は2つの重要な引数を受け取ります。
-
w http.ResponseWriter: これは、クライアントに返すHTTPレスポンスを構築するためのインターフェースです。ヘッダーを設定したり、ステータスコードを指定したり、レスポンスボディにデータを書き込んだりするために使用します。前の例ではfmt.Fprintf(w, ...)を使ってボディに書き込みました。 -
r *http.Request: これは、クライアントからのHTTPリクエストを表す構造体へのポインタです。リクエストメソッド(GET, POSTなど)、URL、ヘッダー、クエリパラメータ、リクエストボディなどの情報が含まれています。
http.ResponseWriter はインターフェースであるため、レスポンスの書き込み方をカスタマイズすることも可能です(例えば、ログ記録や圧縮など)。一方、http.Request は構造体で、受け取ったリクエストの情報が格納されています。
ルーティング: パスごとに処理を分ける
実際のWebアプリケーションでは、アクセスされたURLのパスに応じて異なる処理を行いたい場合がほとんどです。http.HandleFunc を複数回呼び出すことで、簡単にパスごとのルーティング(処理の振り分け)を設定できます。
package main
import (
"fmt"
"net/http"
"time"
)
func rootHandler(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, "Welcome to the Root Page!")
}
func timeHandler(w http.ResponseWriter, r *http.Request) {
currentTime := time.Now().Format(time.RFC1123)
fmt.Fprintf(w, "Current time is: %s", currentTime)
}
func main() {
// パス "/" へのリクエストは rootHandler へ
http.HandleFunc("/", rootHandler)
// パス "/time" へのリクエストは timeHandler へ
http.HandleFunc("/time", timeHandler)
fmt.Println("Server starting on port 8080...")
err := http.ListenAndServe(":8080", nil)
if err != nil {
fmt.Println("Error starting server:", err)
}
}
このサーバを実行し、http://localhost:8080 にアクセスすると “Welcome to the Root Page!” が、http://localhost:8080/time にアクセスすると現在の時刻が表示されます。
http.HandleFunc は、より具体的なパスから先に登録する必要はありません。Goのデフォルトのルーター (ServeMux) は、リクエストされたパスに最も長く一致するパターンを選びます。例えば、/ と /time が登録されている場合、/time へのリクエストは timeHandler に、それ以外の /foo や / へのリクエストは rootHandler にルーティングされます。レスポンスをカスタマイズする
http.ResponseWriter を使うことで、単純な文字列だけでなく、HTTPステータスコードやヘッダー情報も設定できます。
package main
import (
"fmt"
"net/http"
)
func customHandler(w http.ResponseWriter, r *http.Request) {
// Content-Type ヘッダーを設定
w.Header().Set("Content-Type", "text/plain; charset=utf-8")
// カスタムヘッダーを追加
w.Header().Set("X-Custom-Header", "MyValue")
// ステータスコードを 200 OK に設定 (WriteHeader は Write の前に呼ぶ)
w.WriteHeader(http.StatusOK) // http.StatusOK は定数で 200 を表します
// レスポンスボディに書き込み
fmt.Fprintln(w, "This response has custom headers and status code!")
}
func notFoundHandler(w http.ResponseWriter, r *http.Request) {
// http.NotFound は 404 Not Found レスポンスを返す便利な関数です
http.NotFound(w, r)
}
func main() {
http.HandleFunc("/custom", customHandler)
// 上記以外のパスへのアクセスは notFoundHandler が処理するようにします
// 注意: "/" も登録しないと、未定義パスのデフォルト動作になります
http.HandleFunc("/", notFoundHandler) // "/" へのアクセスも404にする例
fmt.Println("Server starting on port 8080...")
err := http.ListenAndServe(":8080", nil)
if err != nil {
fmt.Println("Error starting server:", err)
}
}
w.Header().Set("Key", "Value"): HTTPレスポンスヘッダーを設定します。w.WriteHeader(statusCode): HTTPステータスコードを設定します。これを呼び出さない場合は、暗黙的にhttp.StatusOK(200) が設定されます。注意:WriteHeaderは、Writeメソッド(fmt.Fprintfなどによる書き込みを含む)を呼び出す前に実行する必要があります。http.NotFound(w, r): 404 Not Foundエラーを簡単に返すためのヘルパー関数です。http.Error(w, "Error message", http.StatusInternalServerError): エラーメッセージと指定したステータスコードでエラーレスポンスを返すヘルパー関数もあります。
リクエスト情報を取得する
ハンドラ関数のもう一つの引数 *http.Request からは、クライアントからのリクエストに関する様々な情報を取得できます。
package main
import (
"fmt"
"net/http"
"strings"
)
func requestInfoHandler(w http.ResponseWriter, r *http.Request) {
// リクエストメソッド (GET, POST, etc.)
fmt.Fprintf(w, "Method: %s\n", r.Method)
// リクエストURLのパス
fmt.Fprintf(w, "URL Path: %s\n", r.URL.Path)
// URLクエリパラメータを取得 (例: /info?id=123&type=user)
queryValues := r.URL.Query()
fmt.Fprintf(w, "Query Parameters:\n")
for key, values := range queryValues {
fmt.Fprintf(w, " %s: %s\n", key, strings.Join(values, ", "))
}
// 特定のクエリパラメータを取得 (存在しない場合は空文字列)
id := queryValues.Get("id")
fmt.Fprintf(w, "Query Param 'id': %s\n", id)
// リクエストヘッダーを取得
fmt.Fprintf(w, "Headers:\n")
for name, headers := range r.Header {
for _, h := range headers {
fmt.Fprintf(w, " %s: %s\n", name, h)
}
}
// 特定のヘッダーを取得 (例: User-Agent)
userAgent := r.Header.Get("User-Agent")
fmt.Fprintf(w, "User-Agent: %s\n", userAgent)
// POSTリクエストなどのボディを取得するには r.Body を読み取る (ここでは省略)
// フォームデータを取得するには r.ParseForm() や r.FormValue() を使う (ここでは省略)
}
func main() {
http.HandleFunc("/info", requestInfoHandler)
fmt.Println("Server starting on port 8080...")
fmt.Println("Access http://localhost:8080/info?id=123&type=user to see request details.")
err := http.ListenAndServe(":8080", nil)
if err != nil {
fmt.Println("Error starting server:", err)
}
}
このサーバを起動し、ブラウザや curl などで http://localhost:8080/info?id=123&type=user のようなURLにアクセスすると、リクエストメソッド、パス、クエリパラメータ、ヘッダーなどの情報が表示されます。
r.Method: リクエストメソッド(”GET”, “POST” など)。r.URL.Path: リクエストされたパス(例: “/info”)。r.URL.Query(): クエリパラメータをurl.Values型(map[string][]string)で返します。一つのキーに複数の値を指定できるため、値はスライスになっています。r.URL.Query().Get("key"): 指定したキーの最初の値を取得します。存在しない場合は空文字列を返します。r.Header: リクエストヘッダーをhttp.Header型(map[string][]string)で返します。r.Header.Get("Key"): 指定したヘッダーキーの最初の値を取得します。大文字小文字は区別されません(Canonical Header Key)。
より詳細なサーバ設定 (http.Server)
http.ListenAndServe は内部で http.Server 構造体を作成して使用しています。より細かい制御(タイムアウト設定、独自のエラーハンドリングなど)を行いたい場合は、自分で http.Server を作成して設定することができます。
package main
import (
"fmt"
"net/http"
"time"
)
func main() {
// ハンドラを登録 (ここではデフォルトの ServeMux を使う例)
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
fmt.Fprintln(w, "Hello from custom server!")
})
// http.Server 構造体を設定
server := &http.Server{
Addr: ":8081", // Listen するアドレスとポート
Handler: nil, // nil の場合は http.DefaultServeMux を使う
ReadTimeout: 10 * time.Second, // リクエストヘッダの読み取りタイムアウト
WriteTimeout: 10 * time.Second, // レスポンス書き込みのタイムアウト
IdleTimeout: 120 * time.Second, // Keep-Alive でのアイドルタイムアウト
MaxHeaderBytes: 1 << 20, // リクエストヘッダの最大サイズ (1MB)
// ErrorLog: log.New(os.Stderr, "http: ", log.LstdFlags), // エラーログの出力先を指定する場合
}
fmt.Println("Custom server starting on port 8081...")
// 設定した Server で ListenAndServe を実行
err := server.ListenAndServe()
if err != nil {
fmt.Println("Error starting custom server:", err)
}
}
http.Server を使うことで、以下のような設定が可能です。
Addr: リッスンするアドレスとポート。Handler: リクエストを処理するハンドラ。nilの場合はhttp.DefaultServeMux(http.HandleFuncで登録したものが使われる)が使われます。独自のルーター(ServeMux)を指定することも可能です。ReadTimeout,WriteTimeout,IdleTimeout: 通信に関するタイムアウト設定。これにより、低速なクライアントや悪意のある接続からサーバを保護できます。MaxHeaderBytes: 受け付けるリクエストヘッダーの最大サイズ。
より堅牢なWebサーバを構築する際には、これらのタイムアウト設定などを適切に行うことが推奨されます。
まとめ
このステップでは、Goの標準パッケージ net/http を使って基本的なHTTPサーバを構築する方法を学びました。
http.ListenAndServeとhttp.HandleFuncで簡単にサーバを起動し、リクエストを処理できる。- ハンドラ関数は
http.ResponseWriterと*http.Requestを受け取る。 http.ResponseWriterでレスポンス(ステータスコード、ヘッダー、ボディ)を構築する。*http.Requestからリクエスト情報(メソッド、URL、クエリパラメータ、ヘッダーなど)を取得する。http.Serverを使うことで、タイムアウトなどの詳細な設定が可能。
net/http パッケージは非常に強力で、これだけで本格的なWebアプリケーションやAPIサーバを構築するための基礎となります。次のステップでは、JSONデータの扱いやHTMLテンプレートの利用方法など、より実践的なWeb開発テクニックを学んでいきましょう!