net/http パッケージで使用される ListenAndServe
関数は tcp による Listen のため、 UNIX ドメインソケットで Listen するには自前で準備する必要があります。
func (srv *Server) ListenAndServe() error { addr := srv.Addr if addr == "" { addr = ":http" } ln, err := net.Listen("tcp", addr) if err != nil { return err } return srv.Serve(tcpKeepAliveListener{ln.(*net.TCPListener)}) }
src/net/http/server.go - The Go Programming Language
UNIX ドメインソケット
ソケットファイルの置き場を作成します。/tmp を経由するのはセキュリティの都合上あまりよろしくないため、/var/run を経由するようにします。/var/run への追加は systemd でテンポラリなディレクトリを作成するため /etc/tmpfiles.d に設定ファイルを追記しておきます。
$ cat /etc/tmpfiles.d/gopher.conf d /var/run/gopher 0755 [UID] [GID] -
今回は /var/run/gopher というディレクトリを作成するようにしています。[UID]
, [GID]
は各自で設定してください。
その後、設定ファイルを systemd に登録して再起動します。
$ systemd-tmpfiles --create /etc/tmpfiles.d/gopher.conf $ systemctl daemon-reload
これで UNIX ドメインソケットの作成場所は設定完了です。
NGINX
ほぼ定型文です。今回は /var/run/gopher/go.sock をリッスンします。
upstream backend { server unix:/var/run/gopher/go.sock; } server { listen 80; server_name go.example.com; root /var/www/html; location / { proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header Host $http_host; proxy_redirect off; proxy_pass http://backend/; } }
Web アプリケーション
net/http が持つ ServerMux
にハンドラを登録した後 UNIX ドメインソケットを介してリッスンします。
func main() { mux := http.NewServeMux() mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { w.Write([]byte("<h1>It works!</h1>\n")) }) listener, err := net.Listen("unix", "/var/run/gopher/go.sock") if err != nil { log.Fatalf("error: %v", err) } defer func() { if err := listener.Close(); err != nil { log.Printf("error: %v", err) } }() shutdown(listener) if err := http.Serve(listener, mux); err != nil { log.Fatalf("error: %v", err) } }
また、割り込みで終了のシグナルが来た場合にリスナーをクローズ するようにしておきます。
func shutdown(listener net.Listener) { c := make(chan os.Signal, 2) signal.Notify(c, os.Interrupt, syscall.SIGTERM) go func() { s := <-c if err := listener.Close(); err != nil { log.Printf("error: %v", err) } os.Exit(1) }() }
クローズしないとソケットファイルが生き残り続けます。
起動
バイナリにして起動すればそのまま動くと思います。"502 Bad Gateway" の文字が現れたときはエラーログを見てもらえればいいのですが、大抵はパーミッションによるエラーだと思います。
パーミッションの解決方法(例)
www-data ユーザで実行することを仮定すると
$ cat /etc/tmpfiles.d/gopher.conf d /var/run/gopher 0755 www-data www-data -
として systemd へ登録しなおし、その後に go build
で作成したバイナリを www-data で起動してあげればそのまま動くはずです。
$ go build -o /tmp/bin $ sudo -u www-data /tmp/bin
おわりに
今回のコードは kaneshin/playground/go/unixsocket にあります。