与浏览器跨域相遇

浏览器的同源策略

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

同源,两个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....');
相关推荐
su1ka1112 分钟前
re题(35)BUUCTF-[FlareOn4]IgniteMe
前端
测试界柠檬3 分钟前
面试真题 | web自动化关闭浏览器,quit()和close()的区别
前端·自动化测试·软件测试·功能测试·程序人生·面试·自动化
多多*4 分钟前
OJ在线评测系统 登录页面开发 前端后端联调实现全栈开发
linux·服务器·前端·ubuntu·docker·前端框架
2301_801074155 分钟前
TypeScript异常处理
前端·javascript·typescript
ᅠᅠᅠ@5 分钟前
异常枚举;
开发语言·javascript·ecmascript
小阿飞_6 分钟前
报错合计-1
前端
caperxi8 分钟前
前端开发中的防抖与节流
前端·javascript·html
霸气小男8 分钟前
react + antDesign封装图片预览组件(支持多张图片)
前端·react.js
susu10830189118 分钟前
前端css样式覆盖
前端·css
学习路上的小刘10 分钟前
vue h5 蓝牙连接 webBluetooth API
前端·javascript·vue.js