CORS和JSONP:新旧时代处理跨域问题的方法

在我们介绍JSONP和CORS之前,先要知道:

我们为什么要使用JSONP和CORS

实际上,JSONP和CORS都是来解决跨域问题的,当两个页面的协议、端口、域名中有一个不一样时就存在了跨域。一旦出现跨域,那么浏览器发送跨域请求后,请求回来的数据都会被浏览器拦截。

浏览器对跨域请求的拦截

在解决跨域请求问题方面,主要是通过两种方法:JSONP和CORS。

JSONP是什么

JSONP(JSON withPadding)是一种通过<script>标签绕过同源策略来实现跨域请求的方法。

JSONP的实现原理

  1. 动态创建<script>标签:当需要进行跨域请求时,客户端会动态创建一个<script>标签,并将这个标签的src属性设置为目标服务器的URL。
  2. 服务器端响应:服务器端收到请求后,会返回一段JavaScript代码,而不是标准的JSON数据。这段代码会调用之前指定好的回调函数,并将数据作为参数传递给这个回调函数。
  3. 执行回调函数:浏览器会下载并执行这个脚本,这会调用回调函数,传入的数据会被当做参数被处理,这就让客户端可以获取服务器端的数据。

注意事项

  1. JSONP只支持GEt请求,因为<script>标签只支持GET方法。
  2. JSONP依赖于客户端和服务器端双方的支持,客户端必须正确生成<script>标签,而且服务器端要正确封装数据到回调函数中。
  3. 因为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中的请求分为两点:简单请求和复杂请求。

简单请求 :简单请求指的是那些不会对服务器数据造成意外影响的请求,不需要发送一个预检请求。简单请求的方法包含:GETPOSTHEAD

复杂请求:复杂请求是指不满足简单请求条件的剩下所有其他类型的请求。这类请求通常需要发送一个预检请求来测试请求是否安全可靠。

预检请求 :预检请求是浏览器自行发起的OPTIONS请求,用于询问服务器当前客户端是否有权限执行特定的请求。要设置以下这些:

  • Access-Control-Allow-Methods: 指示允许的HTTP方法。
  • Access-Control-Allow-Headers: 表明允许的自定义请求头。
  • Access-Control-Max-Age: 指定预检请求的结果可以被缓存的时间长度。
  • Access-Control-Allow-Origin:设置哪些域可以访问资源。

这里是用CORS发送了一个复杂请求,可以看到包含了GETPOSTPUT DELETEOPTIONS请求。还有一个预检请求。

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的特点

  1. 全双工通信:当WebSocket建立联系,客户端和服务器端就可以同时发送和接收信息。
  2. 跨平台支持:- 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都是在此基础上优化通信的方法。

相关推荐
萌萌哒草头将军1 小时前
⚡⚡⚡Vite 被发现存在安全漏洞🕷,请及时升级到安全版本
前端·javascript·vue.js
会功夫的李白2 小时前
Electron + Vite + Vue 桌面应用模板
javascript·vue.js·electron·vite·模版
小兵张健2 小时前
运用 AI,看这一篇就够了(上)
前端·后端·cursor
不怕麻烦的鹿丸3 小时前
node.js判断在线图片链接是否是webp,并将其转格式后上传
前端·javascript·node.js
vvilkim3 小时前
控制CSS中的继承:灵活管理样式传递
前端·css
南城巷陌3 小时前
Next.js中not-found.js触发方式详解
前端·next.js
No Silver Bullet3 小时前
React Native进阶(六十一): WebView 替代方案 react-native-webview 应用详解
javascript·react native·react.js
拉不动的猪3 小时前
前端打包优化举例
前端·javascript·vue.js
ok0604 小时前
JavaScript(JS)单线程影响速度
开发语言·javascript·ecmascript
Bigger4 小时前
Tauri(十五)——多窗口之间通信方案
前端·rust·app