上周作了一个移动端表单提交的页面,其中涉及到了跨域问题,想来也是惭愧,由于以前一直都没有遇到过这个问题,所以都没有深刻探索过,只是知道有哪几种方式,此次终于借这个机会能够把遗留的知识点补一补了。javascript
【基本思想】:使用自定义的 HTTP 头部让浏览器与服务器进行沟通,从而决定请求或响应是应该成功仍是失败。html
【实现方式】:
浏览器在发送请求时,检测到跨域时,会自动加上一个额外的 Origin 头部,其中包含请求页面的原信息(协议、域名和端口),以便服务器根据这个头部信息来决定是否给予响应。前端
Origin: http://www.nczonline.net
若是服务器认为这个请求能够接受,就在 Access-Control-Allow-Origin 头部中回发相同的原信息(若是是公共资源,能够回发“*”)。java
Access-Control-Allow-Origin: http://www.nczonline.net
若是没有这个头部,或者有着头部但原信息不匹配,浏览器就会驳回请求。webpack
注意:默认状况下,请求和响应都不包含 cookie 信息。git
为了模拟跨域,在本身的本地起了2个服务,一个采用 webpack 充当静态资源服务器(webpack 脚手架可参考:scaffoldsForFE),另外一个用 Node 搭建,充当接受请求的服务器,分别给两个服务器分配了不一样的端口号。github
Client:http://localhost:8000web
var oDiv = document.getElementById('content'); var xhr = new XMLHttpRequest(); xhr.onload = function() { // 响应接收完毕后将触发 onload 事件 if (xhr.readyState == 4) { if ((xhr.status >= 200 && xhr.status < 300) || xhr.status == 304){ oDiv.innerHTML = 'Request was success:' + xhr.responseText; console.log('Request was success:', xhr.responseText); } else { oDiv.innerHTML = "Request was unsuccessful: " + xhr.status; console.log("Request was unsuccessful: ", xhr.status); } } } xhr.open('get', 'http://localhost:8000', true); // 不跨域 // xhr.open('get', 'http://localhost:8888', true); // 跨域 xhr.send();
Server:http://localhost:8888跨域
var http = require('http'); http.createServer(function(req, res) { res.writeHead(200, {'Content-Type': 'text/plain'}); res.write('This is a server test page.'); res.end(); }).listen(8888);
当 Client 端向 http://localhost:8000 发请求时,不存在跨域,可以成功返回页面信息,此时的页面及其发请求的 Request Headers 以下:浏览器
当 Client 端向 http://localhost:8888 请求服务的时候,因为存在跨域问题,没法得到响应:
可是其请求的头部自动带上了 Origin 字段,并且因为是默认状况,没有带上 Cookie:
解决的方式是,在 Server 端响应的时候,在 Access-Control-Allow-Origin 头部中回发相应的原信息:
var http = require('http'); http.createServer(function(req, res) { // 设置响应头部 res.setHeader('Access-Control-Allow-Origin', 'http://localhost:8000'); res.writeHead(200, {'Content-Type': 'text/plain'}); res.write('This is a server test page.'); res.end(); }).listen(8888);
再从新请求,成功得到响应信息,且请求没有带上 Cookie:
因为在跨域时,默认状况下是不容许客户端向服务器发送请求时带上 Cookie 的,那怎样才能带上 Cookie 呢?须要同时在客户端和服务端同时设置相应字段:
(1)客户端在请求中打开 withCredentials
属性,指定某个请求应该发送凭据:
var xhr = new XMLHttpRequest(); xhr.withCredentials = true;
(2)服务端在响应头部中添加 Access-Control-Allow-Credentials
字段,且设为 true,代表服务器接受带凭据的请求:
res.setHeader('Access-Control-Allow-Credentials', true);
若是发送的是带凭据的请求,但服务器的响应中没有包含这个头部,那么浏览器就不会把响应交给 Javascript,则 responseText 是空字符串,status 的值为0,并且会调用 onerror() 事件处理程序。
cookie 是能够设置访问域的,在设置 cookie 的时候,设定了 cookie 的访问域名为一个顶级域名,则能够达到几个子域名共享 cookie 的效果,如腾讯网 www.qq.com 与微信网页版 wx.qq.com 共享了 pac_uid,关于前端存储的相关内容,可参考我以前在博客园写的博文:前端存储调研总结。
Client 端:经过在URL后面加上查询参数
var xhr = new XMLHttpRequest(); xhr.withCredentials = true; xhr.onload = function() { // 响应接收完毕后将触发 onload 事件 // 处理 xhr.responseText } xhr.open('get', 'http://localhost:8888?method=GET&name=Ruth', true); xhr.send();
Server 端:处理 GET 请求
var http = require('http'); var url = require('url'); http.createServer(function(req, res) { res.setHeader('Access-Control-Allow-Origin', 'http://localhost:8000'); res.setHeader('Access-Control-Allow-Credentials', true); res.writeHead(200, {'Content-Type': 'text/plain'}); // 解析 url 参数 var params = url.parse(req.url, true).query; res.write('请求类型:' + params.method); res.write('<br />'); res.write('姓名:' + params.name); res.end(); }).listen(8888);
不一样于 JSONP,CORS 的好处就是可让咱们实现 POST 请求。
Client 端发送信息:
var xhr = new XMLHttpRequest(); xhr.withCredentials = true; xhr.onload = function() { // 响应接收完毕后将触发 onload 事件 // 处理 xhr.responseText } xhr.open('post', 'http://localhost:8888', true); xhr.setRequestHeader("Content-type","application/x-www-form-urlencoded"); // 设置请求头 xhr.send('method=POST&name=Ruth');
Server 端处理请求:
var http = require('http'); var querystring = require('querystring'); http.createServer(function(req, res) { res.setHeader('Access-Control-Allow-Origin', 'http://localhost:8000'); res.setHeader('Access-Control-Allow-Credentials', true); res.writeHead(200, {'Content-Type': 'text/plain'}); var post = ''; // 经过req的data事件监听函数,每当接受到请求体的数据,就累加到post变量中 req.on('data', function(chunk) { post += chunk; }); // 在end事件触发后,经过querystring.parse将post解析为真正的POST请求格式,而后向客户端返回 req.on('end', function() { post = querystring.parse(post); res.write('请求类型:' + post.method); res.write('<br/>') res.write('姓名:' + post.name); res.end(); }); }).listent(8888);