前后端数据交互经常会碰到请求跨域 , 什么是跨域 , 以及有哪些跨域方式 , 我觉得我应该记录下来。
Cookie , LocalStorage ,IndexedDB等存储性内容。
DOM节点
AJAX请求发送后,非同源会被浏览器拦截。
但是有三个标签是允许跨域加载资源:
<img src=XXX> <link href=XXX> <script src=XXX> 复制代码
这也就是JSONP的来源了。
当协议,子域名,主域名,端口号中任意一格不相同时,都算作不同源。
note: 特别说明两点:
1.1 JSONP原理
利用<script>标签没有跨域限制的漏洞,网页可以得到从其他来源动态产生的JSON数据。JSONP请求一定需要对方的服务器做支持才可以。 复制代码
1.2 JSONP和AJAX对比
JSONP和AJAX相同,都是客户端向服务端发送请求,从服务端获取数据的方式。但AJAX属于同源策略,JSONP属于非同源策略(跨域请求)
1.3 JSONP优缺点
JSONP优点是简单兼容性好,可用于解决主流浏览器的跨域数据访问的问题。缺点是仅仅支持get方法具有局限性,不安全可能会遭受XSS攻击。
1.4 JSONP的实现流程
现在我们来自己封装一个JSONP函数
// 封装 JSONP函数 function jsonp({ url, params, callback }) { return new Promise((resolve, reject) => { let script = document.createElement('script'); params = JSON.parse(JSON.stringify(params)); let arrs = []; for (let key in params) { arrs.push(`${key}=${params[key]}`); } arrs.push(`callback=${callback}`); script.src = `${url}?${arrs.join('&')}`; document.body.appendChild(script); function callback(data) { resolve(data); docement.body.removeChild(script); } window[callback] = function (data) { resolve(data); document.body.removeChild(script); } }) } // 前端调用 jsonp({ url: 'http://localhost:3000/say', params: { wd: 'I Love you' }, callback: 'show' }).then(data => { console.log(data) }) // 后端响应 // 这里用到了 express var express = require('express'); var router = express.Router(); var app = express(); router.get('/say',function(req,res,next) { //要响应回去的数据 let data = { username : 'zs', password : 123456 } let {wd , callback} = req.query; console.log(wd); console.log(callback); // 调用回调函数 , 并响应 res.end(`${callback}(${JSON.stringify(data)})`); }) app.listen(3000); 复制代码
CORS需要浏览器和后端同时支持。IE8和 IE9需要通过 XDomainRequest来实现
浏览器会自动进行 CORS通信,实现CORS通信的关键是后端。只要后端实现了CORS,实现了跨域。
服务端设置 Access-Control-Allow-Origin 就可以开启CORS。该属性表示哪些域名可以访问资源,如果设置通配符则表示所有网站都可以访问资源。
虽然设置CORS和前端没有什么关系,但是通过这种方式解决跨域问题的话,会在发送请求时出现两种情况,分别为简单请求和复杂请求。
只要同时满足以下两个条件,就属于简单请求
条件1 : 使用下列方法之一:
条件2 :Content-Type 的值仅限于下列三者之一 :
不符合以上条件的请求就肯定是复杂请求了。复杂请求的CORS请求,会在正式通信之前,增加一次HTTP查询请,称为"预检"请求,该请求是option方法的 , 通过该请求来知道服务端是否允许跨域请求。
我们用 PUT 向后台请求时, 属于复杂请求,后台需如下配置:
// 允许哪个方法访问我 res.setHeader('Access-Control-Allow-Methods', 'PUT') // 预检的存活时间 res.setHeader('Access-Control-Max-Age', 6) // OPTIONS请求不做任何处理 if (req.method === 'OPTIONS') { res.end() } // 定义后台返回的内容 app.put('/getData', function(req, res) { console.log(req.headers) res.end('我不爱你') }) 复制代码
接下来我们看下一个完整请求的例子,并且介绍下CORS请求相关的字段
//index.html // 前端代码 <script> let xhr = new XMLHttpRequest(); document.cookie = 'name=hw'; xhr.withCredentials = true; //前端设置是否带 cookie xhr.open('PUT','http://localhost:4000/getData',true); xhr.setRequestHeader('name','hw'); xhr.onreadystatechange = function() { if(xhr.readyState === 4) { if(xhr.status >= 200 && xhr.status < 300 || xhr.status === 304) { console.log(JSON.parse(xhr.response)); console.log(xhr.getResponseHeader('name')) } } } xhr.send(); </script> // server1.js let express = require('express'); let app = express(); app.use(express.static(__dirname)) app.listen(3000) // 后端代码 let express = require('express') let app = express() let whitList = ['http://localhost:3000'] //设置白名单 app.use(function(req, res, next) { let origin = req.headers.origin if (whitList.includes(origin)) { // 设置哪个源可以访问我 res.setHeader('Access-Control-Allow-Origin', origin) // 允许携带哪个头访问我 res.setHeader('Access-Control-Allow-Headers', 'name') // 允许哪个方法访问我 res.setHeader('Access-Control-Allow-Methods', 'PUT') // 允许携带cookie res.setHeader('Access-Control-Allow-Credentials', true) // 预检的存活时间 res.setHeader('Access-Control-Max-Age', 6) // 允许返回的头 res.setHeader('Access-Control-Expose-Headers', 'name') if (req.method === 'OPTIONS') { res.end() // OPTIONS请求不做任何处理 } } next() }) app.put('/getData', function(req, res) { let data = { username : 'zs', password : 123456 } console.log(req.headers) res.setHeader('name', 'jw') //返回一个响应头,后台需设置 res.end(JSON.stringify(data)) }) app.get('/getData', function(req, res) { console.log(req.headers) res.end('he') }) app.listen(4000) 复制代码
postMessage是HTML5 XMLHttRequest Level 2中的 API, 并且为数不多跨域跨域操作的window属性之一,它可用于解决以下方面的问题:
postMessage()方法允许来自不同源的脚本采用异步方式进行有限的通信,可以实现跨文本档,多窗口,跨域消息传递
API大概是这样的 otherWindow.postMessage(message,targetOrigin,[transfer]) 复制代码
下面,我们来用一个例子来说明任何使用
Websocket是HTML5的一个持久化协议,它实现了浏览器与服务器的全双工通信,同时也是跨域的一种解决方案。WebSocket和HTTP都是应用层协议,都是基于TCP协议。
但是, WebSocket是一种双向通信协议,在建立连接之后,WebSocket的server与client都能主动向对方发送或接收数据。 同时,WebSocket在建立连接时需要借助HTTP协议,连接建立好了之后client与server之间的双向通信就与HTTP无关了。
原生WebSocket API使用起来不太方便,我们使用 Socket.io ,它很好地封装了 webSocket接口,提供了更简单,灵活的接口,也对不支持webSocket的浏览器提供了向下兼容。
我们再来看一个例子:
//前端 <script> // 定义数据 let Data = { username : 'hw', password : 456789 } let socket = new WebSocket('ws://localhost:4000'); socket.onopen = function() { socket.send(JSON.stringify(Data)); // 向服务器发送数据 } socket.onmessage = function(e) { console.log(JSON.parse(e.data)); // 接收服务器返回的数据 } </script> // 后端 let express = require('express'); let app = express(); let WebSocket = require('ws'); let wss =new WebSocket.Server({port:4000}) // 定义数据 let Data = { username : 'zs', password : 123456 } wss.on('connection', function(ws) { ws.on('message',function(data) { console.log(JSON.parse(data)) ws.send(JSON.stringify(Data)) }) }) 复制代码
实现原理: **同源策略是浏览器需要遵循的标准,而如果是服务器向服务器请求就无需遵循同源策略。**代理服务器,需要做以下几个步骤:
//前端 <script> function ajax( { url = '', method = 'get', headers = {}, data = '' } ) { return new Promise((resolve,reject) => { // 兼容性处理 var xhr = new XMLHttpRequest(); xhr.onreadystatechange = function() { if(xhr.readyState === 4) { if((xhr.status >= 200 && xhr.status < 300) || xhr.status === 304) { try { // 获取数据 var response = JSON.parse(xhr.responseText); resolve(response); } catch( e ) { reject(e); } } else { reject(new Error('Request was unsuccessful' + xhr.statusText)); } } } xhr.open(method,url,true); // 设置头部 for(let key in headers) { xhr.setRequestHeader(key,headers[key]); } xhr.send(JSON.stringify(data)) }) } ajax({ url : 'http://localhost:3000', method : 'post', headers : { 'Content-Type':'application/json;charset=utf-8' }, data : { username : 'hw', password : '789' } }) .then((value,code)=>{ console.log(value) }) .catch(err=>{ console.log(err) }) </script> // 代理服务器(http://localhost:3000) const http = require('http'); // 第一步 : 接受客户端请求 const server = http.createServer((request, response) => { // 代理服务器,直接和浏览器直接交互,需要设置CORS的首部字段 response.writeHead(200, { 'Access-Control-Allow-Origin': '*', 'Access-Control-Allow-Methods': '*', 'Access-Control-Allow-Headers': 'Content-Type' }) if(request.method === 'options') { response.end(); } // 第二步 : 将请求转发给服务器 const proxyRequest = http .request( { host: '127.0.0.1', port: 4000, url: '/', method: request.method, headers: request.headers, }, serverResponse => { // 第三步:收到服务器的响应 var body = '' serverResponse.on('data', chunk => { body += chunk }) serverResponse.on('end', () => { console.log('The data is ' + body) // 第四步:将响应结果转发给浏览器 response.end(body) }) } ) .end() }) server.listen(3000, ()=>{ console.log('The proxyServer is running at http://localhost:3000') }) //后端服务器 // http://localhost:4000 const http = require('http'); const data = { title : 'zs', password : '123'}; const server = http.createServer((request,response) => { if(request.url === '/') { response.end(JSON.stringify(data)) } }) server.listen(4000,'127.0.0.1',()=> { console.log('The server is running at http://127.0.0.1:4000') }) 复制代码
说实话,nginx要熟悉有点花时间,这里我就介绍怎么用nginx跨域好了
这个东西我也不是很会
'Nginx是一款轻量级的HTTP服务器,采用事件驱动的异步非阻塞处理方式框架,这让其具有极好的IO性能,时常用于服务端的反向代理与负载均衡' 复制代码
这似乎与node.js很像啊,但,其实他们都有自己擅长的领域:Nginx更擅长于底层服务端资源的处理(静态资源处理转发,反向代理,负载均衡等),node.js更擅长于上层具体业务逻辑的处理。两者可以实现结合,助力前端开发。
什么是反向代理?互联网应用基本都基于CS基本结构,即 client端和server端。代理其实就是client端和真正的server端之间增加一层提供特定的服务的服务器,即代理服务器。
正向代理大家肯定都用过,其实,翻墙工具其实就是一个正向代理工具。它会把浏览器访问墙外服务器server的网页请求,代理到一个可以访问该网站的代理服务器proxy,这个代理服务器proxy把墙外服务器server上的网页内容获取,再转发给客户端。
具体流程如下:
反向代理与正向代理相反,先看流程图:
在反向代理中(实际上,这种情况发生在所有大型网站的页面请求中),客户端发送的请求,想要访问server服务器上的内容。但将被发送到一个代理服务器proxy,这个代理服务器将把请求代理到和自己属于同一个LAN下的内部服务器上,而用户真正想获得的内容就存储在这些内部服务器上。
即向外部客户端提供一个统一的代理入口,客户端的请求,都先经过这个proxy服务器,至于在内网真正访问哪台服务器内容,由这个proxy去控制。
概括的说:就是代理服务器和真正server服务器可以直接访问,属于一个LAN(服务器内网);代理对用户是透明的,即无感知的。
原因:
经常会遇到希望让某些特定的用户的群体(比如公司内网)访问,或者控制某个url不让人访问.
nginx的配置如下: location / { deny 192.168.1.100; // 禁止 allow 192.168.1.10/200; // 允许 allow 10.110.50.16; deny all; // 禁止所有 } 复制代码
其实 deny和 allow是 ngx_http_access_module(已内置) 模块中的语法。
采用的是从上到下匹配方式,匹配到就跳出不再继续匹配。
比如说,上述配置的意思是,首先禁止 192.168.1.100访问,然后允许 192.168.1.10 - 200 ip段内的访问 (排除 192.168.1.100 ) ,同时允许 10.110.50.16 这个单独ip 的访问,剩下未匹配的全部禁止访问。
在众多的解决跨域方式中,都不可避免的都需要服务端进行支持,使用Nginx可以纯前端解决请求跨域问题。
nginx配置如下:
server { listen 3002; server_name localhost; location /ok { proxy_pass http://localhost:3000; # 指定允许跨域的方法,*代表所有 add_header Access-Control-Allow-Methods *; # 预检命令的缓存,如果不缓存每次会发送两次请求 add_header Access-Control-Max-Age 3600; # 带cookie请求需要加上这个字段,并设置为true add_header Access-Control-Allow-Credentials true; # 表示允许这个域跨域调用(客户端发送请求的域名和端口) # $http_origin动态获取请求客户端请求的域 不用*的原因是带cookie的请求不支持*号 add_header Access-Control-Allow-Origin $http_origin; # 表示请求头的字段 动态获取 add_header Access-Control-Allow-Headers $http_access_control_request_headers; # OPTIONS预检命令,预检命令通过时才发送请求 # 检查请求的类型是不是预检命令 if ($request_method = OPTIONS){ return 200; } } } 复制代码
好吧,我承认最后用nginx划水划过的。
这个,nginx学了我好几天我都没有搞懂这怎么弄。
哎,以后在补充吧。