目录
3.2 Content-length和Content-Type
一、HTTP协议格式
HTTP协议是一种应用非常广泛的应用层协议,当我们在浏览器中输入一个URL(“网址”)时,浏览器就会给客户端发送一个HTTP请求,服务器收到请求之后,就会返回一个HTTP响应。
为了能够看到HTTP请求和响应的详细内容,我们需要使用抓包工具,本文以Fiddler为例。
1、抓包工具的原理
Fiddler抓包工具相当于一个“代理程序”:客户端向服务器发送的HTTP请求时,客户端会先把请求交给Fiddler,Fiddler再把请求转交给服务器;当服务器返回HTTP响应时,会先把响应交给Fiddler,Fiddler再把响应交给客户端。
因此,Fiddler就会很清楚客户端和服务器之间交互的数据细节。
简单理解就是,Fiddler相当于一个给客户端跑腿儿的小弟~
代理分为两种:
正向代理:给客户端提供服务的代理程序,此时正向代理就相当于把真实的客户端隐藏起来了,服务器不知道真实的客户端是谁。
反向代理:给服务器提供服务的代理程序,此时反向代理就相当于把真实的服务器隐藏起来了,客户端不知道真实的服务器是谁。
2、抓包工具的使用
启动Fiddler应用程序后,它就会自动抓取当前主机上每个进程产生的HTTP请求/响应:
>> 左侧窗口显示了所有的HTTP请求/响应,可以选中其中某个请求进行查看;
>> 右侧上方显示了HTTP请求的报文内容,切换到Raw标签页可以看到详细的数据格式;
>> 右侧下方显示了HTTP响应的报文内容,切换到Raw标签页可以看到详细的数据格式;
>> 请求和响应的详细数据,可以点击右下角的 View in notepad 按钮,通过记事本打开。
注意:可以使用ctrl+a全选左侧的抓包结果,再按delete键可以清楚所有被选中的结果。
3、HTTP请求的格式
下图是一个HTTP请求的抓包结果:
★ 一个HTPP请求由四个部分构成:
(1) 首行:即HTTP请求的第一行内容,它包括了:
① HTTP的方法:描述了这次请求要干什么;
② URL:描述了网络上的一个唯一资源;
③ 版本号:描述了当前这个HTTP请求是哪个版本的。
(2) 请求头header:是一个按行组织的键值对,即每一行是一个键值对,键和值之间用: 分割;
(3) 空行:空行表示header的结束标记;
(4) 正文body:正文用来承载一些具体的数据,正文允许为空字符串。
4、HTTP响应的格式
下图是一个HTTP响应的抓包结果:
★ 一个HTPP响应由四个部分构成:
(1) 首行:HTTP响应的第一行内容,它包括:
① 版本号:描述了当前这个HTTP请求是哪个版本的;
② 状态码:描述当前这次请求是否成功;
③ 状态码描述:对状态码的解释说明。
(2) 响应头header:同样也是按行组织的键值对结构,每一行是一个键值对,键和值之间用: 分割;
(3) 空行:空行表示header的结束标记;
(4) 正文body:正文用来承载一些具体的数据,可能是css,html,图片等多种格式;正文允许为空字符串。
二、HTTP请求详解
1、URL
我们平时所说的“网址”其实就是URL(Uniform Resource Locator),唯一资源定位符,用来定位互联网中的一个唯一的资源。
1.1 URL基本格式
一个具体的URL:
信息 | 解释 | 能否省略 |
http:// | 协议方案名,常见的有:http、https,也有其他类型的,比如访问mysql时用的jdbc:mysql。 | 可以省略。省略后默认为http:// |
user:pass | 登录信息,用于登录网站进行身份认证;现在的网站一般不再通过URL进行身份认证。 | 一般都会省略。 |
www.example.jp | 表示服务器的IP地址,此处用域名表示,域名会通过DNS系统解析成一个具体的IP地址。 | 在HTML中可以省略,省略后表示服务器的IP/域名和当前HTML所属的IP/域名一致。 |
80 | 表示服务器的端口号。 | 可以省略,如果是http协议,省略后自动设为80;如果是https协议,省略后自动设为443。 |
dir/index.html | 通过上面的服务器地址和端口号,就能够确定要访问的服务器是互联网上的哪个主机的哪个进程,而进程中可能有很多资源,通过带层次的文件路径就可以访问到进程中的一个具体的资源。 服务器提供的资源可能是硬盘上的一个真实存在的文件(访问静态资源),也可能是服务器根据http请求在内存中构造出的一个html片段(访问动态资源)。 | 可以省略,省略后相当于/,有些服务器会在发现/路径的时候自动访问/index.html。 |
uid=1 | 查询字符串(query string),本质是一个键值对结构,键值对之间使用&分隔,键和值之间使用=分隔,相当于客户端给服务器传递的一些必要的参数,键值对的内容和数量由程序猿自己约定。 | 可以省略。 |
ch1 | 片段标识符一般用于小说、文档等类型的网站进行页面内跳转,通过不同的片段标识符跳转到不同的章节。 | 可以省略。 |
1.2 URL encode
像 / ? @ = 等这样的字符,在URL中具有特定的含义,因此这些字符不能随意出现在URL中,如果要出现,就必须对这些字符进行urlencode。
一个中文字符由UTF-8或者GBK的编码方式构成,虽然在URL中没有特殊含义,但是也需要进行转义,否则浏览器可能会把UTF-8/GBK编码中的某个字节当作URL中的特殊符号。
当我们在浏览器中搜索C++时:
可以发现,+被转义成了%2B~
转义规则:
将待转义的字符转为16进制表示,然后在每个字节前面再加上%,转义后的格式为%XY。
urldecode就是urlencode的逆过程,把转义后的编码再还原为原来的字符。
urlencode工具:https://tool.chinaz.com/Tools/urlencode.aspx
2、方法
方法 | 说明 | 支持的HTTP协议版本 |
GET | 获取资源 | 1.0、1.1 |
POST | 传输实体主体 | 1.0、1.1 |
PUT | 传输文件 | 1.0、1.1 |
HEAD | 类似于GET方法,但只返回响应头header | 1.0、1.1 |
DELETE | 删除服务器指定资源 | 1.0、1.1 |
OPTIONS | 询问服务器支持的请求方法 | 1.1 |
TRACE | 回显服务器端收到的请求,测试的时候用到 | 1.1 |
CONNECT | 要求用隧道协议连接代理 | 1.1 |
LINK | 建立和资源之间的联系 | 1.0 |
UNLINE | 断开连接关系 | 1.0 |
HTTP请求中虽然有很多方法,但是最常用的还是GET方法,然后是POST方法,其他的方法都不太常用。
2.1 GET方法
GET方法是最常用的HTTP方法,常用于用户获取服务器上的某个资源。
★ 构造GET请求的方式:
(1) 在浏览器中直接输入一个URL,浏览器就会触发GET请求,因为浏览器要加载出一个页面,往往要经过多重HTTP请求的交互。
例如,当我们访问搜狗主页时,会产生许多和sogou域名相关的请求:
(2) html里的link、a、img、script等标签也会触发GET请求,因为它们都会引用一个外部资源。
(3) html里的form表单也可以构造出GET请求;
(4) 使用JavaScript里的ajax也能构造GET请求。
★ GET请求的特点:
(1) 首行第一部分为GET;
(2) URL的query string可以为空,也可以不为空,如果需要给服务器传递一些参数,就是通过query string传递的;
(3) header部分有若干个键值对结构;
(4) body部分通常为空。
★ 关于GET请求的URL长度问题
网上有些资料描述,GET请求的长度最多为1kb、2kb……这样的说法是错误的。
HTTP协议由RFC 2616标准定义的,标准中并没有对URL的长度做出任何限制。
标准虽然没有做出限制,但是浏览器和服务器在实现的时候,可能会对URL的长度做出限制,具体的长度取决于浏览器的实现和HTTP服务器的实现。
2.2 POST方法
POST方法是一种较常见的HTTP方法,常用于用户给服务器提供数据(例如登录页面)。
★ 构造POST请求的方式:
(1) html里的form表单构造POST请求;
(2) JavaScript里的ajax构造POST请求。
★ POST请求的特点:
(1) 首行的第一部分为POST;
(2) URL中通常是没有query string的(也可以有);
(3) header部分有若干个键值对结构;
(4) body部分一般不为空,body的数据格式有很多种,json、html、css……
POST在给服务器传递信息的时候,通常就会把信息放到body中。
2.3 比较GET和POST方法
GET和POST没有本质区别,使用GET实现的场景,基本都可以使用POST代替;使用POST实现的场景,也可以使用GET代替。
★ GET和POST细节上的区别
区别 | GET | POST |
语义 | 从服务器获取数据 | 向服务器提交数据 |
使用习惯 | 给服务器传递的数据,GET请求通常是 放在URL的query sring中 | POST请求通常是把给服务器传递的 数据放在body中 |
幂等 | GET请求建议实现成幂等的 | POST请求则不要求实现成幂等的 |
缓存 | 在幂等的基础上,GET的请求结果可以被缓存 | POST则一般不会缓存 |
幂等:如果多次请求得到的结果一样,就视为请求是幂等的。
★ 关于安全性问题
网上有些资料说,POST比GET更安全,依据是:在GET请求中,用户名和密码会以query string的形式放在URL中,就会直接显示在浏览器的地址栏;而POST则是把参数放在body中,不会直接显示在浏览器地址栏,所以更安全。
这种说法也是错误的,即使POST把参数放在body中,也是可以通过抓包工具看到的!是否安全取决于前端在传输密码等敏感信息时是否进行加密,和GET、POST无关。
3、请求头header
请求头header中有很多键值对,这些键值对大部分都是由RFC 2616标准定义的(也可以由程序猿自定制),有特定的含义,本文介绍几个常见的:
3.1 Host
Host表示服务器的IP和端口号。
其中,端口号可以省略,省略后为默认值(http的默认值80,https的默认值443) 。
虽然URL中已经有了服务器的IP和端口号,但是如果浏览器经过代理程序访问服务器时,URL中的IP和端口号可能和Host中的不同(但Fiddler中无法体现):URL中可能存放的是代理程序的服务器IP和端口,而Host中存放的是目标服务器IP和端口。
浏览器通过URL中的IP端口找到代理程序服务器,代理程序通过Host中的IP端口找到目标服务器。
3.2 Content-length和Content-Type
Content-length: 表示body中的数据长度。
Content-Type:表示body中的数据格式。
这两个字段是伴生的,要么两个都有,要么两个都没有:
如果请求中没有body,那么header中就没有这两个字段,比如GET请求;
如果请求中有body,那么header中就有这两个字段,比如POST请求;
★ Content-length的作用:
HTTP协议在传输层是基于TCP协议(3.0版本之前是,3.0版本是基于UDP协议),而TCP协议是面向字节流的,存在粘包问题,解决粘包问题的两种方式:
(1) 约定一个分割符,在HTTP协议中的体现是空行。
(2) 约定报文的长度,即通过Content-length表示body长度。
★ Content-Type的常见选项:
(1) application/x-www-form-urlencoded,这是form表单提交的数据格式,此时body的格式形如:
title=test&content=hello
此种格式和URL中的query string的格式相同。
(2) multipart/form-data,这种格式主要是在上传文件的时候出现。
(3) application/json,数据为json格式,此时body的格式形如:
{"username":"173xxxxxxx","password":"u06Deq8nkTa8FLP6B9809w==","uuid":"153431c6e1464f338f00b7eaa24ea122","status":0}
json格式是最常用的~
关于Content-Type的详细情况:MIME types (IANA media types) - HTTP | MDN (mozilla.org)
3.3 User-Agent
User-Agent用来表示浏览器和操作系统的属性。
★ User-Agent的作用:
UA的主要作用就是描述了用户的操作系统信息和浏览器信息,即用户在使用什么设备上网。
不同的操作系统/浏览器/上网设备,对于某些页面的支持程度不同,有些浏览器只支持文字,有些浏览器能够支持文字、图片……通过UA来收集到浏览器和操作系统的信息,进一步就能够知道这个浏览器/操作系统支持什么样的页面了。
3.4 Referer
Referer表示这个页面是从哪个页面跳转过来的。
在搜狗主页搜索一个内容时,此时的Referer就是搜狗主页:
如果直接在浏览器中输入一个URL,或者通过收藏夹访问一个页面时是没有Referer的。
由于请求中的referer在传输过程中,可能会被修改(运营商劫持),为了解决这个问题,就需要加密,使用HTTPS。
3.5 Cookie
Cookie是浏览器在本地存储数据的一种机制。
浏览器为了安全起见,一般都会禁止网页的JS访问电脑的硬盘(文件系统),为了在本地存储一些数据,所以浏览器给网页提供了一些特殊的API,Cookie就是最经典的一种。
Cookie是按照域名维度来维护的,不同的域名下存放不同的Cookie,一个网站发起的http请求可能是来自多个域名:
其中,每个Cookie是一个键值对:
Cookie中的键值对和URL中的query string一样,都是由程序猿自定制的。
Cookie中的键值对一般都是些简单的字符串,只能存一些简单的信息,比如:上次访问页面的时间、当前访问网页的次数、当前访问页面的身份信息……如果想让Cookie存个图片、视频等内容,Cookie是做不到的。
★ Cookie从哪里来?
Cookie来源于服务器,在服务器返回的响应报文中,会包含若干个Set-Cookie这样的字段,浏览器收到这样的响应报文后,就会把这样的数据保存在浏览器本地。
打开CSDN主页时返回的HTTP响应报文:
★ Cookie到哪里去?
浏览器在本地保存了一个网站的Cookie后,下次访问再同一个网站时,就会把保存的Cookie通过http请求的header发给服务器。
因为一个服务器要服务的客户端有很多,Cookie就相当于一个身份标识,来区分不同的客户端。
★ Cookie的典型应用场景
Cookie最常用的场景就是在客户端维持登录状态。
例如:我们登录CSDN后,在CSDN中进行写文章、查看文章、查看个人信息等操作时,都会维持登录状态,而不是说点开一个新的页面后,还需要重新登录。
4、正文body
有些请求有body,比如POST,有些请求没有body,比如GET。
body中内容的格式就是header的Content-Type中指定的格式。
三、HTTP响应详解
1、状态码
状态码表示访问一个页面的结果,访问成功还是访问失败,或者遇到其他情况……
以下是一些常见的状态:
1.1 200 OK
表示访问成功,这是一个很常见的状态码~
抓包工具抓到的很多包的状态码都是200~
1.2 404 Not Found
表示没有找到要访问的资源。
比如我们在浏览器中输入一个错误的URL:www.sogou.com/test.html,此时就会出现404状态码:
URL表示唯一资源定位符,当我们通过www.sogou.com定位到搜狗的服务器IP和端口号后,再去通过带层次的文件路径访问test.html时,就会发现并没有这样的文件资源,于是就会返回404 Not Found。
1.3 403 Forbidden
表示服务器拒绝访问。有的页面通常需要用户具有一定的权限才能访问,在没有权限的情况去访问时,访问就会被拒绝。
比如在未登录的情况下,访问码云的私有仓库,就会出现这样的状态码:
1.4 405 Method Not Allowed
HTTP中所支持的方法有GET、POST等,但是我们要访问的服务器不一定支持所有的方法,发送服务器不支持的请求时就会出现这样的状态码。
1.5 500 Internal Server Error
服务器内部出现错误,一般是服务器的代码在执行过程中遇到了一些特殊情况,比如服务器异常崩溃时,就会产生这个状态码。
1.6 504 Getaway Timeout
当服务器负载比较大的时候,服务器处理单条请求的时候所消耗的时间会很长,就可能会出现超时的情况,从而产生这个状态码。
比如“双十一”秒杀这样的场景下就可能会出现。
1.7 302 Move temporarily
表示临时重定向。访问一个页面时自动跳转到另一个页面就会出现这样的状态码。
一般登录页面会经常见到302,用于实现登录成功后自动跳转到主页。
响应报文的header部分会包含一个Location字段,表示要跳转到哪个页面。
1.8 301 Moved Permanently
永久重定向。当浏览器收到这种响应时,后续的请求都会自动跳转到新的地址。
301也是通过响应报文的header部分中的Location字段来表示要重定向的地址。
1.9 状态码小结
状态码 | 类别 | 描述 |
1XX | Informational(信息性状态码) | 正在处理接收的请求 |
2XX | Success(成功状态码) | 请求正常处理完毕 |
3XX | Redirection(重定向状态码) | 需要进行附加操作以完成请求 |
4XX | Client Error(客户端错误状态码) | 服务器无法处理请求 |
5XX | Server Error(服务器错误状态码) | 服务器处理请求出错 |
2、响应报头header
响应报头的格式和请求报头的格式基本一致。
但响应报头中的Content-Type的取值和请求报头中的有些差异。
响应报头中Content-Type常见的取值:
(1) text/html,表示body数据格式是HTML;
(2) text/css,表示body数据格式是CSS;
(3) application/javascript,表示body数据格式JavaScript;
(4) application/json,表示body数据格式是JSON。
关于Content-Type的详细情况:MIME types (IANA media types) - HTTP | MDN (mozilla.org)
3、响应正文body
正文的具体格式取决于响应头中的Content-Type。
四、如何构造HTTP请求
除了通过浏览器构造HTTP请求外,还可以通过以下几种方式构造:
1、form表单
form表单是HTML中的一个常用标签,用于构造GET或者POST请求。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>form</title>
</head>
<body>
<form action="https://www.sogou.com" method="get">
<input type="text" name="test1">
<input type="text" name="test2">
<input type="submit" value="提交">
</form>
</body>
</html>
form标签中的重要属性:
(1) action:表示构造的HTTP 请求的URL是什么;
(2) method:表示构造的HTTP的请求是什么?GET还是POST。
input标签中的重要属性:
(1) name属性表示构造出的HTTP请求的query string中的key值,用户在输入框中输入的内容就是对应的value值;
(2) 如果HTTP请求的方法是GET,上述键值对就会放在query string中;
如果HTTP请求的方法是POST,上述键值对就会放在body中,此时Content-Type就是application/x-www-form-urlencoded格式。
此时的页面效果:
点击提交后就会构造出HTTP请求并发送:
2、ajax
form表单只能构造GET、POST方法的请求,而ajax可以构造所有的HTTP方法,并且ajax默认发起的请求是不会引起跳转(可以手动控制),而form构造的http请求一定会触发页面跳转的。
使用ajax不触发跳转,就可以达到“局部刷新”的效果:如果个页面a和另一个页面b的很多内容都一样,当从a跳转到b时,只需要刷新它们不一样的地方就行了~
★ajax全称:Asynchronous JavaScript and XML。
其中Asynchronous表示异步,表示发起请求的主体,不负责接收结果,而是由别人推送过来。
相对应的同步表示:谁发起的请求,谁负责接收结果。网络通信/IO操作中的同步就是这个意思。
而多线程中的synchronized,这里的同步指的是多个线程之间互斥。
由于现在XML用的越来越少,现在使用ajax一般都是用来传输其他数据:比如JSON。
★ 通过ajax构造一个HTTP请求:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<script src="https://apps.bdimg.com/libs/jquery/2.1.4/jquery.min.js"></script>
<script>
$.ajax({
//type表示HTTP请求的方法
type:'get',
url:'https://www.sogou.com',
//data:'请求中的body',
success:function(body){
//服务器返回正确的响应时(比如200 OK)
//浏览器就会调用success对应的这个函数来处理响应
//参数body就是响应的正文
console.log('ok');
},
error:function(){
//服务器返回错误的响应时(比如404 Not Found)
//就会由浏览器调用error对应的这个函数
console.log('error');
}
});
</script>
</body>
</html>
此处我们使用第三方库jquery封装好的ajax API,所以需要先引入一个jquery的网络地址。
$:是jquery中特殊的一个全局对象,通过$就可以调用jquery里的一些方法。
如果此时我们直接运行代码,就会发现代码报错了:
这是一个典型的报错信息——跨域:一个页面在域名a之下,尝试通过ajax访问域名b里的资源。
浏览器默认是禁止跨域操作的,除非b网站返回的响应明确告诉浏览器允许跨域访问,但是大部分的网站、服务器都是不允许跨域的。
要解决这个问题,需要我们自己写个服务器,通过ajax访问自己服务器上的资源。
3、socket
如果一个编程语言,可以进行socket编程,那么就一定可以构造HTTP请求(往一个TCP Socket里写一个符合http协议格式的字符串)。
还可以通过一些第三方工具来构造HTTP请求,比如postman。
发送请求后收到的响应:
postman构造的请求,会自动生成相应的多种语言代码: