首先,得知道什么是域。
再首先,得先知道Web服务从访问到收到数据并展现这个过程发生了什么。
IP
注:下文中 网络空间 = TCP/IP网络空间, IP = IP地址
IP本身是Internet Protocol的缩写,是一种为了计算机相互连接通信而产生的协议,我们在这就用它代指ip地址。
每个在线的网络服务在网络空间的真实存在形式都是以ip地址形式存在的,它属于网络七层模型中的网络层,它是一个地址,用来识别网络空间中互联的主机和路由器,暂且认为用ip地址可以访问到一个服务器。
好比地球上的每个地点都有一个经纬度,只要这个经纬度真实存在并有效可用,我们根据这个经纬度就一定能找到这个地点,且经纬度是永久不变的。
实际上ip地址的“永久不变”是指在这个ip对应的互联网服务的生命周期内,其不会变化。
直接服务场景:假设某公司拉了一条电信专线服务,电信给其分配8个ip地址,其中3个为广播地址,剩余5个配置且部署好对应的网络服务,外部是可以直接访问到该公司所架构的网络服务的。
真实商业生产场景:假设在某云服务厂商购买服务器,默认会分配有唯一ip,服务器重启、重置…ip是不会变化的,但服务器如果到期了,服务器提供商会同时释放掉ip资源,这个ip可能就会分配给其他服务,亦或者回收这个ip,ip本身和服务没有关系,但大多数的商业服务ip都是随服务捆绑的。
简单说:ip是指向网络空间具体某一处的唯一地址,但它并不是永远不变的。
域名和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)分为两类:
- 简单请求
- 非简单请求
简单请求的条件:
请求方法是以下三种方法之一:
- HEAD
- GET
- POST
HTTP的头信息不超出以下几种字段:
- Accept
- Accept-Language
- Content-Language
- Last-Event-ID
- Content-Type:只限于三个值
application/x-www-form-urlencoded
、multipart/form-data
、text/plain
只要不满足以上条件的请求均为非简单请求。
简单请求:
简单请求在发出时,浏览器会自动给请求头加上Origin
字段,该字段为当前请求发出者所在的域(协议 + 域名 + 端口),
服务端根据请求headers里的Origin
来判断是否允许请求者获取资源,如果允许,服务端在返回时会在headers里携带几个特殊的字段,用于告知浏览器,允许此次请求,
否则,即使正常返回数据,浏览器检测到无对应的允许跨域字段,也会在console throw Error,告知你跨域访问失败。
这几个字段便是CORS标准中所实现的三个字段:
Access-Control-Allow-Origin
该字段是必须的。它的值要么是请求时Origin字段的值,要么是一个*,表示接受任意域名的请求。Access-Control-Allow-Credentials
该字段可选。它的值是一个布尔值,表示是否允许发送Cookie。默认情况下,Cookie不包括在CORS请求之中。设为true,即表示服务器明确许可,Cookie可以包含在请求中,一起发给服务器。这个值也只能设为true,如果服务器不要浏览器发送Cookie,删除该字段即可。
如果要发送Cookie,Access-Control-Allow-Origin
就不能设为星号,必须指定明确的、与请求网页一致的域名。同时,Cookie依然遵循同源政策,只有用服务器域名设置的Cookie才会上传,其他域名的Cookie并不会上传,且(跨源)原网页代码中的document.cookie
也无法读取服务器域名下的Cookie。Access-Control-Expose-Headers
该字段可选。CORS请求时,XMLHttpRequest
对象的getResponseHeader()
方法只能拿到6个基本字段:Cache-Control
、Content-Language
、Content-Type
、Expires
、Last-Modified
、Pragma
。如果想拿到其他字段,就必须在Access-Control-Expose-Headers
里指定。
非简单请求:
非简单请求一般出现在对服务端进行CUD操作的场景下,异源RESTful就是一种最典型的场景,会使用到PUT
、DELETE
、PATCH
…等请求类型,且一般以application/json
格式进行数据交互。
当浏览器把一个请求判定为非简单请求,则其发出前,浏览器会预先对服务端发起一个OPTIONS
类型的预检(preflight)请求,同时也会加上Origin
字段,
浏览器会根据此次预检请求返回的响应头来判断,服务端是否允许本域跨域操作资源,若判断为允许,则浏览器立即发出本身要发出的请求,否则,控制台抛出异常,中断请求。
服务端应返回的响应头应包含以下几个CORS字段:
Access-Control-Allow-Origin
必需返回,同简单请求中的含义。Access-Control-Allow-Methods
该字段必需,它的值是逗号分隔的一个字符串,表明服务器支持的所有跨域请求的方法。注意,返回的是所有支持的方法,而不单是浏览器请求的那个方法。这是为了避免多次”预检”请求。Access-Control-Allow-Headers
如果浏览器请求包括Access-Control-Request-Headers
字段,则Access-Control-Allow-Headers
字段是必需的。它也是一个逗号分隔的字符串,表明服务器支持的所有头信息字段,不限于浏览器在”预检”中请求的字段。Access-Control-Allow-Credentials
可选返回,同简单请求中的含义。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…等各种异步库时,可能会存在类似解决跨域的选项,其可能是内部对简单请求和非简单请求进行的一些基本转换,此类并非真正解决跨域的方法,解决跨域务必需要服务端处理。
完
本文部分内容参考来源:
W3C – HTTP/1.1: Method Definitions
原文地址:surmon.me/article/21