英文:
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.Server
的ReadTimeout
和WriteTimeout
值进行超时处理。
修改后的代码不再引发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.Server
的Handler
。另外,不需要对问题中的原始反向代理代码进行修改。
英文:
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.
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论