How to test reverse proxy with martini in go

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

How to test reverse proxy with martini in go

问题

我正在为使用Go作为反向代理的Martini应用编写测试代码,并希望使用httptest.ResponseRecorder进行测试,但是我遇到了以下错误。

[martini] PANIC: interface conversion: *httptest.ResponseRecorder不是http.CloseNotifier:缺少CloseNotify方法

httptest.ResponseRecorder没有CloseNotify()方法。

我应该如何进行测试?

package main

import (
    "github.com/go-martini/martini"
    "github.com/stretchr/testify/assert"
    "net/http"
    "net/http/httptest"
    "net/http/httputil"
    "net/url"
    "testing"
)

func TestReverseProxy(t *testing.T) {
    // 模拟后端
    backendResponse := "I am the backend"
    backend := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
        w.Write([]byte(backendResponse))
    }))
    defer backend.Close()
    backendURL, _ := url.Parse(backend.URL)

    // 前端
    m := martini.Classic()
    m.Get("/", func(w http.ResponseWriter, r *http.Request) {
        proxy := httputil.NewSingleHostReverseProxy(backendURL)
        proxy.ServeHTTP(w, r)
    })

    // 测试
    req, _ := http.NewRequest("GET", "/", nil)
    res := httptest.NewRecorder()
    m.ServeHTTP(res, req)

    assert.Equal(t, 200, res.Code, "应该相等")
}
英文:

I'm writing test code for martini app working as a reverse proxy in go, and want to test it using httptest.ResponseRecorder, but I got an error the following.

[martini] PANIC: interface conversion: *httptest.ResponseRecorder is not http.CloseNotifier: missing method CloseNotify

httptest.ResponseRecorder has no method CloseNotify()

How should I test it?

package main

import (
        "github.com/go-martini/martini"
        "github.com/stretchr/testify/assert"
        "net/http"
        "net/http/httptest"
        "net/http/httputil"
        "net/url"
        "testing"
)

func TestReverseProxy(t *testing.T) {
        // Mock backend
        backendResponse := "I am the backend"
        backend := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
                w.Write([]byte(backendResponse))
        }))
        defer backend.Close()
        backendURL, _ := url.Parse(backend.URL)

        // Frontend
        m := martini.Classic()
        m.Get("/", func(w http.ResponseWriter, r *http.Request) {
                proxy := httputil.NewSingleHostReverseProxy(backendURL)
                proxy.ServeHTTP(w, r)
        })

        // Testing
        req, _ := http.NewRequest("GET", "/", nil)
        res := httptest.NewRecorder()
        m.ServeHTTP(res, req)

        assert.Equal(t, 200, res.Code, "should be equal")
}

答案1

得分: 6

首先,请注意,根据他们的README,Martini框架已经不再维护。

然后,关于你的问题,问题出在Martini框架做了一些看起来很糟糕的事情:它将一个http.ResponseWriter假设为一个http.CloseNotifier,而实际上并没有任何保证。他们应该采用一个自定义接口来包装这两个接口,类似这样:

type ResponseWriterCloseNotifier interface {
    http.ResponseWriter
    http.CloseNotifier
}

你可以在他们的源代码中看到,他们在自己的测试中也遇到了同样的问题,并使用了一些解决方法:https://github.com/go-martini/martini/commit/063dfcd8b0f64f4e2c97f0bc27fa422969baa23b#L13

下面是使用这个解决方法的一些可行代码:

package main

import (
    "net/http"
    "net/http/httptest"
    "net/http/httputil"
    "net/url"
    "testing"

    "github.com/go-martini/martini"
    "github.com/stretchr/testify/assert"
)

type closeNotifyingRecorder struct {
    *httptest.ResponseRecorder
    closed chan bool
}

func newCloseNotifyingRecorder() *closeNotifyingRecorder {
    return &closeNotifyingRecorder{
        httptest.NewRecorder(),
        make(chan bool, 1),
    }
}

func (c *closeNotifyingRecorder) close() {
    c.closed <- true
}

func (c *closeNotifyingRecorder) CloseNotify() <-chan bool {
    return c.closed
}

func TestReverseProxy(t *testing.T) {
    // Mock backend
    backendResponse := "I am the backend"
    backend := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
        w.Write([]byte(backendResponse))
    }))
    defer backend.Close()
    backendURL, _ := url.Parse(backend.URL)

    // Frontend
    m := martini.Classic()
    m.Get("/", func(w http.ResponseWriter, r *http.Request) {
        proxy := httputil.NewSingleHostReverseProxy(backendURL)
        proxy.ServeHTTP(w, r)
    })

    // Testing
    req, _ := http.NewRequest("GET", "/", nil)
    res := newCloseNotifyingRecorder()
    m.ServeHTTP(res, req)

    assert.Equal(t, 200, res.Code, "should be equal")
}
英文:

First, please note that the martini framework is no longer maintained as said in their README.

Then, about your issue, it's because Martini does something that looks pretty bad to me: it takes an http.ResponseWriter and assumes it is also an http.CloseNotifier, while there is absolutely no guarantee of this. They should take a custom interface wrapping both of them, something like that:

type ResponseWriterCloseNotifier interface {
http.ResponseWriter
http.CloseNotifier
}

You can see in their source code that they had the same issue for their own tests, and used some workaround: https://github.com/go-martini/martini/commit/063dfcd8b0f64f4e2c97f0bc27fa422969baa23b#L13

Here is some working code made with it:

package main
import (
&quot;net/http&quot;
&quot;net/http/httptest&quot;
&quot;net/http/httputil&quot;
&quot;net/url&quot;
&quot;testing&quot;
&quot;github.com/go-martini/martini&quot;
&quot;github.com/stretchr/testify/assert&quot;
)
type closeNotifyingRecorder struct {
*httptest.ResponseRecorder
closed chan bool
}
func newCloseNotifyingRecorder() *closeNotifyingRecorder {
return &amp;closeNotifyingRecorder{
httptest.NewRecorder(),
make(chan bool, 1),
}
}
func (c *closeNotifyingRecorder) close() {
c.closed &lt;- true
}
func (c *closeNotifyingRecorder) CloseNotify() &lt;-chan bool {
return c.closed
}
func TestReverseProxy(t *testing.T) {
// Mock backend
backendResponse := &quot;I am the backend&quot;
backend := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.Write([]byte(backendResponse))
}))
defer backend.Close()
backendURL, _ := url.Parse(backend.URL)
// Frontend
m := martini.Classic()
m.Get(&quot;/&quot;, func(w http.ResponseWriter, r *http.Request) {
proxy := httputil.NewSingleHostReverseProxy(backendURL)
proxy.ServeHTTP(w, r)
})
// Testing
req, _ := http.NewRequest(&quot;GET&quot;, &quot;/&quot;, nil)
res := newCloseNotifyingRecorder()
m.ServeHTTP(res, req)
assert.Equal(t, 200, res.Code, &quot;should be equal&quot;)
}

huangapple
  • 本文由 发表于 2015年11月28日 15:25:35
  • 转载请务必保留本文链接:https://go.coder-hub.com/33968840.html
匿名

发表评论

匿名网友

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

确定