Go web服务器会自动重定向POST请求。

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

Go web server is automatically redirecting POST requests

问题

我已经尝试解决一个奇怪的问题很长一段时间了。在逐步查看了很多Angular代码之后,我注意到在通过Charles记录请求到我的服务器时出现了一些奇怪的情况。

当我向一个URL /myurl 发送POST请求时,请求实际上从未到达我的服务器。相反,它会收到一个301响应,然后才会发送一个GET请求到我的服务器。

这真的非常令人困惑。还有其他人遇到过这个问题吗?我已经上传了我的Charles日志的截图,如果你有兴趣可以看一下。

Go web服务器会自动重定向POST请求。

作为参考,这是我的服务器代码:

type FormStruct struct {
    Test string
}

func PHandler(w http.ResponseWriter, r *http.Request) {
    var t FormStruct

    req, _ := httputil.DumpRequest(r, true)

    log.Println(string(req))
    log.Println(r.Method) // GET
    log.Println(r.Body)

    decoder := json.NewDecoder(r.Body)
    err := decoder.Decode(&t)
    log.Println("Decoding complete")
    if err != nil {
        log.Println("Error")
        panic(err.Error()+"\n\n")
    }
    log.Println(t.Test)

    w.Write([]byte("Upload complete, no errors"))
}

func main() {
    http.HandleFunc("/myurl/", PHandler)    
    fmt.Println("Go Server listening on port 8001")
    http.ListenAndServe(":8001", nil)
}
英文:

I've been trying to solve a weird problem for quite some time now. After stepping through lots of angular code, I noticed something weird while logging requests to my server through Charles.

When I post to a url /myurl the request never actually hits my server. Instead, it gets a 301 response and THEN a GET request hite my server.

This is incredibly puzzling. Has anyone else run into this problem? I've uploaded a screenshot of my Charles log incase you are interested.

Go web服务器会自动重定向POST请求。

Just as a reference, this is what my server looks like:

type FormStruct struct {
    Test string
}

func PHandler(w http.ResponseWriter, r *http.Request) {
    var t FormStruct

    req, _ := httputil.DumpRequest(r, true)

    log.Println(string(req))
    log.Println(r.Method) // GET
    log.Println(r.Body)

    decoder := json.NewDecoder(r.Body)
    err := decoder.Decode(&t)
    log.Println("Decoding complete")
    if err != nil {
        log.Println("Error")
        panic(err.Error()+"\n\n")
    }
    log.Println(t.Test)

    w.Write([]byte("Upload complete, no errors"))
}

func main() {
    http.HandleFunc("/myurl/", PHandler)    
    fmt.Println("Go Server listening on port 8001")
    http.ListenAndServe(":8001", nil)
}

答案1

得分: 16

解释很简单:因为在注册PHandler时,你使用了"/myurl/"路径(注意末尾的斜杠/!),但是你在浏览器中输入的是/myurl(注意没有末尾的斜杠)。默认情况下,http包的实现会执行(发送)一个重定向请求,所以如果浏览器跟随重定向(它会这样做),新的URL将与注册的路径匹配。

这在http.ServeMux类型的文档中有说明:

> **如果已经注册了一个子树,并且收到了一个不带末尾斜杠的子树根的请求,ServeMux会将该请求重定向到子树根(添加末尾斜杠)。**可以通过单独为不带末尾斜杠的路径注册来覆盖此行为。例如,注册"/images/"会导致ServeMux将对"/images"的请求重定向到"/images/",除非"/images"已经单独注册。

如果你直接将浏览器定向到/myurl/,就不会发生重定向。

或者,如果你只需要处理一个根路径而不是一个子树(例如/myurl),那么只需将处理程序注册到这个单一路径上:

http.HandleFunc("/myurl", PHandler)

然后,将浏览器定向到/myurl,你也不会遇到任何重定向。

...或者如文档建议的那样:如果你确实需要,将这两个路径都注册到你的处理程序上:

http.HandleFunc("/myurl", PHandler)
http.HandleFunc("/myurl/", PHandler)

现在,无论你调用哪个路径(/myurl/myurl/),都会调用你的处理程序,而不会发生任何重定向。

注意:

在你的情况下,当重定向发送回浏览器时,浏览器不会重复POST请求(而只是一个“简单”的GET请求)。

一般来说,浏览器不会将POST数据发送到重定向的URL,因为浏览器无法判断你是否愿意将相同的数据发送到新的URL,这些数据原本是要发送到原始URL的(想想密码、信用卡号码和其他敏感数据)。但不要试图规避它,只需使用你的处理程序的注册路径进行POST,或者使用上述提到的其他提示。

你可以在这里阅读更多相关内容:

为什么HTTP没有POST重定向?

英文:

The explanation is simple: because you used the "/myurl/" path when you registered your PHandler (note the trailing slash /!) but you directed your browser to /myurl (note there is no trailing slash). And by default the http package implementation will perform (send back) a redirect request so if the browser follows it (it will), the new URL will match the registered path.

This is documented at type http.ServeMux:

> If a subtree has been registered and a request is received naming the subtree root without its trailing slash, ServeMux redirects that request to the subtree root (adding the trailing slash). This behavior can be overridden with a separate registration for the path without the trailing slash. For example, registering "/images/" causes ServeMux to redirect a request for "/images" to "/images/", unless "/images" has been registered separately.

Should you direct your browser directly to /myurl/, you would not experience a redirect.

Or if you don't need to handle a rooted subtree but only a single path (e.g. /myurl), then register your handler only to this single path:

http.HandleFunc("/myurl", PHandler)

And then of course direct your browser to /myurl, and you will not experience any redirect either.

...Or as the documentation suggests: register both paths to your handler if you really need it:

http.HandleFunc("/myurl", PHandler)
http.HandleFunc("/myurl/", PHandler)

And now no matter which path you call (/myurl or /myurl/), both will result in calling your handler without any redirection taking place.

Notes:

In your situation when a redirect was sent back to the browser, the browser will not repeat the POST request (but rather just a "simple" GET request).

Generally speaking a browser will not send POST data to a redirect URL because the browser is not qualified to decide if you're willing to send the same data to the new URL what you intended to send to the original URL (think about passwords, credit card numbers and other sensitive data). But don't try to circumvent it, simply use registered path of your handler to POST to, or any of the other tips mentioned above.

You can read more on the subject here:

Why doesn't HTTP have POST redirect?

huangapple
  • 本文由 发表于 2016年3月31日 02:06:28
  • 转载请务必保留本文链接:https://go.coder-hub.com/36316429.html
匿名

发表评论

匿名网友

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

确定