CORS入门笔记


CORS In Action阅读笔记

CORS(Cross-Origin Resource Sharing)是一种跨域技术,简单的说,就是在浏览器中,A网站是否可以对B网站发起HTTP请求。
CORS In Action这本书对CORS做了详细的介绍。

Hello CORS!

CORS is simply a way of making HTTP requests from one place to another. This is a
trivial thing in other programming languages. But it’s difficult to do in client-side
JavaScript, because for years the browser’s same-origin policy has explicitly prevented
these types of requests.

CORS是一种简单的跨域请求方法。对于其他语言来说(比如Python,Java)是很容易的。但是对于客户端的JavaScript
语言是很困难的,因为浏览器的同源策略会明确阻止这种请求。

这句话是什么意思呢?

比如有一个简单的存储接口是这样的:

1
2
3
4
5
6
7
8
#coding=utf-8
from bottle import post, run
@post('/save')
def save_name():
return 'hello'
run(host='localhost', port=8080)

用以下的Python代码很简单就能访问的:

1
2
3
4
5
6
7
8
#coding=utf-8
import requests
url = 'http://localhost:8080/save'
resp = requests.post(url=url)
print resp.text

但是如果在浏览器中,不同于localhost:8080这个域,使用ajax请求 就会报错:

浏览器跨域失败

说明浏览器中的跨域ajax请求是失败的。

CORS的原理在于,对目标网站进行请求时候,会加上特定的HTTP请求头,从而让跨域请求在浏览器中变得合法。

请求过程如下(图片来自原书):

CORS过程

1.CORS请求由浏览器中的JavaScript代码产生;
2.浏览器在请求前加入额外的HTTP请求头;
3.服务端校验跨域请求是否合法;
4.如果请求允许,浏览器把响应发给客户端JavaScript代码。

注意,这里被拦截的原因是浏览器的安全策略,实际的请求是发给了服务端的,我们在服务端是可以看到有请求过来。
但是console中显示的错误,是因为浏览器拦截了,通过WireShark也能看出,响应已经发回去了:

服务端响应

一个CORS请求的实例(跨域访问Flickr的API得到图片):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
<!DOCTYPE html>
<html>
<body onload="loadPhotos();">
<div id="photos">Loading photos...</div>
<script>
function loadPhotos() {
var api_key = '9931ec9f35941c37b259a0752a884224';
var method = 'GET';
var url = 'https://api.flickr.com/services/rest/?' +
'method=flickr.people.getPublicPhotos&' +
'user_id=32951986%40N05&' +
'extras=url_q&format=json&nojsoncallback=1&' +
'api_key=' + api_key;
var xhr = new XMLHttpRequest();
xhr.open(method, url);
xhr.onerror = function() {
alert('There was an error.');
};
xhr.onload = function() {
var data = JSON.parse(xhr.responseText);
if (data.stat == 'ok') {
var photosDiv = document.getElementById('photos');
photosDiv.innerHTML = '';
var photos = data.photos.photo;
// 设置图片路径
for (var i = 0; i < photos.length; i++) {
var img = document.createElement('img');
img.src = photos[i].url_q;
photosDiv.appendChild(img);
}
} else {
alert(data.message);
}
};
xhr.send();
}
</script>
</body>
</html>

响应头如下:

CORS响应头实例

请求头如下:

CORS请求头

单个图片响应头如下:

CORS响应头实例
可以看出 响应头 中带有 Access-Control-Allow-Origin 字段,表示跨域请求允许的域名。

上面的实例可以看出CORS的基本用法,那么CORS有什么好处呢?

1.实现浏览器跨域访问资源;
2.限制由服务端负责,允许何种请求,允许谁请求,都由服务端决定,安全性好;
3.灵活性好,可以通过配置来决定哪些请求合法;
4.对开发者简单,使用XMLHttpRequest对象;

下面来了解下实例代码所涉及的Ajax知识。

1.setRequestHeader函数

使用setRequestHeader函数,可以给XMLHttpRequest对象添加HTTP请求头,使用方法如:

1
2
3
4
5
// 实例来自 http://www.w3school.com.cn/tiy/t.asp?f=ajax_post2
xmlhttp.open("POST","/ajax/demo_post2.asp",true);
xmlhttp.setRequestHeader("Content-type","application/x-www-form-urlencoded");
xmlhttp.send("fname=Bill&lname=Gates");

使用需要在open函数之后,send函数之前,不然会报错:

1
Uncaught DOMException: Failed to execute 'setRequestHeader' on 'XMLHttpRequest': The object's state must be OPENED.

有特殊含义的请求头添加是无效的,即使添加上,也会被忽略:

Here is the list of headers that cannot be set by the setRequestHeader method:
Accept-Charset, Accept-Encoding, Access-Control-Request-Headers, Access-ControlRequest-Method,
Connection, Content-Length, Cookie, Cookie2, Date, DNT, Expect,
Host, Keep-Alive, Origin, Referer, TE, Trailer, Transfer-Encoding, Upgrade, User-Agent,
Via, and any headers starting with ‘Proxy-’ or ‘Sec-’.
These headers have special meaning and can only be set by the browser. There is no
error if the code tries to set the header. The value is just ignored.

  • Accept-Charset
  • Accept-Encoding
  • Access-Control-Request-Headers
  • Access-ControlRequest-Method,
  • Connection
  • Content-Length
  • Cookie
  • Cookie2
  • Date
  • DNT
  • Expect
  • Host
  • Keep-Alive
  • Origin
  • Referer
  • TE
  • Trailer
  • Transfer-Encoding
  • Upgrade
  • User-Agent
  • Via
  • Proxy-*
  • Sec-*

这些HTTP请求头有着特殊的含义,只能由浏览器本身添加,即便在代码中添加,也不会生效。

2.timeout参数

Once the send method is called, the HTTP request is sent to the server. Even though
the request has been sent, there are still a couple of ways to cancel the request. First, the
timeout property can be used to ensure that the request doesn’t exceed a certain
number of milliseconds. Setting the timeout property to 10000 will kill the request
after 10 seconds. The default value for the timeout property is 0, which means there is
no timeout, and the request will continue until the server responds. Second, the client
can manually kill the request by using the abort method. Calling the abort method
will abort the request immediately.

一旦调用send方法,HTTP请求会发给服务端。即使请求发送了,还是有一些方法取消请求。
第一种方法是timeout属性,可以设置为若干毫秒,超过这个数字,就会超时。
设置timeout为10000会在10秒后取消请求。默认的timeout值为0,表示没有超时时间,会一直等到服务器响应。
第二种方法是使用abort方法,调用abort方法会直接终止请求。

3.异步和同步请求

By default, the XMLHttpRequest object makes asynchronous requests. This means
that the send method makes the request in the background, and fires events when
the status of the request changes. The XMLHttpRequest object can also make synchronous
requests. In a synchronous request, the send method will wait until the
response is received (or an error is encountered).

默认情况下,XMLHttpRequest对象发送异步请求。这意味着,send函数发送的请求在后台,根据请求状态的改变
来触发事件。XMLHttpRequest对象也能被设置为同步请求。在同步请求中,send方法会在响应(或者错误)到来前等待。

使用同步请求的话,页面会在服务端响应前阻塞住,所以需要尽可能避免使用同步请求。

4.XMLHttpRequest对象的事件处理函数表

事件名称 说明
onloadstart Fires when the request starts.
onprogress Fires when sending and loading data.
onabort Fires when the request has been aborted by calling the abort method.
onerror Fires when the request has failed.
onload Fires when the request has successfully completed.
ontimeout Fires when the timeout has been exceeded (if the client code specified a timeout value).
onloadend Fires when the request has completed, regardless of whether there was an error or not.
onreadystatechange Legacy handler from the previous version of XMLHttpRequest; fires when legacy readyState property changes. It is superseded by other events and is only useful for non-tier 1 browsers.

有些事件可能只会触发一次,有些可能触发多次,如下图,来自原书。

ajax请求

5.XMLHttpRequest响应对象的属性表

属性名称 说明
status The HTTP status code (for example, 200 for a successful request).
statusText The response string returned by the server (for example, OK for a successful request).
response The body of the response, in the format defined by responseType. If the client indicated that the response type is json, the response will be a JSON object parsed from the response body.
responseText The body of the response as a string. Can only be used if responseType was not set or was set as text.
responseXML The body of the response as a DOM element (XML is here for historical reasons). Can only be used if responseType was not set or was set as document.

getResponseHeader函数和getAllResponseHeaders函数可以读取响应的返回头。
默认情况下,CORS请求可以读取以下的响应头:

■ Cache-Control
■ Content-Language
■ Content-Type
■ Expires
■ Last-Modified
■ Pragma

6.CORS 和 Cookie

Same-origin HTTP requests will always contain the cookie in the
request. In contrast, cross-origin requests don’t include cookies by default.

同源的HTTP请求一般都会带上cookie请求。不同的是,跨域请求默认不会包括cookie。

可以通过设置XMLHttpRequest对象的withCredentials属性来加上cookie。
不过加上这一行也可能导致请求失败:

xhr.withCredentials = true;

需要服务端明确表示允许带有cookie请求。

注意: withCredentials 在同步模式下无法使用。

7.使用Jquery进行跨域请求

实例如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
<!DOCTYPE html>
<html>
<body onload="loadPhotos();">
<div id="photos">Loading photos...</div>
<script src="http://ajax.googleapis.com/ajax/libs/jquery/2.1.1/
jquery.min.js"></script>
<script>
function loadPhotos() {
var api_key = '9931ec9f35941c37b259a0752a884224';
var method = 'GET';
var url = 'https://api.flickr.com/services/rest/?' +
'method=flickr.people.getPublicPhotos&' +
'user_id=32951986%40N05&' +
'extras=url_q&format=json&nojsoncallback=1&' +
'api_key=' + api_key;
$.ajax(url, {
type: method,
dataType: 'json',
succuess: function(data, textStatus, jqXHR) {
if (data.stat == 'ok') {
$("#photos").empty();
$.each(data.photos.photo, function(i, photo) {
var img = $('<img>').attr('src', photo.url_q);
$('#photos').append(img);
});
} else {
alert(data.message);
}
},
error: function(jqXHR, textStatus, errorThrown) {
alert('There was an error.');
}
});
}
</script>
</body>
</html>

在服务端配置CORS


CORS In Action使用Node.jsExpress框架来举例,如何配置服务端CORS。

一个简单的API实例:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
var express = require('express'); // 引入express框架
// 数据
var POSTS = {
'1': {'post': 'This is the first blog post.'},
'2': {'post': 'This is the second blog post.'},
'3': {'post': 'This is the third blog post.'}
};
var SERVER_PORT = 9999; // 端口
var serverapp = express();
serverapp.use(express.static(__dirname));
// 请求路由,和响应函数
serverapp.get('/api/posts', function(req, res) {
res.json(POSTS);
});
// 监听端口
serverapp.listen(SERVER_PORT, function() {
console.log('Started server at http://127.0.0.1:' + SERVER_PORT);
});


浏览器打开http://127.0.0.1:9999/api/posts 会返回POSTS数据。

下一步在同一个域下,建立一个静态页面,在加载页面时候请求API,显示到页面上,
hello.html:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>ajax request</title>
<style>
.post {margin-bottom: 20px;}
</style>
</head>
<body onload="getBlogPosts();">
<div id="output">
</div>
<script>
// 建立一个XMLHttpRequest对象 并打开链接
var createXhr = function(method, url) {
var xhr = new XMLHttpRequest();
xhr.onerror = function() {
document.getElementById('output').innerHTML = 'ERROR';
};
xhr.open(method, url, true);
return xhr;
};
var getBlogPosts = function() {
var xhr = createXhr("GET", "http://127.0.0.1:9999/api/posts");
// 得到请求数据,并显示到页面上
xhr.onload = function() {
var data = JSON.parse(xhr.responseText);
var elem = document.getElementById('output');
for (var postId in data) {
var postText = data[postId]['post'];
var div = document.createElement('div');
div.className = 'post';
div.appendChild(document.createTextNode(postText));
elem.appendChild(div);
}
};
xhr.send();
};
</script>
</body>
</html>


打开http://127.0.0.1:9999/hello.html会发现,同源的Ajax请求是正常的。

现在,复制一份代码,新的代码换到8888端口,开启两个服务,从8888端口的hello.html请求
9999的API服务,发现是会报错的:

跨域错误

注意:一定是通过浏览器访问的,而且服务端返回的状态码是200,但是还是没法显示出来内容。

从8888端口请求9999端口的API,会带有一个Origin的HTTP请求头:

Origin:http://127.0.0.1:8888
表示自己的域

由于没有设置跨域许可,所以不能正确得到内容。

现在修改9999端口的服务代码,如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
var express = require('express'); // 引入express框架
// 数据
var POSTS = {
'1': {'post': 'This is the first blog post.'},
'2': {'post': 'This is the second blog post.'},
'3': {'post': 'This is the third blog post.'}
};
// 加入设置CORS的函数
var handleCors = function(req, res, next) {
// *表示允许任何域来请求数据,可以对特定的域进行设置
res.set('Access-Control-Allow-Origin',"*");
next();
}
var SERVER_PORT = 9999; // 端口
var serverapp = express();
serverapp.use(express.static(__dirname));
serverapp.use(handleCors);
// 请求路由,和响应函数
serverapp.get('/api/posts', function(req, res) {
res.json(POSTS);
});
// 监听端口
serverapp.listen(SERVER_PORT, function() {
console.log('Started server at http://127.0.0.1:' + SERVER_PORT);
});


再打开http://127.0.0.1:8888/hello.html 就可以了。

API的响应头如下:
1
2
3
4
5
6
7
Access-Control-Allow-Origin:*
Connection:keep-alive
Content-Length:134
Content-Type:application/json; charset=utf-8
Date:Wed, 08 Feb 2017 06:44:14 GMT
ETag:W/"86-KZ6XaVbtcZMWONcZEw0BIg"
X-Powered-By:Express


这里多了一个Access-Control-Allow-Origin,表示跨域是允许的,浏览器看到这里,就不会拦截返回的内容了。

书中有提到个细节,那就是localhost和127.0.0.1是不同的域,所以它们之间是无法直接请求的。

处理preflight请求

对于简单的GET请求,配置上对应域的Access-Control-Allow-Origin头就可以了,但是对于像
DELETE或者PUT这种请求,需要在发送真正的请求前发送preflight请求,用于询问服务端的许可。

The browser asks for permissions by using what is called a preflight request. A
preflight request is a small request that is sent by the browser before the actual
request. It contains information like which HTTP method is used, as well as if any
custom HTTP headers are present. The preflight gives the server a chance to examine
what the actual request will look like before it’s made. The server can then indicate
whether the browser should send the actual request, or return an error to the
client without sending the request.

浏览器请求许可的请求就是preflight请求。preflight请求在真正的请求之前被浏览器发送。
它包含了使用何种HTTP方法,以及目前是否含有订制的HTTP请求头。preflight请求给了服务端一个检查
将要发送的请求是何种样子的机会。服务端会表明,浏览器是否可以发送真正的请求,或者不让浏览器发送请求并
返回错误。

为什么需要preflight请求

The concept of a preflight was introduced to allow cross-origin requests to be made
without breaking existing servers that depend on the browser’s same-origin policy. If
the preflight hits a server that is CORS-enabled, the server knows what a preflight
request is and can respond appropriately. But if the preflight hits a server that doesn’t
know or doesn’t care about CORS, the server won’t send the correct preflight response,
and the actual request will never be sent. The preflight protects unsuspecting servers
from receiving cross-origin requests they may not want.

preflight请求用来允许跨域,并且不破坏浏览器的同源策略。如果服务端设置了CORS许可,那么服务端
知道如何响应请求。但是如果服务端没有设置或者不关心CORS,那么就不能正确响应preflight请求。
那么真正的请求就不会被发送。preflight请求就保护了不想要接收跨域请求的服务器。

加入preflight请求的CORS过程(来自原书):

preflight_request

也就说,如果preflight请求不能被服务端正确响应,那么真实请求就不会发出。

触发失败的preflight请求, 新增一个DELETE POST的方法:
hello.html如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>ajax request</title>
<style>
.post {margin-bottom: 20px;}
</style>
</head>
<body onload="getBlogPosts();">
<div id="output">
</div>
<script>
// 建立一个XMLHttpRequest对象 并打开链接
var createXhr = function(method, url) {
var xhr = new XMLHttpRequest();
xhr.onerror = function() {
document.getElementById('output').innerHTML = 'ERROR';
};
xhr.open(method, url, true);
return xhr;
};
var getBlogPosts = function() {
var xhr = createXhr("GET", "http://127.0.0.1:9999/api/posts");
xhr.onload = function() {
var data = JSON.parse(xhr.responseText);
var elem = document.getElementById('output');
for (var postId in data) {
var postText = data[postId]['post'];
var div = document.createElement('div');
div.className = 'post';
div.id = 'postId' + postId;
div.appendChild(document.createTextNode(postText));
// 新增删除链接
var a = document.createElement('a');
a.innerHTML = 'Delete post #' + postId;
a.href = "#";
a.onclick = function(postId) {
return function() {
deletePost(postId);
}
}(postId);
div.appendChild(document.createTextNode(' '));
div.appendChild(a);
elem.appendChild(div);
}
};
xhr.send();
};
// 删除post的函数
var deletePost = function(postId) {
var url = 'http://127.0.0.1:9999/api/posts/' + postId;
var xhr = createXhr('DELETE', url);
xhr.onload = function() {
// 响应码204的话 删除元素
if (xhr.status == 204) {
var element = document.getElementById('postId' + postId);
element.parentNode.removeChild(element);
}
};
xhr.send();
};
</script>
</body>
</html>

服务端添加delete的API:
index.js如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
var express = require('express'); // 引入express框架
// 数据
var POSTS = {
'1': {'post': 'This is the first blog post.'},
'2': {'post': 'This is the second blog post.'},
'3': {'post': 'This is the third blog post.'}
};
// 加入设置CORS的函数
var handleCors = function(req, res, next) {
res.set('Access-Control-Allow-Origin',"*");
next();
}
var SERVER_PORT = 9999; // 端口
var serverapp = express();
serverapp.use(express.static(__dirname));
serverapp.use(handleCors);
// 请求路由,和响应函数
serverapp.get('/api/posts', function(req, res) {
res.json(POSTS);
});
// 添加删除方法
serverapp.delete('/api/posts/:id', function(req, res) {
delete POSTS[req.params.id];
res.status(204).end();
});
// 监听端口
serverapp.listen(SERVER_PORT, function() {
console.log('Started server at http://127.0.0.1:' + SERVER_PORT);
});

现在尝试跨域删除post,会报错:

跨域删除错误

大致的意思是 DELETE方法在preflight请求的Access-Control-Allow-Methods不被许可。

确认preflight请求

区分preflight请求和一般的HTTP请求不同之处在于:
1.使用OPTIONS方法
2.有一个 Origin 请求头
3.有一个 Access-Control-Request-Method 请求头

>A preflight request must be made via the HTTP OPTIONS method, which is defined by
the HTTP spec and isn’t specific to CORS. The HTTP spec (RFC2616) defines an
OPTIONS request as “a request for information about the communication options
available on the request/response chain.” This means that even before CORS, clients
could use the OPTIONS method to learn more about an endpoint. When used outside
of CORS, the OPTIONS method traditionally conveys which HTTP methods are
supported on a particular URL.

preflight请求必须通过OPTIONS方法进行。OPTIONS用于获知服务器端对跨源请求所支持 HTTP 方法,来查明这个跨站请求对于目的站点是不是安全可接受的。如果不能接受,就无需发送真正请求了。

关于Access-Control-Request-Method请求头

这个请求头用于在确认preflight请求中,表示浏览器想要通过这个里面带有的方法请求资源,看服务端是否
接受,如果不接受就不用在发送真正请求了。

现在修改服务端代码,使得它支持preflight请求:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
var express = require('express'); // 引入express框架
// 数据
var POSTS = {
'1': {'post': 'This is the first blog post.'},
'2': {'post': 'This is the second blog post.'},
'3': {'post': 'This is the third blog post.'}
};
// 判断是否是preflight请求
var isPrefilght = function(req) {
var isHttpOptions = req.method === 'OPTIONS';
var hasOriginHeader = req.headers['origin'];
var hssRequestMethod = req.headers['access-control-request-method'];
return isHttpOptions && hasOriginHeader && hssRequestMethod;
}
// 加入设置CORS的函数
var handleCors = function(req, res, next) {
res.set('Access-Control-Allow-Origin',"*");
if (isPrefilght(req)) {
res.set('Access-Control-Allow-Methods', 'GET, DELETE');
console.log('Got a Prefilght Requests.')
res.status(204).end();
return ;
}
next();
}
var SERVER_PORT = 9999; // 端口
var serverapp = express();
serverapp.use(express.static(__dirname));
serverapp.use(handleCors);
// 请求路由,和响应函数
serverapp.get('/api/posts', function(req, res) {
res.json(POSTS);
});
// 添加删除方法
serverapp.delete('/api/posts/:id', function(req, res) {
delete POSTS[req.params.id];
res.status(204).end();
});
// 监听端口
serverapp.listen(SERVER_PORT, function() {
console.log('Started server at http://127.0.0.1:' + SERVER_PORT);
});

现在,跨域删除就可以顺利进行了:


Access-Control-Allow-Methods 响应头说明:

指明资源可以被请求的方式有哪些(一个或者多个). 这个响应头信息在客户端发出预检请求的时候会被返回。
在OPTIONS请求中返回,如果下次请求的方法在 Access-Control-Allow-Methods 中,说明可以发送真正请求了。

加入额外的HTTP头

hello.html:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>ajax request</title>
<style>
.post {margin-bottom: 20px;}
</style>
</head>
<body onload="getBlogPosts();">
<div id="output">
</div>
<script>
// 建立一个XMLHttpRequest对象 并打开链接
var createXhr = function(method, url) {
var xhr = new XMLHttpRequest();
xhr.onerror = function() {
document.getElementById('output').innerHTML = 'ERROR';
};
xhr.open(method, url, true);
return xhr;
};
var getBlogPosts = function() {
var xhr = createXhr("GET", "http://127.0.0.1:9999/api/posts");
xhr.onload = function() {
var data = JSON.parse(xhr.responseText);
var elem = document.getElementById('output');
for (var postId in data) {
var postText = data[postId]['post'];
var div = document.createElement('div');
div.className = 'post';
div.id = 'postId' + postId;
div.appendChild(document.createTextNode(postText));
// 新增删除链接
var a = document.createElement('a');
a.innerHTML = 'Delete post #' + postId;
a.href = "#";
a.onclick = function(postId) {
return function() {
deletePost(postId);
}
}(postId);
div.appendChild(document.createTextNode(' '));
div.appendChild(a);
elem.appendChild(div);
}
};
xhr.send();
};
// 删除post的函数
var deletePost = function(postId) {
var url = 'http://127.0.0.1:9999/api/posts/' + postId;
var xhr = createXhr('DELETE', url);
// 加入自定义的请求头
xhr.setRequestHeader('Timezone-Offset',"hello");
xhr.onload = function() {
// 响应码204的话 删除元素
if (xhr.status == 204) {
var element = document.getElementById('postId' + postId);
element.parentNode.removeChild(element);
}
};
xhr.send();
};
</script>
</body>
</html>

这样会出现以下错误:

1
2
XMLHttpRequest cannot load http://127.0.0.1:9999/api/posts/3.
Request header field Timezone-Offset is not allowed by Access-Control-Allow-Headers in preflight response.

说明这个请求头不被允许。

修改服务端如下 在OPTIONS请求时候加入一个允许的头就可以了:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
var express = require('express'); // 引入express框架
// 数据
var POSTS = {
'1': {'post': 'This is the first blog post.'},
'2': {'post': 'This is the second blog post.'},
'3': {'post': 'This is the third blog post.'}
};
// 判断是否是preflight请求
var isPrefilght = function(req) {
var isHttpOptions = req.method === 'OPTIONS';
var hasOriginHeader = req.headers['origin'];
var hssRequestMethod = req.headers['access-control-request-method'];
return isHttpOptions && hasOriginHeader && hssRequestMethod;
}
// 加入设置CORS的函数
var handleCors = function(req, res, next) {
res.set('Access-Control-Allow-Origin',"*");
if (isPrefilght(req)) {
res.set('Access-Control-Allow-Methods', 'GET, DELETE');
// 加入允许的请求头
res.set('Access-Control-Allow-Headers', 'Timezone-Offset');
console.log('Got a Prefilght Requests.')
res.status(204).end();
return ;
}
next();
}
var SERVER_PORT = 9999; // 端口
var serverapp = express();
serverapp.use(express.static(__dirname));
serverapp.use(handleCors);
// 请求路由,和响应函数
serverapp.get('/api/posts', function(req, res) {
res.json(POSTS);
});
// 添加删除方法
serverapp.delete('/api/posts/:id', function(req, res) {
delete POSTS[req.params.id];
res.status(204).end();
});
// 监听端口
serverapp.listen(SERVER_PORT, function() {
console.log('Started server at http://127.0.0.1:' + SERVER_PORT);
});

Cookie和响应头

暂时没用得上,以后再了解。。。

参考:
http://www.w3school.com.cn/tiy/t.asp?f=ajax_post2
http://www.netingcn.com/http-status-204.html
http://www.laruence.com/2011/01/20/1844.html
https://developer.mozilla.org/zh-CN/docs/Web/HTTP/Access_control_CORS