1.写在前面
前面介绍完了HTTP协议的请求行、响应行,我们继续接下来的内容。
2.如何管理跨代理服务器的长短连接?
HTTP连接的常见的流程
- 浏览器解析出主机名
- 浏览器查询这个主机名的IP地址(DNS)
- 浏览器获得端口号(80)
- 浏览器发起到202.43.78.3端口80的连接
- 浏览器想服务器发送一条HTTP GET报文
- 浏览器从服务器读取HTTP响应报文
- 浏览器关闭连接
从TCP编程上看HTTP请求处理
短连接与长链接
Connection头部
- Keep-Alive:长连接
- 客户端请求长连接:Connection:Keep-Alive
- 服务器表示支持长连接:Connection:Keep-Alive
- 客户端复用连接
- HTTP/1.1默认支持长连接 Connection:Keep-Alive无意义
- Close:短连接
- 对代理服务器的要求
- 不转发Connection列出头部,该头部仅与当前连接相关
Connection仅针对当前连接有效
user agent 与 origin server 间有层层proxy代理
代理服务器对长连接的支持
-
问题:各方间错误使用了长连接
- 客户端发起长连接
- 代理服务器陈旧,不能正确的处理请求的Connection头部,将客户端请求中的Connection:Keep-Alive原样转发给上游服务器
- 上游服务器正确的处理了Connection头部,在发送响应后没有关闭连接,而试图保持、复用与不认长连接的代理服务器的连接
- 代理服务器收到响应中Connection:Keep-Alive后不认,转发给客户端,同时等待服务器关闭短连接
- 客户端收到了Connection:Keep-Alive,认为可以复用长连接,继续在该连接上发起请求
- 代理服务器出错,因为短连接上不能发起两次请求
-
Proxy-Connection
-
陈旧的代理服务器不识别该头部:退化为短连接
-
新版本的代理服务器理解该头部 与客户端建立长连接 与服务器使用Connection替代Proxy-Connect头部
-
3.HTTP消息在服务器端的路由
Host头部
Host = uri-host[“:” port]
- HTTP/1.1规范要求,不传递Host头部则返回400错误响应码
- 为防止陈旧的代理服务器,发向正向代理的请求request-target必须以absolute-form形式出现
- request-line = method SP request-target SP HTTP-version CRLF
- Absolute-form = absolute-URI = scheme “:” hier-part[“?” query]
Host头部与消息的路由
- 建立TCP连接 确定服务器的IP地址
- 接收请求
- 寻找虚拟主机 匹配Host头部和域名
- 寻找URI的处理代码 匹配URI
- 执行处理请求的代码 访问资源
- 生成HTTP响应 各中间件基于PF架构串行修改响应
- 发送HTTP响应
- 记录访问日志
4.代理服务器转发消息时的相关头部
客户端与源服务器间存在多个代理
问题:如何传递IP地址?
- TCP连接四元组(src ip, src port, dst ip, dst port)
- HTTP 头部X-Forwarded-For用于传递IP
- HTTP 头部X-Real-IP用于传递用户IP
- 网络中存在许多反向代理**(前提)**
消息的转发
- Max-Forwards头部 限制Proxy代理服务器的最大转发次数,仅对TRACE/OPTIONS方法有效 Max-Forwards = 1*DIGIT
- Via头部 指明经过的代理服务器名称及版本
- Cache-Control:no-transform 禁止代理服务器修改响应包体。
5.请求与响应的上下文
请求的上下文:User-Agent
指明客户端的类型信息,服务器可以据此对资源的表述做抉择
- User-Agent = product * (RWS (product / comment))
- product = token [“/” product-version]
- RWS = 1 *(SP / HTAB)
请求的上下文:Referer
浏览器对来自某一页面的请求自动添加的头部
- Referer = absolute-URI / partial-URI
- Referer 不会被添加的场景
- 来源页面采用的协议为表示本地文件的"file"或者"data" URI
- 当前请求页面采用的是http协议,而来源页面采用的是https协议
- 服务器端常用于统计分析、缓存优化、防盗链等功能
请求的上下文:From
主要用于网络爬虫,告诉服务器如何通过邮件联系到爬虫的负责人
响应的上下文:Server
指明服务器上所用软件的信息,用于帮助客户端定位问题或者统计数据
- Server = product *(RWS (product / comment))
- product = token [“/” product-version]
响应的上下文:Allow与Accept-Ranges
Allow:告诉客户端,服务器上该URI对应的资源允许哪些方法的执行
- Allow = #method
Accept-Ranges:告诉客户端服务器上该资源是否允许range请求
- Accept-Ranges = acceptable-ranges
6.内容协商和资源表述
内容协商
每个URI指向的资源可以是任何事物,可以有多种不同的表述,例如一份文档可以有不同语言的翻译、不同的媒体格式、可以针对不同的浏览器提供不同的压缩编码等。
内容协商的两种方式
- Proactive 主动式内容协商:指由客户端在请求头部中提出需要的表述形式,而服务器根据这些请求头部提供特定的representation表述
-
Reactive 响应式内容协商:指服务器返回300 Multiple Choices 或者 406 Not Acceptable,由客户端选择一种表述URI使用
常见的协商要素
- 质量因子q:内容的质量、可接受类型的优先级
- 媒体资源的MIME类型及质量因子。
- 字符编码:由于UTF-8格式广为使用,Accept-Charset已被废弃
- 内容编码:主要指压缩算法 Accept-Encoding:gizp, deflate,br
- 表述语言:Accept-Language
国际化与本地化
- Internationalization (i18n,i和n间有18个字符)
- 指设计软件时,在不同的国家、地区可以不做逻辑实现页面的修改便能够以不同的语言显示
- localization (l10n,l和n间有10个字符)
- 指内容协商时,根据请求中的语言及区域信息,选择特定的语言作为资源表述
资源表述的元数据头部
- 媒体类型、编码 content-type
- 内容编码 content-encoding
- 语言 content-language
7.HTTP包体的传输方式
HTTP包体:承载的消息内容
-
请求或响应都可以携带包体
- HTTP-message = start-line * (header-field CRLF) CRLF [message-body]
- message-body = *OCTET:二进制字节流
-
以下消息不能含有包体
- HEAD方法请求对应的响应
- 1xx、204、304对应的响应
- CONNECT方法对应的2xx响应
两种传输HTTP包体的方式
-
发送HTTP消息时已能够确定包体的全部长度
- 使用Content-Length头部明确指明包体长度 Content-Length = 1*DIGIT 用10进制表示包体中的字节个数,且必须与实际传输的包体长度一致。
- 如果包体的长度小于实际的长度,这个时候再TCP协议的层面是发送实际的长度,但是在HTTP协议层实际上只发送了包体的长度。
- 如果包体的长度大于实际的长度,这个时候浏览器会直接无法解析
-
优点:接收端处理更简单
-
发送HTTP消息时不能确定包体的全部长度
- 使用Transfer-Encoding头部指明使用Chunk传输方式
- 含Transfer-Encoding头部后Content-Length头部应被忽略
-
优点
- 基于长连接持续推送动态内容
- 压缩体积较大的包体时,不必完全压缩完(计算出头部)再发送,可以边发边压缩
- 传递必须在包体传输完才能计算出的Trailer头部
不定长包体的chunk传输方式
- Transfer-Encoding头部
- transfer-coding = “chunked” / “compress” / “deflate” / “gzip” / transfer-extension
- chunked transfer encoding 分块传输编码:Transfer-Encoding:chunked
- chunked-body = *chunk last-chunk trailer-part CRLF
- chunk = chunk-size [chunk-ext] CRLF chunk-data CRLF chunk-size = 1*HEXDIG chunk-data = 1*OCTET
- last-chunk = 1*(“0”)[chunk-ext] CRLF
- trailer-part = *(header-field CRLF)
Trailer头部的传输
- TE头部:客户端在请求在声明是否接受Trailer头部
- TE:trailers
- Trailer头部:服务器告知接下来chunk包体后传输哪些Trailer头部
- Trailer:Date
- 以下头部不允许出现在Trailer值中:
- 用于信息分帧的首部(例如 Transfer-Encoding 和 Content-Length)
- 用于路由用途的首部 (例如Host)
- 请求修饰首部(例如控制类和条件类的,如Cache-Control Max-Forwards 或者TE)
- 身份验证首部(例如Authorization 或者 Set-Cookie)
- Content-Encoding Content-Type Content-Range,以及Trailer自身
MIME
MIME(Multipurpose Internet Mail Extensions)
content:=“Content-Type”“:”“type”/“subtype*(”;"parameter)
-
type := discrete-type / composite-type
-
discrete-type := “text”/“image”/“audio”/“video”/“application”/extension-token
-
composite-type := “message” / “multipart” /extension-token
-
subtype := extension-token / iana-token
-
parameter := attribute “=” value
大小写不敏感,但通常是小写
Content-Disposition头部
- disposition-type = “inline” | “attachment” | disp-ext-type
- inline:指定包体是以inline内联的方式,作为页面的一部分展示
- attachment:指定浏览器将包体以附件的方式下载
- 在multipart/form-data类型应答中,可以用于子消息体部分
8.HTML form 表单提交时的协议格式
HTML FORM表单
HTML:HyperText Markup Language,结构化的标记语言 浏览器可以将HTML文件渲染为可视化网页
FORM表单:HTML中的元素,提供了交互控制元件用来向服务器通过HTTP协议提交信息,常见控件有:
- Text Input Controls:文本输入控件
- Checkboxes Controls:复选框控件
- Radio Box Controls:单选框按钮控件
- Select Box Controls:下拉列表控件
- File Select boxes:选取文件控件
- Clickable Buttons:可点击的按钮控件
- Submit and Reset Button:提交或者重置按钮控件
HTML FORM 表单提交请求时的关键属性
action:提交时发起HTTP请求的URI
method:提交时发起HTTP请求的http方法
- GET:通过URI,将表单数据以URI参数的方式提交
- POST:将表单数据放在请求包体中提交
enctype : 在POST方法下,对表单内容在请求包体中的编码方式
- application/x-www-form-urlencoded 数据被编码成以’&'分隔的键-值对,字符以URL编码方式编码
- multipart/form-data
- boundary 分隔符
- 每部分表述皆有HTTP头部描述子包体,例如Content-Type
- last boundary 结尾
multipart: 一个包体中多个资源表述
- Content-type 头部指明这是一个多表述包体
- Content-type: multipart/form-data
- Boundary 分隔符的格式
- boundary := 0*69 bcharsnospace
- bchars := bcharsnospce /“”
- bcharsnospace := DIGIT / ALPHA / “'” / “(” /“)” / “+” /“_” /“,” / “-” /“.” /“/” /“:” / “=” /“?”
- boundary := 0*69 bcharsnospace
Multipart包体格式
multipart-body = preamble 1*encapsulation close-delimiter epilogue
- premble := discard-text
- epilogue := discard-text
- discard-text := *(*text CRLF)
- 每部分包体格式:encapsulation = delimiter body-part CRLF
- delimiter = “–” boundary CRLF
- body-part = fields *(CRLF * text)
- field = field-name “:” [field-value] CRLF
- close-delimiter = “–” boundary “–” CRLF
9.断点续传与多线程下载如何做到?
多线程、断点续传、随机点播等场景的步骤
- 客户端明确任务:从哪开始下载
- 本地是否已有部分文件
- 文件已下载部分在服务端发生改变?
- 使用多个线程并发下载
- 本地是否已有部分文件
- 下载文件的指定部分内容
- 下载完毕后拼装成统一的文件
HTTP Range规范
- 允许服务器基于客户端的请求只发送响应包体的一部分给到客户端,而客户端自动将多个片断的包体组合成完整的体积更大的包体
- 支持断点续传
- 支持多线程下载
- 支持视频播放器实时拖动
- 服务器通过Accept-Range头部表示是否支持Range请求
- Accept-Ranges:bytes 支持
- Accept-Ranges:none 不支持
Range请求范围的单位
基于字节,设包体总长度为10000
- 第1个500字节:bytes=0~499
- 第2个500字节:bytes = 500~999 bytes = 500600,601999 bytes = 500700,601999
- 最后一个500字节:bytes = -500 bytes = 9500-
- 仅要第1个和最后1个字节:bytes = 0-0,-1
通过Range头部传递请求范围。
Range条件请求
- 如果客户端已经得到了Range响应的一部分,并想在这部分响应为过期的情况下,获取其他部分的响应
- 常与If-Unmodified-Since或者If-Match头部共同使用
- If-Range = entity- tag / HTTP-date
- 可以使用Etag或者Last-Modified
服务器响应
206Partial Content
- Content-Range头部:显示当前片断在完整包体中的位置
- Content-Range = byte-conent-range /other-content-range
- Byte-content-range = bytes-unit SP (byte-range-resp / unsatisfied-range)
- Byte-range-resp = byte-range “/” (complete-length / “*”)
- Complete-length = 1*DIGIT 完整资源的大小,如果未知则用*号替代
- Byte-range = first-byte-pos “-” last-byte-pos
- Byte-content-range = bytes-unit SP (byte-range-resp / unsatisfied-range)
416Range Not Satisfiable
- 请求范围不满足实际资源的大小,其中Content-Range中的complete-length 显示完整响应的长度。
200 OK
- 服务器不支持Range请求时,则以200返回完整的响应包体
多重范围与multipart
- 请求
- Range:bytes = 050,100150
- 响应
- Content-Type: multipart/byteranges;boundary=…
10.Cookie的格式与约束
Cookie是什么?
保存在客户端、由浏览器维护、表示应用状态的HTTP头部
-
存在在内存或者磁盘中
-
服务端生成Cookie在想用中通过Set-Cookie头部告知客户端(允许多个Set-Cookie头部传递多个值)
-
客户端得到Cookie后,后续请求都会自动将Cookie头部携带至请求中
Cookie与Set-Cookie头部的定义
- Cookie头部中可以存放多个name/value名值对
- Cookie-header = "Cookie: " OWS cookie-string OWS
- Cookie-string = cookie-pair * (“;” SP cookie-pair)
- Cookie-pair = cookie-name “=” cookie-value
- Set-Cookie 头部一次只能传递1个name/value名值对,响应中可以含多个头部
- Set-cookie-header = “Set-Cookie:” SP set-cookie-string
- Set-cookie-string = cookie-pair * (“;” SP cookie-av)
- Cookie-pair = cookie-name “=” cookie-value
- Cookie-av: 描述Cookie-pair的可选属性
Set-Cookie中描述cookie-pair的属性
Cookie-av = expires-av /max-age-av / domain-av / path-av / secure-av / httponly-av / extension-av
- expies-av = “Expires=” sane-cookie-date cookie到日期sane-cookie-date后失效
- max-age-av = “Max-Age=” non-zero-digit *DIGIT cookie经过*秒后失效。max-age优先级高于expires
- domain-av = “Domain” = domain-value 指定cookie可用于哪些域名,默认可以访问当前域名
- path-av = “Path=” path-value 指定Path路径下才能使用cookie
- secure-av = “Secure” 只有使用TLS/SSL协议(https)时才能使用cookie
- Httponly-av = “HttpOnly” 不能使用JavaScript(Document.cookie 、XMLHttpRequest、Request APIs)访问到cookie
Cookie使用的限制
RFC规范对浏览器使用Cookie的要求
- 每条Cookie的长度至少要支持达到4KB
- 每个域名下至少支持50Cookie
- 至少要支持3000个Cookie
代理服务器传递Cookie时会有限制
Cookie在协议设计上的问题
Cookie会被附加在每个HTTP请求中,所以无形中增加了流量
由于在HTTP请求中的Cookie是明文传递的,所以安全性成问题(除非用HTTPS)
Cookie的大小不应超过4KB,故对于复杂的存储需求来说是不够用的
11.Session的工作的原理
常见的登录的场景下的Cookie与Session的常见用法
无状态的REST架构VS状态管理
应用状态与资源状态
-
应用状态:应由客户端管理,不应由服务器管理
- 如浏览器目前在哪一页
- REST架构要求服务器不应保存应用的状态
-
资源状态:应有服务器管理,不应由客户端管理
- 如数据库中存放的数据状态,例如用户的登录信息。
-
HTTP请求的状态
- 有状态的请求:服务端保存请求的相关的信息,每个请求可以使用一起保留的请求相关信息
- 服务器session机制使服务器保存请求的相关信息
- cookie使请求可以携带查询信息,与session配合完成有状态的请求
- 无状态的请求:服务器能够处理的所有信息都来自当前请求所携带的信息
- 服务器不会保存session信息
- 请求可以通过cookie携带
- 有状态的请求:服务端保存请求的相关的信息,每个请求可以使用一起保留的请求相关信息
第三方Cookie
浏览器允许对于不安全域下的资源(如广告图片)响应中的set-Cookie保存,并在后续访问该域时自动使用Cookie
- 用户踪迹信息的搜索
12.写在最后
本节博客主要介绍了代理服务器的长短连接,HTTP消息在服务器端的路由,代理服务器转发消息时的相关头部,请求与响应的上下文,内容协商和资源表述,HTTP包体的传输方式,HTML form 表单提交时的协议格式,断点续传与多线程下载如何做到,Cookie的格式与约束,Session的工作的原理。