【Go】ReverseProxyがストリーミングでうまく機能しない

概要

ストリーミング用のエンドポイントに対してリバースプロキシを使いおうとした際にうまく機能しない

失敗例

リバプロ自体はhttputilを使うと楽にできる

package main

import (
    "fmt"
    "github.com/BambooTuna/go-server-lib/config"
    "github.com/gin-contrib/static"
    "github.com/gin-gonic/gin"
    "log"
    "net/http/httputil"
    "net/url"
    "strings"
)

// localhost:8080/v1以下へのアクセスをlocalhost:18080に回す
func main() {
    serverPort := "8080"
    target := "http://localhost:18080"

    r := gin.Default()

    r.Use(reverseProxy("/v1", target))
    r.Use(static.Serve("/", static.LocalFile("./dist", false)))
    r.NoRoute(func(c *gin.Context) {
        c.File("./dist/index.html")
    })

    log.Fatal(r.Run(fmt.Sprintf(":%s", serverPort)))
}

func reverseProxy(urlPrefix string, target string) gin.HandlerFunc {
    url, err := url.Parse(target)
    if err != nil {
        log.Println("Reverse Proxy target url could not be parsed:", err)
        return nil
    }

    proxy := httputil.NewSingleHostReverseProxy(url)

    return func(c *gin.Context) {
        if strings.HasPrefix(c.Request.URL.Path, urlPrefix) {
            c.Request.URL.Path = strings.Replace(c.Request.URL.Path, urlPrefix, "", 1)
            proxy.ServeHTTP(c.Writer, c.Request)
        }
    }
}

回した先(localhost:18080)がストリーミングのAPIだった場合に以下のエラーがでる

2020/04/29 09:27:04 [Recovery] 2020/04/29 - 09:27:04 panic recovered:
POST /account.AccountCredentialsService/StreamReceive HTTP/1.1
Host: localhost:9090
Accept: application/grpc-web-text
Accept-Encoding: gzip, deflate, br
Accept-Language: ja,en-US;q=0.9,en;q=0.8
Cache-Control: no-cache
Connection: keep-alive
Content-Length: 8
Content-Type: application/grpc-web-text
Origin: http://localhost:9091
Pragma: no-cache
Referer: http://localhost:9091/
Sec-Fetch-Dest: empty
Sec-Fetch-Mode: cors
Sec-Fetch-Site: same-site
User-Agent: Mozilla/5.0 (Linux; Android 6.0; Nexus 5 Build/MRA58N) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/80.0.3987.163 Mobile Safari/537.36
X-Grpc-Web: 1
X-User-Agent: grpc-web-javascript/0.1

net/http: abort Handler
/Users/suzukitakeo/go/go1.13.8/src/net/http/httputil/reverseproxy.go:299 (0x1567228)
        (*ReverseProxy).ServeHTTP: panic(http.ErrAbortHandler)
/Users/suzukitakeo/go/src/github.com/BambooTuna/middleware/go-vue/main.go:48 (0x1580aaf)
        reverseProxy.func1: proxy.ServeHTTP(c.Writer, c.Request)
/Users/suzukitakeo/go/src/github.com/gin-gonic/gin/context.go:156 (0x156a16a)
        (*Context).Next: c.handlers[c.index](c)
/Users/suzukitakeo/go/src/github.com/gin-gonic/gin/recovery.go:83 (0x157e073)
        RecoveryWithWriter.func1: c.Next()
/Users/suzukitakeo/go/src/github.com/gin-gonic/gin/context.go:156 (0x156a16a)
        (*Context).Next: c.handlers[c.index](c)
/Users/suzukitakeo/go/src/github.com/gin-gonic/gin/logger.go:241 (0x157d1a0)
        LoggerWithConfig.func1: c.Next()
/Users/suzukitakeo/go/src/github.com/gin-gonic/gin/context.go:156 (0x156a16a)
        (*Context).Next: c.handlers[c.index](c)
/Users/suzukitakeo/go/src/github.com/gin-gonic/gin/gin.go:445 (0x15745f7)
        serveError: c.Next()
/Users/suzukitakeo/go/src/github.com/gin-gonic/gin/gin.go:438 (0x157424f)
        (*Engine).handleHTTPRequest: serveError(c, http.StatusNotFound, default404Body)
/Users/suzukitakeo/go/src/github.com/gin-gonic/gin/gin.go:367 (0x1573bae)
        (*Engine).ServeHTTP: engine.handleHTTPRequest(c)
/Users/suzukitakeo/go/go1.13.8/src/net/http/server.go:2802 (0x12cb763)
        serverHandler.ServeHTTP: handler.ServeHTTP(rw, req)
/Users/suzukitakeo/go/go1.13.8/src/net/http/server.go:1890 (0x12c7004)
        (*conn).serve: serverHandler{c.server}.ServeHTTP(w, w.req)
/Users/suzukitakeo/go/go1.13.8/src/runtime/asm_amd64.s:1357 (0x105c030)
        goexit: BYTE    $0x90   // NOP

解決策

FlushIntervalを設定すればいいだけだった。。。

    proxy := httputil.NewSingleHostReverseProxy(url)
    proxy.FlushInterval = -1

参考

ReverseProxy does not do well with streaming

コメント

タイトルとURLをコピーしました