龙空技术网

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

程序员中的故事大王 776

前言:

今天咱们对“phpajax设置cookies”可能比较关怀,兄弟们都需要知道一些“phpajax设置cookies”的相关知识。那么小编在网络上汇集了一些有关“phpajax设置cookies””的相关内容,希望兄弟们能喜欢,咱们快快来学习一下吧!

一、跨域请求痛点

最近网站新增了一个域名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 FrameHttpexport { 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_exemptdef 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 = TrueSESSION_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 = TrueCORS_ORIGIN_WHITELIST = [    ';,# 域名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方案跨域也实现了。

标签: #phpajax设置cookies #jqueryajax请求方式