英文:
Unit testing of gin's Context.Redirect works for GET response code but fails for POST response code (golang)
问题
我想让我的服务器将特定的端点重定向到另一个服务器。这个端点可以是GET
或POST
请求。无论哪种情况,HTTP响应代码都应该是302。如果我在这个代码上使用curl
命令,它确实会显示302的响应代码,并且curl -L
会正确地跟随重定向。
但是,我的单元测试使用httptest.NewRecorder()
来捕获信息,但只对GET
请求有效,对于POST
请求无效。所以我需要找出如何让单元测试在我知道实际重定向有效的情况下工作。失败的测试显示HTTP响应代码为200,而不是302(http.StatusFound
)。
以下是完整的测试代码:
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")
}
}
当我在实际应用程序上使用CURL进行POST请求时(未显示),我看到它是有效的:
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
希望这能帮到你!
英文:
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
答案1
得分: 1
TL;DR
解决方法是在context.Redirect
之后显式调用context.Writer.WriteHeaderNow
。
解释
这是使用从gin.CreateTestContext
返回的gin上下文的一个特殊情况。
对于GET请求,gin最终会调用http.Redirect
,它会向响应中写入一个短的HTML内容(类似于<a href="http://foobar.com">Found</a>
),这进而导致状态码被写入响应。
而对于POST请求,http.Redirect
不会写入短的HTML内容,因此状态码没有机会被写入响应。
参见http.Redirect的实现。根据源代码,如果在之前设置了Content-Type
头,GET请求也会有同样的问题:
{
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")
}
解决方法是显式调用context.Writer.WriteHeaderNow
:
{
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本身也使用了相同的解决方法。参见TestContextRenderRedirectWithRelativePath。
一个真实的服务器应用程序不会遇到同样的问题,因为(*Engine).handleHTTPRequest
会为我们调用WriteHeaderNow
(参见源代码)。这就是为什么我将其称为一个特殊情况而不是一个错误。
英文:
TL;DR
The workaround is to call context.Writer.WriteHeaderNow
explicitly after context.Redirect
.
Explanation
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.
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论