在我们介绍JSONP和CORS之前,先要知道:
我们为什么要使用JSONP和CORS
实际上,JSONP和CORS都是来解决跨域问题的,当两个页面的协议、端口、域名中有一个不一样时就存在了跨域。一旦出现跨域,那么浏览器发送跨域请求后,请求回来的数据都会被浏览器拦截。
浏览器对跨域请求的拦截
在解决跨域请求问题方面,主要是通过两种方法:JSONP和CORS。
JSONP是什么
JSONP(JSON withPadding)是一种通过<script>
标签绕过同源策略来实现跨域请求的方法。
JSONP的实现原理
- 动态创建
<script>
标签:当需要进行跨域请求时,客户端会动态创建一个<script>
标签,并将这个标签的src
属性设置为目标服务器的URL。 - 服务器端响应:服务器端收到请求后,会返回一段JavaScript代码,而不是标准的JSON数据。这段代码会调用之前指定好的回调函数,并将数据作为参数传递给这个回调函数。
- 执行回调函数:浏览器会下载并执行这个脚本,这会调用回调函数,传入的数据会被当做参数被处理,这就让客户端可以获取服务器端的数据。
注意事项
- JSONP只支持GEt请求,因为
<script>
标签只支持GET方法。 - JSONP依赖于客户端和服务器端双方的支持,客户端必须正确生成
<script>
标签,而且服务器端要正确封装数据到回调函数中。 - 因为JSONP会引入脚本代码,就存在一定的安全隐患。例如:XSS(跨站脚本攻击)。
在这里,我们使用了一个express
框架来实现一个JSONP服务器,
javascript
//导入express模块
const express = require('express');
//创建express服务器实例
const app = express();
//挂载路由
app.get('/jsonp', (req, res) => {
// 通过解构req.query客户端通过查询字符串的形式发送到客户端的参数fn
const { callback } = req.query
//在服务器端定义一个obj对象
const obj = {
uname: 'zjj',
age: '18'
}
//obj对象转为res.send可处理的字符串形式后从服务器端相应回调函数至客户端
res.send(`${callback}(${JSON.stringify(obj)})`)
})
app.listen(80, () => {
console.log('http://127.0.0.1');
})
而这里就是直接通过<script>
标签中的src
属性发送请求。
xml
<script>
function fn(res) {
console.log(res);
}
</script>
<script src="http://127.0.0.1/jsonp?callback=fn"></script>
所以在后来,为了使跨域请求更加安全,诞生了CORS。
CORS是什么
CORS(Cross-Origin Resource Sharing,跨域资源共享)是一种机制,它使用额外的HTTP请求头来告诉浏览器允许一个域上的网页访问另一个域上的资源。
CORS的实现原理
首先要知道一点,CORS中的请求分为两点:简单请求和复杂请求。
简单请求 :简单请求指的是那些不会对服务器数据造成意外影响的请求,不需要发送一个预检请求。简单请求的方法包含:GET
、POST
和HEAD
。
复杂请求:复杂请求是指不满足简单请求条件的剩下所有其他类型的请求。这类请求通常需要发送一个预检请求来测试请求是否安全可靠。
预检请求 :预检请求是浏览器自行发起的OPTIONS
请求,用于询问服务器当前客户端是否有权限执行特定的请求。要设置以下这些:
Access-Control-Allow-Methods
: 指示允许的HTTP方法。Access-Control-Allow-Headers
: 表明允许的自定义请求头。Access-Control-Max-Age
: 指定预检请求的结果可以被缓存的时间长度。Access-Control-Allow-Origin
:设置哪些域可以访问资源。
这里是用CORS发送了一个复杂请求,可以看到包含了GET
、POST
、 PUT
、 DELETE
、 OPTIONS
请求。还有一个预检请求。
javascript
const http = require('http');
const server = http.createServer((req, res) => {
const headers = {
'Access-Control-Allow-Origin': '*',
'Access-Control-Allow-Methods': 'GET, POST, PUT, DELETE, OPTIONS',
'ACCess-Control-Allow-Headers': 'Content-Type, Authorization, X-Custom-Header',
'Access-Control-Max-Age': '86400',
}
if (req.method === 'OPTIONS') {
// 预检请求 200 201 Created 204 No Content + 预检请求
res.writeHead(204, headers)
console.log('预检请求')
res.end()
}
if (req.method === 'PUT' && req.url === '/data') {
res.writeHead(201, headers)
res.end('PUT 请求成功')
} else {
res.writeHead(404, headers);
res.end('404 Not Found')
}
})
server.listen(3000, () => {
})
WebSocket
WebSocket 是一种在单独TCP(传输控制协议)连接上进行全双工通信的通道。和传统的HTTP请求-响应模型不同,WebSocket 可以让服务器和客户端之间实现实时双向通信。
WebSocket的特点
- 全双工通信:当WebSocket建立联系,客户端和服务器端就可以同时发送和接收信息。
- 跨平台支持:- WebSocket协议被设计为可以在各种环境中工作,包括Web浏览器、移动应用以及其他任何支持该协议的应用程序。
webSocket的工作原理
首先要发送一个HTTP请求,这个请求包含Upgrade
头部,用来将连接升级成WebSocket协议。
一旦建立了连接,客户端就可以和服务器端相互发送信息。每个信息都是由一个或多个帧组成。每帧都有固定的头,后面跟着数据部分。
javascript
const WebSocket = require('ws')
const http = require('http')
const server = http.createServer((req, res) => {
res.writeHead(200, {
'Content-Type': 'text/plain'
})
res.end('WebSocket Server')
})
// web + socket
const wss = new WebSocket.Server({
server,
path: '/ws'
})
wss.on('connection', (ws) => {
console.log('Client connected')
// 双向通信
ws.on('message', (message) => {
console.log(`Received message: ${message}`)
ws.send(`Server received: ${message}`)
})
ws.send('Welcome to the WebSocket server')
})
server.listen(8080, () => {
console.log('WebSocket Server is running on port 8080')
})
// 1. 创建服务器 http
// 2. 创建 WebSocket 服务器
// 3. 简历一次 http 连接
注意:WebSocket是基于HTTP请求的。是先发送HTTP请求,成功链接后在升级为WebSocket。
PostMessage
PostMessage是Html5的一个新特性,用于在不同窗口之间安全通信的API。也是绕过同源策略的一种方法,同时还能保证安全性。
index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<h1>This is parent window</h1>
<input type="text" class="inp">
<button class="send">发送信息到iframe</button>
<div class="contents">
<p>接收到的信息</p>
<ul class="messages"></ul>
</div>
<iframe src="child.html" height="600" widht="800"
class="child-iframe" frameborder="0"></iframe>
<script>
window.addEventListener('message', (e) => {
console.log(e, '--------- 收到child消息')
})
const win = document.querySelector('.child-iframe').contentWindow
// console.log(win);
document.querySelector('.send').addEventListener('click', () => {
const message = document.querySelector('.inp').value
// html5 新特性 跨域(窗口)通信
win.postMessage(message, '*')
})
</script>
</body>
</html>
child.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<h1>This is iframe child page.</h1>
<script>
window.addEventListener('message', (e) => {
console.log(e, '--------- 收到消息')
window.parent.postMessage('来自child 的消息', '*')
})
</script>
</body>
</html>
以上代码就是将child.html 嵌入index.html。主要是演示下如何使用PostMessage。
总结
在处理跨域请求的问题上,在过去是使用JSONP,在现在是CORS。WebSocket和PostMessage都是在此基础上优化通信的方法。