百度360必应搜狗淘宝本站头条
当前位置:网站首页 > 文章教程 > 正文

Ajax跨域请求的两种实现方式

yund56 2025-03-30 18:38 6 浏览

一、跨域请求痛点

最近网站新增了一个域名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方案跨域也实现了。

相关推荐

仍需打磨:首款Windows 10X模拟器上手

今天,微软发布了适用于Windows10X的首款模拟器,以便于开发人员初步了解适用于双屏设备的操作系统调整。微软希望在SurfaceNeo今年年底正式发售之前,让开发人员对应用程序进行优化。因此...

Windows10 编译OpenCV4.5源码

在OpenCV4.5+VisualStudio2017开发环境配置中,介绍了OpenCV4.5的下载和安装,待扩展内容OpenCV源码编译,在本文中做补充。研究源码无疑是学习OpenCV的一...

微软7年磨一剑,Windows 10X抢先上手体验

2月22日消息,微软在去年10月正式推出了Windows10X系统,该系统除了可用于传统的电脑外,同样适用于双屏设备或者折叠屏设备,拥有更好的触控操作体验。Windows10X在操作系统底层、命令...

Office重新设计了图标,你觉得如何?

微软重新设计了Office的应用图标,在接下来的几个月里,这些图标将从移动端和网页端开始陆续推广至各大平台。距离Office图标的最近一次更新还是在2013年,那时鲍尔默时代的产物,那时微软还在纠结是...

微软发布 Win10 Build 21376 内测版:重新设计默认用户界面字体

IT之家5月7日消息今年早些时候,微软意外地确认正在为Windows10进行UI改进,并在预览版中发现了相关的非活动代码。微软今天宣布向开发渠道中的内测用户发布Windows1...

前端开发需要了解常用7种JavaScript设计模式

作者|Deven译者|王强策划|小智转发链接:https://mp.weixin.qq.com/s/Lw4D7bfUSw_kPoJMD6W8gg前言JavaScript中的设计模式指的是...

「Qt入门第二篇」基础(二)编写Qt多窗口程序

导语程序要实现的功能是:程序开始出现一个对话框,按下按钮后便能进入主窗口,如果直接关闭这个对话框,便不能进入主窗口,整个程序也将退出。当进入主窗口后,我们按下按钮,会弹出一个对话框,无论如何关闭这个对...

在吴中 ,哪里有学网页设计的培训班?

网页设计介绍Web2.0标准布局之网页长期签约就业班(全日制)课程收费:7680元课程周期:5-6个月(45分钟/课)使用教材:《教师自编教材》考核发证:Adobe《网页设计师》培训内容第一部份:...

Qt快速入门(工程的创建、UI界面布局、多线程、项目)

本文档将介绍QT工程的创建、UI界面布局,并以计数器为例了解QT中多线程的用法,最终完成一个基础的QT项目。1创建QT工程文件在安装好QT之后,能够在其安装组件中找到QtCreator,点击设置项...

应用崩溃有救啦!Windows新更新将解决应用崩溃问题

【CNMO新闻】对于不少上班族来说,当自己的电脑在运行某个应用程序时,突然出现应用程序崩溃问题,常常会让人十分苦恼。尤其是对于设计师或者编辑,当自己的作品未能及时保存应用崩溃全部消失的时候,简直就是痛...

Python Qt GUI设计:窗口布局管理方法【强化】(基础篇—6)

在PythonQtGUI设计:窗口布局管理方法【基础篇】(基础篇—5)文章中,聊到了如何使用QtDesigner进行窗口布局管理,其实在QtDesigner中可以非常方便进行窗口布局管理设计,...

思考:如何设计游戏业务框架

虽然现在连主机游戏都纷纷加入了网战部分,不过其身份主要充当状态同步,矛盾点集中在同步即时性上。以大量数值逻辑为主的业务功能侧重点则不同。如果说写代码就是用状态的操作给问题建模,那么编程范式和设计模式种...

用.NET设计一个假装黑客的屏幕保护程序

本文主要介绍屏幕保护程序的一些相关知识,以及其在安全方面的用途,同时介绍了如何使用.NET开发一款屏幕保护程序,并对核心功能做了介绍,案例代码开源:https://github.com/sangy...

光的艺术:灯具创意设计

本文转自|艺术与设计微信号|artdesign_org_cn“光”是文明的起源,是思维的开端,同样也是人类睁眼的开始。每个人在出生一刻,便接受了光的照耀和洗礼。远古时候,人们将光奉为神明,用火来...

Python Qt GUI设计:将UI文件转换Python文件三种妙招(基础篇—2)

在开始本文之前提醒各位朋友,Python记得安装PyQt5库文件,Python语言功能很强,但是Python自带的GUI开发库Tkinter功能很弱,难以开发出专业的GUI。好在Python语言的开放...