网站首页 > 技术文章 正文
一、跨域请求痛点
最近网站新增了一个域名B用于分离不同的功能。但是需要复用服务器的高防等服务,但是服务和原有域名A绑定,所以新域名B需要直接去调用域名A。
一开始想使用CNAME的方式,让B直接指向A。但是Https支持性有点问题,需要多域名证书。也考虑过反向代理,但是代理服务器的性能和高防等又是一个问题。
最终决定在域名B的网页中,所有请求都直接去调用域名A的接口。于是就遇到了跨域请求的问题。
二、跨域请求的实现方式
网上找了许多资料来实现跨域请求。最终预估下来,有两种方案比较靠谱:通过iframe实现和CORS方案
三、通过iframe实现
初步设想是加载一个域名A的iframe页面,然后通过postMessage将所有Ajax请求,转发给这个页面,通过这个页面来进行请求,最终将结果通过postMessage回发给外层的域名B页面。
于是开始实现:
前端使用的是React,所以实现了一个FrameHttp.js专门用法封装ajax调用。调用FrameHttp.ajax将所有外部Jquery请求转发给iframe中域名A的/util/ajaxrequest页面。
import Tools from "../Tools"
const FrameHttpCmd = {
INIT: 1,
REQUEST: 2,
REQUEST_CALLBACK: 3,
}
class FrameHttp {
static isInit = false
static _request_buffer = []
static frame = null
static message_key = 0
static message_key_max = 10000000
static request_map = {}
static init(domain) {
var the_frame = document.createElement('iframe')
let url_obj = new URL(domain)
url_obj.pathname = Tools.getUrl('/util/ajaxrequest')
the_frame.src = url_obj.toString()
the_frame.style.visibility = 'hidden'
the_frame.style.position = 'absolute'
the_frame.style.width = 0
the_frame.style.height = 0
FrameHttp.frame = the_frame
document.body.appendChild(the_frame)
window.addEventListener('message', this.onMessage)
}
static _initFrame() {
FrameHttp.isInit = true
console.log('(INFO)FrameHttp._initFrame')
for (let i = 0; i < FrameHttp._request_buffer.length; i++) {
let arg = FrameHttp._request_buffer[i]
FrameHttp.ajax(arg)
}
FrameHttp._request_buffer = []
}
static getMessageKey() {
let message_key = FrameHttp.message_key
FrameHttp.message_key = (FrameHttp.message_key+1)%FrameHttp.message_key_max
return message_key
}
static ajax(arg) {
if (FrameHttp.isInit) {
// console.log(arg)
const { success, error, ...others } = arg
let message_key = FrameHttp.getMessageKey()
FrameHttp.request_map[message_key] = { success, error }
FrameHttp.frame.contentWindow.postMessage({
cmd: FrameHttpCmd.REQUEST,
data: others,
key: message_key,
}, '*')
console.log('(INFO)FrameHttp.ajax', others)
}
else {
FrameHttp._request_buffer.push(arg)
console.log('(INFO)FrameHttp.ajax:push buffer')
}
}
static onMessage(e) {
const { data } = e
if (data.cmd == FrameHttpCmd.INIT) {
FrameHttp._initFrame()
}
else if (data.cmd == FrameHttpCmd.REQUEST_CALLBACK) {
// console.log(data.key, data.success)
let item = FrameHttp.request_map[data.key]
if (data.error) {
if (item.error) {
item.error(data.error)
}
}
else {
if (item.success) {
item.success(data.success)
}
}
delete FrameHttp.request_map[data.key]
}
}
}
export default FrameHttp
export { FrameHttpCmd }
然后域名A中的/util/ajaxrequest页面处理请求:
import React, { Component } from 'react'
import { FrameHttpCmd } from '../../../common_js/web_frame/FrameHttp'
import jquery from '../../../common_js/jquery.min'
class AjaxRequest extends Component {
constructor(props) {
super(props)
this.onMessage = this.onMessage.bind(this)
}
onMessage(e) {
const { cmd, data, key, cookie } = e.data
if (cmd == FrameHttpCmd.REQUEST) {
// console.log(key, data)
console.log(document.cookie)
jquery.ajax({
...data,
success: (data)=>{
window.parent.postMessage({
cmd: FrameHttpCmd.REQUEST_CALLBACK,
key,
success: data,
}, '*')
},
error: ()=>{
window.parent.postMessage({
cmd: FrameHttpCmd.REQUEST_CALLBACK,
key,
error: 'error',
}, '*')
},
})
}
}
componentDidMount() {
window.parent.postMessage({
cmd: FrameHttpCmd.INIT,
}, '*')
window.addEventListener('message', this.onMessage)
}
render() {
return null
}
}
export default AjaxRequest
如此实现后,发现iframe因为跨域问题无法加载
因为后端是由Django实现的,经过查阅发现,在views中需要进行处理,添加xframe_options_exempt装饰器,实现跨域加载iframe。
@xframe_options_exempt
def indexCross(request, *args, **kwargs):
return render(request, 'index.html', {})
功能能够正常请求,但是请求中cookie并没有正常传送。
经过排查发现,对于跨域的iframe,google浏览器默认对于cookie中SameSite这个参数是LAX,会导致只有同源才能设置服务器返回的Set-Cookie。所以需要将服务器返回的Set-Cookie中指定SameSite为None,这样前端才能成功设置Cookie。
于是服务端Django引入了中间件django-cookies-samesite。settings.py进行如下修改:
INSTALLED_APPS = [
...
'corsheaders',
...
]
# 中间件request按照注册顺序顺序执行,response按照注册顺序倒序执行
MIDDLEWARE = [
'django_cookies_samesite.middleware.CookiesSameSite', # 此处response需要最后执行
...
]
SESSION_COOKIE_SECURE = True
SESSION_COOKIE_SAMESITE = 'None'
至此实现了iframe方式跨域。
四、CORS方案
这个需要前端Jquery加入两个参数:
jquery.ajax({
...
xhrFields: {
withCredentials: true
},
crossDomain: true,
})
然后服务端需要开启跨域请求支持。Django下引入中间件django-cors-headers。settings.py下如下设置:
MIDDLEWARE = [
...
'corsheaders.middleware.CorsMiddleware', # 这个位置越高越好,至少要高于CommonMiddleware
...
]
# 跨域配置
CORS_ALLOW_CREDENTIALS = True
# CORS_ORIGIN_ALLOW_ALL = True
CORS_ORIGIN_WHITELIST = [
'https://www.xxx.com',# 域名B
]
CORS_ALLOW_METHODS = [
'DELETE',
'GET',
'OPTIONS',
'PATCH',
'POST',
'PUT',
'VIEW',
]
CORS_ALLOW_HEADERS = [
'XMLHttpRequest',
'X_FILENAME',
'accept-encoding',
'authorization',
'content-type',
'dnt',
'origin',
'user-agent',
'x-csrftoken',
'x-requested-with',
'Pragma',
'cache-control',
]
此方案也需要和iframe一样,解决cookie中SameSite的问题。
至此CORS方案跨域也实现了。
猜你喜欢
- 2025-05-15 网页中如何实现点击按钮将文本复制到剪贴板?
- 2025-05-15 JavaScript 事件——“事件类型”中“UI事件”的注意要点
- 2025-05-15 WEB大前端进阶之模块化
- 2025-05-15 多可文档系统在Edge, Chrome等浏览器启动客户端的代码及方法
- 2025-05-15 vue下载excel文件方法
- 2025-05-15 three.js 入门
- 2025-05-15 判断变量是否为数组
- 2025-05-15 Js基础3:节点创建
- 2025-05-15 React对话框组件实现
- 2025-05-15 JS如何判断文字被ellipsis了?
- 1507℃桌面软件开发新体验!用 Blazor Hybrid 打造简洁高效的视频处理工具
- 506℃Dify工具使用全场景:dify-sandbox沙盒的原理(源码篇·第2期)
- 486℃MySQL service启动脚本浅析(r12笔记第59天)
- 466℃服务器异常重启,导致mysql启动失败,问题解决过程记录
- 464℃启用MySQL查询缓存(mysql8.0查询缓存)
- 444℃「赵强老师」MySQL的闪回(赵强iso是哪个大学毕业的)
- 423℃mysql服务怎么启动和关闭?(mysql服务怎么启动和关闭)
- 419℃MySQL server PID file could not be found!失败
- 最近发表
-
- netty系列之:搭建HTTP上传文件服务器
- 让deepseek教我将deepseek接入word
- 前端大文件分片上传断点续传(前端大文件分片上传断点续传怎么操作)
- POST 为什么会发送两次请求?(post+为什么会发送两次请求?怎么回答)
- Jmeter之HTTP请求与响应(jmeter运行http请求没反应)
- WAF-Bypass之SQL注入绕过思路总结
- 用户疯狂点击上传按钮,如何确保只有一个上传任务在执行?
- 二 计算机网络 前端学习 物理层 链路层 网络层 传输层 应用层 HTTP
- HTTP请求的完全过程(http请求的基本过程)
- dart系列之:浏览器中的舞者,用dart发送HTTP请求
- 标签列表
-
- c++中::是什么意思 (83)
- 标签用于 (65)
- 主键只能有一个吗 (66)
- c#console.writeline不显示 (75)
- pythoncase语句 (81)
- es6includes (73)
- windowsscripthost (67)
- apt-getinstall-y (86)
- node_modules怎么生成 (76)
- chromepost (65)
- c++int转char (75)
- static函数和普通函数 (76)
- el-date-picker开始日期早于结束日期 (70)
- js判断是否是json字符串 (67)
- checkout-b (67)
- localstorage.removeitem (74)
- vector线程安全吗 (70)
- & (66)
- java (73)
- js数组插入 (83)
- linux删除一个文件夹 (65)
- mac安装java (72)
- eacces (67)
- 查看mysql是否启动 (70)
- 无效的列索引 (74)