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

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

qiyuwang 2025-03-06 19:15 10 浏览 0 评论

一、跨域请求痛点

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

相关推荐

# 安装打开 ubuntu-22.04.3-LTS 报错 解决方案

#安装打开ubuntu-22.04.3-LTS报错解决方案WslRegisterDistributionfailedwitherror:0x800701bcError:0x80070...

利用阿里云镜像在ubuntu上安装Docker

简介:...

如何将Ubuntu Kylin(优麒麟)19.10系统升级到20.04版本

UbuntuKylin系统使用一段时间后,有新的版本发布,如何将现有的UbuntuKylin系统升级到最新版本?可以通过下面的方法进行升级。1.先查看相关的UbuntuKylin系统版本情况。使...

Ubuntu 16.10内部代号确认为Yakkety Yak

在正式宣布Ubuntu16.04LTS(XenialXerus)的当天,Canonical创始人MarkShuttleworth还非常开心的在个人微博上宣布Ubuntu下个版本16.10的内...

如何在win11的wsl上装ubuntu(怎么在windows上安装ubuntu)

在Windows11的WSL(WindowsSubsystemforLinux)上安装Ubuntu非常简单。以下是详细的步骤:---...

Win11学院:如何在Windows 11上使用WSL安装Ubuntu

IT之家2月18日消息,科技媒体pureinfotech昨日(2月17日)发布博文,介绍了3中简便的方法,让你轻松在Windows11系统中,使用WindowsSubs...

如何查看Linux的IP地址(如何查看Linux的ip地址)

本头条号每天坚持更新原创干货技术文章,欢迎关注本头条号"Linux学习教程",公众号名称“Linux入门学习教程"。...

怎么看电脑系统?(怎么看电脑系统配置)

要查看电脑的操作系统信息,可以按照以下步骤操作,根据不同的操作系统选择对应的方法:一、Windows系统通过系统属性查看右键点击桌面上的“此电脑”(或“我的电脑”)图标,选择“属性”。在打开的...

如何查询 Linux 内核版本?这些命令一定要会!

Linux内核是操作系统的核心,负责管理硬件资源、调度进程、处理系统调用等关键任务。不同的内核版本可能支持不同的硬件特性、提供新的功能,或者修复了已知的安全漏洞。以下是查询内核版本的几个常见场景:...

深度剖析:Linux下查看系统版本与CPU架构

在Linux系统管理、维护以及软件部署的过程中,精准掌握系统版本和CPU架构是极为关键的基础操作。这些信息不仅有助于我们深入了解系统特性、判断软件兼容性,还能为后续的软件安装、性能优化提供重要依据。接...

504 错误代码解析与应对策略(504错误咋解决)

在互联网的使用过程中,用户偶尔会遭遇各种错误提示,其中504错误代码是较为常见的一种。504错误并非意味着网站被屏蔽,它实际上是指服务器在规定时间内未能从上游服务器获取响应,专业术语称为“Ga...

猎聘APP和官网崩了?回应:正对部分职位整改,临时域名可登录

10月12日,有网友反映猎聘网无法打开,猎聘APP无法登录。截至10月14日,仍有网友不断向猎聘官方微博下反映该情况,而猎聘官方微博未发布相关情况说明,只是在微博内对反映该情况的用户进行回复,“抱歉,...

域名解析的原理是什么?域名解析的流程是怎样的?

域名解析是网站正常运行的关键因素,因此网站管理者了解域名解析的原理和流程对于做好域名管理、解决常见解析问题,保障网站的正常运转十分必要。那么域名解析的原理是什么?域名解析的流程是怎样的?接下来,中科三...

Linux无法解析域名的解决办法(linux 不能解析域名)

如果由于误操作,删除了系统原有的dhcp相关设置就无法正常解析域名。  此时,需要手动修改配置文件:  /etc/resolv.conf  将域名解析服务器手动添加到配置文件中  该文件是DNS域名解...

域名劫持是什么?(域名劫持是什么)

域名劫持是互联网攻击的一种方式,通过攻击域名解析服务器(DNS),或伪造域名解析服务器(DNS)的方法,把目标网站域名解析到错误的地址从而实现用户无法访问目标网站的目的。说的直白些,域名劫持,就是把互...

取消回复欢迎 发表评论: