JavaScript

解决跨域的方式

前言


当今互联网行业,大部分Web项目基本都是采用的前后端分离模式。前端为H5项目,后端为Java、PHP、Python等项目。而且大部分后端服务并不会只部署一套服务,而是会采用Nginx对后端服务进行负载均衡。那么,此时就会出现一个问题了:如果一个请求url的
协议、域名、端口
三者之间任意一个与当前页面url不同就会产生跨域的现象。那么如何使用Nginx解决跨域问题呢?接下来,我们就一起探讨下这个问题。

为何会跨域?


出于浏览器的同源策略限制。同源策略(Sameoriginpolicy)是一种约定,它是浏览器最核心也最基本的安全功能,如果缺少了同源策略,则浏览器的正常功能可能都会受到影响。可以说Web是构建在同源策略基础之上的,浏览器只是针对同源策略的一种实现。同源策略会阻止一个域的javascript脚本和另外一个域的内容进行交互。所谓同源(即指在同一个域)就是两个页面具有相同的协议(protocol),主机(host)和端口号(port)。

跨域的概念


  • 1.“源”由协议、域名、端口号组成
  • 2.同源策略是浏览器的一种保护机制。同源顾名思义,指两个源相同(即,两个源的协议、域名、端口号都相同)
  • 3.违反了同源策略的请求就是通常说的跨域请求
只有当「protocol(协议)、domain(域名)、port(端口)三者一致。」

跨域的解决方案-代理转发


代理转发的原理:在前端服务和后端接口服务之间架设一个中间代理服务,它的地址保持和前端服务一致,那么:

  • 代理服务和前端服务之间由于协议域名端口三者统一不存在跨域问题,可以直接发送请求
  • 代理服务和后端服务之间由于并不经过浏览器没有同源策略的限制,可以直接发送请求

这样,我们就可以通过中间这台服务器做接口转发,在开发环境下解决跨域问题(只能解决开发环境下跨域的问题)。

实现方法:vue-cli为我们内置了该技术,我们只需要按照要求配置一下即可。
在vue.config.js配置文件中,有一项是devServer,它就是我们下边要操作的主角。

module.exports = {
  devServer: {
    // ... 省略
    // 代理配置
    proxy: {
      // 如果请求地址以/api打头,就出触发代理机制
      // http://localhost:8080/api/login -> http://localhost:3000/api/login
      '/api': {
        target: 'http://localhost:3000' // 我们要代理的真实接口地址
      }
    }
  }
}

代理的思路为,利用服务端请求不会跨域的特性,让接口和当前站点同域。

这样,所有的资源以及请求都在一个域名下了。

JSONP


JSON with Padding,利用 <script> 标签可以跨域 get 引入 js 脚本这一点,需要服务器端配合接口,用脚本动态生成 js 将数据封装在返回的 js 脚本里,供客户端提前定义好的回调函数使用

JSONP方案和ajax没有任何关系,是通过script标签的src属性实现,因此JSONP方案只支持get请求,并且兼容性好,几乎所有浏览器都支持。
实现原理:在全局定义一个函数,将函数名以get传参的方式写入到script标签的src属性中(如下图所示),后端返回函数名以及参数,全局定义的函数就会自动调用,形参会接收后端传过来的参数。

Tips:仅能应用于 GET 请求

1、get 方式请求数据,返回 JavaScript 脚本

 <script type="text/javascript" src="http://example.com/
      ?some-variable=some-data&jsonp=parseResponse">
 </script>


2、服务端封装数据与 callback 函数

List<Student> studentList = getStudentList();
 JSONArray jsonArray = JSONArray.fromObject(studentList);
 String result = jsonArray.toString();
 ​
 //前端传过来的回调函数名称
 String callback = request.getParameter("jsonp");
 //用回调函数名称包裹返回数据,这样,返回数据就作为回调函数的参数传回去了
 result = callback + "(" + result + ")";
 ​
 response.getWriter().write(result);

3、因为是脚本,并且返回到本地已经预定义了回调函数,因此将直接自动执行,读出返回的目标内容

 <script type="test/javascript">
     // parseResponse({"variable": "value", "variable2": "value2"})
     function parseResponse(response) {
         do sth
     }
 </script>

CORS


HTML5新特性,只需要让服务器端在响应头中明确的授权客户端有权读取其返回的数据即可

CORS(Cross-Origin Resource Sharing,跨域资源共享)方案,就是通过服务器设置一系列响应头来实现跨域。而客户端不需要做什么额外的事情。

 header("Access-Control-Allow-Origin: *")
 header("Access-Control-Allow-Origin: http://server.net")

Tips:存在预检机制,支持浏览器先询问服务器(即将请求的域名、方法和头信息是否许可,如果得到肯定答复,才发出正式请求,否则报错)

Nginx 反向代理解决跨域


在介绍反向代理之前,先来了解一下正向代理。

正向代理:如果把局域网外的Internet想象成一个巨大的资源库,则局域网中的客户端要访问Internet,则需要通过代理服务器来访问,这种代理服务就称为正向代理,下面是正向代理的原理图。

由于工作环境原因,日常工作只能局限于单位的局域网,如果想要访问互联网,怎么办呢?这就需要用到正向代理,本人经常用正向代理来进行上网。

反向代理

看下面原理图,就一目了然。其实客户端对代理是无感知的,因为客户端不需要任何配置就可以访问,我们只需要将请求发送到反向代理服务器,由反向代理服务器去选择目标服务器获取数据后,在返回给客户端,此时反向代理服务器和目标服务器对外就是一个服务器,暴露的是代理服务器地址,隐藏了真实服务器 IP地址。


正向代理和反向代理的区别,一句话就是:如果我们客户端自己用,就是正向代理。如果是在服务器用,用户无感知,就是反向代理。

这里有个问题:反向代理服务器,怎么选择挂在它后面的哪一台具体服务器呢?答案在后文揭晓,这就是负载均衡。

Nginx作为反向代理服务器,就是把http请求转发到另一个或者一些服务器上。通过把本地一个url前缀映射到要跨域访问的web服务器上,就可以实现跨域访问。对于浏览器来说,访问的就是同源服务器上的一个url。而Nginx通过检测url前缀,把http请求转发到后面真实的物理服务器。并通过rewrite命令把前缀再去掉。这样真实的服务器就可以正确处理请求,并且并不知道这个请求是来自代理服务器的。

作为反向代理服务器,就是把http请求转发到另一个或者一些服务器上。通过把本地一个url前缀映射到要跨域访问的web服务器上,就可以实现跨域访问。对于浏览器来说,访问的就是同源服务器上的一个url。而Nginx通过检测url前缀,把http请求转发到后面真实的物理服务器。并通过rewrite命令把前缀再去掉。这样真实的服务器就可以正确处理请求,并且并不知道这个请求是来自代理服务器的。

前端项目,BASE_API是Nginx的端口号。

module.exports = merge(prodEnv, {
  NODE_ENV: '"development"',
  //BASE_API: '"https://easy-mock.com/mock/5950a2419adc231f356a6636/vue-admin"',
  BASE_API: '"http://localhost:9001"',
})

Nginx的配置文件如下:

  • 访问hosp则转发到http://localhost:8201
  • 访问cmn则转发到http://localhost:8202
server {
        listen       9001;
        server_name  localhost;

        #charset koi8-r;

        #access_log  logs/host.access.log  main;

        location / {
            root   html;
            index  index.html index.htm;
        }
		
		location ~ /hosp/ {           
			proxy_pass http://localhost:8201;
		}
		location ~ /cmn/ {           
			proxy_pass http://localhost:8202;
		}

反向代理实例二

现效果:使用 Nginx 反向代理,根据访问的路径跳转到不同端口的服务中,Nginx 监听端口为 9001

访问http://192.168.17.129/edu/直接跳转到 127.0.0.1:8080
访问http://192.168.17.129/vod/直接跳转到 127.0.0.1:8081

第一步,需要准备两个 tomcat,一个 8080 端口,一个 8081 端口,并准备好测试的页面
第二步,修改 nginx 的配置文件,在 http 块中配置 server

server {
	listen       9001;
	server_name  192.168.17.129;

	location ~ /edu/ {
		proxy_pass  http://127.0.0.1:8080
	}

	location ~ /vod/ {
		proxy_pass  http://127.0.0.1:8081
	}
}

根据上面的配置,当请求到达 Nginx 反向代理服务器时,会根据请求路径不同进行分发到不同的服务上。

Websocket


WebSocket 规范定义了一种 API,可在网络浏览器和服务器之间建立“套接字”连接。简单地说:客户端和服务器之间存在持久的连接,而且双方都可以随时开始发送数据。详细教程可以看 https://www.html5rocks.com/zh/tutorials/websockets/basics/

这种方式本质没有使用了 HTTP, 因此也没有跨域的限制,没有什么过多的解释直接上代码吧。

前端部分

<script>
  let socket = new WebSocket("ws://localhost:8080");
  socket.onopen = function() {
    socket.send("秋风的笔记");
  };
  socket.onmessage = function(e) {
    console.log(e.data);
  };
</script>

后端部分

const WebSocket = require("ws");
const server = new WebSocket.Server({ port: 8080 });
server.on("connection", function(socket) {
  socket.on("message", function(data) {
    socket.send(data);
  });
});