はじめに
今回はGo
の標準ライブラリであるnet/http
の使い方を備忘録代わりに残しておきたいと思います。
Package http provides HTTP client and server implementations.
// DeepL翻訳
httpパッケージは、HTTPクライアントとサーバーの実装を提供します。
簡単なWebサーバー作成
package main import ( "net/http" ) func main() { // addr : ネットワークアドレス (""の場合はポート80) http.ListenAndServe("", nil) }
設定を加えたWebサーバーを作る場合はServer
構造体を利用すると良いでしょう。
package main import ( "net/http" ) func main() { // Server構造体を生成する server := http.Server{ Addr: "127.0.0.1:8080", Handler: nil, } // Serverを立てる server.ListenAndServe() }
ハンドラ
ハンドラ(Handler
とは、ServeHTTP
というメソッドを持ったインターフェイスのことです。
ServeHTTP(http.ResponseWriter, *http.Request)
複数のハンドラによってリクエストを処理する記述方法。
package main import ( "fmt" "net/http" ) type HelloHandler struct{} func (h *HelloHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { fmt.Fprintf(w, "Hello!") } type WorldHandler struct{} func (h *WorldHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { fmt.Fprintf(w, "World!") } func main() { hello := HelloHandler{} world := WorldHandler{} server := http.Server{ Addr: "127.0.0.1:8080", } http.Handle("/hello", &hello) http.Handle("/world", &world) server.ListenAndServe() }
ハンドラ関数
ハンドラ関数はハンドラのように振る舞う関数のことです。ハンドラ関数はメソッドServeHTTP
と同じシグネチャを持ちます。
package main import ( "fmt" "net/http" ) func hello(w http.ResponseWriter, r *http.Request) { fmt.Fprintf(w, "Hello!") } func world(w http.ResponseWriter, r *http.Request) { fmt.Fprintf(w, "World!") } func main() { server := http.Server{ Addr: "127.0.0.1:8080", } http.HandleFunc("/hello", hello) http.HandleFunc("/world", world) server.ListenAndServe() }
ServeMux
ServeMux
はHTTPリクエストのマルチプレクサ(多重通信の入口となるもの)です。HTTPリクエストを受け取って、URLに応じて適切なハンドラに転送します。
HttpRouter
HttpRouter is a lightweight high performance HTTP request router (also called multiplexer or just mux for short) for Go.
In contrast to the default mux of Go's net/http package, this router supports variables in the routing pattern and matches against the request method. It also scales better.
// DeepL翻訳
HttpRouterは、Go用の軽量で高性能なHTTPリクエストルータ(マルチプレクサ、略してmuxとも呼ばれます)です。
Goのnet/httpパッケージのデフォルトのmuxとは対照的に、このルーターはルーティングパターンの変数をサポートし、リクエストメソッドに対してマッチします。また、スケールも良くなっています。
パターンマッチングで変数を利用した例。
package main import ( "fmt" "net/http" "github.com/julienschmidt/httprouter" ) func hello(w http.ResponseWriter, r *http.Request, p httprouter.Params) { fmt.Fprintf(w, "Helllo, %s\n", p.ByName("name")) } func main() { mux := httprouter.New() mux.GET("/hello/:name", hello) server := http.Server{ Addr: "127.0.0.1:8080", Handler: mux, } server.ListenAndServe() }
Request
構造体Request
は、サーバーが受信したHTTPリクエストまたはクライアントが送信するHTTPリクエストを表します。
A Request represents an HTTP request received by a server or to be sent by a client.
構造体URL
は解析されたURLを表します。
A URL represents a parsed URL (technically, a URI reference).
URL
の一般形式は以下の通り。
scheme://[userinfo@]host/path[?query][#fragment]
scheme:opaque[?query][#fragment]
fragment
はサーバに送信される前にブラウザによって除去されます。
ヘッダー読み取りサンプル
package main import ( "fmt" "net/http" ) func headers(w http.ResponseWriter, r *http.Request) { h := r.Header fmt.Fprintln(w, h) fmt.Fprintln(w, h["Accept-Encoding"]) fmt.Fprintf(w, h.Get("Accept-Encoding")) } func main() { server := http.Server{ Addr: "127.0.0.1:8080", } http.HandleFunc("/headers", headers) server.ListenAndServe() }
ボディ読み取りサンプル
import ( "fmt" "net/http" ) func body(w http.ResponseWriter, r *http.Request) { len := r.ContentLength body := make([]byte, len) r.Body.Read(body) fmt.Fprintln(w, string(body)) } func main() { server := http.Server{ Addr: "127.0.0.1:8080", } http.HandleFunc("/body", body) server.ListenAndServe() }
フォームデータ解析のサンプル
package main import ( "fmt" "net/http" ) func process(w http.ResponseWriter, r *http.Request) { r.ParseForm() fmt.Fprintln(w, r.Form) } func main() { server := http.Server{ Addr: "127.0.0.1:8080", } http.HandleFunc("/process", process) server.ListenAndServe() }
ParseForm
でリクエストの解析をしてから、Form
フィールドにアクセスする必要があります。
<html> <head> <meta http-equiv="Content-Type" content="text/html; charset=utf-8"/> <title>Go Web Programming</title> </head> <body> <form action=http://127.0.0.1:8080/process?hello=world&thread=123 method="post" enctype="application/x-www-form-urlencoded"> <input type="text" name="hello" value="sau sheong"/> <input type="text" name="post" value="456"/> <input type="submit"/> </form> </body> </html>
- POSTリクエスト
- URLは
http://localhost:8080/process?hello=world&thread=123
- HTMLフォームのキーと値のペアが2つ
PostForm
フォームのキーバリューのペアが必要ですが、URLのペアは必要でない場合にはPostForm
を利用します。
func process(w http.ResponseWriter, r *http.Request) {
r.ParseForm()
fmt.Fprintln(w, r.Form)
fmt.Fprintln(w, r.PostForm)
}
FromValue と PostFormValue
FromValue
とPostFormValue
を用いると簡単にキーと値のペアにアクセスすることができます。これらを実行すると1番目の値だけが取得されます。ただしParseForm
を呼び出す必要がないことに注意してください。
func process(w http.ResponseWriter, r *http.Request) { fmt.Fprintln(w, r.FormValue("hello")) fmt.Fprintln(w, r.PostFormValue("hello")) }
ファイル
multipart/form-data
&FormFile
メソッドを使った例。
package main import ( "fmt" "io/ioutil" "net/http" ) func process(w http.ResponseWriter, r *http.Request) { file, _, err := r.FormFile("uploaded") if err == nil { data, err := ioutil.ReadAll(file) if err == nil { fmt.Fprintln(w, string(data)) } } } func main() { server := http.Server{ Addr: "127.0.0.1:8080", } http.HandleFunc("/process", process) server.ListenAndServe() }
<html> <head> <meta http-equiv="Content-Type" content="text/html; charset=utf-8"/> <title>Go Web Programming</title> </head> <body> <form action=http://127.0.0.1:8080/process?hello=world&thread=123 method="post" enctype="multipart/form-data"> <input type="text" name="hello" value="sau sheong"/> <input type="text" name="post" value="456"/> <input type="file" name="uploaded"> <input type="submit"/> </form> </body> </html>
ResponseWriter
ResponseWriter
はハンドラがHTTPレスポンスを生成するために利用するインターフェイスです。
type ResponseWriter interface { Header() Header Write([]byte) (int, error) WriteHeader(statusCode int) }
http package - net/http - Go Packages
Write
メソッドはバイト配列を受け取って、HTTPレスポンスのボディに書き込みを行います。
func writeExample(w http.ResponseWriter, r *http.Request) { str := ` <html> <head><title>Go Web Programming</title></head> <body><h1>Hello, World!</h1></body> </html> ` w.Write([]byte(str)) }
WriteHeader
はHTTPレスポンスの返すステータスコードを書き込みます。
func writeHeaderExample(w http.ResponseWriter, r *http.Request) { w.WriteHeader(501) fmt.Fprintln(w, "Not Implemented") }
Json
をレスポンスとして返すようなサンプル。
type Post struct { User string Threads []string } func jsonExample(w http.ResponseWriter, r *http.Request) { w.Header().Set("Content-Type", "applcation/json") post := &Post{ User: "Hanachiru", Threads: []string{"1番目", "2番目", "3番目"}, } json, _ := json.Marshal(post) w.Write(json) }
クッキー
クッキーはクライアントに保管される情報であり、サーバーからHTTPレスポンスメッセージを通して送られるものです。クライアントがHTTPリクエストをサーバーに送信するたびにクッキーも一緒に送られます。
Go言語にはCookie
構造体が存在します。
type Cookie struct { Name string Value string Path string Domain string Expires time.Time RawExpires string MaxAge int Secure bool HttpOnly bool SameSite SameSite Raw string Unparsed []string }
- セッションクッキー:
Expires
が設定されていないもの。ブラウザが閉じられるとブラウザから削除される。 - 永続性クッキー:セッションクッキー以外。期限切れや除去されない限り維持される。
クッキーを送信するサンプルはこちら。(Expires
やMaxAge
を設定していないのでセッションクッキー)
func setCookie(w http.ResponseWriter, r *http.Request) { c1 := http.Cookie{ Name: "first_cookie", Value: "Go Web Programming", HttpOnly: true, } c2 := http.Cookie{ Name: "second_cookie", Value: "Manning Publications Co", HttpOnly: true, } http.SetCookie(w, &c1) http.SetCookie(w, &c2) }
Chromeのディベロッパーツールのアプリケーション -> ストレージ -> Cookie
の中をみると、クッキーの情報を知ることができます。
逆にクッキーを取得するサンプルコードはこちら。
func getCookie(w http.ResponseWriter, r *http.Request) { c1, err := r.Cookie("first_cookie") if err != nil { fmt.Fprintln(w, "Cannot get the first cookie") } cs := r.Cookies() fmt.Fprintln(w, c1) fmt.Fprintln(w, cs) }
フラッシュメッセージ
ページを再表示したときいんはメッセージが残らないようにする「フラッシュメッセージ」をセッションクッキーを用いて実装したいと思います。
func setMessage(w http.ResponseWriter, r *http.Request) { msg := []byte("Hello, World!") c := http.Cookie{ Name: "flash", Value: base64.URLEncoding.EncodeToString(msg), } http.SetCookie(w, &c) } func showMessage(w http.ResponseWriter, r *http.Request) { c, err := r.Cookie("flash") if err != nil { if err == http.ErrNoCookie { fmt.Fprintln(w, "メッセージがありません") } } else { rc := http.Cookie{ Name: "flash", MaxAge: -1, Expires: time.Unix(1, 0), } http.SetCookie(w, &rc) val, _ := base64.URLEncoding.DecodeString(c.Value) fmt.Fprintln(w, string(val)) } }
同じ名前のクッキーを設定すると、新しいものに置き換えられます。また負数のMaxAge
と過去の時点を表すExpires
により、新しいクッキーが削除されます。