CORS策略不允许和阻止Angular前端POST到Golang后端方法

I'm trying to make a post request from my Angular front end to my Golang back end, both served from the same machine. I keep getting:

OPTIONS http://localhost:12345/anteroom 405 (Method Not Allowed)

and

Access to XMLHttpRequest at 'http://localhost:12345/anteroom' from origin 'http://localhost:4200' has been blocked by CORS policy: Response to preflight request doesn't pass access control check: No 'Access-Control-Allow-Origin' header is present on the requested resource.

Golang back end, using Gorilla mux router:

func main() {
    router := mux.NewRouter()
    router.HandleFunc("/anteroom", anteroom).Methods("POST")
    log.Fatal(http.ListenAndServe(":12345", router))
}

func anteroom(res http.ResponseWriter, req *http.Request) {
    res.Header().Set("Access-Control-Allow-Origin", "*")
    // *Edit 17 Jan 19 12.44pm*: Billy, user268396, and Peter suggest that OPTIONS should be added. As mentioned below, I've already tried that.
    res.Header().Set("Access-Control-Allow-Methods", "POST")
    res.Header().Set("Content-Type", "application/json")

    // Put it into a struct variable and print a bit of it.
    _ = json.NewDecoder(req.Body).Decode(&member)
    fmt.Println(member.ID)
}

Angular front end component:

export class AnteroomComponent implements OnInit {
  public profile: string;

  constructor(private http: HttpClient, private cookieService: CookieService) {}

  ngOnInit() {}

  // This is the relevant function.
  // Triggered by a button.
  sendProfile(Profile) {
    let httpHeaders = new HttpHeaders({
      "Content-Type": "application/json",
      "Origin": "http://localhost:4200"
    });

    return this.http
      .post("http://localhost:12345/anteroom", this.profile, {
        headers: httpHeaders,
        observe: "response"
      })
      .subscribe(
        data => {
          console.log("POST Request is successful ", data);
        },
        error => {
          console.log("Error", error);
        }
      );
  }
}

Here're some of the many things I tried:

  • I read that it's the browser's job to set headers, not mine (which doesn't make sense to me because then what is HttpHeaders() for?), so I removed all the headers from Angular.

  • I tried enabling CORS in Golang as shown here:

    (*w).Header().Set("Access-Control-Allow-Methods", "POST, GET, OPTIONS, PUT, DELETE")
    if (*req).Method == "OPTIONS" {
        return
    }
    
  • I tried changing Access-Control-Allow-Origin from * to Origin because I read somewhere that * prevents cookies from being sent/received. (Lost the link.) Maybe it prevents some other MIME types too? I don't know, just trying.

  • Mozilla says "The constructor initializes an XMLHttpRequest. It must be called before any other method calls." So I thought I'd do that but then Angular says "The HttpClient in @angular/common/http offers a simplified client HTTP API for Angular applications that rests on the XMLHttpRequest interface exposed by browsers." So, I guess that's not necessary? Constructor's already got HttpClient in it.

  • this.Profile seems pretty standard as JSON: {id: testid, username: "Mr Odour", age: "87"}. But maybe it's the problem. So I put it into String(). Then I dumped the response from the front end with Golang's httputil.DumpRequest():

    output, err := httputil.DumpRequest(req, true)
    if err != nil {
        fmt.Println("Error dumping request:", err)
        return
    }
    fmt.Println(string(output))
    

    This provided a bit more insight, maybe. It printed out:

    POST /anteroom HTTP/1.1
    Host: localhost:12345
    Accept: application/json, text/plain, */*
    Accept-Encoding: gzip, deflate, br
    Accept-Language: en-US,en;q=0.9,en-GB;q=0.8
    Connection: keep-alive
    Content-Length: 15
    Content-Type: text/plain
    Dnt: 1
    Origin: http://localhost:4200
    Referer: http://localhost:4200/anteroom
    User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML,
     like Gecko) Chrome/71.0.3578.98 Safari/537.36
    

I think it came through? I'm not sure. It doesn't say 200 OK. Console does print "POST Request is successful ", but without the data. I tried to read it with this in Golang:

body, err := ioutil.ReadAll(req.Body)
    if err != nil {
        fmt.Println(err)
    }
    fmt.Println(body)

This produced []. Empty.

It also says Content-Type: text/plain. Is that the problem? Shouldn't it be application/json? But other people's code uses it the way it is, without String(), like this:

return this.http.post<Article>(this.url,
     {
    id: 100, 
    title: 'Java Functional Interface', 
    category: 'Java 8', 
    writer: 'Krishna'
     }
  );
  • Edit 17 Jan 19 12.45pm: Billy suggests to use Header().Add() instead of Header().Set() like so:

    res.Header().Add("Access-Control-Allow-Origin", "*")
    res.Header().Add("Access-Control-Allow-Methods", "POST")
    res.Header().Add("Access-Control-Allow-Methods", "OPTIONS")
    res.Header().Add("Content-Type", "application/json")
    

    So, I did. Didn't work either.

The back end seems ok. I posted to it with curl and it produces:

Connected to localhost (::1) port 12345 (#0)
> POST /anteroom HTTP/1.1
> Host: localhost:12345
> User-Agent: curl/7.47.1
> Accept: */*
> Content-Type: application/json
> Content-Length: 126
>
} [126 bytes data]
* upload completely sent off: 126 out of 126 bytes
< HTTP/1.1 200 OK
*Edit 17 Jan 19 12.45pm*: Methods contains OPTIONS once the header is set in backend. So, no problem with curl this way.
< Access-Control-Allow-Methods: POST
< Access-Control-Allow-Origin: *
< Date: Tue, 15 Jan 2019 19:34:32 GMT
< Content-Length: 0

It also printed out fmt.Println(member.ID) and everything else from the JSON string just fine.

Edit 17 Jan 19 13.25pm: When I issue curl -i -X OPTIONS http://localhost:12345/anteroom, this comes back: "Access-Control-Allow-Methods: POST, OPTIONS". So OPTIONS is working. But front end request remains the same, no explicit 200 OK and JSON doesn't seem to go through even though console logs it as a success. The MIME type is listed as "text/plain" in response, but isn't that ok for JSON? As mentioned, other solutions use the same format and it works for them. I'm working on this part of the problem now.

I'm new to Golang, TypeScript, and Angular, so I hope I'm posting all the relevant code. It seems like a popular problem so I looked through quite a handful of similar questions on here but still can't figure it out. What on earth have I missed?

douhutongvm382381
douhutongvm382381 抱歉,我忘记在router.HandleFunc()中添加“OPTIONS”。curl现在正在返回选项,但前端仍未发送JSON。
一年多之前 回复
dqfxao2898
dqfxao2898 我的一部分想和那一起去。我一部分说:“不!我必须了解发生了什么!”但是,谢谢。它可能归结为那个。
一年多之前 回复
douxian4376
douxian4376 我也尝试过在后端设置和添加OPTIONS。但是,运行curl-i-XOPTIONS产生的http://localhost:12345/anteroom:TP/1.1405方法不允许。我现在的猜测是,以某种方式,OPTIONS作为后端中的标头失败了。我不知道为什么。
一年多之前 回复
dongyong8098
dongyong8098 “[...]405(不允许使用方法)”的意思恰如其分:您的服务器不允许OPTIONS请求,因为您只为POST请求配置了一条路由,而没有为OPTIONS配置任何路由。
一年多之前 回复
drsfgwuw61488
drsfgwuw61488 我在flask中遇到了这个问题,我的解决方法是将nginx安装在我的开发盒上,将前端运行在/上,将后端运行在/api/上,这完全解决了CORS错误,因为两者都位于浏览器的同一服务器和端口上透视。可能不是您要找的解决方案,但是它为我解决了这个令人头疼的问题。我在生产中使用了类似的端点配置,但仍在不同的localhost端口上运行应用程序服务器。
一年多之前 回复

3个回答



这不是您在后端执行 CORS </ code>的方式。 您的后端需要侦听HTTP OPTIONS </ code>类型的请求并在其中发送CORS标头。</ p>

控制流程大致为:</ p>

< ol>

您在前端请求了一些内容</ li>
浏览器确定:嗯,这需要CORS </ li>
浏览器首先执行OPTIONS请求以 协商结果所需的资源</ li>
取决于结果,要么浏览器向您的前端抛出错误</ li>
,要么继续您的前端发出的实际请求。</ li >
从这里开始一切正常工作</ li>
当浏览器获得实际API调用的结果时,它会过滤掉在步骤3和步骤3中未列入白名单/未进行谈判的内容 4。</ li>
它表示HTTP调用的结果可能会将审查的结果屏蔽到前端。</ li>
</ ol>
</ div>

展开原文

原文

This is not how you do CORS on the backend. Your backend needs to listen to HTTP OPTIONS type requests and send the CORS headers there.

The control flow is roughly:

  1. You request something in the frontend
  2. The browser determines: uh-oh, this requires CORS
  3. The browser first performs an OPTIONS request to the requested resources to negotiate CORS
  4. Depending on the result, either browser throws your frontend an error
  5. Or it continues with the actual request your frontend issued.
  6. Things work almost normally from here on out
  7. When the browser gets the result of your actual API call back it filters out things that have not been whitelisted/negotiated in steps 3 and 4.
  8. It presents that potentially censored result to the frontend as the result of the HTTP call.

dongzhe3171
dongzhe3171 抱歉,我忘记在router.HandleFunc()中添加“ OPTIONS”。 curl现在正在返回选项,但前端仍未发送JSON。
一年多之前 回复
douxiong4250
douxiong4250 感谢您的解释。 现在,我对该过程有了更清晰的了解。 我确实尝试在后端设置OPTIONS。 结果相同。 但是,运行curl -i -X OPTIONS产生的http:// localhost:12345 / anteroom:TP / 1.1 405方法不允许。 我现在的猜测是,以某种方式在后端中设置/添加OPTIONS作为标头失败了。
一年多之前 回复

Problem solved thanks in large part to everyone who helped me realise I wasn't doing CORS properly. Basically, there were two problems: 1. I didn't know to set headers properly in the back end, and 2. I didn't format my JSON data properly.

I noticed that this is a fairly popular problem on Stack Overflow, possibly in part because some of us don't really understand how requests work. So here is a summary, drawn from Mozilla's invaluable and simply written documentation and user268396's input. I'm new to this so please correct me if I'm wrong.

Usually, it's straightforward: when you want to request something, the browser sets the appropriate headers based on whatever content you already have, and sends the request out. Server responds.

But, assuming you have an API POST endpoint on your back end and a front end on the same domain (I suppose that's most of us in development, though maybe not in production), and you're working with anything other than application/x-www-form-urlencoded, multipart/form-data, and text/plain, then it's a bit different. In my case, I'm working with application/json. So, it looks like this:

  1. Browser sends request with the OPTIONS method asking for what methods and content-types and other things that are allowed. This is called a preflight request. It doesn't send my JSON object yet.

  2. Server responds to the preflight request.

For the server to respond with the allowed stuff, we need to properly set the back end to allow the OPTIONS method in Access-Control-Allow-Methods, and if like me you're using Gorilla mux, remember to allow it in router.HandleFunc() too.

Because our front and back ends are on the same domain (http://localhost), the easiest thing to do is to set Access-Control-Allow-Origin to "*". However, we probably don't want to do this in production depending on our needs because * means all the world and beyond gets to send in requests.

Because I want to receive JSON, I also set Content-Type in the back end to application/json. This was the other half of my problem. Long story short, no matter what worked for other people, for some reason I still had to apply JSON.stringify() to my raw JSON data. Then it worked perfectly.

  1. Once the browser receives the allowed OPTIONS back, it filters out the stuff that's not allowed, and sends back a request with only the appropriate data. In my case, this would be the JSON object.

And that's it.



@ user268396告诉了您原因,我将告诉您如何。</ p>

Access-Control-Allow-Origin </ code>”表示您允许向此服务器发出请求的来源。 </ p>

它可以采用以下形式:</ p>


  • “ *” </ li>
  • https://github.com “ </ li>
  • http://127.0.0.1:8080 ” </ li>
  • “ chrome-extension:// *” </ li>
    < li>“ *。some-domain.com” </ li>
    </ ul>

    Access-Control-Allow-Methods </ code>”应该为[] string( 要从CORS发布数据时,请输入“ POST”,“ OPTION”)。</ p>

    涉及golang时,请参阅godoc https://golang.org/pkg/net/http/#Header </ p>

      type  Header map [string] [] string 
    </ code> </ pre>

    我总是建议使用 Header.Add()</ code>代替 Header。 除非您确切知道自己在做什么,否则请设置()</ code>。 因为标头中的每个值始终为[]字符串。 </ p>

    应该是</ p>

      res.Header()。Add(“ Access-Control-Allow-Origin”,“ *”  )
    res.Header()。Add(“ Access-Control-Allow-Methods”,“ POST”)
    res.Header()。Add(“ Access-Control-Allow-Methods”,“ OPTION”)\ n res.Header()。Add(“ Content-Type”,“ application / json”)
    </ code> </ pre>
    </ div>

展开原文

原文

@user268396 had told you why, and I will tell you how.

"Access-Control-Allow-Origin" means which origin you allowed to make request to this server.

It can be in forms like:

"Access-Control-Allow-Methods" should be []string("POST", "OPTION") when you want to post data from CORS.

When it come to golang, see godoc https://golang.org/pkg/net/http/#Header

type Header map[string][]string

And I always suggest to use Header.Add() instead of Header.Set() unless you know exactly what you are doing. Because each value in header always be []string.

So it should be

    res.Header().Add("Access-Control-Allow-Origin", "*")
    res.Header().Add("Access-Control-Allow-Methods", "POST")
    res.Header().Add("Access-Control-Allow-Methods", "OPTION")
    res.Header().Add("Content-Type", "application/json")

dongyao2001
dongyao2001 公共资料:字符串; 和.post(“ http:// localhost:12345 / anteroom”,this.profile,,要以json格式将字符串作为正文发布,因此该正文为字符串。您应该尝试.post(url,{id: '11111',名称:'aaaaa'}之类的。
一年多之前 回复
dse55384
dse55384 json部分是另一个问题。
一年多之前 回复
dongqi9125
dongqi9125 如果将原点设置为“ *”,则下一步是仅以角度调用http.post()。 在chrome中打开开发工具,选中“网络”,您将看到两个请求。 第一个是“ option”,第二个是“ post”。 CORS部分已完成。
一年多之前 回复
duanjucong3124
duanjucong3124 抱歉,我忘记在router.HandleFunc()中添加“ OPTIONS”。 curl现在正在返回选项,但前端仍未发送JSON。
一年多之前 回复
doormen2014
doormen2014 感谢您的解释。 现在将Origin设置为“ *”,并且像您说的那样尝试了Add()。 结果相同。 但是,运行curl -i -X OPTIONS产生的http:// localhost:12345 / anteroom:TP / 1.1 405方法不允许。 我现在的猜测是,以某种方式,OPTIONS作为后端中的标头失败了。 我不知道为什么。
一年多之前 回复
Csdn user default icon
上传中...
上传图片
插入图片
抄袭、复制答案,以达到刷声望分或其他目的的行为,在CSDN问答是严格禁止的,一经发现立刻封号。是时候展现真正的技术了!
立即提问