Golang gin-gonic reverse proxying leads to panic "interface conversion: *http.timeoutWriter is not http.CloseNotifier: missing method CloseNotify"

huangapple go评论150阅读模式
英文:

Golang gin-gonic reverse proxying leads to panic "interface conversion: *http.timeoutWriter is not http.CloseNotifier: missing method CloseNotify"

问题

我正在使用Gin Gonic框架创建一个反向代理端点,目标端点使用grpc Gateway提供服务,使用下面的代码。这类似于gin中建议的反向代理方法这里这里

ep1 := v1.Group("/ep1")
{
	ep1.GET("/ep2", reverseProxy("http://localhost:50000"))
}

func reverseProxy(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) {
    	proxy.ServeHTTP(c.Writer, c.Request)
    }
}

然而,当向这个gin端点(/ep1/ep2)发送请求时,会出现一个go panic错误:

interface conversion: *http.timeoutWriter is not http.CloseNotifier: missing method CloseNotify
/usr/local/Cellar/go/1.8/libexec/src/runtime/panic.go:489 (0x10288df)
	gopanic: reflectcall(nil, unsafe.Pointer(d.fn), deferArgs(d), uint32(d.siz), uint32(d.siz))
/usr/local/Cellar/go/1.8/libexec/src/runtime/iface.go:131 (0x100c3af)
	additab: panic(&TypeAssertionError{"", typ.string(), inter.typ.string(), iname})
/usr/local/Cellar/go/1.8/libexec/src/runtime/iface.go:79 (0x100bc34)
	getitab: additab(m, true, canfail)
/usr/local/Cellar/go/1.8/libexec/src/runtime/iface.go:256 (0x100cbb8)
	assertI2I: r.tab = getitab(inter, tab._type, false)
/path/to/vendor/github.com/gin-gonic/gin/response_writer.go:110 (0x14de6f3)
	(*responseWriter).CloseNotify: return w.ResponseWriter.(http.CloseNotifier).CloseNotify()
/usr/local/Cellar/go/1.8/libexec/src/net/http/httputil/reverseproxy.go:142 (0x14d4d12)
	(*ReverseProxy).ServeHTTP: notifyChan := cn.CloseNotify()
/path/to/main.go:379 (0x16d2ead)
	reverseProxy.func1: proxy.ServeHTTP(c.Writer, c.Request)
/path/to/vendor/github.com/gin-gonic/gin/context.go:97 (0x14d657a)
	(*Context).Next: c.handlers[c.index](c)
/path/to/middlewares/locale.go:12 (0x15737d9)
	getLocaleMiddleware.func1: c.Next()
/path/to/vendor/github.com/gin-gonic/gin/context.go:97 (0x14d657a)
	(*Context).Next: c.handlers[c.index](c)
/path/to/middlewares/session_cookie.go:27 (0x1574e7c)
	getSessionCookieMiddleware.func1: c.Next()
/path/to/vendor/github.com/gin-gonic/gin/context.go:97 (0x14d657a)
	(*Context).Next: c.handlers[c.index](c)
/path/to/middlewares/affiliate_api.go:27 (0x15729a1)
	getAffiliateAPIMiddleware.func1: c.Next()
/path/to/vendor/github.com/gin-gonic/gin/context.go:97 (0x14d657a)
	(*Context).Next: c.handlers[c.index](c)
/path/to/middlewares/metrics.go:17 (0x157465b)
	getMetricsMiddleware.func1: c.Next()
/path/to/vendor/github.com/gin-gonic/gin/context.go:97 (0x14d657a)
	(*Context).Next: c.handlers[c.index](c)
/path/to/middlewares/input_validations.go:75 (0x1572dcb)
	getInputValidationMiddleware.func1: c.Next()
/path/to/vendor/github.com/gin-gonic/gin/context.go:97 (0x14d657a)
	(*Context).Next: c.handlers[c.index](c)
/path/to/middlewares/logger.go:68 (0x1573aea)
	LoggerWithWriter.func1: c.Next()
/path/to/vendor/github.com/gin-gonic/gin/context.go:97 (0x14d657a)
	(*Context).Next: c.handlers[c.index](c)
/path/to/middlewares/request_tracer.go:13 (0x1574d6c)
	getTracerContext.func1: c.Next()
/path/to/vendor/github.com/gin-gonic/gin/context.go:97 (0x14d657a)
	(*Context).Next: c.handlers[c.index](c)
/path/to/vendor/github.com/gin-gonic/gin/recovery.go:45 (0x14e4b6a)
	RecoveryWithWriter.func1: c.Next()
/path/to/vendor/github.com/gin-gonic/gin/context.go:97 (0x14d657a)
	(*Context).Next: c.handlers[c.index](c)
/path/to/vendor/github.com/gin-gonic/gin/gin.go:284 (0x14dc710)
	(*Engine).handleHTTPRequest: context.Next()
/path/to/vendor/github.com/gin-gonic/gin/gin.go:265 (0x14dc02b)
	(*Engine).ServeHTTP: engine.handleHTTPRequest(c)
/usr/local/Cellar/go/1.8/libexec/src/net/http/server.go:2967 (0x140fa53)
	(*timeoutHandler).ServeHTTP.func1: h.handler.ServeHTTP(tw, r)
/usr/local/Cellar/go/1.8/libexec/src/runtime/asm_amd64.s:2197 (0x1054851)

有关为什么会发生这种情况或代码中的问题,有什么想法吗?

英文:

I'm using the Gin Gonic framework to create a reverse proxy endpoint, with the target endpoint being served using grpc Gateway using the code given below. This is similar to the reverse proxy methodology suggested for gin here and here

ep1 := v1.Group("/ep1")
{
	ep1.GET("/ep2", reverseProxy("http://localhost:50000"))
}

func reverseProxy(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) {
	    proxy.ServeHTTP(c.Writer, c.Request)
    }
}

However, when on actually sending a request to this gin endpoint (/ep1/ep2) a go panic is seen:

interface conversion: *http.timeoutWriter is not http.CloseNotifier: missing method CloseNotify
/usr/local/Cellar/go/1.8/libexec/src/runtime/panic.go:489 (0x10288df)
	gopanic: reflectcall(nil, unsafe.Pointer(d.fn), deferArgs(d), uint32(d.siz), uint32(d.siz))
/usr/local/Cellar/go/1.8/libexec/src/runtime/iface.go:131 (0x100c3af)
	additab: panic(&TypeAssertionError{"", typ.string(), inter.typ.string(), iname})
/usr/local/Cellar/go/1.8/libexec/src/runtime/iface.go:79 (0x100bc34)
	getitab: additab(m, true, canfail)
/usr/local/Cellar/go/1.8/libexec/src/runtime/iface.go:256 (0x100cbb8)
	assertI2I: r.tab = getitab(inter, tab._type, false)
/path/to/vendor/github.com/gin-gonic/gin/response_writer.go:110 (0x14de6f3)
	(*responseWriter).CloseNotify: return w.ResponseWriter.(http.CloseNotifier).CloseNotify()
/usr/local/Cellar/go/1.8/libexec/src/net/http/httputil/reverseproxy.go:142 (0x14d4d12)
	(*ReverseProxy).ServeHTTP: notifyChan := cn.CloseNotify()
/path/to/main.go:379 (0x16d2ead)
	reverseProxy.func1: proxy.ServeHTTP(c.Writer, c.Request)
/path/to/vendor/github.com/gin-gonic/gin/context.go:97 (0x14d657a)
	(*Context).Next: c.handlers[c.index](c)
/path/to/middlewares/locale.go:12 (0x15737d9)
	getLocaleMiddleware.func1: c.Next()
/path/to/vendor/github.com/gin-gonic/gin/context.go:97 (0x14d657a)
	(*Context).Next: c.handlers[c.index](c)
/path/to/middlewares/session_cookie.go:27 (0x1574e7c)
	getSessionCookieMiddleware.func1: c.Next()
/path/to/vendor/github.com/gin-gonic/gin/context.go:97 (0x14d657a)
	(*Context).Next: c.handlers[c.index](c)
/path/to/middlewares/affiliate_api.go:27 (0x15729a1)
	getAffiliateAPIMiddleware.func1: c.Next()
/path/to/vendor/github.com/gin-gonic/gin/context.go:97 (0x14d657a)
	(*Context).Next: c.handlers[c.index](c)
/path/to/middlewares/metrics.go:17 (0x157465b)
	getMetricsMiddleware.func1: c.Next()
/path/to/vendor/github.com/gin-gonic/gin/context.go:97 (0x14d657a)
	(*Context).Next: c.handlers[c.index](c)
/path/to/middlewares/input_validations.go:75 (0x1572dcb)
	getInputValidationMiddleware.func1: c.Next()
/path/to/vendor/github.com/gin-gonic/gin/context.go:97 (0x14d657a)
	(*Context).Next: c.handlers[c.index](c)
/path/to/middlewares/logger.go:68 (0x1573aea)
	LoggerWithWriter.func1: c.Next()
/path/to/vendor/github.com/gin-gonic/gin/context.go:97 (0x14d657a)
	(*Context).Next: c.handlers[c.index](c)
/path/to/middlewares/request_tracer.go:13 (0x1574d6c)
	getTracerContext.func1: c.Next()
/path/to/vendor/github.com/gin-gonic/gin/context.go:97 (0x14d657a)
	(*Context).Next: c.handlers[c.index](c)
/path/to/vendor/github.com/gin-gonic/gin/recovery.go:45 (0x14e4b6a)
	RecoveryWithWriter.func1: c.Next()
/path/to/vendor/github.com/gin-gonic/gin/context.go:97 (0x14d657a)
	(*Context).Next: c.handlers[c.index](c)
/path/to/vendor/github.com/gin-gonic/gin/gin.go:284 (0x14dc710)
	(*Engine).handleHTTPRequest: context.Next()
/path/to/vendor/github.com/gin-gonic/gin/gin.go:265 (0x14dc02b)
	(*Engine).ServeHTTP: engine.handleHTTPRequest(c)
/usr/local/Cellar/go/1.8/libexec/src/net/http/server.go:2967 (0x140fa53)
	(*timeoutHandler).ServeHTTP.func1: h.handler.ServeHTTP(tw, r)
/usr/local/Cellar/go/1.8/libexec/src/runtime/asm_amd64.s:2197 (0x1054851)

Any ideas on why this might be happening or what's wrong in the code?

答案1

得分: 0

发现问题的原因是代码库没有直接使用gin-gonic中的Run()方法。相反,它在启动http服务器时使用了一个超时,代码如下(这里只展示了部分相关代码):

type H struct {
    sync.Mutex
    Engine   *gin.Engine
    listener net.Listener
    running  bool
}
.
.
.
var h H
s := &http.Server{
    Addr:         address,
    Handler:      http.TimeoutHandler(h.Engine, time.Duration(100000)*time.Millisecond, ""),
    ReadTimeout:  time.Duration(100000) * time.Millisecond,
    WriteTimeout: time.Duration(100000) * time.Millisecond,
}

h.listener, err := net.Listen("tcp", s.Addr)
if err != nil {
    return err
}

h.running = true
s.Serve(h.listener)

然而,正如在http://grokbase.com/t/gg/golang-dev/13796p5h1n/net-http-timeouthandler-vs-closenotify中提到的,http.TimeoutHandler没有实现http.CloseNotifier接口。这导致了一个panic错误,错误消息为interface conversion: *http.timeoutWriter is not http.CloseNotifier: missing method CloseNotify

因此,为了解决这个问题,将服务器处理程序修改为直接使用gin引擎,并使用http.ServerReadTimeoutWriteTimeout值进行超时处理。

修改后的代码不再引发panic,并成功进行了反向代理:

type H struct {
    sync.Mutex
    Engine   *gin.Engine
    listener net.Listener
    running  bool
}
.
.
.
var h H
s := &http.Server{
    Addr:         address,
    Handler:      h.Engine,
    ReadTimeout:  time.Duration(100000) * time.Millisecond,
    WriteTimeout: time.Duration(100000) * time.Millisecond,
}

h.listener, err := net.Listen("tcp", s.Addr)
if err != nil {
    return err
}

h.running = true
s.Serve(h.listener)

请注意,这里只需要修改&http.ServerHandler。另外,不需要对问题中的原始反向代理代码进行修改。

英文:

It was found that the issue was seen because the codebase wasn't using the Run() method from gin-gonic directly. Instead, it was using a timeout when starting an http server as follows (using partial, relevant code here):

type H struct {
    sync.Mutex
    Engine   *gin.Engine
    listener net.Listener
    running  bool
}
.
.
.
var h H
s := &http.Server{
	Addr:         address,
	Handler:      http.TimeoutHandler(h.Engine, time.Duration(100000)*time.Millisecond, ""),
	ReadTimeout:  time.Duration(100000) * time.Millisecond,
	WriteTimeout: time.Duration(100000) * time.Millisecond,
}


h.listener, err := net.Listen("tcp", s.Addr)
if err != nil {
	return err
}

h.running = true
s.Serve(h.listener)

However, http.TimeoutHandler doesn't implement the http.CloseNotifer interface as mentioned at http://grokbase.com/t/gg/golang-dev/13796p5h1n/net-http-timeouthandler-vs-closenotify This resulted in a panic with the error message interface conversion: *http.timeoutWriter is not http.CloseNotifier: missing method CloseNotify

Therefore, as a workaround for this issue, the Server Handler was modified to be the gin engine directly while using the ReadTimeout and WriteTimeout values of http.Server for timeout purposes.

Modified code which no longer panics, and results in successful reverse-proxying:

type H struct {
    sync.Mutex
    Engine   *gin.Engine
    listener net.Listener
    running  bool
}
.
.
.
var h H
s := &http.Server{
    Addr:         address,
    Handler:      h.Engine,
    ReadTimeout:  time.Duration(100000) * time.Millisecond,
    WriteTimeout: time.Duration(100000) * time.Millisecond,
}

h.listener, err := net.Listen("tcp", s.Addr)
if err != nil {
    return err
}

h.running = true
s.Serve(h.listener)

Note that only the Handler for &http.Server needed to be modified here. Also, no modification to the original reverse proxy code from the question was needed.

huangapple
  • 本文由 发表于 2017年4月28日 10:15:50
  • 转载请务必保留本文链接:https://go.coder-hub.com/43670872.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定