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都是在此基础上优化通信的方法。

相关推荐
mldong4 小时前
你的 Vue3 项目也能有钉钉同款审批流设计器:npm 装包,10 分钟画出第一条审批流
前端·vue.js
2分钟速写快排5 小时前
什么是 RAG?如何用 RAG 实现一个用户记忆?
前端·后端·ai编程
passerby60615 小时前
如何自己造一个时间处理库
前端·javascript·github
走到天涯海角6 小时前
react里面的长列表渲染优化
前端·react.js·前端框架
小羊没烦恼!7 小时前
Hello Web API系列教程——Web API与国际化
java·服务器·前端·javascript·php
北岛贰7 小时前
迷茫焦虑期,我做了一个带支付带官网的 AI 聊天虚拟恋人 App
前端·人工智能·后端
mayaairi8 小时前
Vue2 组件通讯(三):全局事件总线、PubSub、插槽与组件实例属性
前端·javascript·vue.js
kyriewen9 小时前
面试官问我:AI 都能写代码了,前端凭什么还值 25K
前端·javascript·人工智能
风骏时光牛马10 小时前
AI源码分析:拆解模型底层实现逻辑
前端
BillKu10 小时前
全局样式变量:CSS 自定义属性(--border-color)和SCSS 变量($border-color)的说明
javascript·css·scss