はじめに
Go 言語は、標準パッケージで HTTP サーバーと基本的な HTTP クライアントを提供します。 使用するのは、net/httpというパッケージです。以下は、もっとも簡単な Web アプリケーションの実装例です。
package main
import (
"fmt"
"net/http"
)
func hello(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, "Hello")
}
func main() {
server := http.Server{
Addr: "127.0.0.1:8080",
}
http.HandleFunc("/hello", log(hello))
server.ListenAndServe()
}
個々の関数が、具体的にどのような役割を担っているのか、見ていきましょう。
net/http
パッケージの概要
net/http
は、クライアントとサーバーの両方の実装を提供するパッケージです。各種の構造体や関数がその一方または両方で使用されます。
- クライアント
- Client
- Response
- Header
- Request
- Cookie
- サーバー
- Server
- ServeMux
- Handler/HandlerFunc
- ResponseWriter
- Header
- Request
- Cookie
クライアントの実装は例えば次のように簡単な GET リクエストを送信できます。
package main
import (
"fmt"
"io/ioutil"
"net/http"
)
func main() {
resp, err := http.Get("http://example.com/")
if err != nil {
return
}
defer resp.Body.Close()
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
return
}
fmt.Println(string(body))
}
しかし、今回はクライアント機能よりもサーバー機能に焦点を合わせているので、これ以上は深くは関わりません。
Server構造体
Server 構造体を用いて、サーバーの設定を変更できます。
はじめの例においては、Addr
というフィールドを用いてホスト名とポート番号を設定していました。
server := http.Server{
Addr: "127.0.0.1:8080",
}
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
}
設定内容はタイムアウトの設定やエラー出力、後に出てくるハンドラの設定などです。
Server.ListenAndServe
func (srv *Server) ListenAndServe() error
HTTP リクエストを受け取るには、Server 構造体の ListenAndServe
メソッドを使用します。
ListenAndServe は常に nil
以外のエラーを返します。シャットダウンまたは終了後,返されるエラーは ErrServerClosed
です。
Server.ListenAndServeTLS
ListenAndServeTLS
メソッドは、HTTPS によるサーバー間の通信の暗号化を提供します。それ以外の機能は ListenAndServe
と変わりありません。
サーバー構造体の TLSConfig.Certificates
と TLSConfig.GetCertificate
が入力されていない場合は、サーバーの証明書と一致する秘密鍵を含むファイル名を指定する必要があります。
server := http.Server{
Addr: "127.0.0.1:8080",
}
server.ListenAndServeTLS("cert.pem", "key.pem")
// cert.pemとkey.pemを同一ディレクトリ内に設置
ListenAndServe関数
func ListenAndServe(addr string, handler Handler) error
Sever に細かい設定が必要ないのなら単純に http.ListenAndServe
関数を用いることができます。
ListenAndServe
はサーバーのアドレスとハンドラを受け取ります。
ハンドラに nil
を渡した場合には、デフォルトのハンドラである DefaultServeMux
が使用されます。
package main
import (
"io"
"net/http"
)
func main() {
helloHandler := func(w http.ResponseWriter, r *http.Request) {
io.WriteString(w, "Hello, world!\n")
}
http.HandleFunc("/hello", helloHandler)
http.ListenAndServe(":8080", nil)
}
Handler構造体
しばしば説明の中で出てきたハンドラとはなんでしょうか。
ハンドラとはずばり、ServeHTTP
というメソッドを持ったインターフェースです。このメソッドはインターフェース HTTPResponseWriter
と構造体 Request
へのポインタという 2 つにの引数を取ります。
type Handler interface {
ServeHTTP(ResponseWriter, *Request)
}
この ServeHTTP
というメソッドを実装してさえいれば、ハンドラとなることができます。当然ながら、ハンドラのデフォルト値として使用される DefaultServeMux
も ServeHTTP
メソッドを実装しています。ただし、これは特殊なタイプのハンドラで、与えられた URL に応じてリクエストを各種ハンドラに転送できます。
ServeHTTP
メソッド内では、ヘッダとデータを ResponseWriter
に書き込み return する必要があります。
以下の例は、独自のハンドラを用いて実装しています。
package main
import (
"fmt"
"net/http"
)
type MyHandler struct{}
func (h *MyHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, "Hello world")
}
func main() {
handler := MyHandler{}
server := http.Server{
Addr: "127.0.0.1:8080",
Handler: &handler,
}
server.ListenAndServe()
}
しかし、この場合にはどの URL にアクセスしたとしても(例えば http://localhost:8080/
や http://localhost:8080/hello
)常におなじ結果が返されることでしょう。大抵の場合には URL に応じた結果を返したいはずなので、通常はデフォルトの DefaultServeMux
を使用することになります。
HandleFunc関数
ハンドラは ServeHTTP
を持つインターフェースですが、ハンドラ関数はハンドラのように振る舞い関数です。第一引数にはリクエストを処理するパス名を受け取り、第 2 引数はハンドラ関数は ServeHTTP
と同じく ResponseWriter
と Request
を引数に受け取る関数を受け取ります。
func HandleFunc(pattern string, handler func(ResponseWriter, *Request))
次の例はハンドラ関数でリクエストを処理します。
package main
import (
"fmt"
"net/http"
)
func hello(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, "Hello")
}
func main() {
server := http.Server{
Addr: "127.0.0.1:8080",
}
http.HandleFunc("/hello", hello)
server.ListenAndServe()
}
HandlerFunc
HandlerFunc
は、HandleFunc
と名前がよく似ていて間際らしいですが、こちらは関数の型です。
type HandlerFunc func(ResponseWriter, *Request)
HandlerFunc
という型は、メソッド ServeHTTP
を実装しています。
そのメソッドないでは、自身の関数 f
をただ呼び出しているだけです。
func (f HandlerFunc) ServeHTTP(w ResponseWriter, r *Request) {
f(w, r)
}
ですので、さきほど使用したハンドラ関数を HandlerFunc
型にキャストすればそれ自身をハンドラとして使用することも可能です。
package main
import (
"fmt"
"net/http"
)
func hello(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, "Hello")
}
func main() {
handler := http.HandlerFunc(hello)
server := http.Server{
Addr: "127.0.0.1:8080",
Handler: handler,
}
server.ListenAndServe()
}
HandleFuncをもっと詳しく知る
このことは、HandlerFunc
関数が実際になにをしているか知ることの手がかりになります。
HandleFunc
の実装は以下のとおりです。
func HandleFunc(pattern string, handler func(ResponseWriter, *Request)) {
DefaultServeMux.HandleFunc(pattern, handler)
}
DefaultServeMux
はデフォルトで使用される ServeMux
です。HandleFunc
は ServeMux
がもつ HandleFunc
のラッパー関数であることがわかりました。さらに処理を追ってきます。
func (mux *ServeMux) HandleFunc(pattern string, handler func(ResponseWriter, *Request)) {
if handler == nil {
panic("http: nil handler")
}
mux.Handle(pattern, HandlerFunc(handler))
}
ServeMux
がもつ HandleFunc
もまた ServeMux.Handle
のラッパーです。注目すべき箇所は、HandlerFunc(handler)
と渡されたハンドラ関数が HandlerFunc
への型変換によってハンドラとして登録されていることです。つまり、ハンドラ関数も結局は実際のハンドラに変換されて使用されているのです。
ちなみに、ServeMux.Handle
の実装は以下のとおりです。
パスとハンドラーを受け取り、マッピングを登録しています。
すでに同じパスが登録されていた場合には panic
をおこします。
func (mux *ServeMux) Handle(pattern string, handler Handler) {
mux.mu.Lock()
defer mux.mu.Unlock()
if pattern == "" {
panic("http: invalid pattern")
}
if handler == nil {
panic("http: nil handler")
}
if _, exist := mux.m[pattern]; exist {
panic("http: multiple registrations for " + pattern)
}
if mux.m == nil {
mux.m = make(map[string]muxEntry)
}
e := muxEntry{h: handler, pattern: pattern}
mux.m[pattern] = e
if pattern[len(pattern)-1] == '/' {
mux.es = appendSorted(mux.es, e)
}
if pattern[0] != '/' {
mux.hosts = true
}
}
参考
Package http GoのHTTPサーバーの実装 Goのhttp.Handlerやhttp.HandlerFuncをちゃんと理解する