Posted at
HTTP2Day 15

nghttp2 を使った Go HTTP/2 server

More than 3 years have passed since last update.

nghttp2 C ライブラリを Go から呼び出して HTTP/2 Web サーバーを書いたという記事です.

ソースコードは https://github.com/tatsuhiro-t/go-nghttp2 にあります.

Go から nghttp2 の API を呼び出すために cgo を使いました.

session.go が nghttp2 API をラップしている部分です.

cgo の標準的な書き方で, import "C" の上に CFLAGS やら include やらを書いておきます.

// #cgo CFLAGS: -O2

// #cgo LDFLAGS: -lnghttp2
// #include <string.h>
// #include <nghttp2/nghttp2.h>
// #include "cnghttp2.h"
import "C"

現在 cgo では C の関数ポインター経由の関数呼び出しができないとうことで, 1度 C の関数を関数ポインター経由で呼んで, そこから export した Go の関数を呼ぶようにしています.

Go の関数を export するには //export functionName とします.

//export onBeginHeaders

func onBeginHeaders(fr *C.nghttp2_frame, ptr unsafe.Pointer) C.int {
...
}

間違って // export と空白を export の前に書いてしまうと正しく認識されないので要注意です.

cnghttp2.h に C 関数のプロトタイプを書いておいて, nghttp2.c にその関数から Go 関数を呼ぶようにしておきます.

#include "_cgo_export.h"


int on_begin_headers(nghttp2_session *session, const nghttp2_frame *frame,
void *user_data)
{
int rv;
rv = onBeginHeaders((nghttp2_frame *)frame, user_data);
return rv;
}

_cgo_export.h に export した Go 関数のプロトタイプが書かれているので include しておきます.

C のタイプ は C.size_t のように C.* の形をしていて, Go へ値を渡すときは必ず変換が必要です.

C.int32_t と Go の int32 も当然別タイプ扱いです.

また現在の cgo の実装では C 関数呼び出しは割とコストが高いようです.

goroutine のスケジューラーとの調整が必要なためとのことですが詳しくは分かっておりません.

https://github.com/bradfitz/http2 のコードを参考に net.http のフレームワークにのるように設計しています.

package main

import (
"github.com/tatsuhiro-t/go-nghttp2"
"io"
"log"
"net/http"
)

func main() {
var srv http.Server
srv.Addr = "localhost:3000"

http.HandleFunc("/upload", func(w http.ResponseWriter, r *http.Request) {
buf := make([]byte, 4096)
for {
n, err := r.Body.Read(buf[0:])
if err != nil {
break
}
w.Write(buf[:n])
}
})
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
io.WriteString(w, "hello world")
})
log.Printf("Listening on " + srv.Addr)
nghttp2.ConfigureServer(&srv, &nghttp2.Server{})
log.Fatal(srv.ListenAndServeTLS("server.crt", "server.key"))
}

Go の実験用に適度に遊んで行きたいと考えています.

go build で C ソースのビルドも含めて全部できるので (go run でも OK), cython + Python よりも面倒がなくていい感じです.

nghttp2 では Python バインディングでサーバーを実装していますが, Python 3.4 の asyncio はフルスクラッチで HTTP サーバーの部分も実装が必要でしたが Go の場合は net.http のフレームワークにある程度のせればいいという面もプラスですし, GOMAXPROCS で複数スレッド使えるのも魅力的です.

余談ですが, 現在の仕様では "HTTP2.0" ではなく "HTTP/2" もしくは "HTTP2" が正しい名称です.