http 跨域资源共享详解
由于浏览器同源策略限制,会导致出现跨域问题。而跨域资源共享(CORS)可以突破浏览的同源策略的限制,不过需要服务端配合设置相应的响应头,从而使跨源数据传输得以安全进行。
跨域资源共享新增了一些HTTP字段,用于与服务器相互配合。
Origin: http://foo.example
(告诉服务器来自哪个域, 浏览器自动设置,不允许手动设置)
Access-Control-Request-Method: POST
(告诉服务器使用的请求方式)
Access-Control-Request-Headers: X-PINGOTHER, Content-Type
(告诉服务器要携带的特殊请求头字段)
而服务器也需要相应设置这些内容
Access-Control-Allow-Origin: http://foo.example
(允许这个域的请求访问服务器资源,如果为*代表允许任何域的请求访问服务器资源, 手动设置)
Access-Control-Allow-Methods: POST, GET, OPTIONS
(允许这些种类的请求方式访问服务器资源)
Access-Control-Allow-Headers: X-PINGOTHER, Content-Type
(允许请求头中可以包含这些特殊字段)
Access-Control-Max-Age: 86400
(设置预检请求的缓存时间,即针对同一个跨域的复杂请求,不必每次都先发送一次预检请求,该属性不是必须)
跨域资源共享的规范要求,对于一些可能对服务器数据产生副作用的http方法(特别是 GET 以外的 HTTP 请求,或者搭配某些 MIME 类型 的 POST 请求),浏览器必须首先使用 OPTIONS 方法发起一个预检请求,从而获知服务端是否允许该跨域请求。服务器确认允许之后,才发起实际的HTTP请求。在预检请求的返回中,服务器端也可以通知客户端,是否需要携带身份凭证。
预检(preflight request)
预检用来检查服务器是否支持跨域资源共享。
预检并不需要前端开发者手动发送,当需要时,浏览器会自动发送(使用OPTIONS)
fetch 标准
对于XMLHttpRequest来说,可以使用以下三种场景来描述跨源资源共享机制的工作原理。
工作原理
简单请求
某些请求不会触发 CORS 预检请求。在废弃的 CORS spec 中称这样的请求为简单请求,但是目前的 Fetch spec(CORS 的现行定义规范)中不再使用这个词语。
当满足下面所有条件,这个请求就是简单请求:
- 使用请求方法为
GET、HEAD、POST的其中一个 - 不得手动设置除
Accept、Accept-Language、Content-Language、Content-Type外的请求头。 Content-Type的值只能为text/plain、multipart/form-data、application/x-www-form-urlencoded的其中一个
const xhr = new XMLHttpRequest();
xhr.open("GET", "http://localhost:3000");
xhr.onreadystatechange = function () {
console.log("readyState:" + xhr.readyState);
};
xhr.send();
请求标头
Accept: */*
Accept-Encoding: gzip, deflate, br
Accept-Language: zh-CN,zh;q=0.9
Cache-Control: no-cache
Connection: keep-alive
Content-Length: 0
Host: localhost:3000
Origin: http://localhost:8080
Pragma: no-cache
Referer: http://localhost:8080/
Sec-Fetch-Dest: empty
Sec-Fetch-Mode: cors
Sec-Fetch-Site: same-site
User-Agent: Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/86.0.4240.198 Safari/537.36
响应标头
Access-Control-Allow-Origin: http://localhost:8080
Connection: keep-alive
Date: Thu, 17 Nov 2022 15:10:23 GMT
Keep-Alive: timeout=5
Transfer-Encoding: chunked
预检请求
与前述简单请求不同,“需预检的请求”要求必须首先使用 OPTIONS 方法发起一个预检请求到服务器,以获知服务器是否允许该实际请求。
const xhr = new XMLHttpRequest();
xhr.open("GET", "http://localhost:3000");
xhr.setRequestHeader("Content-Type", "application/json;charset=utf-8");
xhr.onreadystatechange = function () {
console.log("readyState:" + xhr.readyState);
};
xhr.send(JSON.stringify({ data: 1 }));
通过浏览器的控制台可以看到实际上发了两个请求

上面请求头的Content-Type为application/json;charset=utf-8,因此,该请求需要首先发起“预检请求”。
预检请求标头
Accept: */*
Accept-Encoding: gzip, deflate, br
Accept-Language: zh-CN,zh;q=0.9,en;q=0.8
Access-Control-Request-Headers: content-type
Access-Control-Request-Method: GET
...
实际请求标头
Accept: */*
Accept-Encoding: gzip, deflate, br
Accept-Language: zh-CN,zh;q=0.9,en;q=0.8
...
Access-Control-Max-Age用于设置预检请求得缓存时间,即对于同一个跨域得复杂请求,不需要每次都发送一次预检请求。
实际的请求不会携带 Access-Control-Request-* 首部,它们仅用于 OPTIONS 请求。
附带身份凭证的请求
一般而言,对于跨域 XMLHttpRequest 或 Fetch 请求,浏览器不会发送身份凭证信息。如果要发送凭证信息,需要进行额外设置(同源请求会默认带上Cookie, 当响应中携带Cookie,浏览器会将Cookie进行存储,当请求时携带上去)。
当跨域时,对于XMLHttpRequest对象,需要将实例的withCredentials设置成true,同时服务端也需要设置对应的响应头Access-Control-Allow-Credentials设置成true。
以node为例
const app = http.createServer((req, res) => {
res.writeHead(200, {
"Access-Control-Allow-Origin": "http://localhost:8080",
"Access-Control-Allow-Credentials": true,
"Set-Cookie": serialize("name", "leo", "utf8", { maxAge: "6000" }),
});
});

![[Spring Cloud] Hystrix通过配置文件统一设置参数/与OpenFeign结合使用](https://img-blog.csdnimg.cn/f45df8692b7d4e56be3ddbe34b3aea9b.png)

















