HTTP全知道(下)

HTTP1.1 是如何解决队头阻塞问题

书接上文,HTTP 传输是基于请求-应答的模式进行的,报文必须是一发一收,任务被放在一个任务队列中串行执行,而所谓的HTTP队头阻塞问题。是指一旦队首的请求处理太慢,就会阻塞后面请求的处理。

  • 并发连接
    对于一个域名允许分配多个长连接,那么相当于增加了任务队列,不至于一个队伍的任务阻塞其它所有任务。在RFC2616规定过客户端最多并发 2 个连接,不过、现在的浏览器标准中,这个上限要多很多,就比如Chrome 中是 6 个。
  • 域名分片
    一个域名不是可以并发 多个长连接吗?那我就多分几个域名。
    比如 content1.test.com 、content2.test.com。
    这样一个test.com域名下可以分出非常多的二级域名,而它们都指向同样的一台服务器,能够并发的长连接数更多了。事实上也更好地解决了队头阻塞的问题。

即使是提高了并发连接,也满足不了对性能的需求,而只有能够并发的长连接数更多了,才能更好地解决了队头阻塞的问题。

HTTP 代理

HTTP 是基于请求-响应模型的协议,一般由客户端发请求,服务器来进行响应。
但是也有代理服务器的情况。引入代理之后,代理的服务器相当于一个中间人的角色,对于客户端而言,表现为服务器进行响应;而对于源服务器,表现为客户端发起请求,具有双重身份。

  • 能干嘛?
    1. 负载均衡。客户端的请求只会先到达代理服务器,后面到底有多少源服务器,IP 都是多少,客户端是不知道的。因此,代理服务器可以拿到这个请求之后,可以通过特定的算法分发给不同的源服务器,让各台源服务器的负载尽量平均。

    2. 保障安全。利用心跳机制监控后台的服务器,一旦发现故障机就将其踢出集群。并且对于上下行的数据进行过滤,对非法 IP 限流,这些都是代理服务器的工作。

    3. 缓存代理。将内容缓存到代理服务器,使得客户端可以直接从代理服务器获得而不用到源服务器那里。

  • 相关头部字段
  1. Via
    代理服务器需要标明自己的身份,假如有两台代理服务器,在客户端发送请求后会经历这样一个过程:
    客户端 => 代理1 => 代理2 => 源服务器
    源服务器收到请求后,会在请求头拿到这个字段:
    Via: proxy_server1, proxy_server2
    而源服务器响应时,最终在客户端会拿到这样的响应头:
    Via: proxy_server2, proxy_server1
    Via中代理的顺序即为在 HTTP 传输中 报文传达的顺序

  2. X-Forwarded-For
    是为谁转发, 它记录的是请求方的IP地址(和Via区分开只记录请求方这一个IP)。

  3. X-Real-IP
    是一种获取用户真实 IP 的字段,不管中间经过多少代理,这个字段始终记录最初的客户端的IP。
    相应的,还有X-Forwarded-Host和X-Forwarded-Proto,分别记录客户端(注意哦,不包括代理)的域名和协议名。

  4. X-Forwarded-For产生的问题
    前面可以看到,只记录了请求方的 IP,这意味着每经过一个不同的代理,这个字段的名字都要变,由此产生了问题:

  • 意味着代理必须解析 HTTP 请求头,然后修改,比直接转发数据性能下降。
  • 在 HTTPS 通信加密的过程中,原始报文是不允许修改的。

为了解决这个问题产生了代理协议,一般使用明文版本,只需要在 HTTP 请求行上面加如下文本即可:

// PROXY + TCP4/TCP6 + 请求方地址 + 接收方地址 + 请求端口 + 接收端口
PROXY TCP4 0.0.0.1 0.0.0.2 1111 2222
GET / HTTP/1.1

什么是跨域

scheme(协议)、host(主机)和port(端口)都相同则为同源
说到http 就得说到跨域了,在前后端分离的开发模式中,经常会遇到跨域问题,即 Ajax 请求发出去了,服务器也成功响应了,前端就是拿不到这个响应。

非同源站点有这样一些限制
  • 不能读取和修改对方的 DOM
  • 不读访问对方的 Cookie、IndexDB 和 LocalStorage
  • 限制 XMLHttpRequest 请求。

跨域请求是被浏览器拦截,响应其实是成功到达客户端了。那这个拦截是如何发生呢?

  1. 浏览器是多进程的,以 Chrome 为例,进程组成如下:
    当Ajax 请求准备发送的时候,其实还只是在渲染进程的处理。为了防止黑客通过脚本触碰到系统资源,浏览器将每一个渲染进程装进了沙箱,采取了站点隔离的手段。
  2. 在沙箱当中的渲染进程只能通过网络进程来发送网络请。在数据传递给了浏览器主进程,主进程接收到后,才真正地发出相应的网络请求。
    在服务端处理完数据后,将响应返回,主进程检查到跨域,且没有cors响应头,将响应体全部丢掉,并不会发送给渲染进程。这就达到了拦截数据的目的。
解决方案
CORS

CORS 就是是跨域资源共享。它需要浏览器和服务器的共同支持,具体来说,非 IE 和 IE10 以上支持CORS,服务器需要附加特定的响应头,后面具体拆解。
简单请求和非简单请求。

  • 简单请求:
    请求方法为 GET、POST 或者 HEAD
    请求头的取值范围: Accept、Accept-Language、Content-Language、Content- Type(只限于三个值application/x-www-form-urlencoded、multipart/form-data、 text/plain)
    请求发出去之前,浏览器会自动在请求头当中,添加一个Origin字段,用来说明 请求来自哪个源。服务器拿到请求之后,在回应时对应地添加Access-Control-Allow-Origin字段,如果Origin不在这个字段的范围中,那么浏览器就会将响应拦截。
  1. Access-Control-Allow-Origin字段是服务器用来决定浏览器是否拦截这个响应,这是必需的字段。

  2. Access-Control-Allow-Credentials表示是否允许发送 Cookie,对于跨域请求,浏览器对这个字段默认值设为 false,而如果需要拿到浏览器的 Cookie,需要添加这个响应头并设为true, 并且在前端也需要设置withCredentials属性:
    let xhr = new XMLHttpRequest();xhr.withCredentials = true;

  3. Access-Control-Expose-Headers。这个字段是给 XMLHttpRequest 对象赋值,让它不仅可以拿到基本的 响应头字段,还能拿到这个字段声明的响应头字段。比如这样设置:Access-Control-Expose-Headers: test

    前端可以通过 XMLHttpRequest.getResponseHeader('test') 拿到 test 这个字段的值。

  • 非简单请求
    除了上述的就是非简单请求,针对这种请求进行不同的处理。
    主要体现在两个方面: 预检请求和响应字段。
    我们以 PUT 方法为例。

    var url = 'http://xxx.com';
    var xhr = new XMLHttpRequest();
    xhr.open('PUT', url, true);
    xhr.setRequestHeader('X-Custom-Header', 'xxx');
    xhr.send()
    
    1. 预检请求
    OPTIONS / HTTP/1.1
    Origin: 当前地址
    Host: xxx.com
    Access-Control-Request-Method: PUT
    Access-Control-Request-Headers: X-Custom-Header
    

    通过OPTIONS,同时会加上Origin源地址和Host目标地址,同时也会加上两个关键的字段:
    Access-Control-Request-Method, 列出 CORS 请求用到哪个HTTP方法
    Access-Control-Request-Headers,指定 CORS 请求将要加上什么请求头

    1. 响应字段
    • 预检请求的响应
    HTTP/1.1 200 OK
    Access-Control-Allow-Origin: *
    Access-Control-Allow-Methods: GET, POST, PUT
    Access-Control-Allow-Headers: X-Custom-Header
    Access-Control-Allow-Credentials: true
    Access-Control-Max-Age: 1728000
    Content-Type: text/html; charset=utf-8
    Content-Encoding: gzip
    Content-Length: 0
    

    Access-Control-Allow-Origin: 表示可以允许请求的源,可以填具体的源名,也可以填*
    Access-Control-Allow-Methods: 表示允许的请求方法列表
    Access-Control-Allow-Credentials: 表示是否允许发送 Cookie
    Access-Control-Allow-Headers: 表示允许发送的请求头字段
    Access-Control-Max-Age: 预检请求的有效期,在此期间,不用发出另外一条预检请求

    在预检请求的响应返回后,如果请求不满足响应头的条件,则触发XMLHttpRequest的onerror方法,当然后面真正的CORS请求也不会发出去了。
    之后和简单请求一样浏览器自动加上Origin字段,服务端响应头返回Access-Control-Allow-Origin。

JSONP

虽然XMLHttpRequest对象遵循同源政策,但script标签可以通过 src 填上目标地址从而发出 GET 请求,实现跨域请求并拿到响应。这也就是 JSONP 的原理
和CORS相比,JSONP 最大的优势在于兼容性好,IE 低版本不能使用 CORS 但可以使用 JSONP,缺点也很明显,请求方法单一,只支持 GET 请求。

Nginx

Nginx通过反向代理服务器来轻松解决跨域问题。


image.png
  • 正向代理帮助客户端访问客户端自己访问不到的服务器(梯子),然后将结果返回给客户端。
image.png
  • 反向代理拿到客户端的请求,将请求转发给其他的服务器,主要的场景是维持服务器集群的负载均衡,反向代理帮其它的服务器拿到请求,然后选择一个合适的服务器,将请求转交给它。

  • 那 Nginx 是如何来解决跨域的呢?
    客户端的域名为client.com,服务器的域名为server.com,客户端向服务器发送 Ajax 请求,会跨域产生跨域问题了,通过下面这个配置:

server {
  listen  80;
  server_name  client.com;
  location /api {
    proxy_pass server.com;
  }
}

Nginx 相当于起了一个跳板,这个跳板的名字也是client.com,让客户端首先访问 client.com/api,这当然没有跨域,然后 Nginx 服务器作为反向代理,将请求转发给server.com,当响应返回时又将响应给到客户端,这就完成整个跨域请求的过程。就像是在饭馆点餐时,服务员来回穿梭顾客和厨房是一样的道理,服务员记录了我们点的菜品,然后也将厨房的食物送到我们的餐桌上。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 214,504评论 6 496
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,434评论 3 389
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 160,089评论 0 349
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,378评论 1 288
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,472评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,506评论 1 292
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,519评论 3 413
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,292评论 0 270
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,738评论 1 307
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,022评论 2 329
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,194评论 1 342
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,873评论 5 338
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,536评论 3 322
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,162评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,413评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,075评论 2 365
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,080评论 2 352

推荐阅读更多精彩内容

  • 引用自HTTP访问控制(CORS) 当 Web 资源请求由其它域名或端口提供的资源时,会发起跨域 HTTP 请求(...
    有涯逐无涯阅读 2,583评论 0 4
  • 什么是跨域 跨域,是指浏览器不能执行其他网站的脚本。它是由浏览器的同源策略造成的,是浏览器对JavaScript实...
    HeroXin阅读 835评论 0 4
  • 什么是跨域 跨域,是指浏览器不能执行其他网站的脚本。它是由浏览器的同源策略造成的,是浏览器对JavaScript实...
    他方l阅读 1,062评论 0 2
  • 什么是跨域 跨域,是指浏览器不能执行其他网站的脚本。它是由浏览器的同源策略造成的,是浏览器对JavaScript实...
    Yaoxue9阅读 1,295评论 0 6
  • 简介 由于受浏览器的同源策略(same-origin policy)的影响, Ajax 请求默认只能在同一域名下进...
    叫我峰兄阅读 487评论 0 2