1. 同源策略

浏览器出于安全方面的考虑,只允许与同源下的接口交互
同源:同协议 同域名 同端口

案例**

总结:**比较当前页面的url与接口url的协议、域名、端口


动手测试**
打开 https://www.baidu.com,检查元素开启控制台,在控制台中输入如下测试代码,看看控制台有什么报错提示,为什么? 把接口url换成 https://www.baidu.com 试试

  1. fetch('https://baidu.com')
  2. .then(res => res.text())
  3. .then(data => console.log(data))

image.png

思考:
1. 同源策略的初衷是什么? 保护知识产权
2. 是服务器拒绝还是浏览器拒绝? 浏览器
3. 如果想跨域请求第三方接口,或者把自己的接口提供给第三方,要如何做?
JSON CORS 服务器中转

2. JSONP

JSON with Padding

原理:
同源策略只限制Ajax请求,不限制script标签加载js。
可以通过script标签请求资源,并提前写好接收函数

<script>
  function handleData(data) {
    console.log(data)
  }
</script>
<script src="http://api.xxxx.com/getWeather.php?callback=handleData">

服务器收到请求后,从callback参数得到fn,把原始数据(假设是 {a:1})处理后变成handleData({a:1}) script里的资源加载后会当成js执行,相当于执行 handleData({a:1}), 即可在预定义的handleData函数里处理数据

测试

// 服务器端
const http = require('http')
const url = require('url')
http.createServer((req, res) => {
    let urlObj = url.parse(req.url, true)
    if(urlObj.pathname === '/getWeather') {
        let data = { city: 'hangzhou', weather: 'sunny' }
        res.end(`${urlObj.query.callback}(${JSON.stringify(data)})`)
    } else {
        res.writeHead(404, 'Not Found')
        res.end('not found')
    }
}).listen(8888)
// 浏览器端
<script>
    function showData(data) {
        console.log(data)
    }
</script>
<script src="http://127.0.0.1:8888/getWeather?callback=showData">

封装JSONP

function jsonp(url, data={}) {
    return new Promise((resolve, reject) => {
        window.__fn__ = data => resolve(data)
        let script = document.createElement('script')
        let query = Object.entries(data).map(a=>`${a[0]}=${a[1]}`).join('&')
        script.src = url + '?callback=__fn__&' + query
        script.onerror = () => reject('加载失败')
        document.head.appendChild(script)
        document.head.removeChild(script)
    })
}

jsonp('http://api.xxx.com/getWeather.php', {city: '北京'}).then(data=>{
    console.log(data)
}).catch(error => console.log(error))

3. CORS

跨域资源共享Cross-origin resource sharing

原理**
发送Ajax请求时,浏览器发现该请求不符合同源策略,会给该请求加一个请求头:Origin
后台收到请求后,如果确定接受请求则在返回结果中加入一个响应头:Access-Control-Allow-Origin浏览器判断该响应头中是否包含当前Origin 的值,如果有则浏览器会处理响应,我们就可以拿到响应数据,如果没有浏览器直接驳回

  • 前端需要做什么?
    • 什么都不需要做
  • 服务端需要做什么?
    • 加一个 Access-Control-Allow-Origin 响应头


测试**

// 服务器端
const http = require('http')
const url = require('url')
http.createServer((req, res) => {
    let urlObj = url.parse(req.url, true)
    if(urlObj.pathname === '/getWeather') {
    res.setHeader('Access-Control-Allow-Origin', 'http://js.xxx.com')
        // res.setHeader('Access-Control-Allow-Origin', '*')
    // 服务器说:"浏览器兄弟,我允许任何域都能获取资源,你不用替我拦截非同源的请求了了"
        res.end(JSON.stringify( { city: 'hangzhou', weather: 'sunny' } ))
    } else {
        res.writeHead(404, 'Not Found')
        res.end('not found')
    }
}).listen(8888)
// 浏览器端
//当前代码在 http://js.xxx.com 下运行
fetch('http://127.0.0.1:8888/getWeather')
.then(res => res.json())
.then(data => console.log(data))

JSONP和CORS都需要服务器配合,如果不配合怎么办?
**

4. 服务器中转

image.png

// 中转服务器
const http = require('http')
const url = require('url')
http.createServer((req, res) => {
    let urlObj = url.parse(req.url, true)
    if(urlObj.pathname === '/bridge') {
        http.get(urlObj.query.url, req => {
            let text = '' req.on('data', data => text += data)
            req.on('end',() => {
                res.setHeader('Access-Control-Allow-Origin','*')
                res.end(text)
      })
        })
    } else {
        res.writeHead(404, 'Not Found')
        res.end('not found')
    }
}).listen(8888)
// 浏览器端
//当前代码在 http://js.xxx.com 下运行
fetch('http://localhost:8888/bridge?url='+encodeURIComponent('http://baidu.com'))
.then(res=>res.text())
.then(data => console.log(data))