title: 前端跨域處理方式
date: 2018-07-08 00:37:29
categories:html
tags:前端
關於瀏覽器跨域問題,項目中也遇到了,看了項目上一些代碼的處理方式,感受存在很多不大完善的地方,所以對於跨域,想好好梳理一下,可是最近一直在忙,所以週六抽出一天的時間了學習消化作了寫筆記。vue
跨域和同源問題node
跨域實質就是跨域名、跨端口、跨協議。webpack
同源就是同域名、同端口、同協議。nginx
假如一個網址是 http://baidu.com:8080?user=name&pwd=password http:// 是協議 baidu.com 是域名(注意:前面加上「wwww」即www.baidu.com不是域名) 8080 是端口 user=name&pwd=password 是地址帶的參數
引用地址 https://www.cnblogs.com/shirly77/p/6440635.html?utm_source=itdadao&utm_medium=referralweb
跨域的解決方式主要有如下幾種方式:1.jsonp(侷限性比較大,缺點比較多,下面有具體介紹)。2.Cors 3.使用代理 4.websocket跨域ajax
在js中,咱們直接用XMLHttpRequest請求不一樣域上的數據時,是不能夠的。可是,在頁面上引入不一樣域上的js腳本文件倒是能夠的,jsonp正是利用這個特性來實現的。express
好比,有個a.html頁面,它裏面的代碼須要利用ajax獲取一個不一樣域上的json數據,假設這個json數據地址是http://example.com/data,那麼a.html中的代碼就能夠這樣:json
<script> function doSomething(jsonData){ } </script> <script src="http://example.com/data?callback=doSomething"></script> 由於是當作一個js文件來引入的,因此http://example.com/data返回的必須是一個能執行的js文件
這樣jsonp的原理就很清楚了,經過script標籤引入一個js文件,這個js文件載入成功後會執行咱們在url參數中指定的函數,而且會把咱們須要的json數據做爲參數傳入。因此jsonp是須要服務器端的頁面進行相應的配合的。
基於JSONP的實現原理,因此JSONP只能是「GET」請求,不能進行較爲複雜的POST和其它請求,因此遇到那種狀況,就得參考下面的CORS解決跨域了(因此現在它也基本被淘汰了)
Cross-origin resource sharingCORS須要瀏覽器和服務器同時支持。目前,全部瀏覽器都支持該功能,IE瀏覽器不能低於IE10。
整個CORS通訊過程,都是瀏覽器自動完成,不須要用戶參與。對於開發者來講,CORS通訊與同源的AJAX通訊沒有差異,代碼徹底同樣。瀏覽器一旦發現AJAX請求跨源,就會自動添加一些附加的頭信息,有時還會多出一次附加的請求,但用戶不會有感受。
所以,實現CORS通訊的關鍵是服務器。只要服務器實現了CORS接口,就能夠跨源通訊。
瀏覽器將CORS請求分紅兩類:簡單請求(simple request)和非簡單請求(not-so-simple request)。
只要同時知足如下兩大條件,就屬於簡單請求。
(1) 請求方法是如下三種方法之一: HEAD GET POST (2)HTTP的頭信息不超出如下幾種字段: Accept Accept-Language Content-Language Last-Event-ID Content-Type:只限於三個值application/x-www-form-urlencoded、multipart/form-data、text/plain 凡是不一樣時知足上面兩個條件,就屬於非簡單請求。
1.對於簡單請求,瀏覽器直接發出CORS請求。具體來講,就是在頭信息之中,增長一個Origin字段。
2.非簡單請求是那種對服務器有特殊要求的請求,好比請求方法是PUT或DELETE,或者Content-Type字段的類型是application/json。
非簡單請求的CORS請求,會在正式通訊以前,增長一次HTTP查詢請求,稱爲"預檢"請求(preflight).也就是說在正式的請求後臺以前,瀏覽器會先發一個預請求options表示這個請求是用來詢問的。
頭信息裏面,關鍵字段是Origin,表示請求來自哪一個源。
除了Origin字段,"預檢"請求的頭信息包括兩個特殊字段。
(1)Access-Control-Request-Method
該字段是必須的,用來列出瀏覽器的CORS請求會用到哪些HTTP方法,上例是PUT。
(2)Access-Control-Request-Headers
該字段是一個逗號分隔的字符串,指定瀏覽器CORS請求會額外發送的頭信息字段,上例是X-Custom-Header。
這是一段http對應的響應
HTTP/1.1 200 OK Date: Mon, 01 Dec 2008 01:15:39 GMT Server: Apache/2.0.61 (Unix) Access-Control-Allow-Origin: http://api.bob.com Access-Control-Allow-Methods: GET, POST, PUT Access-Control-Allow-Headers: X-Custom-Header Content-Type: text/html; charset=utf-8 Content-Encoding: gzip Content-Length: 0 Keep-Alive: timeout=2, max=100 Connection: Keep-Alive Content-Type: text/plain
上面的HTTP迴應中,關鍵的是Access-Control-Allow-Origin字段,表示http://api.bob.com能夠請求數據。該字段也能夠設爲星號,表示贊成任意跨源請求。
服務器迴應的其餘CORS相關字段以下。
Access-Control-Allow-Methods: GET, POST, PUT 該字段必需,它的值是逗號分隔的一個字符串,代表服務器支持的全部跨域請求的方法。注意,返回的是全部支持的方法,而不單是瀏覽器請求的那個方法。這是爲了不屢次"預檢"請求。 Access-Control-Allow-Headers: X-Custom-Header 若是瀏覽器請求包括Access-Control-Request-Headers字段,則Access-Control-Allow-Headers字段是必需的。它也是一個逗號分隔的字符串,代表服務器支持的全部頭信息字段,不限於瀏覽器在"預檢"中請求的字段。 Access-Control-Allow-Credentials: true 該字段可選。它的值是一個布爾值,表示是否容許發送Cookie。若是服務器不要瀏覽器發送Cookie,刪除該字段便可。 Access-Control-Max-Age: 1728000 該字段可選,用來指定本次預檢請求的有效期,單位爲秒。上面結果中,有效期是20天(1728000秒),即容許緩存該條迴應1728000秒(即20天),在此期間,不用發出另外一條預檢請求。
實現思路:經過nginx配置一個代理服務器(域名與domain1相同,端口不一樣)作跳板機,反向代理訪問domain2接口,而且能夠順便修改cookie中domain信息,方便當前域cookie寫入,實現跨域登陸。
nginx具體配置:
#proxy服務器 server { listen 81; server_name www.domain1.com; location / { proxy_pass http://www.domain2.com:8080; #反向代理 proxy_cookie_domain www.domain2.com www.domain1.com; #修改cookie裏域名 index index.html index.htm; # 當用webpack-dev-server等中間件代理接口訪問nignx時,此時無瀏覽器參與,故沒有同源限制,下面的跨域配置可不啓用 add_header Access-Control-Allow-Origin http://www.domain1.com; #當前端只跨域不帶cookie時,可爲* add_header Access-Control-Allow-Credentials true; } }
1.) 前端代碼示例:
var xhr = new XMLHttpRequest(); // 前端開關:瀏覽器是否讀寫cookie xhr.withCredentials = true; // 訪問nginx中的代理服務器 xhr.open('get', 'http://www.domain1.com:81/?user=admin', true); xhr.send();
2.) Nodejs後臺示例:
var http = require('http'); var server = http.createServer(); var qs = require('querystring'); server.on('request', function(req, res) { var params = qs.parse(req.url.substring(2)); // 向前臺寫cookie res.writeHead(200, { 'Set-Cookie': 'l=a123456;Path=/;Domain=www.domain2.com;HttpOnly' // HttpOnly:腳本沒法讀取 }); res.write(JSON.stringify(params)); res.end(); }); server.listen('8080'); console.log('Server is running at port 8080...');
node中間件實現跨域代理,原理大體與nginx相同,都是經過啓一個代理服務器,實現數據的轉發,也能夠經過設置cookieDomainRewrite參數修改響應頭中cookie中域名,實現當前域的cookie寫入,方便接口登陸認證。
利用node + express + http-proxy-middleware搭建一個proxy服務器。
1.)前端代碼示例:
var xhr = new XMLHttpRequest(); // 前端開關:瀏覽器是否讀寫cookie xhr.withCredentials = true; // 訪問http-proxy-middleware代理服務器 xhr.open('get', 'http://www.domain1.com:3000/login?user=admin', true); xhr.send();
2.)中間件服務器:
var express = require('express'); var proxy = require('http-proxy-middleware'); var app = express(); app.use('/', proxy({ // 代理跨域目標接口 target: 'http://www.domain2.com:8080', changeOrigin: true, // 修改響應頭信息,實現跨域並容許帶cookie onProxyRes: function(proxyRes, req, res) { res.header('Access-Control-Allow-Origin', 'http://www.domain1.com'); res.header('Access-Control-Allow-Credentials', 'true'); }, // 修改響應信息中的cookie域名 cookieDomainRewrite: 'www.domain1.com' // 能夠爲false,表示不修改 })); app.listen(3000); console.log('Proxy server is listen at port 3000...');
3.)Nodejs後臺同(nginx)
利用node + webpack + webpack-dev-server代理接口跨域。在開發環境下,因爲vue渲染服務和接口代理服務都是webpack-dev-server同一個,因此頁面與代理接口之間再也不跨域,無須設置headers跨域信息了。
webpack.config.js部分配置:
module.exports = { entry: {}, module: {}, ... devServer: { historyApiFallback: true, proxy: [{ context: '/login', target: 'http://www.domain2.com:8080', // 代理跨域目標接口 changeOrigin: true, secure: false, // 當代理某些https服務報錯時用 cookieDomainRewrite: 'www.domain1.com' // 能夠爲false,表示不修改 }], noInfo: true } }
WebSocket protocol是HTML5一種新的協議。它實現了瀏覽器與服務器全雙工通訊,同時容許跨域通信,是server push技術的一種很好的實現。
原生WebSocket API使用起來不太方便,咱們使用Socket.io,它很好地封裝了webSocket接口,提供了更簡單、靈活的接口,也對不支持webSocket的瀏覽器提供了向下兼容。
1.)前端代碼:
<div>user input:<input type="text"></div> <script src="./socket.io.js"></script> <script> var socket = io('http://www.domain2.com:8080'); // 鏈接成功處理 socket.on('connect', function() { // 監聽服務端消息 socket.on('message', function(msg) { console.log('data from server: ---> ' + msg); }); // 監聽服務端關閉 socket.on('disconnect', function() { console.log('Server socket has closed.'); }); }); document.getElementsByTagName('input')[0].onblur = function() { socket.send(this.value); }; </script>
2.)Nodejs socket後臺:
var http = require('http'); var socket = require('socket.io'); // 啓http服務 var server = http.createServer(function(req, res) { res.writeHead(200, { 'Content-type': 'text/html' }); res.end(); }); server.listen('8080'); console.log('Server is running at port 8080...'); // 監聽socket鏈接 socket.listen(server).on('connection', function(client) { // 接收信息 client.on('message', function(msg) { client.send('hello:' + msg); console.log('data from client: ---> ' + msg); }); // 斷開處理 client.on('disconnect', function() { console.log('Client socket has closed.'); }); });