那些年咱们“跨”过的“域”(接口篇)

做为前端开发,从入行起,应该就接触过跨域的概念。工做中,在与服务端配合时,也常常须要处理跨域相关问题。若是你不能理解到底什么是跨域,那在与服务端配合解决跨域问题时,你可能就要落入对方的掌控之中了(哈哈,开个玩笑)。html

为了不这一尴尬的境地,今天我来带着你们一块儿重温并巩固一下如下内容:什么是跨域,跨域有哪些实际的开发场景,有哪些方式能够快速的处理跨域问题。前端

整篇内容没有涵盖网上全部的与跨域“沾边”的知识,而是从实际场景出发,旨在解决工做中常常遇到的跨域问题。webpack

1、什么是跨域?

1.1 同源策略 VS 跨域

若是让你直接定义什么是跨域,你可能会发现很难定义。因此须要借助与之对立的概念——同源策略(SOP,Same-Origin Policy)。只要不是同源的,那就是跨域的。nginx

同源策略是浏览器中一个极为相当重要的安全机制,能够用来限制某一源内的文档或脚本与另外一源内的资源如何进行交互。其目的在于隔离潜在的恶意文档,减小可能存在的攻击。git

而对于同源的定义是:协议、主机名(host)以及端口三者均相同github

维基百科中对于 URI 的结构组成说明以下:web

[协议名]://[用户名]:[密码]@[主机名]:[端口]/[路径]?[查询参数]#[片断ID]
复制代码

常规状况下,咱们无需在 URI 中带有用户名密码等信息用于验证。这只是一个完整的 URI 组成示例。ajax

因此对于同源,只要 URI 中协议名、主机名、端口三者有其中一条不一样,则视为不一样源。不一样源之间请求资源,则为跨域。其中主机名部分,主域和子域视为不一样、域名与其对应的 IP 也视为不一样,这就是说看着必须得同样。后端

1.2 跨域的限制

当存在跨域问题时,浏览器会作出必定的限制措施。主要包括如下三点:api

  1. Cookie、LocalStorage 和 IndexDB 没法读取。
  2. DOM 没法得到。
  3. AJAX 请求被拦截。

注:Cookie 获取不检测端口

2、常见的跨域开发场景/业务场景

撇开场景谈概念,必定是晦涩难懂的,开始说了,本文旨在解决实际工做中遇到的跨域问题。下面咱们来一块儿看看工做过程当中比较常见的跨域场景。

2.1 先后端分离:纯前端 + 接口层 (开发模式)

在先后端分离的开发模式下,开发环境应该用webpack的居多(固然有的可能不是,以此为例),与之相应的 web 服务器就是webpack-dev-server。这类开发模式的架构通常以下:

这一架构下,dev-server 中的页面若是经过 ajax 直接调用服务端的 API 会存在跨域问题。

2.2 先后端分离:纯前端 + 接口层 (生产模式)

与第一种方式类似,先后端分离的项目在开发完成后,每每经过 nginx 等做为静态资源服务器,前端页面直接经过 ajax 发送请求,依然存在跨域请求问题。

架构以下:

2.3 先后端分离:纯前端 + BFF + 接口层

有时候,咱们所要调用的接口层可能并不仅是给咱们提供服务,他们只会提供一些通用的数据,咱们须要对数据进行必定程度的二次加工;也可能咱们须要本身给前端页面提供一些通用的功能,如图片上传等。这时,就须要在前端页面和接口层之间增长一个 BFF 层(Backends For Frontends)。

BFF 层通常由前端维护,因此使用 Node.js 居多。

这一架构以下:

使用这种架构其实自己已经解决了跨域问题,是一种跨域解决方式,后面咱们再细说。

2.4 服务端渲染 + web 服务器(不跨域)

最后一种是最原始的 web 服务架构,html 页面以及其余静态资源都直接从服务器获取,接口也直接由所在服务器处理。这种方式不存在跨域问题。前端和服务端逻辑彻底绑定,互相支撑提供服务。

3、跨域的解决方式

前面咱们提到,跨域是浏览器的限制。因此咱们想解决跨域问题能够有两个方向,第一是绕开浏览器限制,第二是经过浏览器支持的方式来容许跨域。

下面咱们分别会介绍三种绕开浏览器限制的解决方式,分别为webpack-dev-server 代理/Nginx 代理转发/服务器代理,以及浏览器自己支持的 CORS 方式。

没有你们耳熟能详的 JSONP,你们自行科普一下吧。

3.1 webpack-dev-server 代理

对于上面说到的“先后端分离:纯前端 + 接口层 (开发模式)”这一场景,当咱们在http://co.com的页面上直接调用http://api.co.com的接口时,会出现跨域问题。

咱们能够将全部的接口请求都从http://co.com发出,如http://co.com/api/getSomeData(额外加了/api,方便统一转发),最后经过 proxy 配置代理,转发到最终的接口服务器http://api.co.com/getSomeData

proxy 配置以下:

devServer: {
  proxy: {
    '/api': {
      target: 'http://api.co.com',
      // 若是转发后的pathname须要改变,能够经过如下方式重写
      // 下面是把api前缀去掉
      pathRewrite: {
        '^/api/': '',
      },
    },
  }
}
复制代码

经过上述方式,咱们能够在接口请求发起的时候,统一从当前所在源发起,最后经过 proxy 代理的方式转发到真正的接口层。这样就绕开了调用接口时浏览器的同源限制。

3.2 Nginx 代理转发

针对第二部分提到的“先后端分离:纯前端 + 接口层(生产模式)”这一场景,这时咱们没有webpack-dev-server可用了,不过不要紧,咱们在使用 nginx 做为静态资源服务器时,也能够作一些代理转发。能够将接口请求所有转发到对应接口服务器。

配置以下:

location /api {
  proxy_redirect off;
  proxy_set_header Host $host;
  proxy_set_header X-Real-IP $remote_addr;
  proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
  proxy_set_header X-NginX-Proxy true;
  # 转发时重写地址
  rewrite ^/api/(.*)$ /$1 break;
  # 转发目的地
  proxy_pass http://api.co.com;
}
复制代码

这种方式其实与第一种相似,只不过是经过不一样的方式进行代理。这种方式也是经过绕过浏览器限制的方式解决跨域的。

3.3 服务器转发

第二部分的第三种架构“先后端分离:纯前端 + BFF + 接口层”,这种架构其实就已经解决了跨域的问题,前端页面的全部接口都由 BFF 层进行管理。

对于 BFF 层,能够经过添加中间件或者其余的方式对于接口进行拦截。若是是静态资源或者是当前服务所提供的接口,则直接处理。若是是调用 api 的接口请求,将其转发到对应的服务便可。

不一样的框架有不一样的方式来处理接口拦截与转发,因此此处没有代码。

3.4 CORS 跨域资源共享

跨域资源共享(CORS) 是一种机制,服务端能够经过额外的 HTTP 头来告诉浏览器容许某一源内的 Web 应用访问不一样源服务器上的指定资源。

CORS 使用通用的跨域解决方式,须要服务端配合进行实现。

这里面会涉及到简单请求以及预检请求的概念。关于什么是简单请求,你们能够移步MDN看下详细的定义,这里再也不详述了。

这两种请求的区别在于,对于预检请求,浏览器必须首先使用 OPTIONS 方法发起一个预检请求(preflight request),从而获知服务端是否容许该跨域请求。服务器确认容许以后,才发起实际的 HTTP 请求。

  1. 为何要区分简单请求和预检请求能够参考贺老的 这篇文章
  2. IE8/9 不支持 CORS,经过 XDomainRequest 来实现

3.4.1 简单请求

对于简单请求,服务端经过简单的设置Access-Control-Allow-Origin: *便可容许任意来源进行跨域请求。若是只想容许来自http://co.com的访问,能够设置Access-Control-Allow-Origin: http://co.com

通讯过程示意图以下:

注:在发起跨域请求时,浏览器会在请求头字段中自动带上 Origin 字段,值为当前所在域。

3.4.2 预检请求

对于预检请求,服务端须要额外再多作一些事情。以下步骤:

  1. 首先,发起预检请求,带上真实请求的 Method。
  2. 服务端判断是否容许跨域请求,若是容许则返回容许的来源、容许的请求 Methods 以及预检请求的有效时长( 有效时间内,同一请求无需再次发送预检请求,不过不能够任意设置,浏览器有最大时长限制)。
  3. 客户端发起真实的跨域请求。
  4. 服务端返回。

通讯过程示意图以下:

须要注意的是,服务端在处理预检请求时,若是容许跨域,服务端只须要设置对应的响应头,而后直接返回便可,无需其余处理。

3.4.3 附带身份凭证的请求

常规来讲,咱们的请求都须要带有身份凭证(如 Cookie),这时服务器端的响应中须要额外设置Access-Control-Allow-Credentials: true,若是未设置,浏览器将不会把响应内容返回给请求的发送者。

还有个别不是很经常使用的请求头和响应头字段,你们可前往MDN查看完整的列表。

总结

如上,咱们从如下三个方面介绍了跨域:什么是跨域,跨域有哪些实际的开发场景,有哪些方式能够快速的处理跨域问题。

你们在遇到跨域问题时,能够根据具体的场景选择绕过跨域问题,仍是选择通用的 CORS 模式来解决。

最后但愿你们看完这篇文章以后,都会是『那些年咱们“跨”过的“域”(接口篇)』。而不是『那些年咱们都没“跨”过去的“域”』🤣 🤣 🤣。


相关文章
相关标签/搜索