net/http
この記事ではGoの標準パッケージであるnet/httpを利用したリクエスト、レスポンスの処理の方法について解説する。
「Goプログラミング実践入門 標準ライブラリでゼロからWebアプリを作る」という書籍を参考にしており、この書籍のまとめでもある。
リクエストの受信
ListenAndServe
GoにおいてHTTPサーバーを起動させるためのメソッド。
第一引数:ネットワークアドレス。「””」を指定した場合、80番ポートになる。
第二引数:リクエストを処理するハンドラ。nilを指定した場合、DefaultServeMuxが使われる。
server.ListenAndServe(ネットワークアドレス, ハンドラ)
server構造体を使ってサーバーの設定ができる。ネットワークアドレスやハンドラもこの中で指定できるので、これを使った場合引数はいらない。
type Server struct {
Addr string
Handler Handler
TLSConfig *tls.Config
ReadTimeout time.Duration
ReadHeaderTimeout time.Duration
WriteTimeout time.Duration
IdleTimeout time.Duration
MaxHeaderBytes int
TLSNextProto map[string]func(*Server, *tls.Conn, Handler)
ConnState func(net.Conn, ConnState)
ErrorLog *log.Logger
BaseContext func(net.Listener) context.Context
ConnContext func(ctx context.Context, c net.Conn) context.Context
}
こんな感じで使う。
func main() {
server := http.Server{
Addr: "127.0.0.1:8080",
Handler: nil,
}
server.ListenAndServe()
}
ハンドラの設定
GoにおけるハンドラとはServeHTTPといいメソッドを持ったインターフェースのこと。
ServeHTTPh第一引数にインターフェースHTTPResponseWriter、第二引数に構造体Requestへのポインタを取る。
ServeHTTP(w http.ResponseWriter, r *http.Request)
http.Handle
http.Handle()
というメソッドを使ってハンドラーの設定ができる。以下が使用例。
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()
}
ポイント
- URLごとにハンドラーを用意する。この場合は
HelloHandler{}
とWorldHandler{}
を用意している。 - server構造体のHandlerフィールドは設定しない。
-
http.Handle()
を使うことでURLごとのハンドラーを設定する。以下のように使う。
http.Handle(URL, ハンドラーへのポインタ)
http.HandleFunc
http.HandleFunc()
というメソッドを利用してハンドラーを設定することもできる。
http.HandleFunc()
を使うとわざわざ構造体を宣言することなく、関数を生成するだけでハンドラーの設定ができる。
以下が使用例。
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()
}
ポイント
-
http.HandleFunc()
は第一引数にURLを、第二引数に関数名を渡す。渡された名前の関数がハンドラーに変換される。
http.HandleFunc(URL, 関数名)
レスポンスの送信
ResponseWriter
レスポンスを送信するために利用するインターフェース。
Write
HTTPレスポンスのボディに書き込むメソッド。以下の例ではHTMlを書き込む。
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))
}
レスポンスは以下のようになる。
$ curl -i 127.0.0.1:8080/write
HTTP/1.1 200 OK
Date: Wed, 23 Nov 2022 02:02:53 GMT
Content-Length: 95
Content-Type: text/html; charset=utf-8
<html>
<head><title>Go Web Programming</title></head>
<body><h1>Hello World</h1></body>
</html>%
ポイント
- レスポンスのボディに書き込むにはバイト配列である必要がある。
WriteHeader
HTTPレスポンスのステータスコードを書き込む。
func writeHeaderExample(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(501)
fmt.Fprintln(w, "エラーレスポンスです。")
}
レスポンスは以下のようになる。ステータスコードとして501を返せていることがわかる。
$ curl -i 127.0.0.1:8080/writeheader
HTTP/1.1 501 Not Implemented
Date: Wed, 23 Nov 2022 02:15:37 GMT
Content-Length: 34
Content-Type: text/plain; charset=utf-8
エラーレスポンスです。
ポイント
- このメソッドを呼び出した後はヘッダに書き込むことはできない。ResponseWriterに書き込むことはできる。
- このメソッドを呼び出さない場合、ステータスは「200 OK」になる。
Header
ヘッダーと転送先URLを書き込む。
w.Header().Set()
の形で使う。第一引数にヘッダーのキー名、第二引数に値を書く。
func headerExample(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Location", "http://google.com")
w.WriteHeader(302)
}
レスポンスは以下のようになる。
$ curl -i 127.0.0.1:8080/redirect
HTTP/1.1 302 Found
Location: http://google.com
Date: Wed, 23 Nov 2022 02:24:21 GMT
Content-Length: 0
ブラウザで127.0.0.1:8080/redirectにアクセスするとGoogleに転送される。
ポイント
-
w.Header().Set()
はw.WriteHeader()
よりも先に書く必要がある。
JSONを返す
Postという構造体を利用してJSONを返すとする。
type Post struct {
User string
Threads []string
}
以下のように関数を定義する。
func jsonExample(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json")
post := &Post{
User: "Goromaru",
Threads: []string{"1try", "2try", "3try"},
}
json, _ := json.Marshal(post)
w.Write(json)
}
レスポンスはこのように返ってくる。
$ curl -i 127.0.0.1:8080/json
HTTP/1.1 200 OK
Content-Type: application/json
Date: Wed, 23 Nov 2022 02:37:30 GMT
Content-Length: 52
{"User":"Goromaru","Threads":["1try","2try","3try"]}%
ポイント
- まず最初に
w.Header().Set()
を利用してコンテンツタイプを「application/json」に設定する。 - その後、Post構造体のインスタンスpostを作成する。
-
json.Marshal()
を利用してJSONを生成する。 -
w.Write()
を利用してボディに書き込む。