Overview
AppEngine の Go1.11 対応において gin の version を最新の 1.3 系にあげたときに以下の問題にはまったのでその調査メモです。
- gin の version を現時点(201904)での最新の 1.3 にあげ、
appengine.Main()
を使うコードに変更すると、AppEngine / Go1.9 で動いていたc.Request.Header.Get("Host")
が空文字で返ってきてしまい、既存コードに影響が出てしまったこと。
原因
gin1.3 以降の
appengine.Main()
ではinternal.Main()
が呼ばれる。appengine.Main() の中身は appengineかどうかのビルドタグで制御されていて、Go1.9 までのビルドと Go1.11 からのビルドでは appengine.Main() の実行される先が異なる。gin 1.3 系(Go1.11)
- https://github.com/golang/appengine/blob/master/internal/main_vm.go#L19 のファイルがビルドされます。
- このファイルを見るとappengine タグが指定されない時はビルドされていない。
- AppEngine Go1.11 でビルドされるのはこの
internal.Main()
が呼ばれている方です。
gin 1.1系(~Go1.9)
- https://github.com/golang/appengine/blob/master/internal/main.go#L13 にある
appengine.Main()
が実行されます。 - 1.9 以下ではこちらがビルドされます。
- https://github.com/golang/appengine/blob/master/internal/main.go#L13 にある
挙動の違いを調査するために Go1.9 までで動作していた appengine.Main()
の定義元の appengine_internal.Main()
の中身を探します。
appengine_internal
パッケージについて。
appengine_internal
パッケージは Go1.11 で対応した gin の appengine.Main()
では呼ばれません。
探し方
goapp env GOROOT /PATHTO/google-cloud-sdk/platform/google_appengine/goroot-1.9
このように appengine の goroot が出力されるので、このディレクトリ配下の src を探します。
※ goapp の go の version は 1.9
まず見たのは以下
- /PATHTO/google-cloud-sdk/platform/google_appengine/goroot-1.9/src/appengine_internal/internal.go
ここの Main
関数を確認します。
func Main() { close(appPackagesInitialized) flag.Parse() serveHTTP() }
serveHTTP()
メソッドを確認します。
// serveHTTP serves App Engine HTTP requests. func serveHTTP() { // The development server reads the HTTP address and port that the // server is listening to from stdout. We listen on 127.0.0.1:0 or // [::1]:0 to avoid firewall restrictions. conn, err := net.Listen("tcp", "127.0.0.1:0") if err != nil { log.Print("appengine: couldn't listen on IPv4 TCP socket: ", err) conn, err = net.Listen("tcp", "[::1]:0") if err != nil { log.Fatal("appengine: couldn't listen on IPv6 TCP socket: ", err) } } addr := conn.Addr().(*net.TCPAddr) fmt.Fprintf(os.Stdout, "%s\t%d\n", addr.IP, addr.Port) os.Stdout.Close() err = http.Serve(conn, http.HandlerFunc(handleFilteredHTTP)) if err != nil { log.Fatal("appengine: ", err) } }
この err = http.Serve(conn, http.HandlerFunc(handleFilteredHTTP))
に着目します。
func handleFilteredHTTP(w http.ResponseWriter, r *http.Request) { // Patch up RemoteAddr so it looks reasonable. if addr := r.Header.Get("X-Appengine-Remote-Addr"); addr != "" { r.RemoteAddr = addr } else { // Should not normally reach here, but pick // a sensible default anyway. r.RemoteAddr = "127.0.0.1" } // Create a private copy of the Request that includes headers that are // private to the runtime and strip those headers from the request that the // user application sees. creq := *r r.Header = make(http.Header) for name, values := range creq.Header { if !strings.HasPrefix(name, "X-Appengine-Dev-") { r.Header[name] = values } } ctx := &httpContext{req: &creq, done: make(chan struct{})} r = registerContext(r, ctx) http.DefaultServeMux.ServeHTTP(w, r) close(ctx.done) unregisterContext(r) }
以下の部分を抜粋します。
r.Header = make(http.Header) for name, values := range creq.Header { if !strings.HasPrefix(name, "X-Appengine-Dev-") { r.Header[name] = values } }
- 1.9以下でビルドされ、call されていた
appengine_intenal.Main()
では Header に関してX-Appengine-Dev-XXXX
と言う文字列を持つヘッダー以外を Request Header に入れ直していました。
念の為、go1.11 以降で使用される appengine.Main()
の実装について調べてみます。
エントリの冒頭でも記載してますが、 https://github.com/golang/appengine/blob/master/internal/main_vm.go#L19 を確認します。
func Main() { MainPath = filepath.Dir(findMainPath()) installHealthChecker(http.DefaultServeMux) port := "8080" if s := os.Getenv("PORT"); s != "" { port = s } host := "" if IsDevAppServer() { host = "127.0.0.1" } if err := http.ListenAndServe(host+":"+port, http.HandlerFunc(handleHTTP)); err != nil { log.Fatalf("http.ListenAndServe: %v", err) } }
この実装の中の handleHTTP
の実装の中を見ます。
(ref:https://github.com/golang/appengine/blob/master/internal/api.go#L87-L152 )
※ 長いので header に値を set してる箇所のみ抜粋。
(ref: https://github.com/golang/appengine/blob/master/internal/api.go#L140 )
w.Header().Set(logFlushHeader, strconv.Itoa(flushes))
go1.11 以降で call されてる appengine.Main() の実装の中身を確認すると go1.9 までとは実装が異なっていました。
1.9以下でビルドされ、call されていた
appengine_intenal.Main()
では Header に関してX-Appengine-Dev-XXXX
と言う文字列を持つヘッダー以外を Request Header に入れ直していました。
go1.11 以降では、Header から 取り出して Header に入れ直す処理を通っていないので c.Request.Header.Get("XXXX")
で取り出せないものが発生していました。
まとめ
1.11 対応では call される appengine.Main() の中身が違うので、既存のコードを見直してみると良いかもしれません。
ビルドタグについて
追記
godoc の Request の説明を見ると
For incoming requests, the Host header is promoted to the Request.Host field and removed from the Header map.
ref: https://golang.org/pkg/net/http/#Request
と記載されていた。最新版の go のドキュメントによるとそもそも Request.Header の中に Host
field は含まないことになったらしい。