首页/文章/http-网络

没有koa的cors你怎么处理跨域?

2024-11-29
311411 分钟
...

关联问题: JSONP有何局限性 CORS如何处理复杂请求 代理在生产环境效果如何

前言

“金九银十”的秋招,就像是落进池塘的羽毛,连点水花都没砸起来。已读不回是常态,能给面试已是情分,能拿offer的更是少之又少。可怜我在寒潮来临的深秋之际,也只得哀叹一句“越来越冷了啊...”

不过人总是要吃饭的,所以再怎么冷,卖炭翁也得卖炭,牛马攻城狮也得“攻城”,既是为了填饱肚子,也是为了“春天”做准备。所以今天为各位前端工程狮细聊一下前端面试中几乎必考的跨域

正文

同源策略

回想之前的字节面试,问到项目的时候,面试官亲切地问了一句:

你的项目中用到了koa,那你为什么要用koa这个框架呢?

主要是为了利用koa自带的cors去处理跨域

那你能讲讲cors处理跨域的原理吗?

(我已经慌了)这...这个不是很了解

(面试官扶额苦笑)那如果你不用koa,那么你怎么处理跨域呢

(我汗流浃背)呃...这个...可以用jsonp...

在聊跨域这个问题之前,我们先得搞清楚什么是同源策略。举个栗子,微信数据库存储了很多很多的微信用户的数据,那么当我们打开微信的时候,是会发送接口请求的,这很好理解。

假设,有那么一天,我对我前女友的思念之情如滔滔江水一般连绵不绝,于是我想去看看她的朋友圈,看见她最近有没有和别的男生聊天。再假设,我无意之间在沙漠里捡到一张小纸条,上面又刚好写着微信聊天数据的后端接口。那我是不是就能看到我前女友是如何在深夜与他人倾诉对我的思念之情呢?

很显然,用脚趾头都能想到这绝对不可能。而其中一道防火墙就是同源策略。同源策略是Web浏览器的一项重要安全机制,旨在防止一个网站的脚本非法访问另一个网站的数据。根据同源策略,只有当两个URL的协议、域名和端口号都相同时,它们才被认为是同源的。如果这三个条件中有任何一个不同,那么这两个URL就被认为是跨域的。

跨域问题是指当一个请求从一个源发起,试图访问另一个源的资源时,如果这两个源不相同,浏览器会根据同源策略阻止这个请求。即使后端服务器会正常响应请求,但由于浏览器的同源策略限制,客户端无法接收到这些响应数据。也就是说,不管如何,只要发送了接口请求,后端就一定会响应,只不过数据到浏览器手里的时候被浏览器拦截了。所以同源策略发生在数据返回的过程中而不是请求发送的过程中。

这里我跑起来了两个js项目,一个前端vue,另一个后端的node。假设同源策略不存在,那么前端页面中就应该打印一个hello,但是很显然不行。再看看报错,果不其然被浏览器拦截了。

<body>
    <script>
        fetch('http://localhost:3000', {
            method: 'GET',
        }).then(res => { return res.json() })
            .then(res => {
                console.log(res);

            })
    </script>
</body>
const http = require('http')

const server = http.createServer((req, res) => {
    res.writeHead(200, {
        'Content-Type': 'text',
        "accept-charset": "utf-8"
    })
    const data = {
        name: '彭于晏',
        age: 18
    }
    res.end(JSON.stringify(data))
})

server.listen(3000, () => {
    console.log('listening on port 3000');
})

跨域的解决方案

显然同源策略帮我们很大程度上解决了部分安全性的问题,但是对于前端而言似乎不那么友好。远的不说,就连我们自己写的前后端都拿不到数据,更何况是实际开发的大型项目呢?所以对于前端而言,掌握解决跨域问题是必不可少的

JSONP

JSONP(JSON with Padding)是一种解决跨域问题的古老方法,众所周知在html中可能会嵌入 script 标签,但是 script 可能会有src属性,既然有src就意味着会发送请求而 script 的src发送的请求不受同源策略的影响,所以jsonp主要利用了 script 标签不受同源策略限制的特点。其工作原理如下:

  1. 创建 script 标签:在客户端动态创建一个 script 标签,并将其 src 属性设置为目标URL。

  2. 定义回调函数:客户端定义一个全局函数,该函数用于处理返回的数据。

  3. 服务端响应:服务端将数据包裹在一个函数调用中返回,这个函数名就是客户端定义的回调函数名。

代码demo如下

<body>
    <button id="btn">请求按钮</button>

    <!-- 这一步不受同源策略的影响,否则前端无法引入任何框架 -->
    <script src="http://localhost:3000"></script>


    <script>
        function jsonp(url, cb) {
            return new Promise((resolve, reject) => {
                const script = document.createElement('script')
                script.src = `${url}?cb=${cb}`
                document.body.appendChild(script)
                

                window[cb] = (data) => {
                    console.log(data);
                }
            })
        }

        const btn = document.getElementById('btn')
        btn.addEventListener('click', () => {
            
            jsonp('http://localhost:3000', 'callback')
                .then(res => {
                    console.log(res);
                })
            
            
            
            

        })
    </script>
</body>
const koa = require('koa')
const app = new koa()

const main = (ctx, next) => {
    console.log(ctx.query);
    const cb = ctx.query.cb
    const data = 'hello world'
    const str = `${cb}('${data}')`
    
    ctx.body = str
}

app.use(main)
app.listen(3000, () => {
    console.log('server start on port 3000')
})

需要解释一下的是后端用了一下koa框架让代码更简洁(这不重要),不过,后端返回的东西是一个字符串,值为“callback('hello world')”,可能很多人会好奇为什么字符串会变成代码去触发Window也就是全局身上的函数,实际上这里是把后端返回的字符串塞进了两个script标签字符串中,变成了一个更长的字符串

"<script> callback("hello world") </script>"

然后把字符串通过 document.body.appendChild(script)插入dom元素,就能成功触发全局身上的函数了。

CORS

CORS是一种现代的跨域解决方案,一般是后端通过在HTTP响应头中添加特定的字段来允许跨域请求。CORS的主要步骤如下:

  1. 预检请求:对于某些复杂的请求(如PUT、DELETE等),浏览器会先发送一个OPTIONS请求(预检请求),询问服务器是否允许该跨域请求。
  2. 实际请求:如果预检请求通过,浏览器会发送实际的请求。
  3. 响应头:服务器在响应头中添加以下字段来控制跨域行为:
  • Access-Control-Allow-Origin:指定允许访问该资源的外部域名。
  • Access-Control-Allow-Methods:指定允许的HTTP方法。
  • Access-Control-Allow-Headers:指定允许的HTTP头部字段。
  • Access-Control-Allow-Credentials:指定是否允许发送Cookie。

这里我们的后端就设置一个Access-Control-Allow-Origin用作演示

<body>
    <button id="btn">请求按钮</button>

    <script>
        const btn = document.getElementById('btn')
        btn.addEventListener('click', () => {
            
            fetch('http://localhost:3000', {
                method: 'GET',

            }).then(res => { return res.json() })
                .then(res => { console.log(res) })
        })
    </script>
</body>
const http = require('http')

const server = http.createServer((req, res) => {
    
    
    res.writeHead(200, {
        
        'Access-Control-Allow-Origin': 'http://127.0.0.1:5500/',
        
    })
    const data = {
        name: '彭于晏'
    }
    res.end(JSON.stringify(data))
})

server.listen(3000, () => {
    console.log('3000 is running ');

})

Node.js 代理

根据刚刚这张图大家不难发现,跨域是因为同源策略,而同源策略又是浏览器干的。那能不能明知山有虎,咱就不去明知山呢?Node代理就是这个道理。前端与后端数据交互关我后端与后端数据交互什么事?还是来个前后端的demo,二者依旧是不同接口。

<template>
  <div>
    hello world
  </div>
</template>

<script setup>
import axios from 'axios'
import { onMounted } from 'vue'

onMounted(() => {
  axios.get("/api").then(res => {
    console.log(res);
  })
})
</script>

<style scoped></style>

const http = require('http')

const server = http.createServer((req, res) => {
    
    

    const data = {
        name: 'hgjw'
    }
    res.end(JSON.stringify(data))
})

server.listen(3000, () => {
    console.log('3000 is running ');

})

前端是靠vite创建的,所以会有一个vite.config文件,在这个文件中做如下配置

import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'


export default defineConfig({
  plugins: [vue()],
  server: {
    proxy: {
      '/api': {
        target: 'http://localhost:3000',
        changeOrigin: true,
        rewrite: path => path.replace(/^\/api/, '')
      }
    }
  }
  
  
  
  
  
  
})

这样就可以绕开同源策略的监察,从而实现前后端数据交互,但是问题在于,vite只是手脚架,项目搭建完打包的时候可不会把手脚架打包进去,所以这种方法只能在开发环境中生效。

Nginx 反向代理

既然node代理只能在开发环境生效,那么生产环境呢?在生产环境中,可以使用Nginx作为反向代理服务器来解决跨域问题。通过配置Nginx,可以将客户端请求转发到目标服务器,并且在响应中添加必要的CORS头。这种方法原理类似cors配置白名单,是目前绝大多数公司使用的方法。不过原理涉及到Linux和数据库,这里就不做赘述了。

冷门方法

通常情况下,讲完上面四种基本也就差不多了,但是考虑到最近“越来越冷了”,还是拓展两种比较冷门的方法比较好。这里的所谓“冷门方法”主要都是用在iframe标签上,负责父子页面之间的通信。

Document.domain

在某些情况下,可以通过设置document.domain来实现子域之间的通信。这里有a,b两个页面,其中src地址要根据自己电脑实际跑起来的html项目去设置 a页面

<body>
    <h2>父级页面</h2>
    <iframe src="http://127.0.0.1:5500/domain/child.html" frameborder="0"></iframe>
   
   <script>
        document.domain = '127.0.0.1'
        var user = '彭于晏'
    </script>
</body>

b页面

<body>
    <h3>子界面</h3>
    <script>
        document.domain = '127.0.0.1'
        console.log(window.parent.user);
    </script>
</body>

postMessage

postMessage 方法允许不同源的窗口之间进行安全的消息传递。这在嵌套的iframe场景中特别有用。 a页面

<body>
    <h2>aaa</h2>
    <iframe src="http://127.0.0.1:5500/postMseeage/b.html" frameborder="0" id="iframe"></iframe>

    <script>
        let iframe = document.getElementById('iframe')
        iframe.onload = function () {
            
            let data = { name: '彭于晏' }

            setInterval(() => {
                iframe.contentWindow.postMessage(JSON.stringify(data), 'http://127.0.0.1:5500/postMseeage/b.html')
            }, 500);
        }

        window.addEventListener('message', function (e) {
            console.log(JSON.parse(e.data));
        })
    </script>
</body>

b页面

<body>
    <h4>bbbbbbbb</h4>
    <script>
        window.addEventListener('message', function (e) {
            console.log(JSON.parse(e.data));
            if (e.data) {
                window.parent.postMessage(JSON.stringify({ age: 157 }), 'http://127.0.0.1:5500/postMseeage/a.html')
            }
        })
    </script>
</body>

postMessage原理类似vue路由之间的传值子父页面通过发送监听接收去进行数据的传输

总结

跨域问题是Web开发中常见的挑战,但通过多种技术手段可以有效解决。无论是古老的JSONP,还是现代的CORS,或是灵活的代理和消息传递机制,每种方法都有其适用场景和优缺点。了解这些技术背后的原理,可以帮助开发者更好地选择合适的方案,确保应用程序的安全性和功能性。希望本文能够帮助你更深入地理解跨域和同源策略,以及如何在实际项目中应对这些问题。最后祝各位读者姥爷0 waring(s),0 error(s)!

如果您觉得这篇文章有帮助,请点个赞吧~

分享文章

相关文章

更多文章 →
http-网络2025-08-06
按下回车后网页是怎么出来的
当你在浏览器中输入一个网址,比如 ,然后按下回车键,几秒钟后页面就完整地呈现在你眼前。这个看似简单的操作,背后其实是一整套精密协作的网络机制在起作用。本文将带你从基础讲起,理解 Web 开发中最重要的网络知识,帮助你搞清楚: 数据是如何从服务器传到你的电脑,并最终变成网页的? 一、互联网通信的分层模型:OSI 七层模型 为了管理复杂的网络通信,工程师们提出了一个通用的参考模型—— OSI 七层模型 。它把整个通信过程划分为七个层次,每一...
学习面试
http-网络2025-03-04
网络代理详解
一、 的基础概念 1\. 什么是代理? 代理是一种通过中间服务器来帮助 访问目标服务器的技术。代理服务器位于客户端与服务器之间,充当两者的中介,从而实现客户端和服务器之间的隔离。用户的所有请求经过代理后,再发往目标服务器,而目标服务器的响应则返回代理,再由代理传递给用户。 2\. 网络代理的工作流程 在基本的网络代理工作流程中, 扮演了“请求转发者”和“响应返回者”的角色: 1. 客户端请求 :客户端将请求发送到代理服务器,而不是直接发...
学习面试
http-网络2024-09-11
面试常客系列之跨域前言
跨域?什么是跨域呢?我们先来看看一个url的组成, ,这一个url我们可以分成四部分来看 是协议, 是域名, 是端口, 是路径.在浏览器中,我们从一个域名发送请求到另一个域名,如果两个域名的协议,域名,端口三部分只要存在一部分不同就会发生跨域,也就是说只有两个域名的协议,域名,端口都相同才会不会发生跨域,所以我们又如何解决跨域的问题呢?在下文中会给大家介绍几种方法去解决跨域问题. 那么要解决跨域就不得不谈及 ,同源策略是一种安全措施,用...
学习面试
http-网络2024-09-02
HTTP缓存:别傻傻不会回答了
前言 在有大量数据交换的应用程序中,我们会采取一些方式将那些实时性要求不高的数据生成副本,并存储在某个相对来说可以快速到达、访问、获取的仓库,这样在需要这些数据的时候可以直接从这个仓库中获取数据。 缓存的目的: 提升数据交换的性能,提高速度。 缓解服务器或数据库的压力。 HTTP 根据是否要向服务器发送请求将缓存规则分为两类:强缓存和协商缓存。 强缓存 在强缓存中,如果命中缓存,则直接从缓存数据库中取出资源,无需再发送请求到服务器上。...
学习面试
http-网络2024-08-19
网络-navigator.sendBeacon前言
navigator.sendBeacon 是浏览器的 API,用于在浏览器后台发送异步请求,它不会等服务端响应。 它可以用于在页面卸载或关闭的时候,发送数据,以及在不阻塞页面卸载的情况下发送数据。 使用 方法发送的请求是异步的,不会阻塞页面的卸载或关闭过程。 这对于需要在用户离开页面之前发送数据的情况非常有用。 优点 不受页面卸载过程的影响,确保数据可靠发送。 普通的 请求如 和 在页面关闭或者刷新、前进、后退等操作时 , 这些请求有可...
学习面试
http-网络2024-08-16
前端项目中的Server-sent Events(SSE)项目实践及其与websocket的区别在前端开发中,实时数据更
前端项目中的 项目实践 前言 在前端开发中,实时数据更新是提升用户体验的重要因素之一。 是一种高效的技术,允许服务器通过单向连接将实时数据推送到客户端。下面将从 的基本改变,使用场景展开了解,并提供一个实际案例来更好的理解如何在前端项目中应用 。 的主要特点 1. 单项数据流:数据从服务器推送到客户端。 2. 自动重连:在链接丢失时,浏览器会自动尝试重新链接。 3. 简单易用:基于 协议,不需要额外的库或协议。 的使用场景 适用于需要从...
学习面试

评论

请登录后发表评论

去登录
加载评论中...

目录