HTTP知多少? calss 2 基本概念

"Web"

Posted by 許敲敲 on December 8, 2019

我想做个好Web developer……

HTTP

前言

在互联网世界里,HTTP 通常跑在 TCP/IP 协议栈之上,依靠 IP 协议实现寻址和路由、TCP 协议实现可靠数据传输、DNS 协议实现域名查找、SSL/TLS 协议实现安全通信。此外,还有一些协议依赖于 HTTP,例如 WebSocket、HTTPDNS 等。这些协议相互交织,构成了一个协议网,而 HTTP 则处于中心地位。

作为一名Web developer,我们基本每天都要和HTTP打交道。平时基本上了解一些状态码,了解一些GET、POST等方法,遇到问题百度一下就能work。但HTTP 是浏览器中最重要且使用最多的协议,它是浏览器和服务器之间的通信语言。把HTTP相关知识点学扎实,或者学的更深入点,会对我们的开发工作很有帮助。比如涉及到HTTP相关的性能问题,涉及到网络安全问题等等,这些都需要我们对HTTP有更深入的了解才行。 在看完极客时间专栏的《透视HTTP协议》,以及相关的一些资料,感觉收获很多,还是记录一下。(如有侵权请联系,,,欢迎大家原文阅读扫码购买,需要返现也请联系我) 扫码购买

HTTP

HTTP 翻译成中文是“超文本传输协议”,是一个应用层的协议,通常基于 TCP/IP,能够在网络的任意两点之间传输文字、图片、音频、视频等数据。HTTP 协议中的两个端点称为请求方和应答方。请求方通常就是 Web 浏览器,也叫 user agent,应答方是 Web 服务器,存储着网络上的大部分静态或动态的资源。在浏览器和服务器之间还有一些“中间人”的角色,如 CDN、网关、代理等,它们也同样遵守 HTTP 协议,可以帮助用户更快速、更安全地获取资源。HTTP 协议不是一个孤立的协议,需要下层很多其他协议的配合。最基本的是 TCP/IP,实现寻址、路由和可靠的数据传输,还有 DNS 协议实现对互联网上主机的定位查找。对 HTTP 更准确的称呼是“HTTP over TCP/IP”,而另一个“HTTP over SSL/TLS”就是增加了安全功能的 HTTPS。

HTTP 协议基本工作流程,也就是“请求 - 应答”“一发一收”的模式。HTTP 的工作模式也是很简单,由于 TCP/IP 协议负责底层的具体传输工作,HTTP 协议基本上不用太操心所谓的“超文本传输协议”中“传输”的事情。 HTTP 协议的核心部分是什是它传输的报文内容。HTTP 协议在规范文档里详细定义了报文的格式,规定了组成部分,解析规则,还有处理策略,所以可以在 TCP/IP 层之上实现更灵活丰富的功能,例如连接控制,缓存管理、数据编码、内容协商等等。

HTTP报文

如果你对 TCP/UDP 的报文格式有所了解,TCP/UDP在实际要传输的数据之前附加了一个 20 字节的头部数据,存储 TCP 协议必须的额外信息,例如发送方的端口号、接收方的端口号、包序号、标志位等等。有了这个附加的 TCP 头,数据包才能够正确传输,到了目的地后把头部去掉,就可以拿到真正的数据。 http_header

HTTP 协议也是与 TCP/UDP 类似,同样也需要在实际传输的数据前附加一些头数据,不过与 TCP/UDP 不同的是,它是一个“纯文本”的协议,所以头数据都是 ASCII 码的文本,可以很容易地用肉眼阅读,不用借助程序解析也能够看懂。HTTP 协议的请求报文和响应报文的结构基本相同,由三大部分组成:

  1. 起始行(start line):描述请求或响应的基本信息;
  2. 头部字段集合(header):使用 key-value 形式更详细地说明报文;
  3. 消息正文(entity):实际传输的数据,它不一定是纯文本,可以是图片、视频等二进制数据。这其中前两部分起始行和头部字段经常又合称为“请求头”或“响应头”,消息正文又称为“实体”,但与“header”对应,很多时候就直接称为“body”。

HTTP 协议规定报文必须有 header,但可以没有 body,而且在 header 之后必须要有一个“空行”,也就是“CRLF”,十六进制的“0D0A”。所以,一个完整的 HTTP 报文就像是下图的这个样子,注意在 header 和 body 之间有一个“空行”。

可以通过wireshark,或者Chrome工具进行网络抓包分析。如下图为某次抓包结果 http_header

在这个请求报文里,第一行“GET / HTTP/1.1”就是请求行,而后面的“Host”“Connection”等等都属于 header,报文的最后是一个空白行结束,没有 body。在很多时候,特别是浏览器发送 GET 请求的时候都是这样,HTTP 报文经常是只有 header 而没 body,相当于只发了一个超级“大头”过来,你可以想象的出来:每时每刻网络上都会有数不清的“大头儿子”在跑来跑去。不过这个“大头”也不能太大,虽然 HTTP 协议对 header 的大小没有做限制,但各个 Web 服务器都不允许过大的请求头,因为头部太大可能会占用大量的服务器资源,影响运行效率。

请求行

了解了 HTTP 报文的基本结构后,我们来看看请求报文里的起始行也就是请求行(request line),它简要地描述了客户端想要如何操作服务器端的资源。请求行由三部分构成: http_header_req

  1. 请求方法:是一个动词,如 GET/POST,表示对资源的操作;
  2. 请求目标:通常是一个 URI,标记了请求方法要操作的资源;
  3. 版本号:表示报文使用的 HTTP 协议版本。 比如对于请求行:
    GET / HTTP/1.1
    

    这三个部分通常使用空格(space)来分隔,最后要用 CRLF 换行表示结束。 在这个请求行里,“GET”是请求方法,“/”是请求目标,“HTTP/1.1”是版本号,把这三部分连起来,意思就是“服务器你好,我想获取网站根目录下的默认文件,我用的协议版本号是 1.1,请不要用 1.0 或者 2.0 回复我。”

状态行

看完了请求行,我们再看响应报文里的起始行,在这里它不叫“响应行”,而是叫“状态行”(status line),意思是服务器响应的状态。比起请求行来说,状态行要简单一些,同样也是由三部分构成: res_http

  1. 版本号:表示报文使用的 HTTP 协议版本;
  2. 状态码:一个三位数,用代码的形式表示处理的结果,比如 200 是成功,500 是服务器错误;
  3. 原因:作为数字状态码补充,是更详细的解释文字,帮助人理解原因。
HTTP/1.1 404 Not Found

翻译成人话就是:“抱歉啊浏览器,刚才你的请求收到了,但我没找到你要的资源,错误代码是 404,接下来的事情你就看着办吧。

头部字段

请求行或状态行再加上头部字段集合就构成了 HTTP 报文里完整的请求头或响应头,可以参见下面示意图。 req_head res_header

请求头和响应头的结构是基本一样的,唯一的区别是起始行。头部字段是 key-value 的形式,key 和 value 之间用“:”分隔,最后用 CRLF 换行表示字段结束。比如在“Host: 127.0.0.1”这一行里 key 就是“Host”,value 就是“127.0.0.1”。HTTP 头字段非常灵活,不仅可以使用标准里的 Host、Connection 等已有头,也可以任意添加自定义头,这就给 HTTP 协议带来了无限的扩展可能。不过使用头字段需要注意下面几点:

  1. 字段名不区分大小写,例如“Host”也可以写成“host”,但首字母大写的可读性更好;
  2. 字段名里不允许出现空格,可以使用连字符“-”,但不能使用下划线“_”。例如,“test-name”是合法的字段名,而“test name”“test_name”是不正确的字段名;
  3. 字段名后面必须紧接着“:”,不能有空格,而“:”后的字段值前可以有多个空格;
  4. 字段的顺序是没有意义的,可以任意排列不影响语义;
  5. 字段原则上不能重复,除非这个字段本身的语义允许,例如 Set-Cookie。

常用头字段

HTTP 协议规定了非常多的头部字段,实现各种各样的功能,但基本上可以分为四大类:

  1. 通用字段:在请求头和响应头里都可以出现;
  2. 请求字段:仅能出现在请求头里,进一步说明请求信息或者额外的附加条件;
  3. 响应字段:仅能出现在响应头里,补充说明响应报文的信息;
  4. 实体字段:它实际上属于通用字段,但专门描述 body 的额外信息。 对 HTTP 报文的解析和处理实际上主要就是对头字段的处理,理解了头字段也就理解了 HTTP 报文。

基本的头字段:

Host 字段,它属于请求字段,只能出现在请求头里,它同时也是唯一一个 HTTP/1.1 规范里要求必须出现的字段,也就是说,如果请求头里没有 Host,那这就是一个错误的报文。Host 字段告诉服务器这个请求应该由哪个主机来处理,当一台计算机上托管了多个虚拟主机的时候,服务器端就需要用 Host 字段来选择,有点像是一个简单的“路由重定向”。例如我们的试验环境,在 127.0.0.1 上有三个虚拟主机:“www.chrono.com”“www.metroid.net”和“origin.io”。那么当使用域名的方式访问时,就必须要用 Host 字段来区分这三个 IP 相同但域名不同的网站,否则服务器就会找不到合适的虚拟主机,无法处理。

User-Agent 是请求字段,只出现在请求头里。它使用一个字符串来描述发起 HTTP 请求的客户端,服务器可以依据它来返回最合适此浏览器显示的页面。但由于历史的原因,User-Agent 非常混乱,每个浏览器都自称是“Mozilla”“Chrome”“Safari”,企图使用这个字段来互相“伪装”,导致 User-Agent 变得越来越长,最终变得毫无意义。不过有的比较“诚实”的爬虫会在 User-Agent 里用“spider”标明自己是爬虫,所以可以利用这个字段实现简单的反爬虫策略。

Date字段是一个通用字段,但通常出现在响应头里,表示 HTTP 报文创建的时间,客户端可以使用这个时间再搭配其他字段决定缓存策略。

Server 字段是响应字段,只能出现在响应头里。它告诉客户端当前正在提供 Web 服务的软件名称和版本号,该字段非必须。

Content-Length,它表示报文里 body 的长度,也就是请求头或响应头空行后面数据的长度。服务器看到这个字段,就知道了后续有多少数据,可以直接接收。如果没有这个字段,那么 body 就是不定长的,需要使用 chunked 方式分段传输。

请求方法

目前HTTP/1.1 规定了八种请求方法(单词大写),主要包含以下:

  1. GET:获取资源,可以理解为读取或者下载数据;
  2. HEAD:获取资源的元信息;
  3. POST:向资源提交数据,相当于写入或上传数据;
  4. PUT:类似 POST;
  5. DELETE:删除资源;
  6. CONNECT:建立特殊的连接隧道;
  7. OPTIONS:列出可对资源实行的方法;
  8. TRACE:追踪请求 - 响应的传输路径

看到这些方法可以简单联想成为对‘Sever’ 数据库文件的CRUD.其中前四种为常见的方法。

  • GET/HEAD

    GET表示从服务器请求资源,搭配 URI 和其他头字段能实现对资源更精细的操作。比如在URI后面使用“#”表示请求某个页面后直接定位到某个标签位置;当搭配“If-Modified-Since”字段表示资源被修改后才执行获取;“Range”字段表示获取部分数据。HEAD与GET 类似,但仅请求元信息,适合更加轻量化的查询操作如检查文件是否具有新版本应该使用HEAD方法。

  • POST/PUT

    POSTPUT方法表示对URL资源进行提交、修改资源,与GET/HEAD 方法相反。比如在论坛上发表评论,点击加入购物车都会发起POST的请求。PUT与POST区别在于,POST表示“新建”,PUT更多具有“修改”,“update”的含义。

  • DELETE 方法指示服务器删除资源,因为这个动作危险性太大,所以通常服务器不会执行真正的删除操作,而是对资源做一个删除标记。当然,更多的时候服务器就直接不处理 DELETE 请求。
  • CONNECT 是一个比较特殊的方法,要求服务器为客户端和另一台远程服务器建立一条特殊的连接隧道,这时 Web 服务器在中间充当了代理的角色。
  • OPTIONS 方法要求服务器列出可对资源实行的操作方法,在响应头的 Allow 字段里返回。它的功能很有限,用处也不大,有的服务器(例如 Nginx)干脆就没有实现对它的支持。
  • TRACE 方法多用于对 HTTP 链路的测试或诊断,可以显示出请求 - 响应的传输路径。它的本意是好的,但存在漏洞,会泄漏网站的信息,所以 Web 服务器通常也是禁止使用。

以上8种方法,仅GET/HEAD方法是“安全”的。而对于“幂等概念来讲”,GET 和 HEAD 既是安全的也是幂等的,DELETE 可以多次删除同一个资源,效果都是“资源不存在”,所以也是幂等的。POST 是“新增或提交数据”,多次提交数据会创建多个资源,所以不是幂等的;而 PUT 是“替换或更新数据”,多次更新一个资源,资源还是会第一次更新的状态,所以是幂等的。

安全”:请求方法不会“破坏”服务器上的资源,即不会对服务器上的资源造成实质的修改。
幂等”: 实际上是一个数学用语,被借用到了 HTTP 协议里,意思是多次执行相同的操作,结果也都是相同的,即多次“幂”后结果“相等”

URL

URL —— 统一资源定位符(Uniform Resource Locator),在HTTP中非常重要,具体的HTTP方法都会用到url。彻底理解URL需要掌握以下内容。

  • URL 格式

    URI 最常用的形式,由 scheme、host:port、path 和 query 四个部分组成。 scheme :// host:port path ? query,如下图所示。

    url

    scheme表示,资源应该使用哪种协议进行访问资源如http、https,ftp,file等。host表示资源所在的主机名,path用来表示资源所在的位置。 query 表示查询参数,通常使用 “key=value”形式,并通过‘&’连接。如URI: http://www.chrono.com:8080/11-1?uid=1234&name=mario&refer=xxx后面的查询参数就表示为查询参数为

    uid:1234
    name:mario
    refer:xxx
    
  • URL 的完整格式

    entire_url 除了常用的URL格式外,URL还具备完整的形式如上图所示。其中“user:passwd@” 表示身份信息,该方法具有隐患。第二个“#fragment”表示身份标志符用来作为锚点,当浏览器获得资源后可以直接跳转到它指示的位置,该方法仅在浏览器客户端使用。

  • URI 的编码 在 URI 里只能使用 ASCII 码,当出现其他特殊字符时候,使用非 ASCII 码或特殊字符转换成十六进制字节值,然后前面再加上一个“%”。例如,空格被转义成“%20”,“?”被转义成“%3F”。而中文、日文等则通常使用 UTF-8 编码后再转义,例如“银河”会被转义成“%E9%93%B6%E6%B2%B3”。

    “http://www.chrono.com:8080/11-1 ? %E5%A4%B8%E7%88%B6%E9%80%90%E6%97%A5”,输入到浏览器(Trim space)后会变为以下URL。

http://www.chrono.com:8080/11-1?夸父逐日