我正试图从我的Angular前端向我的Golang后端发出一个post请求,两者都来自同一台机器。我不断得到:
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后端,使用Gorilla mux路由器:
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中删除了所有标题。我尝试在果朗启用CORS,如下所示:
(*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/htt中的HttpClient为Angular应用程序提供了一个简化的客户端http API,该客户端基于浏览器公开的XMLHttpRequest接口。"所以,我想这不是必要的吗?构造函数已经包含
HttpClient
。作为JSON:
{id: testid, username: "Mr Odour", age: "87"}
,this.Profile
看起来相当标准。但也许这就是问题所在。所以我把它放进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)
这产生了CCD_ 14。空的
它还说Content-Type: text/plain
。这就是问题所在吗?不应该是application/json
吗?但其他人的代码使用它的方式,没有String()
,就像这样:
return this.http.post<Article>(this.url,
{
id: 100,
title: 'Java Functional Interface',
category: 'Java 8',
writer: 'Krishna'
}
);
编辑17 Jan 19下午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
时,它会返回:"访问控制允许方法:POST,OPTIONS"。因此OPTIONS正在发挥作用。但前端请求保持不变,没有明确的200 OK,即使控制台将其记录为成功,JSON似乎也不会通过。MIME类型在响应中被列为"text/plain",但这对JSON来说不好吗?如前所述,其他解决方案使用相同的格式,并且对它们有效。我现在正在处理这部分问题。
我是Golang、TypeScript和Angular的新手,所以我希望我能发布所有相关的代码。这似乎是一个流行的问题,所以我在这里看了很多类似的问题,但仍然无法弄清楚。我到底错过了什么?
这不是在后端执行CORS
的方式。您的后端需要监听HTTPOPTIONS
类型的请求,并在那里发送CORS头。
控制流程大致为:
- 你在前端请求一些东西
- 浏览器确定:哦,这需要CORS
- 浏览器首先对请求的资源执行OPTIONS请求以协商CORS
- 根据结果,任一浏览器都会向前端抛出错误
- 或者它继续执行您的前端发出的实际请求
- 从现在开始一切正常
- 当浏览器得到实际API回调的结果时,它会过滤掉步骤3和4中未列入白名单/协商的内容
- 它将潜在的审查结果作为HTTP调用的结果呈现给前端
问题的解决在很大程度上要感谢所有帮助我意识到我没有正确执行CORS的人。基本上有两个问题:1。我不知道如何在后场正确地设置头球,还有2个。我没有正确格式化JSON数据。
我注意到这是Stack Overflow上一个相当流行的问题,部分原因可能是我们中的一些人并不真正了解请求是如何工作的。因此,这里有一个总结,来自Mozilla宝贵的、简单的书面文档和用户268396的输入。我是新手,所以如果我错了,请纠正我。
通常情况下,这很简单:当你想请求某个内容时,浏览器会根据你已经拥有的内容设置适当的标题,并发送请求。服务器响应。
但是,假设您在后端有一个API POST端点,在同一个域上有一个前端(我想我们大多数人都在开发中,但可能不是在生产中),并且您使用的是application/x-www-form-urlencoded、multipart/form-data和text/plain之外的任何东西,那么这就有点不同了。在我的例子中,我使用的是application/json。看起来是这样的:
-
Browser使用OPTIONS方法发送请求,询问允许的方法和内容类型以及其他内容。这被称为飞行前请求。它还没有发送我的JSON对象。
-
服务器响应飞行前请求。
为了让服务器用允许的东西进行响应,我们需要在Access Control allow Methods中正确设置后端以允许OPTIONS方法,如果你像我一样使用Gorilla mux,请记住在router.HandleFunc()
中也允许它。
因为我们的前端和后端在同一个域上(http://localhost),最简单的方法是将Access Control Allow Origin设置为"*"。然而,根据我们的需求,我们可能不想在生产中这样做,因为*意味着全世界和其他地方都可以发送请求。
因为我想接收JSON,所以我还在后端将ContentType设置为application/JSON。这是我问题的另一半。长话短说,无论什么对其他人有效,出于某种原因,我仍然必须将JSON.stringify()
应用于我的原始JSON数据。然后它完美地工作了。
- 一旦浏览器收到允许的OPTIONS,它就会过滤掉不允许的内容,并只发送一个包含适当数据的请求。在我的情况下,这将是JSON对象
就这样。
@user268396告诉过你为什么,我会告诉你怎么做。
"Access-Control-Allow-Origin
"表示您允许向该服务器发出请求的来源。
它可以是以下形式:
- "*">
- "https://github.com">
- "http://127.0.0.1:8080">
- "镀铬加长件://*">
- "*.some domain.com">
当您想要从CORS发布数据时,"Access-Control-Allow-Methods
"应该是[]字符串("POST","OPTION")。
当它来到戈兰,见godochttps://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")