go-http

Go 言語 標準パッケージでHTTPサーバー

Go言語は、標準パッケージでHTTPサーバーと基本的なHTTPクライアントを提供します。 使用するのは、net/httpというパッケージです。

はじめに

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.CertificatesTLSConfig.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 というメソッドを実装してさえいれば、ハンドラとなることができます。当然ながら、ハンドラのデフォルト値として使用される DefaultServeMuxServeHTTP メソッドを実装しています。ただし、これは特殊なタイプのハンドラで、与えられた 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 と同じく ResponseWriterRequest を引数に受け取る関数を受け取ります。

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 です。HandleFuncServeMux がもつ 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をちゃんと理解する


Contributors

> GitHub で修正を提案する
この記事をシェアする
はてなブックマークに追加

関連記事