与浏览器跨域相遇

浏览器的同源策略

浏览器出于对安全的考虑,对同源请求放行,对异源请求限制,这些限制的规则统称为同源策略 ,因此因为同源策略引发的开发问题,称为跨域(异源)问题

同源,两个URL的协议域名端口号都相同,我们称为同源,当三者中任意一项或多项不同,我们就称之为异源

URL URL
http://a.com:80/a https://a.com:80/a
http://a.com:80/a http://b.com:80/a
http://a.com:80/a http://a.com:81/a
http://a.com:80/a http://a.com:80/a/b

同源请求

浏览器对标签发出请求进行轻微的限制,对ajax进行严厉的限制

浏览器对Ajax的限制

跨域方案

解决跨域的方式有很多,现在主流的就是CORS代理两种,根据具体的生产环境我们酌情选择。

生产环境存在跨域问题

第一种情况是前端打包成静态资源,然后上传到静态服务器,通过一个域名来访问(a.com),在运行过程中,我们的js可能需要通过ajax去请求一些数据资源,但是数据服务器的访问域名是另外的(b.com),这就引发了跨域问题。同时也是说明生产环境下是存在跨域问题。这种情况下,我们就不需要考虑开发环境是怎么样的,首先是要解决生产环境的开发问题,那么只有通过JSONPCORS ,现在主流的,同时也是官方推荐的解决方案就是CORS

使用CORS解决跨域问题

CORS 是现在跨域问题最正统,也是官方最推荐的解决方案。浏览器检验跨域的规则也称之为CORS规则,我们要解决跨域问题,就是要让它通过校验。

CORS规则

CORS是一套机制,用于浏览器校验跨域请求,他的基本理念是:

  1. 只要服务器明确表示允许,则校验通过
  2. 服务器明确拒绝或者没有表示,则校验不通过

从上所述,我们明确了要通过CORS解决跨域问题,必须要后端解决,同时要保证服务器是"自己人"。

CORS 将请求分为简单请求预检请求

简单请求
  • 请求方式为GETHEADPOST
  • 头部字段满足CORS安全规范
  • Content-Type 标头所指定的媒体类型的值仅限于下列三者之一:
    • text/plain
    • multipart/form-data
    • application/x-www-form-urlencoded
预检请求

预检请求 在请求前先会发送一个请求方式为OPTIONS 的预检请求,询问请求头、请求源、请求方式是否允许,通过后再和简单请求相同发送

生产环境不存在跨域问题

另一种情况,前端开发打包上传到一个静态资源目录,后端程序也搭建好了一个服务器,但是在这两者之间存在一个反向代理,这意味着用户在请求的时候使用的是同一个域名,例如,a.com 访问的是前端页面资源,但是 a.com/api/xxx 就被代理服务器转发到了道理服务器,也就意味着生产环境是不存在跨域问题的,那么就不需要后端使用CORS,这种情况就需要前端在开发服务器中配置代理

使用代理解决跨域问题

Express框架

js 复制代码
const express = require('express')
 // const proxy = require('express-http-proxy')
const app = express() //创建服务器

//接受对目标 的GET请求
app.get('目标请求', async (req,res) => {
     // 使用CORS解决对代理服务器的跨域
     res.header('access-control-allow-origin', '*');
      // 相应一段文本
      res.send('hello,world')
 })

 // 监听8080端口
 app.listen(8080, () => {
     console.log('服务器已启动')
 })

HTTP模块

js 复制代码
let http = require('http');
let fs = require('fs');

// 代理配置
let conifg = {
    '/': { // 需要拦截的本地请求路径
        target: 真实代理地址, // 真实代理地址
        port: 80, // 端口,默认80
    }
};

// 创建http服务
let app = http.createServer(function (request, response) {
    let url = request.url === '/' ? 'index.html' : request.url;
    // response.setHeader('Access-Control-Allow-Origin', '*');
    // response.setHeader('Access-Control-Allow-Methods', 'GET, POST, OPTIONS, PUT, PATCH, DELETE'); // If needed
    // response.setHeader('Access-Control-Allow-Headers', 'Authorization,Access-Control-Allow-Headers, Origin,Accept, X-Requested-With, Content-Type, Access-Control-Request-Method, Access-Control-Request-Headers,RequestID,X-Content-Type-Options,X-Content-Type-Options,X-Frame-Options,X-Powered-By,X-Version,x-xss-protection,Strict-Transport-Security') // If needed
    // response.setHeader('Access-Control-Allow-Credentials', true); 


    // 存在代理地址,走代理请求
    if (hasProxy(url, request, response)) {
        return;
    }

    // 普通请求和资源加载
    fs.readFile(__dirname + url, function (err, data) {
        if (err) {
            console.log('请求失败', err);
        } else {
            response.end(data);
        }
    });
});

// 判断是否存在代理地址
function hasProxy(url, request, response) {
    for (const key in conifg) {
        if (url.indexOf(key) < 0) {
            continue;
        }
        const { target, port } = conifg[key];
        let info = target.split('//');
        let opts = { // 请求参数
            protocol: info[0],
            host: info[1],
            port: port || 80,
            method: request.method,
            path: url,
            json: true,
            headers: {
                ...request.headers,
                host: info[1],
                referer: target + url,
            }
        }
        proxy(opts, request, response);
        return true;
    }
    return false;
}

// 代理转发
function proxy(opts, request, response) {
    // 请求真实代理接口
    var proxyRequest = http.request(opts, function (proxyResponse) {
        // 代理接口返回数据,写入本地response
        proxyResponse.on('data', function (chunk) {
            console.log('chunk', chunk, opts)
            response.write(chunk, 'binary');
            // response.header('access-control-allow-origin', '*');

        });
        // 代理接口结束,通知本地response结束
        proxyResponse.on('end', function () {
            response.end();
        });
        response.writeHead(proxyResponse.statusCode, proxyResponse.headers);
        // console.log(response,'proxy')
    });

    // 本地接口数据传输,通知代理接口请求
    request.on('data', function (chunk) {
        console.log(chunk, 777)
        proxyRequest.write(chunk, 'binary');
    });

    // 本地请求结束,通知代理接口请求结束
    request.on('end', function (data) {
        proxyRequest.end();
    });
}

app.listen(8080);
console.log('server is listen on 8080....');
相关推荐
庸俗今天不摸鱼18 分钟前
【万字总结】前端全方位性能优化指南(十)——自适应优化系统、遗传算法调参、Service Worker智能降级方案
前端·性能优化·webassembly
QTX1873019 分钟前
JavaScript 中的原型链与继承
开发语言·javascript·原型模式
黄毛火烧雪下25 分钟前
React Context API 用于在组件树中共享全局状态
前端·javascript·react.js
Apifox36 分钟前
如何在 Apifox 中通过 CLI 运行包含云端数据库连接配置的测试场景
前端·后端·程序员
一张假钞39 分钟前
Firefox默认在新标签页打开收藏栏链接
前端·firefox
高达可以过山车不行39 分钟前
Firefox账号同步书签不一致(火狐浏览器书签同步不一致)
前端·firefox
m0_5937581040 分钟前
firefox 136.0.4版本离线安装MarkDown插件
前端·firefox
掘金一周43 分钟前
金石焕新程 >> 瓜分万元现金大奖征文活动即将回归 | 掘金一周 4.3
前端·人工智能·后端
三翼鸟数字化技术团队1 小时前
Vue自定义指令最佳实践教程
前端·vue.js
Jasmin Tin Wei2 小时前
蓝桥杯 web 学海无涯(axios、ecahrts)版本二
前端·蓝桥杯