C/C++教程

跨域总结:从CORS到Ngnix

本文主要是介绍跨域总结:从CORS到Ngnix,对大家解决编程问题具有一定的参考价值,需要的程序猿们随着小编来一起学习吧!

前言

前后端数据交互经常会碰到请求跨域 , 什么是跨域 , 以及有哪些跨域方式 , 我觉得我应该记录下来。

一.什么是跨域?

1. 什么是同源策略及其限制内容?

  1. 同源策略是一个安全策略。所谓的同源,指的是协议,域名,端口相同。浏览器处于安全方面的考虑,只允许本域名下的接口交互,不同源的客户端脚本,在没有明确授权的情况下,不能读写对方的资源。
  2. 同源策略限制的内容有:
  • Cookie , LocalStorage ,IndexedDB等存储性内容。

  • DOM节点

  • AJAX请求发送后,非同源会被浏览器拦截。
    但是有三个标签是允许跨域加载资源:

      <img src=XXX>
      <link href=XXX>
      <script src=XXX>
    复制代码

这也就是JSONP的来源了。

2. 常见的跨域场景

当协议,子域名,主域名,端口号中任意一格不相同时,都算作不同源。

note: 特别说明两点:

  • 第一 : 如果是协议和端口造成的跨域问题"前端"是无能为力的。
  • 第二: 在跨域问题上,仅仅是通过"URL的首部"来识别而不会根据域名对应的IP地址是否相同来判断。"URL的首部"可以理解为""协议,域名和端口必须匹配"。
  • 第三: 请求跨域了,那么到底发出去没有? 跨域并不是请求发不出去,请求能发出去,服务端能收到请求并正常返回结果,只是结果被浏览器拦截了

二.跨域解决方案

1. JSONP

1.1 JSONP原理

利用<script>标签没有跨域限制的漏洞,网页可以得到从其他来源动态产生的JSON数据。JSONP请求一定需要对方的服务器做支持才可以。
复制代码

1.2 JSONP和AJAX对比 JSONP和AJAX相同,都是客户端向服务端发送请求,从服务端获取数据的方式。但AJAX属于同源策略,JSONP属于非同源策略(跨域请求)

1.3 JSONP优缺点 JSONP优点是简单兼容性好,可用于解决主流浏览器的跨域数据访问的问题。缺点是仅仅支持get方法具有局限性,不安全可能会遭受XSS攻击。

1.4 JSONP的实现流程

  • 声明一个回调函数,其函数名(如show)当做参数值,要传递给跨域请求数据的服务器,函数形参为要获取目标数据(服务器返回的data)
  • 创建一个 <script src=>标签 ,把那个跨域的API数据接口地址,赋值给script的src, 还要在这个地址中向服务器传递该函数名(可以通过问号传参?callback=show)。
  • 服务器接收到请求后,需要进行特殊的处理:把传递进来的函数名和它需要给你的数据拼接成一个字符串,例如:传递进去的函数名是show,它准备好的数据是 show('我不爱你')。
  • 最后服务器把准备的数据通过HTTP协议返回给客户端,客户端再调用执行之前声明的回调函数(show),对返回的数据进行操作。

现在我们来自己封装一个JSONP函数

// 封装 JSONP函数
function jsonp({ url, params, callback }) {
    return new Promise((resolve, reject) => {
        let script = document.createElement('script');
        params = JSON.parse(JSON.stringify(params));
        let arrs = [];
        for (let key in params) {
            arrs.push(`${key}=${params[key]}`);
        }
        arrs.push(`callback=${callback}`);
        script.src = `${url}?${arrs.join('&')}`;
        document.body.appendChild(script);
        function callback(data) {
            resolve(data);
            docement.body.removeChild(script);
        }
        window[callback] = function (data) {
            resolve(data);
            document.body.removeChild(script);
        }
    })
}
// 前端调用
jsonp({
    url: 'http://localhost:3000/say',
    params: {
        wd: 'I Love you'
    },
    callback: 'show'
}).then(data => {
    console.log(data)
})

// 后端响应
// 这里用到了 express
var express = require('express');
var router = express.Router();
var app = express();
router.get('/say',function(req,res,next) {
 //要响应回去的数据
  let data = {
    username : 'zs',
    password : 123456
  }

  let {wd , callback} = req.query;
  console.log(wd);
  console.log(callback);
  // 调用回调函数 , 并响应
  res.end(`${callback}(${JSON.stringify(data)})`);
})
app.listen(3000);
复制代码

2. CORS

CORS需要浏览器和后端同时支持。IE8和 IE9需要通过 XDomainRequest来实现
浏览器会自动进行 CORS通信,实现CORS通信的关键是后端。只要后端实现了CORS,实现了跨域。
服务端设置 Access-Control-Allow-Origin 就可以开启CORS。该属性表示哪些域名可以访问资源,如果设置通配符则表示所有网站都可以访问资源。
虽然设置CORS和前端没有什么关系,但是通过这种方式解决跨域问题的话,会在发送请求时出现两种情况,分别为简单请求复杂请求

简单请求

只要同时满足以下两个条件,就属于简单请求

条件1 : 使用下列方法之一:

  • GET
  • HEAD
  • POST

条件2 :Content-Type 的值仅限于下列三者之一 :

  • text/plain
  • multipart/form-data
  • application/x-www-form-urlencoded 请求中的任意 XMLHttpRequestUpload 对象均没有注册任何事件监听器;

复杂请求

不符合以上条件的请求就肯定是复杂请求了。复杂请求的CORS请求,会在正式通信之前,增加一次HTTP查询请,称为"预检"请求,该请求是option方法的 , 通过该请求来知道服务端是否允许跨域请求。
我们用 PUT 向后台请求时, 属于复杂请求,后台需如下配置:

// 允许哪个方法访问我
res.setHeader('Access-Control-Allow-Methods', 'PUT')
// 预检的存活时间
res.setHeader('Access-Control-Max-Age', 6)
// OPTIONS请求不做任何处理
if (req.method === 'OPTIONS') {
  res.end() 
}
// 定义后台返回的内容
app.put('/getData', function(req, res) {
  console.log(req.headers)
  res.end('我不爱你')
})
复制代码

接下来我们看下一个完整请求的例子,并且介绍下CORS请求相关的字段

//index.html
// 前端代码
<script>
    let xhr = new XMLHttpRequest();
    document.cookie = 'name=hw';
    xhr.withCredentials = true; //前端设置是否带 cookie
    xhr.open('PUT','http://localhost:4000/getData',true);
    xhr.setRequestHeader('name','hw');
    xhr.onreadystatechange = function() {
        if(xhr.readyState === 4) {
            if(xhr.status >= 200 && xhr.status < 300 || xhr.status === 304) {
                console.log(JSON.parse(xhr.response));
                console.log(xhr.getResponseHeader('name'))
            }
        }
    }
    xhr.send();
</script>
// server1.js
let express = require('express');
let app = express();
app.use(express.static(__dirname))
app.listen(3000)

// 后端代码
let express = require('express')
let app = express()
let whitList = ['http://localhost:3000'] //设置白名单
app.use(function(req, res, next) {
  let origin = req.headers.origin
  if (whitList.includes(origin)) {
    // 设置哪个源可以访问我
    res.setHeader('Access-Control-Allow-Origin', origin)
    // 允许携带哪个头访问我
    res.setHeader('Access-Control-Allow-Headers', 'name')
    // 允许哪个方法访问我
    res.setHeader('Access-Control-Allow-Methods', 'PUT')
    // 允许携带cookie
    res.setHeader('Access-Control-Allow-Credentials', true)
    // 预检的存活时间
    res.setHeader('Access-Control-Max-Age', 6)
    // 允许返回的头
    res.setHeader('Access-Control-Expose-Headers', 'name')
    if (req.method === 'OPTIONS') {
      res.end() // OPTIONS请求不做任何处理
    }
  }
  next()
})
app.put('/getData', function(req, res) {
  let data = {
      username : 'zs',
      password : 123456
  }
  console.log(req.headers)
  res.setHeader('name', 'jw') //返回一个响应头,后台需设置
  res.end(JSON.stringify(data))
})
app.get('/getData', function(req, res) {
  console.log(req.headers)
  res.end('he')
})
app.listen(4000)
复制代码

3. postMessage

postMessage是HTML5 XMLHttRequest Level 2中的 API, 并且为数不多跨域跨域操作的window属性之一,它可用于解决以下方面的问题:

  • 页面和其打开的新窗口的数据传递
  • 多窗口之间消息传递
  • 页面与嵌套的iframe消息传递
  • 上面三个场景的跨域数据传递

postMessage()方法允许来自不同源的脚本采用异步方式进行有限的通信,可以实现跨文本档,多窗口,跨域消息传递

API大概是这样的
otherWindow.postMessage(message,targetOrigin,[transfer])
复制代码
  • message:将要发送到其他window的数据
  • targetOrigin:通过窗口的origin属性来指定哪些窗口能接收到消息事件,其值可以是字符串"*"(表示无限制)。或者一个URL。在发送消息的时候,如果目标窗口的协议,主机地址或端口这三者的任意一项不匹配 targetOrigin提供的值,那么消息就不会被发送;只有三者完全匹配,消息才会被发送。
  • transfer(可选):是一串和 message同时传递的 Transferable对象。这些对象的所有权将被转移给消息的接收方,而发送一方将不再保有所有权。

下面,我们来用一个例子来说明任何使用

4. websocket

Websocket是HTML5的一个持久化协议,它实现了浏览器与服务器的全双工通信,同时也是跨域的一种解决方案。WebSocket和HTTP都是应用层协议,都是基于TCP协议。
但是, WebSocket是一种双向通信协议,在建立连接之后,WebSocket的server与client都能主动向对方发送或接收数据。 同时,WebSocket在建立连接时需要借助HTTP协议,连接建立好了之后client与server之间的双向通信就与HTTP无关了。

原生WebSocket API使用起来不太方便,我们使用 Socket.io ,它很好地封装了 webSocket接口,提供了更简单,灵活的接口,也对不支持webSocket的浏览器提供了向下兼容。
我们再来看一个例子:

//前端
<script>
    // 定义数据
    let Data = {
        username : 'hw',
        password : 456789
    }
    let socket = new WebSocket('ws://localhost:4000');
    socket.onopen = function() {
        socket.send(JSON.stringify(Data)); // 向服务器发送数据
    }
    socket.onmessage = function(e) {
        console.log(JSON.parse(e.data)); // 接收服务器返回的数据
    }
</script>

// 后端
let express = require('express');
let app = express();
let WebSocket = require('ws');
let wss =new WebSocket.Server({port:4000})


// 定义数据
let Data = {
    username : 'zs',
    password : 123456
}

wss.on('connection', function(ws) {
    ws.on('message',function(data) {
        console.log(JSON.parse(data))
        ws.send(JSON.stringify(Data))
    })
})
复制代码

5. node中间件代理(两次跨域)

实现原理: **同源策略是浏览器需要遵循的标准,而如果是服务器向服务器请求就无需遵循同源策略。**代理服务器,需要做以下几个步骤:

  • 接受客户端请求
  • 将请求转发给服务器
  • 拿到服务器响应数据
  • 将响应转发给客户端

我们现在再来看一个例子: 本地文件index.html文件,通过代理服务器http://localhost:3000向目标服务器http://localhost:4000请求数据

//前端
<script>
    function ajax(
        {
            url = '',
            method = 'get',
            headers = {},
            data = ''
        }
    ) {
        return new Promise((resolve,reject) => {
            // 兼容性处理
            var xhr = new XMLHttpRequest();

            xhr.onreadystatechange = function() {
                if(xhr.readyState === 4) {
                    if((xhr.status >= 200 && xhr.status < 300) || xhr.status === 304) {
                        try {
                            // 获取数据
                            var response = JSON.parse(xhr.responseText);
                            resolve(response);
                        } catch( e ) {
                            reject(e);
                        }
                    } else {
                        reject(new Error('Request was unsuccessful' + xhr.statusText));
                    }
                }
            }
            xhr.open(method,url,true);
            // 设置头部
            for(let key in headers) {
                xhr.setRequestHeader(key,headers[key]);
            }
            xhr.send(JSON.stringify(data))
        })
    }
    ajax({
        url : 'http://localhost:3000',
        method : 'post',
        headers : {
            'Content-Type':'application/json;charset=utf-8'
        },
        data : {
            username : 'hw',
            password : '789'
        }
    })
    .then((value,code)=>{
        console.log(value)
    })
    .catch(err=>{
        console.log(err)
    })
</script>

// 代理服务器(http://localhost:3000)
const http = require('http');
// 第一步 : 接受客户端请求
const server = http.createServer((request, response) => {
    // 代理服务器,直接和浏览器直接交互,需要设置CORS的首部字段
    response.writeHead(200, {
        'Access-Control-Allow-Origin': '*',
        'Access-Control-Allow-Methods': '*',
        'Access-Control-Allow-Headers': 'Content-Type'
    })
    if(request.method === 'options') {
        response.end();
    }    
    // 第二步 : 将请求转发给服务器
const proxyRequest = http
        .request(
            {
                host: '127.0.0.1',
                port: 4000,
                url: '/',
                method: request.method,
                headers: request.headers,
            },
            serverResponse => {
                // 第三步:收到服务器的响应
                var body = ''
                serverResponse.on('data', chunk => {
                  body += chunk
                })
                serverResponse.on('end', () => {
                  console.log('The data is ' + body)
                  // 第四步:将响应结果转发给浏览器
                  response.end(body)
                })
            }
        )
    .end()
})

server.listen(3000, ()=>{
    console.log('The proxyServer is running at http://localhost:3000')
})

//后端服务器
// http://localhost:4000
const http = require('http');
const data = { title : 'zs', password : '123'};
const server = http.createServer((request,response) => {
    if(request.url === '/') {
        response.end(JSON.stringify(data))
    }
})

server.listen(4000,'127.0.0.1',()=> {
    console.log('The server is running at http://127.0.0.1:4000')
})
复制代码

nginx反向代理

前言

说实话,nginx要熟悉有点花时间,这里我就介绍怎么用nginx跨域好了
这个东西我也不是很会

nginx与node.js

'Nginx是一款轻量级的HTTP服务器,采用事件驱动的异步非阻塞处理方式框架,这让其具有极好的IO性能,时常用于服务端的反向代理与负载均衡'
复制代码

这似乎与node.js很像啊,但,其实他们都有自己擅长的领域:Nginx更擅长于底层服务端资源的处理(静态资源处理转发,反向代理,负载均衡等),node.js更擅长于上层具体业务逻辑的处理。两者可以实现结合,助力前端开发。

代理服务器

什么是反向代理?互联网应用基本都基于CS基本结构,即 client端和server端。代理其实就是client端和真正的server端之间增加一层提供特定的服务的服务器,即代理服务器。

正向代理

正向代理大家肯定都用过,其实,翻墙工具其实就是一个正向代理工具。它会把浏览器访问墙外服务器server的网页请求,代理到一个可以访问该网站的代理服务器proxy,这个代理服务器proxy把墙外服务器server上的网页内容获取,再转发给客户端。
具体流程如下:

反向代理

反向代理与正向代理相反,先看流程图:

在反向代理中(实际上,这种情况发生在所有大型网站的页面请求中),客户端发送的请求,想要访问server服务器上的内容。但将被发送到一个代理服务器proxy,这个代理服务器将把请求代理到和自己属于同一个LAN下的内部服务器上,而用户真正想获得的内容就存储在这些内部服务器上。
即向外部客户端提供一个统一的代理入口,客户端的请求,都先经过这个proxy服务器,至于在内网真正访问哪台服务器内容,由这个proxy去控制。
概括的说:就是代理服务器和真正server服务器可以直接访问,属于一个LAN(服务器内网);代理对用户是透明的,即无感知的。

为什么要nginx反向代理

原因:

  • 安全及权限。可以看出,使用反向代理后,用户端将无法直接通过请求访问真正的内容服务器,而必须首先通过nginx.可以通过在nginx层上将危险或者没有权限的请求内容过滤,从而保证了服务器的安全。
  • 负载均衡。例如一个网站的内容被部署在若干台服务器上,可以把这些机子看成一个集群,那么nginx可以将接收到的客户端请求"均匀地"分配到这个集群中所有的服务器上(内部模块提供了多种负载均衡算法),从而实现服务器压力的负载均衡。此外,nginx还带有健康检查功能(服务器心跳检查),会定期轮询向集群里的所有服务器发送健康检查请求,来检查集群中是否有服务器处于异常状态,一旦发现某台服务器异常,那么在以后代理进来的客户端请求都不会被发送到该服务器上(直达后面的健康检查发现该服务器恢复正常),从而保证客户端访问的稳定性。

windows下nginx的下载与安装

nginx下载
nginx配置

nginx的一点功能

1. 快速实现简单的访问控制

经常会遇到希望让某些特定的用户的群体(比如公司内网)访问,或者控制某个url不让人访问.

nginx的配置如下:
location / {
    deny 192.168.1.100; // 禁止
    allow 192.168.1.10/200; // 允许
    allow 10.110.50.16;
    deny all; // 禁止所有
}
复制代码

其实 deny和 allow是 ngx_http_access_module(已内置) 模块中的语法。
采用的是从上到下匹配方式,匹配到就跳出不再继续匹配。
比如说,上述配置的意思是,首先禁止 192.168.1.100访问,然后允许 192.168.1.10 - 200 ip段内的访问 (排除 192.168.1.100 ) ,同时允许 10.110.50.16 这个单独ip 的访问,剩下未匹配的全部禁止访问。

2.解决跨域

在众多的解决跨域方式中,都不可避免的都需要服务端进行支持,使用Nginx可以纯前端解决请求跨域问题。
nginx配置如下:

server
{
    listen 3002;
    server_name localhost;
    location /ok {
        proxy_pass http://localhost:3000;

        #   指定允许跨域的方法,*代表所有
        add_header Access-Control-Allow-Methods *;

        #   预检命令的缓存,如果不缓存每次会发送两次请求
        add_header Access-Control-Max-Age 3600;
        #   带cookie请求需要加上这个字段,并设置为true
        add_header Access-Control-Allow-Credentials true;

        #   表示允许这个域跨域调用(客户端发送请求的域名和端口) 
        #   $http_origin动态获取请求客户端请求的域   不用*的原因是带cookie的请求不支持*号
        add_header Access-Control-Allow-Origin $http_origin;

        #   表示请求头的字段 动态获取
        add_header Access-Control-Allow-Headers 
        $http_access_control_request_headers;

        #   OPTIONS预检命令,预检命令通过时才发送请求
        #   检查请求的类型是不是预检命令
        if ($request_method = OPTIONS){
            return 200;
        }
    }
}
复制代码

参考资料

九种跨域请求实现方式
nginx与前端开发

总结

  • CORS支持所有类型的HTTP请求,是跨域HTTP请求的根本解决方法。
  • JSONP只支持GET请求,JSONP的优势在于支持老式浏览器,以及可以向不支持CORS的网站请求数据
  • 不管是Node中间件代理还是nginx反向代理,主要是通过同源策略对服务器不加限制
  • 日常工作中,用的比较多的跨域方案还是cors和nginx反向代理

最后

好吧,我承认最后用nginx划水划过的。
这个,nginx学了我好几天我都没有搞懂这怎么弄。
哎,以后在补充吧。

这篇关于跨域总结:从CORS到Ngnix的文章就介绍到这儿,希望我们推荐的文章对大家有所帮助,也希望大家多多支持为之网!