Обратное проксирование Golang gin-gonic приводит к паническому преобразованию интерфейса: * http.timeoutWriter не является http.CloseNotifier: отсутствует метод 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)
    }
}

Однако при фактической отправке запроса на эту конечную точку джина (/ ep1 / ep2) наблюдается паника:

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)

Есть идеи, почему это может происходить или что не так в коде?


person Agrim    schedule 28.04.2017    source источник
comment
вы используете go 1.8 .... я бы удостоверился, что у вас также установлена ​​самая последняя версия gin. Похоже, что некоторые интерфейсы могли измениться в связи с этим: golang.org/doc/go1.8   -  person misterManager    schedule 28.04.2017
comment
@misterManager Спасибо за предложение. Я обнаружил, что проблема возникла из-за использования тайм-аутов чтения / записи при запуске http-сервера, как упоминалось в grokbase.com/t/gg/golang-dev/13796p5h1n/. См. Мой ответ ниже, в котором упоминается, как я решил эту проблему.   -  person Agrim    schedule 10.05.2017


Ответы (1)


Было обнаружено, что проблема возникла из-за того, что кодовая база не использовала метод Run() из gin-gonic напрямую. Вместо этого он использовал тайм-аут при запуске 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.TimeoutHandler не реализует интерфейс http.CloseNotifer, упомянутый в http://grokbase.com/t/gg/golang-dev/13796p5h1n/net-http-timeouthandler-vs-closenotify Это привело к panic с сообщением об ошибке interface conversion: *http.timeoutWriter is not http.CloseNotifier: missing method CloseNotify

Таким образом, в качестве обходного пути для этой проблемы обработчик сервера был изменен, чтобы быть непосредственно механизмом gin при использовании значений ReadTimeout и WriteTimeout http.Server для целей тайм-аута.

Модифицированный код, который больше не вызывает панику и приводит к успешному обратному проксированию:

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)

Обратите внимание, что здесь нужно было изменить только Handler для &http.Server. Кроме того, не потребовалось никаких изменений исходного кода обратного прокси из вопроса.

person Agrim    schedule 10.05.2017