跨域
非同源策略请求(比较协议、域名、端口号,只要有一个不一样就是跨域):
- 页面的访问地址(Web地址)
- 数据接口的请求地址
情况1:开发时候是跨域的,但是服务器部署的时候是同源的
情况2:开发和上线都是跨域的
JSONP(不安全,并且只支持GET请求)
其它方案
document.domain + iframe
window.name + iframe
H5 postMessage
CORS 跨域资源共享
proxy 跨域代理(目前最常用的)
JSONP 跨域资源请求
利用<script>
或者<link> <img> <iframe>...
不存在域的限制
- 特征:资源访问一定都是GET请求,不可能有POST
全局函数 function fn(result) { ... }
<script src='http://127.0.0.1:8888/user/list?callback=fn'>
一定能发送到服务器(不存在域的限制),把全局函数 fn 名字,当做参数传递给服务器
- 服务器接收到这个请求,同时也可以获取 callback 传递的值(fn)
- 准备数据,最后返回给客户端
fn([10,20,30])
- 客户端把函数 fn 执行,把服务器准备的数据作为实参传递给函数的形参
服务器代码
- 首先看一下服务器代码,这里提供了
jsonpTest
这个资源路径,并返回给客户端 一个字符串(包含执行函数和参数)
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| const express = require('express') const bodyParser = require('body-parser') const app = express()
const port = 1001 app.listen(port, () => { console.log(`The Web Service Is Listening To The Port: ${port}`) })
app.get('/jsonpTest', (req, res) => { let fname = req.query.callback let data = [10, 20, 30] res.send(`${fname}(${JSON.stringify(data)})`) })
|
JSONP 简单案例
1 2 3 4 5 6
| <script> function func(result) { console.log(result) } </script> <script src="http://127.0.0.1:1001/jsonpTest?callback=func"></script>
|
封装 JSONP
- 每次像简单案例那样调用太过麻烦,现在我们想像 Axios 那样调用 jsonp
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| <script src="node_modules/qs/dist/qs.js"></script> <script src="1.js"></script> <script> jsonp({ url: 'https://www.baidu.com/sugrec', params: { prod: 0, from: 'qq', }, jsonpName: 'callback', success: result => { console.log(result) }, }) </script>
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29
| ;(function () { const jsonp = function jsonp(config) { config == null ? (config = {}) : null typeof config !== 'object' ? (config = {}) : null let { url, params = {}, jsonpName = 'callback', success = Function.prototype } = config
let f_name = `jsonp${+new Date()}` window[f_name] = function (result) { typeof success === 'function' ? success(result) : null delete window[f_name] document.body.removeChild(script) }
params = Qs.stringify(params) if (params) url += `${url.includes('?') ? '&' : '?'}${params}` url += `${url.includes('?') ? '&' : '?'}${jsonpName}=${f_name}`
let script = document.createElement('script') script.src = url document.body.appendChild(script) } if (typeof window !== 'undefined') { window.jsonp = jsonp } })()
|
其它网站 JSONP 案例
1 2 3 4 5 6 7 8 9 10 11 12 13
| <script> jsonp({ url: 'https://www.baidu.com/sugrec', params: { prod: 'pc', wd: '百度', }, jsonpName: 'cb', success: result => { console.log(result) }, }) </script>
|
CORS 跨域资源共享
在发送真实请求之前,浏览器会先发送一个试探性请求 OPTIONS(目的:测试客户端和服务器之间是否可以正常通信)如果可以正常通信,接下来再发送真实请求信息
服务器代码
Allow-Origin 可以设置的值
- 单一源
*
所有源(但是此时不安全,而且不允许携带资源凭证)
假如你希望有多个源(不是所有源)都可以跨域,这时就需要设置一个白名单
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| const safeList = [, 'http://127.0.0.1:5500', 'http://127.0.0.1:5501'] app.use((req, res, next) => { let origin = req.headers.origin || req.headers.referer origin = origin.replace(/\/$/, '') if (safeList.includes(origin)) { res.header('Access-Control-Allow-Origin', origin) res.header('Access-Control-Allow-Credentials', true) req.method === 'OPTIONS' ? res.send('Current Services Support Domain Request!') : next() } }) app.get('/test', (req, res) => { res.send('OK') })
|
客户端代码
1 2 3
| <script> fetch('http://127.0.0.1:1001/test').then(response => response.text()).then(data => console.log(data)) </script>
|
Proxy 跨域代理
爬虫:自己写一个后台,去爬取别的后台的数据(平台和平台之间没有跨域)
- 后台和后台之间没有跨域限制(服务器一般会做白名单)
- 客户端和服务器才有跨域限制(浏览器的安全性)
使用 webpack devServer
插件
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| module.exports = { devServer: { port: '3000', compress: true, open: true, hot: true, proxy: { '/': { target: 'https://www.jianshu.com', changeOrigin: true, }, }, }, }
|
通过代理爬取简书
1 2 3 4 5 6 7 8
| const request = require('request') app.get('/subscriptions/recommended_collections', (req, res) => { let url = 'https://www.jianshu.com/asimov' + req.url req.pipe(request(url)).pipe(res) })
app.use(express.static('./'))
|
1 2 3 4
| <script> fetch('/subscriptions/recommended_collections').then(response => response.text()).then(data => console.log( data)) </script>
|