I want my server to redirect a particular end point to another server. This end point can be either GET
ted or POST
ed. The HTTP response code should be 302 in both cases. And if I use curl
on this code, it does indeed show response code 302 in both cases, and curl -L
follows the redirect properly. Whew.
BUT
my unit test uses the httptest.NewRecorder()
to capture the information, but it only works for GET
and not for POST
. So I need to figure out how to get the unit test to work, when I know that the actual redirect is working. The fail test shows that the HTTP response code is 200 instead of 302 (http.StatusFound
).
$ go run foo.go
POST code 200
GET code 302
Here's the self contained test.
package main
import (
"net/http"
"net/http/httptest"
"github.com/gin-gonic/gin"
)
func main() {
gin.SetMode(gin.ReleaseMode)
{
w := httptest.NewRecorder()
context, _ := gin.CreateTestContext(w)
context.Request = httptest.NewRequest("POST", "http://localhost:23632/foobar", nil)
context.Redirect(http.StatusFound, "http://foobar.com")
print("POST code ",w.Code,"\n")
}
{
w := httptest.NewRecorder()
context, _ := gin.CreateTestContext(w)
context.Request = httptest.NewRequest("GET", "http://localhost:23632/foobar", nil)
context.Redirect(http.StatusFound, "http://foobar.com")
print("GET code ",w.Code,"\n")
}
}
When I do CURL POST on the actual app (not shown), I see that it is working:
curl -v -XPOST localhost:23632/foobar
* About to connect() to localhost port 23632 (#0)
* Trying 127.0.0.1...
* Connected to localhost (127.0.0.1) port 23632 (#0)
> POST /foobar HTTP/1.1
> User-Agent: curl/7.29.0
> Host: localhost:23632
> Accept: */*
>
< HTTP/1.1 302 Found
< Location: http://foobar.com
< Vary: Origin
< Date: Tue, 23 May 2023 22:38:42 GMT
< Content-Length: 0
<
* Connection #0 to host localhost left intact
The workaround is to call context.Writer.WriteHeaderNow
explicitly after context.Redirect
.
This is a corner case of using the gin context returned from gin.CreateTestContext
.
For the GET request, gin will call http.Redirect
finally, which will write a short HTML body (something like <a href="http://foobar.com">Found</a>
) to the response, which in turn causes the status code to be written to the response.
While for the POST request, http.Redirect
does not write the short HTML body, and the status code don't have the chance to be written to the response.
See the implementation of http.Redirect. According to the source code, if the header Content-Type
is set before, the GET request will have the same issue too:
{
w := httptest.NewRecorder()
context, _ := gin.CreateTestContext(w)
context.Request = httptest.NewRequest("GET", "http://localhost:23632/foobar", nil)
+ context.Header("Content-Type", "text/html")
context.Redirect(http.StatusFound, "http://foobar.com")
print("GET code ", w.Code, "\n")
}
The workaround is to call context.Writer.WriteHeaderNow
explicitly:
{
w := httptest.NewRecorder()
context, _ := gin.CreateTestContext(w)
context.Request = httptest.NewRequest("POST", "http://localhost:23632/foobar", nil)
context.Redirect(http.StatusFound, "http://foobar.com")
+ context.Writer.WriteHeaderNow()
print("POST code ", w.Code, "\n")
}
gin itself uses the same workaround. See TestContextRenderRedirectWithRelativePath.
A real server app does not suffer from the same issue because (*Engine).handleHTTPRequest
will call WriteHeaderNow
for us (see the source code). That's why I call it a corner case instead of a bug.