CORS策略不允许并禁止从Angular前端POST到Golang后端方法

时间:2019-01-16 22:04:08

标签: angular go cors

我正在尝试从Angular前端向Golang后端发出发布请求,这两者都是在同一台计算机上进行的。我不断得到:

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

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后端:

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)
}

角度前端组件:

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);
        }
      );
  }
}

这是我尝试过的许多事情:

  • 我读到,设置标题而不是我的标题是浏览器的工作(这对我来说没有意义,因为HttpHeaders()的用途是什么?),所以我从Angular中删除了所有标题。

  • 我尝试在Golang中启用CORS,如here所示:

    (*w).Header().Set("Access-Control-Allow-Methods", "POST, GET, OPTIONS, PUT, DELETE")
    if (*req).Method == "OPTIONS" {
        return
    }
    
  • 我尝试将Access-Control-Allow-Origin*更改为Origin,因为我读到某个地方*阻止了cookie的发送/接收。 (丢失了链接。)也许它也阻止了其他一些MIME类型?我不知道,只是尝试。

  • Mozilla说:“构造函数初始化XMLHttpRequest。必须在任何其他方法调用之前调用它。”因此,我想我会这样做,但是Angular说:“ @ angular / common / http中的HttpClient为Angular应用程序提供了简化的客户端HTTP API,该API基于浏览器公开的XMLHttpRequest接口。”所以,我猜这没有必要吗?构造函数已经包含HttpClient

  • this.Profile看起来很像JSON:{id: testid, username: "Mr Odour", age: "87"}。但这也许是问题所在。因此,我将其放入String()中。然后,我使用Golang的httputil.DumpRequest()从前端转储了响应:

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

    也许这提供了更多的见解。它打印出:

    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
    

我认为这是成功的吗?我不确定。它没有说200 OK。控制台会打印“ POST请求成功”,但不会显示data。我试图用Golang来阅读它:

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

这产生了[]。空的。

它也显示Content-Type: text/plain。那是问题吗?不应该是application/json吗?但是其他人的代码按原样使用它,而没有this这样的String()

return this.http.post<Article>(this.url,
     {
    id: 100, 
    title: 'Java Functional Interface', 
    category: 'Java 8', 
    writer: 'Krishna'
     }
  );
  • 编辑19年1月17日下午12:45 :Billy建议使用Header().Add()代替Header().Set(),如下所示:

    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")
    

    所以,我做到了。也不起作用。

后端似乎还可以。我用curl贴到上面,它产生了:

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

它也可以打印出fmt.Println(member.ID),并且JSON字符串中的所有其他内容都很好。

编辑19年1月17日下午13:25 :当我发出curl -i -X OPTIONS http://localhost:12345/anteroom时,返回:“ Access-Control-Allow-Methods:POST,OPTIONS”。因此,OPTIONS正在工作。但是前端请求保持不变,即使控制台成功记录下来,也没有明确的200 OK和JSON似乎没有通过。作为响应,MIME类型被列为“文本/纯文本”,但是对于JSON来说不是吗?如前所述,其他解决方案使用相同的格式,并且适用于它们。我现在正在解决问题的这一部分。

我是Golang,TypeScript和Angular的新手,所以我希望发布所有相关代码。这似乎是一个很普遍的问题,所以我在这里浏览了很多类似的问题,但仍然无法解决。我到底错过了什么?

3 个答案:

答案 0 :(得分:2)

这不是您在后端进行CORS的方式。您的后端需要侦听HTTP OPTIONS类型的请求,然后在其中发送CORS标头。

控制流程大致为:

  1. 您在前端请求一些东西
  2. 浏览器确定:哦,这需要CORS
  3. 浏览器首先对所请求的资源执行OPTIONS请求以协商CORS
  4. 取决于结果,两个浏览器都会向您的前端抛出错误
  5. 或者继续您的前端发出的实际请求。
  6. 从现在开始,一切几乎都能正常工作
  7. 当浏览器得到您的实际API调用的结果时,它会过滤掉未在步骤3和4中列入白名单/未进行协商的内容。
  8. 它表示由于HTTP调用的结果,可能会将结果审查到前端。

答案 1 :(得分:1)

@ user268396告诉您原因,我将告诉您如何。

Access-Control-Allow-Origin”表示您允许向该服务器发出请求的来源。

它可以采用以下形式:

当您要从CORS发布数据时,

Access-Control-Allow-Methods”应该为[] string(“ POST”,“ OPTION”)。

关于golang,请参阅godoc https://golang.org/pkg/net/http/#Header

type Header map[string][]string

我总是建议使用Header.Add()而不是Header.Set(),除非您确切地知道自己在做什么。因为标头中的每个值始终为[]字符串。

应该是

    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")

答案 2 :(得分:1)

问题得到了解决,这在很大程度上要感谢所有帮助我意识到我没有正确执行CORS的人。基本上,有两个问题:1.我不知道在后端正确设置标头,并且2.我没有正确格式化JSON数据。

我注意到这在Stack Overflow上是一个相当普遍的问题,可能部分是因为我们当中有些人并不真正了解请求的工作方式。因此,这里是一个摘要,摘自Mozilla的宝贵且简单编写的documentation和user268396的输入。我是新手,如果我错了,请纠正我。

通常,这很简单:当您要请求某些内容时,浏览器会根据您已有的内容设置适当的标头,然后将请求发送出去。服务器响应。

但是,假设您在后端有一个API POST端点,并且在同一个域上有一个前端(我想这是我们大多数人正在开发中,尽管可能不在生产中),并且您正在使用除application / x-www-form-urlencoded,multipart / form-data和text / plain,则有所不同。就我而言,我正在使用application / json。因此,它看起来像这样:

  1. 浏览器使用OPTIONS方法发送请求,询问允许使用哪些方法和内容类型以及其他内容。这称为预检请求。它还没有发送我的JSON对象。

  2. 服务器响应预检请求。

要使服务器响应所允许的内容,我们需要正确设置后端以允许Access-Control-Allow-Methods中的OPTIONS方法,如果像我一样,您正在使用Gorilla mux,请记住允许它在router.HandleFunc()中也是如此。

因为我们的前端和后端在同一个域(http://localhost)上,所以最简单的方法是将Access-Control-Allow-Origin设置为“ *”。但是,我们可能不希望根据自己的需要在生产中执行此操作,因为*表示全世界和其他地区都可以发送请求。

因为我想接收JSON,所以我还将后端的Content-Type设置为application / json。这是我问题的另一半。长话短说,无论对其他人有什么用,出于某种原因,我仍然必须将JSON.stringify()应用于原始JSON数据。然后效果很好。

  1. 浏览器一旦收到允许的OPTIONS,就会过滤掉不允许的内容,并仅发送包含适当数据的请求。就我而言,这将是JSON对象。

就是这样。