處理跨域:手動實現CORS和JSONP流程

準備

首先明白一件事:
跨域是瀏覽器的限制html

也就是說不是你真的不能訪問,而是瀏覽器出於他本身的種種擔憂攔截了你的訪問。前端

至於瀏覽器在擔憂什麼(什麼是跨域,爲何會有跨域),參考:百度node

其實cors的原理就是,告訴瀏覽器:"你別擔憂啦,這個請求我ok的,讓他訪問吧"。
而jsonp的原理是,使用瀏覽器不擔憂的方式去請求:script標籤中的src屬性(其餘帶有src屬性的標籤也能夠,好比img)。ajax

先來模擬出跨域的場景,在本地啓動一個最簡單的node服務,返回查詢參數。json

// 新建一個server.js文件,固然前提要安裝node

const http = require('http');
const querystring = require('querystring');

const server = http.createServer((req, res) => {
  const query = querystring.parse(req.url.split('?')[1]);
  const queryStr = JSON.stringify(query);
  res.writeHead(200, {
    "Content-type": "text/plain; charset=utf-8"
  });
  res.end(queryStr);
});

server.listen(9999);
console.log('server run at 9999');
複製代碼

啓動服務後端

node server.js
複製代碼

先在瀏覽器裏直接訪問一下 http://127.0.0.1:9999/?name=無用書生&age=25 看到返回沒有問題跨域

而後就在當前頁面(你如今正在閱讀文章的掘金頁面)f12打開控制檯,在console裏建立一個ajax請求瀏覽器

var xhr = new XMLHttpRequest();
xhr.open('get', 'http://127.0.0.1:9999/?name=無用書生&age=25');
xhr.send();
複製代碼

執行之後就出現跨域報錯了bash

CORS

首先來看一下cors的跨域原理,其實報錯裏就寫的很明白,咱們訪問的資源沒有設置對掘金這個訪問源的頭。markdown

在cors的規則中,請求分爲簡單請求和非簡單請求,咱們上面發送的就是一個簡單請求。

關於簡單請求和非簡單請求,參考:CORS跨域原理解析

對於簡單請求

只要在響應頭中(response header)指明容許哪些訪問源訪問就能夠了。

在server.js中給響應頭添加 Access-Control-Allow-Origin

const http = require('http');
const querystring = require('querystring');

const server = http.createServer((req, res) => {
  const query = querystring.parse(req.url.split('?')[1]);
  const queryStr = JSON.stringify(query);
  res.writeHead(200, {
    "Content-type": "text/plain; charset=utf-8",
    "Access-Control-Allow-Origin": "*"    // * 表明容許全部的源訪問
  });
  res.end(queryStr);
});

server.listen(9999);
console.log('server run at 9999');
複製代碼

重啓node服務,再試一次

報錯沒有了,打開network能夠看到 Access-Control-Allow-Origin 已經生效,數據也成功獲取到

對於非簡單請求

將上面請求的方法從get改爲put,再次請求 (put方法就屬於非簡單請求)

var xhr = new XMLHttpRequest();
xhr.open('put', 'http://127.0.0.1:9999/?name=無用書生&age=25');
xhr.send();
複製代碼

能夠看到跨域報錯又出現了

剛纔設置的響應頭依然存在,卻不起做用了。
另外這裏能夠看到咱們原本發送的是put請求,請求方法那裏寫的倒是options。緣由就是對於非簡單請求瀏覽器會先發送一次預檢,預檢經過纔會發送真正的請求,這個options就是預檢請求,由於沒有經過,因此也就沒有發送真正的請求

其實報錯中也寫的很明白,咱們訪問的資源沒有設置容許對PUT這個方法的訪問

在server.js中給響應頭添加 Access-Control-Allow-Methods,設置容許put方法的請求

const http = require('http');
const querystring = require('querystring');

const server = http.createServer((req, res) => {
  const query = querystring.parse(req.url.split('?')[1]);
  const queryStr = JSON.stringify(query);
  res.writeHead(200, {
    "Content-type": "text/plain; charset=utf-8",
    "Access-Control-Allow-Origin": "*",
    "Access-Control-Allow-Methods": "GET, POST, PUT",
  });
  res.end(queryStr);
});

server.listen(9999);
console.log('server run at 9999');
複製代碼

重啓服務之後再次請求,能夠看到跨域報錯就消失了

還能夠看到依然先進行了一次預檢請求,此次預檢請求經過了,繼續發送了put請求

非簡單請求還對請求頭的信息有所限制,原理仍是同樣的,經過Access-Control-Allow-Headers在返回頭中設置容許的訪問頭就ok了,好比

var xhr = new XMLHttpRequest();
xhr.open('put', 'http://127.0.0.1:9999/?name=無用書生&age=25');
xhr.setRequestHeader("X-Corx-Test", "aabbcc");
xhr.send();
複製代碼

設置容許 X-Corx-Test這個請求頭

const http = require('http');
const querystring = require('querystring');

const server = http.createServer((req, res) => {
  const query = querystring.parse(req.url.split('?')[1]);
  const queryStr = JSON.stringify(query);
  res.writeHead(200, {
    "Content-type": "text/plain; charset=utf-8",
    "Access-Control-Allow-Origin": "*",
    "Access-Control-Allow-Methods": "GET, POST, PUT",
    "Access-Control-Allow-Headers": "X-Corx-Test"
  });

  res.end(queryStr);
});

server.listen(9999);
console.log('server run at 9999');
複製代碼

JSONP

因爲掘金作了csp處理,沒法測試jsonp,用百度進行演示。

什麼是csp,參考:阮一峯博客

去掉node服務中對cors的配置

const http = require('http');
const querystring = require('querystring');

const server = http.createServer((req, res) => {
  const query = querystring.parse(req.url.split('?')[1]);
  const queryStr = JSON.stringify(query);
  res.writeHead(200, {
    "Content-type": "text/plain; charset=utf-8"
  });

  res.end(queryStr);
});

server.listen(9999);
console.log('server run at 9999');
複製代碼

在百度首頁f12打開控制檯,這時候若是再使用ajax請求咱們的服務又會報跨域的錯誤

上面說過jsonp的原理就是使用script標籤的src不受瀏覽器跨域限制的原理
在console中建立一個jsonp請求

var script = document.createElement('script');
script.src = 'http://127.0.0.1:9999/?name=無用書生&age=25';
document.head.appendChild(script);
複製代碼

執行之後看到這時候就沒有報錯了

在network中能夠看到這是一個成功的get請求,注意jsonp只能發送get請求

這時候請求雖然成功了,還沒拿到返回的數據
獲取數據的方法就是在前端定義一個接收數據的函數,而後後端返回的js中執行這個函數,並把要返回的數據做爲參數傳入
好比在前端定義一個叫作 getData 的函數

var script = document.createElement('script');
script.src = 'http://127.0.0.1:9999/?name=無用書生&age=25';
document.head.appendChild(script);
function getData(res) {
  console.log(res);
}
複製代碼

在後端返回的內容中調用這個函數,把數據傳進去

const http = require('http');
const querystring = require('querystring');

const server = http.createServer((req, res) => {
  const query = querystring.parse(req.url.split('?')[1]);
  const queryStr = JSON.stringify(query);
  res.writeHead(200, {
    "Content-type": "text/plain; charset=utf-8",
  });
  let jsonpStr = `getData(${queryStr})`;
  res.end(jsonpStr);
});

server.listen(9999);
console.log('server run at 9999');
複製代碼

重啓服務之後,執行前端代碼,數據就能夠取到了

這個函數名字要先後端約定一致,另外獲取完數據之後最好移除一下script標籤

var script = document.createElement('script');
script.src = 'http://127.0.0.1:9999/?name=無用書生&age=25';
document.head.appendChild(script);
function getData(res) {
  console.log(res);
  document.head.removeChild(script);
}
複製代碼
相關文章
相關標籤/搜索