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")
}
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 (
"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")
}
Related
The whole question is in the title.
I was searching on SO if a subrouter will use a middleware of its parent, in the case the middleware is applied to the parent router with the method Use(), but I couldn't find a clear concise answer.
I couldn't find that information in the package documentation either, so I decided to test it and post a question and an answer here for everyone in the same case.
In the following code sample, does requesting on /john will trigger the logMiddleware ?
mainRouter := mux.NewRouter()
mainRouter.Use(logMiddleware)
subRouter := mainRouter.PathPrefix("/users/").Subrouter()
subRouter.Handle("/john", johnHandler())
Yes, mux subrouters inherit their parent's middlewares when applied with Use() method.
Here is the test I created in case you want to try in your favorite IDE :
router code
package so
import (
"context"
"net/http"
"github.com/gorilla/mux"
)
func newRouter(useMainMiddleware bool) mux.Router {
mainRouter := mux.NewRouter()
if useMainMiddleware {
mainRouter.Use(middleware)
}
subRouter := mainRouter.PathPrefix("/users/").Subrouter()
subRouter.Handle("/test", testHandler())
return *mainRouter
}
func middleware(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
r = r.WithContext(context.WithValue(r.Context(), "is_using_middleware", true))
next.ServeHTTP(w, r)
})
}
func testHandler() http.Handler {
return http.HandlerFunc(
func(w http.ResponseWriter, r *http.Request) {
if using, castOk := r.Context().Value("is_using_middleware").(bool); castOk && using {
w.WriteHeader(http.StatusOK)
return
}
w.WriteHeader(http.StatusInternalServerError)
return
},
)
}
test file
package so
import (
"net/http"
"net/http/httptest"
"testing"
"github.com/stretchr/testify/assert"
)
func TestSubrouterWithMiddleware(t *testing.T) {
// GIVEN
request := httptest.NewRequest(http.MethodGet, "/users/test", nil)
recorder := httptest.NewRecorder()
router := newRouter(true) // using a middleware
// WHEN
router.ServeHTTP(recorder, request)
// THEN
assert.Equal(t, http.StatusOK, recorder.Result().StatusCode)
}
func TestSubrouterWithoutMiddleware(t *testing.T) {
// GIVEN
request := httptest.NewRequest(http.MethodGet, "/users/test", nil)
recorder := httptest.NewRecorder()
router := newRouter(false) // not using a middleware
// WHEN
router.ServeHTTP(recorder, request)
// THEN
assert.Equal(t, http.StatusInternalServerError, recorder.Result().StatusCode)
}
I have created some Go functions that make HTTP GET calls to services that are out there on the internet and parse the results.
I am now working on writing test-cases for these functions.
In my test cases, I'm using the go package httptest to simulate calls to these external services. Below is my code. Error checking is purposefully removed for brevity. Here is the go-playground.
package main
import (
"fmt"
"io"
"context"
"net/http"
"net/http/httptest"
)
func handlerResponse() http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusOK)
w.Write([]byte(`{"A":"B"}`))
})
}
func buildMyRequest(ctx context.Context, url string) *http.Request {
request, _ := http.NewRequestWithContext(ctx, "GET", url, nil)
return request
}
func myPrint(response *http.Response) {
b := make([]byte, 60000)
for {
_, err := response.Body.Read(b)
if err == io.EOF {
break
}
}
fmt.Println(string(b))
}
func main() {
srv := httptest.NewServer(handlerResponse())
client := http.Client{}
myResponse1, _ := client.Do(buildMyRequest(context.Background(), srv.URL))
fmt.Println("myResponse1:")
myPrint(myResponse1)
myResponse2, _ := client.Do(buildMyRequest(context.Background(), srv.URL))
fmt.Println("myResponse2:")
myPrint(myResponse2)
}
This is the output it produces:
myResponse1:
{"A":"B"}
myResponse2:
{"A":"B"}
As you can see, I have created some dummy HTTP response data {"A":"B"} and when you send an HTTP request to srv.URL, it actually hits an ephemeral HTTP server which responds with the dummy data. Cool!
When you send the second HTTP request to srv.URL, it again responds with the same dummy data. But this is where my problem arises. I want the ephemeral HTTP server to return some different data the second time {"C":"D"} and third time {"E":"F"} it receives a request.
How can I change the first line of the main() function so that the server responds with my desired data on subsequent HTTP calls?
you could use a hack like follows ( playground : here)
package main
import (
"fmt"
"io"
"context"
"net/http"
"net/http/httptest"
"sync"
)
type responseWriter struct{
resp map[int]string
count int
lock *sync.Mutex
}
func NewResponseWriter()*responseWriter{
r := new(responseWriter)
r.lock = new(sync.Mutex)
r.resp = map[int]string{
0: `{"E":"F"}`,
1: `{"A":"B"}`,
2: `{"C":"D"}`,
}
r.count = 0
return r
}
func (r *responseWriter)GetResp()string{
r.lock.Lock()
defer r.lock.Unlock()
r.count ++
return r.resp[r.count%3]
}
func handlerResponse(rr *responseWriter) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusOK)
w.Write([]byte(rr.GetResp()))
})
}
func buildMyRequest(ctx context.Context, url string) *http.Request {
request, _ := http.NewRequestWithContext(ctx, "GET", url, nil)
return request
}
func myPrint(response *http.Response) {
b := make([]byte, 60000)
for {
_, err := response.Body.Read(b)
if err == io.EOF {
break
}
}
fmt.Println(string(b))
}
func main() {
rr := NewResponseWriter()
srv := httptest.NewServer(handlerResponse(rr))
client := http.Client{}
myResponse1, err := client.Do(buildMyRequest(context.Background(), srv.URL))
if err != nil{
fmt.Println(err)
return
}
defer myResponse1.Body.Close()
fmt.Println("myResponse1:")
myPrint(myResponse1)
myResponse2, err := client.Do(buildMyRequest(context.Background(), srv.URL))
if err != nil{
fmt.Println(err)
return
}
defer myResponse2.Body.Close()
fmt.Println("myResponse2:")
myPrint(myResponse2)
}
I am trying to have a reverse proxy in Golang, but I am unable to stop target ip from showing in browser ie. it is simply redirecting to wikipedia (target) rather than showing doing reverseproxy.
Can anyone tell what I am doing wrong?
package main
import (
"net/http"
"net/http/httputil"
"net/url"
"github.com/gorilla/mux"
)
func main() {
target := "https://www.wikipedia.org"
remote, err := url.Parse(target)
if err != nil {
panic(err)
}
proxy := httputil.NewSingleHostReverseProxy(remote)
r := mux.NewRouter()
r.HandleFunc("/forward/{rest:.*}", handler(remote, proxy))
http.ListenAndServe(":8080", r)
}
func handler(ur *url.URL, p *httputil.ReverseProxy) func(http.ResponseWriter, *http.Request) {
return func(w http.ResponseWriter, r *http.Request) {
r.URL.Host = ur.Host
r.URL.Scheme = ur.Scheme
r.Host = ur.Host
r.URL.Path = mux.Vars(r)["rest"]
p.ServeHTTP(w, r)
}
}
For example, I want to do /api/v1/users/id/{id}.
At the moment, I have this:
mux := http.NewServeMux()
mux.Handle("/api/v1/users", HandleUsersV1{db: db, mux: mux})
log.Fatal(http.ListenAndServe(fmt.Sprintf("%s%d", ":", portNumber), mux))
I want:
mux := http.NewServeMux()
mux.Handle("/api/v1", HandleV1{})
And then in HandleV1:
mux.HandleFunc("/users/{id}", handler)
I know Gorilla Mux can do it for me with PathPrefix, but I prefer net/http.
The standard net/http does not support dynamic path segments, so /{id} is not gonna work the way you might imagine. As for the prefix thing, you can use this https://golang.org/pkg/net/http/#StripPrefix.
v1mux := http.NewServeMux()
v1mux.HandleFunc("/users/", handler)
mux := http.NewServeMux()
mux.Handle("/api/v1/", http.StripPrefix("/api/v1", v1mux))
log.Fatal(http.ListenAndServe(fmt.Sprintf("%s%d", ":", portNumber), mux))
I suggest using https://github.com/julienschmidt/httprouter. But if you insist.
package main
import (
"log"
"net/http"
"strconv"
"strings"
)
func main() {
res := resourceone{}
http.HandleFunc("/api/v1/users/", res.router)
log.Fatal(http.ListenAndServe(":8080", nil))
}
type resourceone struct {
}
func (res *resourceone) router(w http.ResponseWriter, r *http.Request) {
args := strings.Split(r.URL.String()[len(`/api/v1/users/`):], `/`)
switch args[0] {
case `id`:
id, _ := strconv.Atoi(args[1])
res.one(w, r, id)
case `name`:
res.two(w, r, args[1])
}
}
func (res *resourceone) one(w http.ResponseWriter, r *http.Request, id int) {
w.Write([]byte(strconv.Itoa(id)))
}
func (res *resourceone) two(w http.ResponseWriter, r *http.Request, name string) {
w.Write([]byte(name))
}
Test:
curl 127.0.0.1:8080/api/v1/users/name/david
curl 127.0.0.1:8080/api/v1/users/id/1234
In this bit of code:
handler := func(w http.ResponseWriter, r *http.Request) {
io.WriteString(w, "<html><body>Hello World!</body></html>")
}
What is the func keyword doing? I've been reading through the Tour of Go and I'm confused as to what is going on here.
EDITED: Added import list and function that it was apart of
It's part of a function here:
func ExampleResponseRecorder() {
handler := func(w http.ResponseWriter, r *http.Request) {
io.WriteString(w, "<html><body>Hello World!</body></html>")
}
req := httptest.NewRequest("GET", "http://example.com/foo", nil)
w := httptest.NewRecorder()
handler(w, req)
resp := w.Result()
body, _ := ioutil.ReadAll(resp.Body)
fmt.Println(resp.StatusCode)
fmt.Println(resp.Header.Get("Content-Type"))
fmt.Println(string(body))
// Output:
// 200
// text/html; charset=utf-8
// <html><body>Hello World!</body></html>
}
import (
"fmt"
"io"
"io/ioutil"
"log"
"net/http"
"net/http/httptest"
)
func defines a closure, an annonymous function. handler is a variable that holds a reference to this function, which you can later call by passing the arguments in parenthesis:
handler(w, req)