mainvisual

最近私は、golangにはまりかけている。 ソースコードの管理がしやすく、書きやすく、また早い。 しかし、ライブラリが発展途上なのが難点と感じる。気に入った機能がないものもある。

プロキシサーバーがgolangでないかな?と探しても、速度が遅かったり、機能不十分であったりと、実用レベルに達していなかったりする。 それなら、自分で作っちゃえ!と思ったのですが、手本になる文章だったり、ドキュメントであったりがなかなか見つからない。 それならば、公式のドキュメントを読んで、深い内容まで学んじゃえばいいや!と思ったわけだ。

というわけで、今回は、golangでよくあるHTTPサーバーについて考察しようと思う。

皆さんは、golangを学ぶ時、一番最初に書くコードはこれではないか?

package main

import (
  "fmt"
  "net/http"
)

func handler(w http.ResponseWriter, r *http.Request) {
  fmt.Fprintln(w, "Hello, world.")
}

func main() {
  http.HandleFunc("/", handler)
  http.ListenAndServe(":8080", nil)
}

今回は、このソースが内部でどのようなことをやっているのか?その関数が呼び出されているのかなどをできるだけ書こうと思う。

メインの部分は http.HandleFunchttp.ListenAndServe である。

http.HandleFunc について

この場合、呼び出しているのはこの部分

http.HandleFunc("/", handler)

ソースコードでは、この部分である。

// HandleFunc registers the handler function for the given pattern
// in the DefaultServeMux.
// The documentation for ServeMux explains how patterns are matched.
func HandleFunc(pattern string, handler func(ResponseWriter, *Request)) {
  DefaultServeMux.HandleFunc(pattern, handler)
}

参照元: [https://github.com/golang/go/blob/5fea2ccc77eb50a9704fa04b7c61755fe34e1d95/src/net/http/server.go#L1963-L1968:title]

ちなみに、 DefaultServeMux はこのようになっている。

// ServeMux is an HTTP request multiplexer.
// It matches the URL of each incoming request against a list of registered
// patterns and calls the handler for the pattern that
// most closely matches the URL.
//
// Patterns name fixed, rooted paths, like "/favicon.ico",
// or rooted subtrees, like "/images/" (note the trailing slash).
// Longer patterns take precedence over shorter ones, so that
// if there are handlers registered for both "/images/"
// and "/images/thumbnails/", the latter handler will be
// called for paths beginning "/images/thumbnails/" and the
// former will receive requests for any other paths in the
// "/images/" subtree.
//
// Note that since a pattern ending in a slash names a rooted subtree,
// the pattern "/" matches all paths not matched by other registered
// patterns, not just the URL with Path == "/".
//
// If a subtree has been registered and a request is received naming the
// subtree root without its trailing slash, ServeMux redirects that
// request to the subtree root (adding the trailing slash). This behavior can
// be overridden with a separate registration for the path without
// the trailing slash. For example, registering "/images/" causes ServeMux
// to redirect a request for "/images" to "/images/", unless "/images" has
// been registered separately.
//
// Patterns may optionally begin with a host name, restricting matches to
// URLs on that host only. Host-specific patterns take precedence over
// general patterns, so that a handler might register for the two patterns
// "/codesearch" and "codesearch.google.com/" without also taking over
// requests for "http://www.google.com/".
//
// ServeMux also takes care of sanitizing the URL request path,
// redirecting any request containing . or .. elements or repeated slashes
// to an equivalent, cleaner URL.
type ServeMux struct {
  mu    sync.RWMutex
  m     map[string]muxEntry
  hosts bool // whether any patterns contain hostnames
}

type muxEntry struct {
  explicit bool
  h        Handler
  pattern  string
}

// NewServeMux allocates and returns a new ServeMux.
func NewServeMux() *ServeMux { return &ServeMux{m: make(map[string]muxEntry)} }

// DefaultServeMux is the default ServeMux used by Serve.
var DefaultServeMux = NewServeMux()

[https://github.com/golang/go/blob/bd68b8abc2e3ceaa3b9bde98568a4a9af8bde40f/src/net/http/server.go#L1755-L1806:title]

ここで何をしているのかは、説明を読めは荒くわかる。
つまり、こういうことだ

ServeMux is an HTTP request multiplexer. It matches the URL of each incoming request against a list of registered patterns and calls the handler for the pattern that most closely matches the URL.

「ServeMuxはHTTPリクエストのマルチプレクサ(注1)である。 次々に入るリクエストを登録しているパターンリストに 照合させ、その上、最も合うURLのハンドラー(注2)を呼び出す。」

Patterns name fixed, rooted paths, like “/favicon.ico”, or rooted subtrees, like “/images/” (note the trailing slash). Longer patterns take precedence over shorter ones, so that if there are handlers registered for both “/images/” and “/images/thumbnails/”, the latter handler will be called for paths beginning “/images/thumbnails/” and the former will receive requests for any other paths in the “/images/” subtree.

「パターンは “/favicon.ico” のような、絶対パスやルートパス か、”/images/“のような、サブツリーパスである。 より長いパターンは、短いパターンよりも優先度が高い。なので、 もし、”/images/” と “/images/thumbnails/“の両方をハンドラー(注2)に登録すると、 “/images/thumbnails/” から始まるときは、後に登録したハンドラが呼ばれる。 “/images/” から始まるパスのリクエストは、先に登録したものから呼ばれる。」

注1 => 一つのチャネルから、多数のオペレーションが扱えるようにコントロールする機構
注2 => 登録してある関数

概念図としてはこんな感じ

[f:id:k4zzk:20160309125115p:plain]

ServeMux の中に、 muxEntry のマップがあって、これに次章で説明する Handle メソッドからハンドラーやパターンを追加していく。
そして、hosts は、’/’ から始まるルーティングのハンドラーが登録されているかを表している。

ちなみに、Handlerは以下のように定義されていた。

type Handler interface {
  ServeHTTP(ResponseWriter, *Request)
}

[https://github.com/golang/go/blob/bd68b8abc2e3ceaa3b9bde98568a4a9af8bde40f/src/net/http/server.go#L57-L59:title]

ソースからも分かる通り、引数に(ResponseWriter, *Request)を取るように関数を定義すれば、それがHandlerになる。

話を戻そう。

ServeMux のメソッドは以下の6つである。

  • func (mux *ServeMux) match(path string) (h Handler, pattern string)
  • func (mux *ServeMux) Handler(r *Request) (h Handler, pattern string)
  • func (mux *ServeMux) handler(host, path string) (h Handler, pattern string)
  • func (mux *ServeMux) ServeHTTP(w ResponseWriter, r *Request)
  • func (mux *ServeMux) Handle(pattern string, handler Handler)
  • func (mux *ServeMux) HandleFunc(pattern string, handler func(ResponseWriter, *Request))

そして、今回呼び出されているのは、5つ目の

func (mux *ServeMux) Handle(pattern string, handler Handler)

である。

この関数をもっと詳しくみてみる。

// Handle registers the handler for the given pattern.
// If a handler already exists for pattern, Handle panics.
func (mux *ServeMux) Handle(pattern string, handler Handler) {
  mux.mu.Lock()
  defer mux.mu.Unlock()

  if pattern == "" {
    panic("http: invalid pattern " + pattern)
  }
  if handler == nil {
    panic("http: nil handler")
  }
  if mux.m[pattern].explicit {
    panic("http: multiple registrations for " + pattern)
  }

  mux.m[pattern] = muxEntry{explicit: true, h: handler, pattern: pattern}

  if pattern[0] != '/' {
    mux.hosts = true
  }

  // Helpful behavior:
  // If pattern is /tree/, insert an implicit permanent redirect for /tree.
  // It can be overridden by an explicit registration.
  n := len(pattern)
  if n > 0 && pattern[n-1] == '/' && !mux.m[pattern[0:n-1]].explicit {
    // If pattern contains a host name, strip it and use remaining
    // path for redirect.
    path := pattern
    if pattern[0] != '/' {
      // In pattern, at least the last character is a '/', so
      // strings.Index can't be -1.
      path = pattern[strings.Index(pattern, "/"):]
    }
    url := &url.URL{Path: path}
    mux.m[pattern[0:n-1]] = muxEntry{h: RedirectHandler(url.String(), StatusMovedPermanently), pattern: pattern}
  }
}

[https://github.com/golang/go/blob/bd68b8abc2e3ceaa3b9bde98568a4a9af8bde40f/src/net/http/server.go#L1913-L1951:title]

Handle registers the handler for the given pattern. If a handler already exists for pattern, Handle panics.

「登録された関数にはパターンが与えられている。 もし、ハンドラーのパターンがすでに存在しているなら、ハンドルパニック(注3)を起こす。」

注3 => 強制終了

ということになっている。

ソースコードを見ると、パニックになる条件は以下の通りである。

  • パターンが空文字であるとき。
  • パターンがnilであるとき。
  • 同じパターンがすでに登録されているとき

の以上3つである。

そして、登録の流れは次のようになる。

  1. 3つのパニックになる条件をチェック
  2. パターンとハンドラーを追加
  3. パターンのはじめの文字が ‘/’ から始まっているかをチェック
  4. パターンの最後の文字が、 ‘/’ で終わっている場合、リダイレクトハンドラでオーバーライドする。

ここで注目して欲しいのが、メソッドのこの最後の部分である、

// Helpful behavior:
// If pattern is /tree/, insert an implicit permanent redirect for /tree.
// It can be overridden by an explicit registration.
n := len(pattern)
if n > 0 && pattern[n-1] == '/' && !mux.m[pattern[0:n-1]].explicit {
  // If pattern contains a host name, strip it and use remaining
  // path for redirect.
  path := pattern
  if pattern[0] != '/' {
    // In pattern, at least the last character is a '/', so
    // strings.Index can't be -1.
    path = pattern[strings.Index(pattern, "/"):]
  }
  url := &url.URL{Path: path}
  mux.m[pattern[0:n-1]] = muxEntry{h: RedirectHandler(url.String(), StatusMovedPermanently), pattern: pattern}
}

[https://github.com/golang/go/blob/bd68b8abc2e3ceaa3b9bde98568a4a9af8bde40f/src/net/http/server.go#L1935-L1950:title]

Helpful behavior: If pattern is /tree/, insert an implicit permanent redirect for /tree. It can be overridden by an explicit registration.

「参考: もし、パターンが “/tree/” だったら、暗黙的に “/tree” にリダイレクトする処理を挿入します。 それは、優先的に書き換えられます。」

If pattern contains a host name, strip it and use remaining path for redirect.

「もし、パターンがホスト名を含む(パターンとホスト名がマッチする)なら、 リダイレクトするようにパスを書き換えます。」

In pattern, at least the last character is a ‘/’, so strings.Index can’t be -1.

「パターンで、最後の文字が ‘/’ である場合、 strings.Index-1 にではありません。」

となっています。

そして、ハンドラーを書き換えるとき、それに用いられるハンドラーはこのようになっています。

// Redirect to a fixed URL
type redirectHandler struct {
  url  string
  code int
}

func (rh *redirectHandler) ServeHTTP(w ResponseWriter, r *Request) {
  Redirect(w, r, rh.url, rh.code)
}

// RedirectHandler returns a request handler that redirects
// each request it receives to the given url using the given
// status code.
//
// The provided code should be in the 3xx range and is usually
// StatusMovedPermanently, StatusFound or StatusSeeOther.
func RedirectHandler(url string, code int) Handler {
  return &redirectHandler{url, code}
}

[https://github.com/golang/go/blob/bd68b8abc2e3ceaa3b9bde98568a4a9af8bde40f/src/net/http/server.go#L1735-L1753:title]

RedirectHandler returns a request handler that redirects each request it receives to the given url using the given status code.

The provided code should be in the 3xx range and is usually StatusMovedPermanently, StatusFound or StatusSeeOther.

RedirectHandler は、urlとステータスコードを受け取るたびに リダイレクトをするリクエストハンドラーを返します。」 「ステータスコードを3xxの範囲にしたければ、ふつうは、 StatusMovedPermanently StatusFound StatusSeeOther のいずれかを指定します。」

ということです。

次に、http.ListenAndServeについてみていくことにしよう

と思ったが、長くなりすぎたので、次の記事に分けて書こうと思う。

参考文献