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

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

bigegpt 2024-10-12 05:55 7 浏览

一、跨域请求痛点

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

相关推荐

得物可观测平台架构升级:基于GreptimeDB的全新监控体系实践

一、摘要在前端可观测分析场景中,需要实时观测并处理多地、多环境的运行情况,以保障Web应用和移动端的可用性与性能。传统方案往往依赖代理Agent→消息队列→流计算引擎→OLAP存储...

warm-flow新春版:网关直连和流程图重构

本期主要解决了网关直连和流程图重构,可以自此之后可支持各种复杂的网关混合、多网关直连使用。-新增Ruoyi-Vue-Plus优秀开源集成案例更新日志[feat]导入、导出和保存等新增json格式支持...

扣子空间体验报告

在数字化时代,智能工具的应用正不断拓展到我们工作和生活的各个角落。从任务规划到项目执行,再到任务管理,作者深入探讨了这款工具在不同场景下的表现和潜力。通过具体的应用实例,文章展示了扣子空间如何帮助用户...

spider-flow:开源的可视化方式定义爬虫方案

spider-flow简介spider-flow是一个爬虫平台,以可视化推拽方式定义爬取流程,无需代码即可实现一个爬虫服务。spider-flow特性支持css选择器、正则提取支持JSON/XML格式...

solon-flow 你好世界!

solon-flow是一个基础级的流处理引擎(可用于业务规则、决策处理、计算编排、流程审批等......)。提供有“开放式”驱动定制支持,像jdbc有mysql或pgsql等驱动,可...

新一代开源爬虫平台:SpiderFlow

SpiderFlow:新一代爬虫平台,以图形化方式定义爬虫流程,不写代码即可完成爬虫。-精选真开源,释放新价值。概览Spider-Flow是一个开源的、面向所有用户的Web端爬虫构建平台,它使用Ja...

通过 SQL 训练机器学习模型的引擎

关注薪资待遇的同学应该知道,机器学习相关的岗位工资普遍偏高啊。同时随着各种通用机器学习框架的出现,机器学习的门槛也在逐渐降低,训练一个简单的机器学习模型变得不那么难。但是不得不承认对于一些数据相关的工...

鼠须管输入法rime for Mac

鼠须管输入法forMac是一款十分新颖的跨平台输入法软件,全名是中州韵输入法引擎,鼠须管输入法mac版不仅仅是一个输入法,而是一个输入法算法框架。Rime的基础架构十分精良,一套算法支持了拼音、...

Go语言 1.20 版本正式发布:新版详细介绍

Go1.20简介最新的Go版本1.20在Go1.19发布六个月后发布。它的大部分更改都在工具链、运行时和库的实现中。一如既往,该版本保持了Go1的兼容性承诺。我们期望几乎所...

iOS 10平台SpriteKit新特性之Tile Maps(上)

简介苹果公司在WWDC2016大会上向人们展示了一大批新的好东西。其中之一就是SpriteKitTileEditor。这款工具易于上手,而且看起来速度特别快。在本教程中,你将了解关于TileE...

程序员简历例句—范例Java、Python、C++模板

个人简介通用简介:有良好的代码风格,通过添加注释提高代码可读性,注重代码质量,研读过XXX,XXX等多个开源项目源码从而学习增强代码的健壮性与扩展性。具备良好的代码编程习惯及文档编写能力,参与多个高...

Telerik UI for iOS Q3 2015正式发布

近日,TelerikUIforiOS正式发布了Q32015。新版本新增对XCode7、Swift2.0和iOS9的支持,同时还新增了对数轴、不连续的日期时间轴等;改进TKDataPoin...

ios使用ijkplayer+nginx进行视频直播

上两节,我们讲到使用nginx和ngixn的rtmp模块搭建直播的服务器,接着我们讲解了在Android使用ijkplayer来作为我们的视频直播播放器,整个过程中,需要注意的就是ijlplayer编...

IOS技术分享|iOS快速生成开发文档(一)

前言对于开发人员而言,文档的作用不言而喻。文档不仅可以提高软件开发效率,还能便于以后的软件开发、使用和维护。本文主要讲述Objective-C快速生成开发文档工具appledoc。简介apple...

macOS下配置VS Code C++开发环境

本文介绍在苹果macOS操作系统下,配置VisualStudioCode的C/C++开发环境的过程,本环境使用Clang/LLVM编译器和调试器。一、前置条件本文默认前置条件是,您的开发设备已...