說說CORS與jsonp

前言

瀏覽器出於防止潛在安全風險的考慮,使用了同源策略,這一方面保證了咱們數據的安全, 另外一方面卻又限制了咱們的手腳,基於此,開發者們與標準制定組織提供了不一樣的解決方案, 這裏主要說說CORS與jsonp。javascript

什麼是同源策略

同源指的是兩個域須要協議,子域名,主域名與端口號都保持一致,四者有一個不一樣,即屬於跨域。 注意: http://localhost:8080與http://127.0.0.1:8080不屬於同源,也就是說,即便IP地址一致,可是一個是域名,一個是IP地址,也不屬於同源。html

CORS的使用與注意點

CORS:跨域資源共享,是W3C制定的一個草案,定義了在必須訪問跨源資源時,瀏覽器和服務器該怎麼溝通。java

CORS的實現原理是,瀏覽器發出請求報文中會額外包含一個Origin頭部,該頭部的值是當前頁面的源信息(協議,域名和端口),服務器收到請求報文後,若是贊成這個跨源請求,就在響應報文的頭部添加Access-Control-Allow-Origin,值與請求報文中的Origin頭部的值一致,若是響應報文中沒有這個頭部或者有,可是值不一致,此次的跨源請求就會失敗。express

瀏覽器原生支持CORS,可是不一樣的瀏覽器支持的方式不一樣。 IE8及以上版本引用了XDR(XDomainRequest)類型,這個對象和XHR對象相似,使用這個對象,能夠實現安全可靠的跨域通訊。 XDR的使用與XHR相似,使用過程以下:npm

  1. 實例化XDR
var xdr = new XDomainRequest();
複製代碼
  1. 調用open(),open()接收兩個參數,請求所用的方法以及URL,全部的XDR都是異步執行的,全部不須要第三個參數。
xdr.open('get','#');
複製代碼
  1. 調用send(),若是使用的是get方法,傳入null,若是是post方法,傳入字符串。
xdr.send(null);
複製代碼
  1. 爲xdr綁定事件處理函數。xdr支持的事件包括load,error,timeout。須要說明一點的是,這部分的事件監聽器須要在調用open()以前聲明,這裏只是行文須要。 4.1 load事件 在接收到響應後,你能夠訪問響應的原始文本,可是沒有辦法肯定響應的狀態碼,只有在響應有效的/狀況下才會觸發load事件,若是接收到的響應中不包含Access-Control-Allow-Origin頭部的話,則會觸發error事件。
xdr.onload=function(){
  console.log(xdr.resonseText);
}
複製代碼

4.2 error事件 致使XDR請求失敗的因素不少,全部爲每一個xdr對象綁定該事件的處理函數是頗有必要的,可是該事件拋出的信息有限,咱們只能肯定請求失敗了,並不能得知請求失敗的緣由。json

xdr.onerror=function(){
  console.log('get a error');
}
複製代碼

4.3 timeout事件 xdr對象有一個timeout屬性,該屬性代表請求自發出多久後超時,當爲其賦值後,在給定的時間內還沒接受到響應,就會觸發timeout事件。跨域

xdr.ontimeout=function(){
  alert('time is too long');
}
複製代碼

使用XDR須要注意的點:瀏覽器

  1. cookie不會隨請求發送,也不會隨請求返回。
  2. 不能訪問響應頭部信息,這意味着xdr對象沒有getResponseHeader()和getAllResponseHeaders()。
  3. 只支持get和post方法。
  4. 當使用post方法時,xdr對象有一個contentType屬性,這個屬性能夠表示要發送的數據的格式,這是xdr對象可以影響頭部信息的惟一方法。

其餘瀏覽器對CORS的實現 其餘主流瀏覽器經過XHR對象原生支持CORS,在嘗試打開不一樣來源的資源時,XML能夠自動觸發這個行爲,有一點不一樣的是,open方法中的URL參數須要傳入絕對路徑。緩存

使用XHR對象進行跨域通訊的注意點:安全

  1. 不能使用setRequestHeader()設置自定義頭部。
  2. 不能發送和接收cookie。
  3. 調用getAllResponseHeaders()返回空字符串,調用getResponseHeader()報錯。
  4. 因爲同源請求和跨源請求都使用一樣的接口,所以對於本地資源,使用相對URL,對跨源的資源,使用絕對路徑,這樣能夠避免跨源請求時的限制(見1,2)。

這個給出一個例子,本地文件是index.html,服務端文件爲server.js,須要注意的是,咱們須要使用http-server(其餘的也能夠)搭建一個靜態資源服務器,關於http-server的使用,點擊這裏,這樣可使用http協議訪問index.html文件,使用file協議沒法使用CORS。

// index.html
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>Document</title>
</head>
<body>
    <div>
        <a href="#">clcik me</a>
    </div>
</body>
<script>
    let aElement = document.getElementsByTagName('a')[0]
    aElement.addEventListener('click',cors)
    function cors(){
        var xhr = new XMLHttpRequest();
        xhr.onreadystatechange=(req,res)=>{
            if(xhr.readyState==4){
                if((xhr.status>=200&& xhr.status<300)||xhr.status==304) {
                    console.log(xhr.responseText);
                    console.log(xhr.getResponseHeader('Connection'));  //null
                    console.log(xhr.getResponseHeader('content-type'))  //null
                    console.log(xhr.getResponseHeader('date'))  //null
                } else {
                    console.log('Request was unsuccessful: '+xhr.statusText);
                }
            }
        };
        xhr.open('get','http://127.0.0.1:3000/',true);
        xhr.send(null);
    }
        
</script>
</html>
複製代碼
//server.js
const express = require("express");  //記得安裝express

const app = express();

app.get("/", function (req, res) {
    res.setHeader('Access-Control-Allow-Origin','http://localhost:8080');
    res.end('hello world!');
});

app.listen(3000, function () {
    console.log("app is listening 3000");
});
複製代碼

header
)

CORS的高級使用技巧

CORS支持在跨域請求的過程當中,使用自定義的頭部信息,post和GET以外的方法,不一樣類型的主體內容和提升憑據(cookie)。在使用這些高級選項發送請求時,瀏覽器會首先發送一個Prefight請求,這種請求使用options方法,這是一種透明的服務器驗證機制,開發者不須要作這些。 該請求須要包含如下的頭部:

  1. Origin:與簡單的請求相同。
  2. Access-Control-Allow-Method:請求自身使用的方法(是指咱們主動發起的跨域請求的方法,不是options)。
  3. Access-Control-Request-Headers:(可選)自定義的頭部信息,多個頭部已逗號隔開。 除了這些頭部信息外,xhr有withCredentials屬性,將該屬性設置爲true,能夠在請求過程當中攜帶cookie。

服務器在接收到這個請求後,若是贊成此次跨域請求,就會在響應中設置響應的頭部信息。 這些頭部信息包括:

  1. Access-Control-Allow-Origin:與簡單的請求相同。
  2. Access-Control-Allow-Methods:容許的方法,多個方法以逗號分隔。
  3. Access-Control-Allow-Headers:容許的頭部,多個頭部以逗號隔開。
  4. Access-Control-Max-Age:應該將這個Preflight請求緩存多長事件(以秒錶示)。 5.Access-Control-Allow-Credentials:布爾值。

這裏給一個完整的例子: 一樣使用http-server搭建靜態文件服務器

//index.html
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>Document</title>
</head>
<body>
    <div>
        <a href="#">clcik me</a>
    </div>
</body>

<script>
       let aElement = document.getElementsByTagName('a')[0]
       aElement.addEventListener('click',cors)
       function cors(){
        var xhr = new XMLHttpRequest();
        document.cookie = "name=wang";  //BOM提供的接口,用於設置當前頁面所在的域的cookie
        xhr.withCredentials = true;  //容許在此次請求中攜帶cookie
        xhr.onreadystatechange = function() {
          if (xhr.readyState == 4) {
            if ((xhr.status >= 200) & (xhr.status < 300) || xhr.status == 304) {
              console.log(xhr.responseText);
              console.log('name:'+xhr.getResponseHeader("name"));   //成功接收到服務器端設置的自定義響應頭
            } else {
              console.log("Request was unsuccessful: " + xhr.status);
            }
          }
        };
        xhr.open("PUT", "http://127.0.0.1:5000/getData", true);  //使用put方法
        xhr.setRequestHeader("age", 12);  
        xhr.send(null);
       }
        
        
</script>


</html>
複製代碼
//server1.js
//server2.js
const Koa = require('koa');
const router = require('koa-router')();
const app = new Koa();

app.use(async (ctx, next) => {
    console.log(`Process ${ctx.request.method}: ${ctx.request.url}`)
   await next();
})


app.use(async (ctx, next) => {
    ctx.response.set('Access-Control-Allow-Origin', 'http://localhost:8080');  
    ctx.response.set('Access-Control-Allow-Headers', "age"); //自定義的頭部信息
    ctx.response.set('Access-Control-Allow-Methods', "PUT");
    ctx.response.set('Access-Control-Allow-Credentials', true); 
    ctx.response.set('Access-Control-Allow-Max-Age', 6);
    ctx.response.set('Access-Control-Expose-Headers', 'name');
    // 以上這些響應頭都需設置
    ctx.body = 'I am a OPTION method request'   //爲option請求報文設置響應
    await next();
})

router.put('/getData',async (ctx,next)=>{
    console.log('request header:'+ctx.request.header.age);
    ctx.response.set('name','wang');   //設置自定義響應頭
    ctx.body='success put request';
    await next();
})
app.use(router.routes());

app.listen(5000, () => {
    console.log('app is listening at port 5000');
});
複製代碼

運行以上server1.js件,訪問http://localhost:8080。

1.服務器端運行結果以下,能夠看到,服務器端在客戶端一次跨域請求中,接收到兩條不一樣方法的請求,且成功接收到客戶端自定義的頭部信息。

server
2. 此爲option請求響應相關信息
option
3.這是客戶端經過option請求與服務器端進行溝通後,進行的put請求(該請求中才是客戶端真正需求的載體)
put
從以上的圖片中,能夠看到,在進行跨域請求時,攜帶了cookie,而且瀏覽器在後臺替咱們發送了一個options方法的請求。

總結:CORS的高級用法其實就是在真正與服務器進行跨域通訊時,瀏覽器會先發送一個options方法的請求,幫咱們跟服務器進行‘溝通’,基於溝通結果,決定咱們真正須要的跨域通訊的成功或失敗。

jsonp

jsonp利用script標籤能夠不受限制的從其餘域加載資源的能力,進行跨域通訊。 jsonp由兩部分組成:回調函數數據。回調函數是響應帶來時,應該調用的函數,它須要在URL中指定;數據就是服務器返回給瀏覽器的響應。

jsonp的使用

  1. 建立一個script元素。
  2. 聲明一個回調函數。
  3. 爲script指定src屬性的值,須要將回調函數做爲URL的查詢字符串,形式爲:'callback=functionName' 這裏給出一個完整的例子
//index.html
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <meta http-equiv="X-UA-Compatible" content="ie=edge">
  <title>Document</title>
</head>
<body>
    <script> var script = document.createElement('script'); //回調函數 var blog = function(str){ console.log(str); } script.src='http://localhost:3000/?callback=blog'; document.body.appendChild(script); </script>
</body>
</html>
複製代碼
//server.js
const Koa = require('koa');
const app = new Koa();
const router = require('koa-router')();

router.get('/',async(ctx,next)=>{
    var name = ctx.request.querystring.split('=')[1];
    console.log(name);
    var value='hello world!';
    ctx.body = `${name}('${value}')`;
})

app.use(router.routes());

app.listen(3000,()=>{
    console.log('app is running at port 3000');
})
複製代碼

其實jsonp的內在邏輯很簡單,在script標籤中聲明的函數是屬於全局的,當服務器返回字符串後,這個字符串會被當作JavaScript代碼執行,也就是調用以前聲明的函數。 ##總結 CORS屬於瀏覽器原生支持,支持全部類型的HTTP請求,是跨域通訊的根本解決方案。 jsonp是開發者們爲了繞開同源策略的權宜之計,雖然只支持get方法,可是使用簡單。

總結

CORS屬於瀏覽器原生支持,支持全部類型的HTTP請求,是跨域通訊的根本解決方案。 jsonp是開發者們爲了繞開同源策略的權宜之計,雖然只支持get方法,可是使用簡單。

參考

相關文章
相關標籤/搜索