HTTP协议学习(上)
首先,协议是干嘛的?为什么需要协议?我想到一个不太恰当的比喻,战争期间情报人员接头都要使用暗号:
“脸红什么?”
“精神焕发。”
“怎么又黄拉?”
“防冷涂的蜡。”
在外人看来就是普通的对话,但是对于掌握而破解规则的情报人员,里面就包含了大量的信息。所以说什么不要紧,重要的是规则,也就是如何去解读。
地球人都知道,计算机内部以及构建在计算机基础上的网络世界,二进制才是他们使用的语言,也就是一串的“010111010100012”。(为什么是二进制?因为二进制表示起来很方便啊!这个以后会写一篇文章详谈。)同样的道理,浏览器给服务器发送了一堆0-1序列,服务器怎么才能听得懂?所以,二者需要共同制定一种大家都遵守的解读规则,这就是针对Web服务的HTTP协议。当然也有其他的协议,比如用于邮件的SMTP协议,文件传输的FTP协议,本质上都是一个道理。
HTTP协议分为请求和响应两部分,他们具有相似但不同的格式。这篇先介绍请求。我们平时上网我们使用的都是浏览器,然而浏览器都设计的十分智能以方便普罗大众,但是这样以来也屏蔽掉了太多底层技术细节。为了真实的展现一次http协议通信的过程,我们可以试试更加原始的telnet程序,纯手敲协议与服务器进行交流(话说这样真的很酷~)。
在Ubuntu的终端下敲入以下命令(Windows也有telnet程序,自己搜索安装一下):
telnet www.baidu.com 80
这一步是为了连接到服务器(可以理解为建立TCP连接,这是HTTP协议所依赖的底层协议),所以给telnet加上了两个参数:URL和端口号,URL可以是IP,也可以是域名;端口号一般都是80。不出意外会有以下回应:
Trying 119.75.217.109...
Connected to www.a.shifen.com.
Escape character is '^]'.
这时,就可以输入HTTP协议本身了:
GET / HTTP/1.1
Host: www.baidu.com
输入完之后,连敲两个回车,就会出现以下内容:
HTTP/1.1 200 OK
Date: Sat, 29 Oct 2016 10:12:11 GMT
Content-Type: text/html
Content-Length: 14613
Last-Modified: Tue, 18 Oct 2016 06:34:00 GMT
Connection: Keep-Alive
。。。略
Pragma: no-cache
Cache-control: no-cache
Accept-Ranges: bytes
。。。。。。略
请求头分为三个部分:请求行、请求头、请求体(可选)。每一部分都必须以结尾,也就是一个回车。
这里需要注意的是,请求头和请求体之间需要加一个空行,即此行内容只有只有。为什么要这样呢?这得从请求头说起,请求头实际上是一组键值对,每个键值对的格式是“键: 值”,注意冒号后面有个空格。但是键值对之间如何分隔呢?答案是。我试过把两个键值对放在一行,中间用一个空格分开,然后服务器主动关闭连接了。。。这说明,键值对之间必须以回车分隔。
然后问题来了,请求头内部就以回车来分隔了,那它如何跟请求体分隔呢?所以,只有加一个空行了。
当然,我是一个善于思考的淫,我很好奇为啥请求行与请求头之间不来一个空行呢?甚至,三者之间为什么一定要分隔呢?
对于第二个问题,三者解读的方式应该是不一样的,所以必须要分隔。比如请求行,一共三个部分,分别是请求方法、请求资源的路径和协议版本,它们之间以空格分隔,当遇到第一个回车时,意味着请求行的结束,下面的数据就是请求头了(第一个问题顺便解决)。但是请求头每个键值对都是一行,到底遇到哪个回车才算完呢?所以需要一个空行代表请求头的结束。在这之后就是请求体了,这部分是可选的,基本上只有请求方法是POST的时候,这部分才是必须的。同样的,这部分也需要一个回车作为结束标志。
好,下面分析请求行。先看第二部分,即资源路径。在写这个路径的时候,我们不必再加上IP和端口号了,因为TCP已经帮HTTP做好了——IP定位到某台机器,而端口号定位到机器上的某个进程。这时候,我们可以认为请求的资源是存放一个文件系统中的,尤其是静态资源,动态资源先不管。根路径“/”就代表了进程所规定的根目录,各级子路径代表对应的子目录。要注意的是,“/”所代表的根目录未必是该服务器文件系统真正的根目录。实际上,出于安全的考虑,前者肯定不能是真正的根目录,只能是某个子目录。因为一旦黑客通过攻击该进程而获取了整台机器的最高权限,那就什么都完了。
第三部分是协议版本,目前HTTP最新的版本时1.1,这也是使用最广泛的一个版本。不过为了学习,我们还是看一下最初的版本长啥样:
GET /index.html
。。。。是的,只有一行,两部分。第一部分是请求方法,只有GET一个类型,且因为是第一版(HTTP/0.9),连版本号都省略了。对这个请求的回应,也只能是HTML字符串,不能有其他东西。。多简陋啊,所以有了第二个版本HTTP/1.0,首先就是增加了POST和HEAD两个请求方法,同时由于版本多了,所以请求行多了一个协议版本部分;然后就是增加了请求头,比如User-Agent、Accept等。
下面以HTTP/1.1为蓝本介绍请求格式。
首先是常见的请求方法:
GET - get嘛,就是“获取”的意思,向服务器请求特定的、由URI标识的资源,但不会改变服务器的数据,仅仅是被动的接收。
POST - 用于向指定URI提交数据,数据被包含在请求体中。正如上文所说,POST请求是在第二个版本中增加的,因为第一个版本中只有GET,浏览器只能被动接收服务器的数据;但一些场景下,需要浏览器主动向服务器发送数据,以对服务器产生影响。这就是POST方法的用途。
一般情况下,我们只会用到这两个请求方法,面试的时候经常会被问到这两个方法的区别,在这里也捎带说一下:
1、GET方法实际上也可以向服务器发送数据,但是这个数据依然不会对服务器造成改变,这些参数只是为了限定查询条件,类似于SQL查询的where子句;POST方法也可以不带任何参数,纯粹当个GET来使,只不过有点浪费而已。
2、GET方法的参数是放在URL里面,在请求资源路径的后面用一个问号分隔;参数的形式也是键值对,但是使用等号相连;键值对之间以“&”符号分隔。然而GET的参数有个限制,因为参数是放在URL里面,但是URL的总长度是有限制的,所以GET方法的参数也不能太长,最多只有1024字节。而POST方法将参数置于请求体中,理论上是没有长度限制的。
更正(来源不详,是我抄的):
因为GET是通过URL提交数据,那么GET可提交的数据量就跟URL的长度有直接关系了。而实际上,URL不存在参数上限的问题,HTTP协议规范没有对URL长度进行限制。这个限制是特定的浏览器及服务器对它的限制。IE对URL长度的限制是2083字节(2K+35)。对于其他浏览器,如Netscape、FireFox等,理论上没有长度限制,其限制取决于操作系统的支持。注意这是限制是整个URL长度,而不仅仅是你的参数值数据长度。
3、发送给服务器的时候,GET的参数会直接显示在地址栏里面;而POST的参数不显示在地址栏里,而后台发送,想要查看只能通过浏览器的控制台。置于所谓的安全性,讲真,对黑客来说没太大区别,普通人也不会有“从URL里面找到密码”的意识。
4、后台解析方式不同。在ASP中,服务端获取GET请求参数用Request.QueryString,获取POST请求参数用Request.Form。在JSP中,用request.getParameter(\"XXXX\")来获取,虽然jsp中也有request.getQueryString()方法,但使用起来比较麻烦,比如:传一个test.jsp?name=hyddd&password=hyddd,用request.getQueryString()得到的是:name=hyddd&password=hyddd。在PHP中,可以用$_GET和$_POST分别获取GET和POST中的数据,而$_REQUEST则可以获取GET和POST两种请求中的数据。
更多的区别参见这里,还有一篇标题很二的文章也讲二者的区别,都他妈分析到TCP层面上了,我也是服了。
PUT - 向指定资源位置上传其最新内容。
DELETE - 请求服务器删除Request-URI所标识的资源。
这两个方法用的很少,理论上它们可以做到的POST都可以做到,先不多说。
OPTIONS - 返回服务器针对特定资源所支持的HTTP请求方法。也可以利用向Web服务器发送'*'的请求来测试服务器的功能性。看个例子:
m@sys:$ telnet localhost 8080
Trying 127.0.0.1...
Connected to localhost.
Escape character is '^]'.
OPTIONS * HTTP/1.1
host: localhost:8080
HTTP/1.1 200 OK
Server: Apache-Coyote/1.1
Allow: GET, HEAD, POST, PUT, DELETE, OPTIONS
Content-Length: 0
Date: Sat, 29 Oct 2016 23:59:08 GMT
HEAD- 向服务器索要与GET请求相一致的响应,只不过响应体将不会被返回。这一方法可以在不必传输整个响应内容的情况下,就可以获取包含在响应消息头中的元信息。该方法常用于测试超链接的有效性,是否可以访问,以及最近是否更新。栗子:
m@sys:$ telnet localhost 8080
Trying 127.0.0.1...
Connected to localhost.
Escape character is '^]'.
HEAD / HTTP/1.1
host: localhost:8080
HTTP/1.1 200 OK
Server: Apache-Coyote/1.1
Content-Type: text/html;charset=ISO-8859-1
Transfer-Encoding: chunked
Date: Sun, 30 Oct 2016 14:52:37 GMT
TRACE- 回显服务器收到的请求,主要用于测试或诊断。这个用的也很少,略。
然后是常见的请求头的属性:
Host:客户端IP和端口,发送请求时,该报头域是必需的。我试过不加这个,真不行。。浏览器发送的请求消息中,就会包含Host请求报头域,如下:
Host:www.guet.edu.cn
此处使用缺省端口号80,若指定了端口号,则变成:Host:www.guet.edu.cn:指定端口号
User-Agent:浏览器信息
Content-Length:表示请求消息正文的长度。
Cookie:最重要的请求头之一, 将cookie的值发送给HTTP服务器。
Accept:浏览器能接收的数据类型
Accept-encoding:浏览器能够进行解码的数据编码方式,比如gzip。
Accept-charset:浏览器可接受的字符集。
Accept-Language:浏览器所希望的语言种类,当服务器能够提供一种以上的语言版本时要用到。
Authorization:请求报头域主要用于证明客户端有权查看某个资源。当浏览器访问一个页面时,如果收到服务器的响应代码为401(未授权),可以发送一个包含Authorization请求报头域的请求,要求服务器对其进行验证。
Pragma:指定“no-cache”值表示服务器必须返回一个刷新后的文档,即使它是代理服务器而且已经有了页面的本地拷贝;在HTTP/1.1版本中,它和Cache-Control:no-cache作用一模一样。Pargma只有一个用法, 例如: Pragma: no-cache