Go言語のhttpパッケージ周りについて整理

背景

この記事で静的ファイルサーブをすることになったが,普通に
localhost:8080
を開くのと同時にやる方法がわからなかったから整理する.


localhost:8080開くだけのコード

package main
 
import (
	"log"
	"net/http"
)
 
func main() {
	port := "8080"
	http.Handle("/", http.FileServer(http.Dir("../docs/")))
	log.Printf("Listen on port: %s", port)
	log.Fatal(http.ListenAndServe(":"+port, nil))
} 


静的ファイルサーブだけするコード

package main

import (
    "net/http"
)

func main() {
    mux := http.NewServeMux()
    // 公開したいディレクトリを指定
    fileServer := http.FileServer(http.Dir("./src3"))
    // /staticに公開する
    mux.Handle("/static/", http.StripPrefix("/static/", fileServer))
    server := http.Server{
        Addr:    ":8080",
        Handler: mux,
    }
    server.ListenAndServe()
}

これによって次のように指定したローカルなファイルをブラウザ上におくことができる.


ただし,これだけではlocalhost:8080にアクセスすると次のようになる.

ただ単に同時にlocalhost:8080だけを開くコードを動かそうとすると以下のようになってしまう.

server % go run server.go
2022/11/15 10:13:38 Listen on port: 8080
2022/11/15 10:13:38 listen tcp :8080: bind: address already in use
exit status 1


これら二つをいい感じに融合させる必要がある.
ただ,今まで呪文的に何も考えずこれらを使っていて各部分の詳しいことには目を向けていなかったので,それを復習しながら考える.


http.Handleとは

http.Handle("/", http.FileServer(http.Dir("../docs/")))

の部分を見ていく.
まず,構造は次のようになっている.

func Handle(pattern string, handler Handler) {
    DefaultServeMux.Handle(pattern, handler) 
}

http.Handle は表示する URL と、URL に対応する http.Handler を DefaultServeMux に登録する関数である.


http.Handlerとは

http.Handler は ServeHTTP 関数を持つだけのインターフェイスで HTTP リクエストを受けてレスポンスを返す

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

ServerHTTP関数

func (sh serverHandler) ServeHTTP(rw ResponseWriter, req *Request) {
    // 一部抜粋
	handler := sh.srv.Handler
	handler.ServeHTTP(rw, req)
}

https://github.com/golang/go/blob/master/src/net/http/server.go#L2867 
ここの2915行あたりに抜粋してないのは書いてある(内容変更されていたらずれてるかも).

  1. sh.srv.Handlerで、http.Handler型インターフェースを得る

  2. Handlerインターフェースのメソッド、ServeHTTPを呼ぶ

DefaultServeMux とは

DefaultServeMuxは、net/httpパッケージ内に存在する公開グローバル変数.

// DefaultServeMux is the default ServeMux used by Serve.
var DefaultServeMux = &defaultServeMux
var defaultServeMux ServeMux

ServeMux型の型定義は次のようになっている.

type ServeMux struct {
	mu    sync.RWMutex
	m     map[string]muxEntry
	es    []muxEntry // slice of entries sorted from longest to shortest.
	hosts bool       // whether any patterns contain hostnames
}

type muxEntry struct {
	h       Handler
	pattern string
}

DefaultServeMuxの役割は、ServeMuxのmフィールドが中心部分で,mフィールドのmapには、「URLパス(開発者がhttp.HandleFunc関数で登録したハンドラ関数)」の対応関係が格納されているルーターのようなもの.

Goのhttpサーバーは、http.ListenAndServeの第二引数nilの場合ではDefaultServeMux内に格納された情報を使って、ルーティングを行っている

この記事に,DefaultServeMuxに開発者が書いたハンドラが登録されるまでの流れなどが説明されている.


http.FileServer

では,handlerに渡しているhttp.FileServer()について見てみる.

func FileServer(root FileSystem) Handler

http.FileServerはHandlerを返し,そのHandlerはルートにあるファイルシステムの内容をHTTPリクエストに返す.

引数となっているFileSystemの中身は次のようになっている.

type FileSystem interface {
	Open(name string) (File, error)
}

  

冷静になってよく見てみれば,最初にあげた2つのコードのどちらもフォルダの指定にこのメソッドを使っている.
それならmelody.txtをdocsに置けばいいのではということになる.

実際置いてみると正しく静的ファイルサーブできた.


いいなと思ったら応援しよう!