从网络链路到跨域问题

首先,得知道什么是域。javascript

再首先,得先知道Web服务从访问到收到数据并展示这个过程发生了什么。html

IP

注:下文中 网络空间 = TCP/IP网络空间, IP = IP地址vue

IP自己是Internet Protocol的缩写,是一种为了计算机相互链接通讯而产生的协议,咱们在这就用它代指ip地址。java

每一个在线的网络服务在网络空间的真实存在形式都是以ip地址形式存在的,它属于网络七层模型中的网络层,它是一个地址,用来识别网络空间中互联的主机和路由器,暂且认为用ip地址能够访问到一个服务器。node

比如地球上的每一个地点都有一个经纬度,只要这个经纬度真实存在并有效可用,咱们根据这个经纬度就必定能找到这个地点,且经纬度是永久不变的。webpack

实际上ip地址的“永久不变”是指在这个ip对应的互联网服务的生命周期内,其不会变化。ios

直接服务场景:假设某公司拉了一条电信专线服务,电信给其分配8个ip地址,其中3个为广播地址,剩余5个配置且部署好对应的网络服务,外部是能够直接访问到该公司所架构的网络服务的。nginx

真实商业生产场景:假设在某云服务厂商购买服务器,默认会分配有惟一ip,服务器重启、重置...ip是不会变化的,但服务器若是到期了,服务器提供商会同时释放掉ip资源,这个ip可能就会分配给其余服务,亦或者回收这个ip,ip自己和服务没有关系,但大多数的商业服务ip都是随服务捆绑的。git

简单说:ip是指向网络空间具体某一处的惟一地址,但它并非永远不变的。github

域名和DNS

像经纬度同样,ip地址是一长串数字,不便于记忆,因此咱们须要一个相似地名同样的别名,当咱们一旦说出别名,就知道它大概在哪,且无需care它的真实经纬度。

咱们之因此听到地名就知道这个位置大概所在,是由于咱们大脑内已经存储了这个地名和真实地点的关联信息,暂且称之为咱们存储的这块用于关联地点的数据为“数据库”。

人脑有限,咱们不可能记住全部的地名和地理位置,因此须要有一个容易专门来存储这些关联数据的数据库,最好其能够直接把咱们带到目的地。

这就是DNS,全称Domain Name System,他作的事情很简单,就是将咱们输入的ip别名(域名)经过数据库解析为ip地址返回。

实际上,浏览器或咱们发起请求的客户端会根据DNS返回的ip去请求网络资源,并返回解析展现。

然而,浏览器请求时如何知道域名使用的是哪家DNS服务商呢,因而浏览器便须要先把域名发送到本地配置的DNS服务商(本地域名服务器/Local DNS Server)那里获得该域名的NS(Name Server),而后再把该域名拿到Name Server去获取IP,而后再向该IP请求数据。

实际上整个域名解析的链路十几步不止,浏览器请求LDNS以前会对浏览器自己的DNS缓存和本机DNS缓存的判断,判断会根据命中状况和TTL和其余数据决定是否进入下一步。

LDNS若是查询失败,则会直接请求root DNS Servers,root DNS Servers只为全球只有十三台的gTLD(generic Top-Level DNS Server)进行服务,RDNS会返回域名所在的主域名服务器的地址,即对应的gTLD地址,而后继续向gTLD发送请求以获得NS地址,因而又回到了上一步。

一张图来表示:

简单说就是:DNS是一套完整的系统,这套系统作的事就是根据一个个的表去查对应的数据,最终返回一个目标IP。

跨域

该说域了;咱们能够把域理解为一个域名或IP所表明的范围,好比访问a.com指向了A服务器,访问b.com指向了B服务器,咱们能够认为a和b是分开独立的两个域。

大多数状况下,a.com b.com都应该是两个没有关系的单独网络服务,他们默认不该该产生关联(静态资源引用除外),起码浏览器是这么认为的。

因此若是你在a.com下向b.com发起一个触发浏览器安全机制的xhr的网络请求,浏览器默认是会拦截的,并附赠一大串Error,他认为你这么作不安全,不容许跨域请求数据。

CORS

CORS(Cross-origin resource sharing)是一个W3C标准,全称"跨域资源共享"。
它规定容许浏览器向跨源服务器,发出XMLHttpRequest请求,从而解决跨域问题。

咱们先看浏览器的安全机制是怎样的?

浏览器把异步请求(xhr/fetch)分为两类:

  • 简单请求
  • 非简单请求

简单请求的条件:

  1. 请求方法是如下三种方法之一:

    • HEAD
    • GET
    • POST
  2. HTTP的头信息不超出如下几种字段:

    • Accept
    • Accept-Language
    • Content-Language
    • Last-Event-ID
    • Content-Type:只限于三个值application/x-www-form-urlencodedmultipart/form-datatext/plain

只要不知足以上条件的请求均为非简单请求

简单请求

简单请求在发出时,浏览器会自动给请求头加上Origin字段,该字段为当前请求发出者所在的域(协议 + 域名 + 端口),
服务端根据请求headers里的Origin来判断是否容许请求者获取资源,若是容许,服务端在返回时会在headers里携带几个特殊的字段,用于告知浏览器,容许这次请求,
不然,即便正常返回数据,浏览器检测到无对应的容许跨域字段,也会在console throw Error,告知你跨域访问失败。

这几个字段即是CORS标准中所实现的三个字段:

  1. Access-Control-Allow-Origin
    该字段是必须的。它的值要么是请求时Origin字段的值,要么是一个*,表示接受任意域名的请求。

  2. Access-Control-Allow-Credentials
    该字段可选。它的值是一个布尔值,表示是否容许发送Cookie。默认状况下,Cookie不包括在CORS请求之中。设为true,即表示服务器明确许可,Cookie能够包含在请求中,一块儿发给服务器。这个值也只能设为true,若是服务器不要浏览器发送Cookie,删除该字段便可。
    若是要发送Cookie,Access-Control-Allow-Origin就不能设为星号,必须指定明确的、与请求网页一致的域名。同时,Cookie依然遵循同源政策,只有用服务器域名设置的Cookie才会上传,其余域名的Cookie并不会上传,且(跨源)原网页代码中的document.cookie也没法读取服务器域名下的Cookie。

  3. Access-Control-Expose-Headers
    该字段可选。CORS请求时,XMLHttpRequest对象的getResponseHeader()方法只能拿到6个基本字段:Cache-ControlContent-LanguageContent-TypeExpiresLast-ModifiedPragma。若是想拿到其余字段,就必须在Access-Control-Expose-Headers里指定。

非简单请求

非简单请求通常出如今对服务端进行CUD操做的场景下,异源RESTful就是一种最典型的场景,会使用到PUTDELETEPATCH...等请求类型,且通常以application/json格式进行数据交互。

当浏览器把一个请求断定为非简单请求,则其发出前,浏览器会预先对服务端发起一个OPTIONS类型的预检(preflight)请求,同时也会加上Origin字段,
浏览器会根据这次预检请求返回的响应头来判断,服务端是否容许本域跨域操做资源,若判断为容许,则浏览器当即发出自己要发出的请求,不然,控制台抛出异常,中断请求。

服务端应返回的响应头应包含如下几个CORS字段:

  1. Access-Control-Allow-Origin
    必需返回,同简单请求中的含义。

  2. Access-Control-Allow-Methods
    该字段必需,它的值是逗号分隔的一个字符串,代表服务器支持的全部跨域请求的方法。注意,返回的是全部支持的方法,而不单是浏览器请求的那个方法。这是为了不屡次"预检"请求。

  3. Access-Control-Allow-Headers
    若是浏览器请求包括Access-Control-Request-Headers字段,则Access-Control-Allow-Headers字段是必需的。它也是一个逗号分隔的字符串,代表服务器支持的全部头信息字段,不限于浏览器在"预检"中请求的字段。

  4. Access-Control-Allow-Credentials
    可选返回,同简单请求中的含义。

  5. Access-Control-Max-Age
    该字段可选,用来指定本次预检请求的有效期,单位为秒。上面结果中,有效期是20天(1728000秒),即容许缓存该条回应1728000秒(即20天),在此期间,不用发出另外一条预检请求。

在每一次真正的数据请求时,Access-Control-Allow-Origin字段都是服务端必定会返回的。

为避免频繁的预检请求下降效率,真实生产环境下,建议设置Access-Control-Max-Age字段。

解决方法

开发环境下解决方案

经过本机开启代理实如今开发环境下将api转化为子路径,Node.js、apache、nginx都可实现。

webpack版本代码: 所有代码

proxy: {
    '/api': {
        target: 'http://localhost:8000',
        secure: false,
        changeOrigin: true,
        pathRewrite: {
            '^/api': ''
        }
    }
},复制代码

生产环境下解决方法

为服务端设置预检请求的响应及相关的CORS字段。Node.js版本代码

误区

JSONP只是在CORS未规范以前用于解决基本跨域的曲径(奇技淫巧),其并不是解决跨域的真正途径。

web开发者在使用vue-resource、axios...等各类异步库时,可能会存在相似解决跨域的选项,其多是内部对简单请求和非简单请求进行的一些基本转换,此类并不是真正解决跨域的方法,解决跨域务必须要服务端处理。

本文部份内容参考来源:

《跨域资源共享 CORS 详解》

MDN - HTTP访问控制(CORS)

W3C - HTTP/1.1: Method Definitions

原文地址:surmon.me/article/21

相关文章
相关标签/搜索
本站公众号
   欢迎关注本站公众号,获取更多信息