Unit testing of gin's Context.Redirect works for GET response code but fails for POST response code (golang)

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

Unit testing of gin's Context.Redirect works for GET response code but fails for POST response code (golang)

问题

我想让我的服务器将特定的端点重定向到另一个服务器。这个端点可以是GETPOST请求。无论哪种情况,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 GETted or POSTed. 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 (
    &quot;net/http&quot;
    &quot;net/http/httptest&quot;
    &quot;github.com/gin-gonic/gin&quot;
)

func main() {
    gin.SetMode(gin.ReleaseMode)
    {
        w := httptest.NewRecorder()
        context, _ := gin.CreateTestContext(w)
        context.Request = httptest.NewRequest(&quot;POST&quot;, &quot;http://localhost:23632/foobar&quot;, nil)
        context.Redirect(http.StatusFound, &quot;http://foobar.com&quot;)

        print(&quot;POST code &quot;,w.Code,&quot;\n&quot;)
    }

    {
        w := httptest.NewRecorder()
        context, _ := gin.CreateTestContext(w)
        context.Request = httptest.NewRequest(&quot;GET&quot;, &quot;http://localhost:23632/foobar&quot;, nil)
        context.Redirect(http.StatusFound, &quot;http://foobar.com&quot;)

        print(&quot;GET code &quot;,w.Code,&quot;\n&quot;)
    }
}

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)
&gt; POST /foobar HTTP/1.1
&gt; User-Agent: curl/7.29.0
&gt; Host: localhost:23632
&gt; Accept: */*
&gt;
&lt; HTTP/1.1 302 Found
&lt; Location: http://foobar.com
&lt; Vary: Origin
&lt; Date: Tue, 23 May 2023 22:38:42 GMT
&lt; Content-Length: 0
&lt;
* Connection #0 to host localhost left intact

答案1

得分: 1

TL;DR

解决方法是在context.Redirect之后显式调用context.Writer.WriteHeaderNow

解释

这是使用从gin.CreateTestContext返回的gin上下文的一个特殊情况。

对于GET请求,gin最终会调用http.Redirect,它会向响应中写入一个短的HTML内容(类似于&lt;a href=&quot;http://foobar.com&quot;&gt;Found&lt;/a&gt;),这进而导致状态码被写入响应。

而对于POST请求,http.Redirect不会写入短的HTML内容,因此状态码没有机会被写入响应。

参见http.Redirect的实现。根据源代码,如果在之前设置了Content-Type头,GET请求也会有同样的问题:

  {
  	w := httptest.NewRecorder()
  	context, _ := gin.CreateTestContext(w)
  	context.Request = httptest.NewRequest(&quot;GET&quot;, &quot;http://localhost:23632/foobar&quot;, nil)
+ 	context.Header(&quot;Content-Type&quot;, &quot;text/html&quot;)
  	context.Redirect(http.StatusFound, &quot;http://foobar.com&quot;)
  	print(&quot;GET code &quot;, w.Code, &quot;\n&quot;)
  }

解决方法是显式调用context.Writer.WriteHeaderNow

  {
  	w := httptest.NewRecorder()
  	context, _ := gin.CreateTestContext(w)
  	context.Request = httptest.NewRequest(&quot;POST&quot;, &quot;http://localhost:23632/foobar&quot;, nil)
  	context.Redirect(http.StatusFound, &quot;http://foobar.com&quot;)
+ 	context.Writer.WriteHeaderNow()

  	print(&quot;POST code &quot;, w.Code, &quot;\n&quot;)
  }

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 &lt;a href=&quot;http://foobar.com&quot;&gt;Found&lt;/a&gt;) 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(&quot;GET&quot;, &quot;http://localhost:23632/foobar&quot;, nil)
+ 	context.Header(&quot;Content-Type&quot;, &quot;text/html&quot;)
  	context.Redirect(http.StatusFound, &quot;http://foobar.com&quot;)
  	print(&quot;GET code &quot;, w.Code, &quot;\n&quot;)
  }

The workaround is to call context.Writer.WriteHeaderNow explicitly:

  {
  	w := httptest.NewRecorder()
  	context, _ := gin.CreateTestContext(w)
  	context.Request = httptest.NewRequest(&quot;POST&quot;, &quot;http://localhost:23632/foobar&quot;, nil)
  	context.Redirect(http.StatusFound, &quot;http://foobar.com&quot;)
+ 	context.Writer.WriteHeaderNow()

  	print(&quot;POST code &quot;, w.Code, &quot;\n&quot;)
  }

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.

huangapple
  • 本文由 发表于 2023年5月24日 07:22:53
  • 转载请务必保留本文链接:https://go.coder-hub.com/76319196.html
匿名

发表评论

匿名网友

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

确定