超文本传输协议(HTTP,HyperText Transfer Protocol)是构建于TCP/IP协议之上的无连接无状态的协议,默认端口号是80
HTTP请求报文
http请求报文分为三部分,请求头,请求行与请求体
请求头又包括:请求方法,请求地址和版本
请求方法(Method)常见的有:GET POST DELETE PUT
URL由以下几个部分组成:
其中Path中?后面的部分称为Query String(查询参数),Path即为请求地址
Header可用于传递一些附加信息,以key: value形式表达,常见请求Header:
Content-Type 请求体的类型,例如:text/plain application/json
Accept 说明接收的类型,可以多个值,用,(半角逗号)分开
Content-Length 请求体的长度,单位字节
Content-Encoding 请求体的编码格式,如gzip,deflate
Accept-Encoding 告知对方我方接受的Content-Encoding
ETag 给当前资源的标识,和Last-Modified、If-None-Match、If-Modified-Since配合,用于缓存控制
Cache-Control 取值为一般为no-cache或max-age=XX,XX为个整数,表示该资源缓存有效期(秒)
Authorization 用于设置身份认证信息
User-Agent 用户标识,如:OS和浏览器的类型和版本
If-Modified-Since 值为上一次服务器返回的 Last-Modified 值,用于确认某个资源是否被更改过,没有更改过(304)就从缓存中读取
If-None-Match 值为上一次服务器返回的 ETag 值,一般会和If-Modified-Since一起出现
Cookie 已有的Cookie
Referer 表示请求引用自哪个地址,比如你从页面A跳转到页面B时,值为页面A的地址
Host 请求的主机和端口号
HTTP响应报文
相对应的,http响应报文也分为三部分:响应状态行,响应头和响应体
其中响应状态行由协议版本、状态码和响应信息组成。
常见的状态码有如下几种:
200 OK 客户端请求成功
301 Moved Permanently 请求永久重定向
302 Moved Temporarily 请求临时重定向
304 Not Modified 文件未修改,可以直接使用缓存的文件。
400 Bad Request 由于客户端请求有语法错误,不能被服务器所理解。
401 Unauthorized 请求未经授权。这个状态代码必须和WWW-Authenticate报头域一起使用
403 Forbidden 服务器收到请求,但是拒绝提供服务。服务器通常会在响应正文中给出不提供服务的原因
404 Not Found 请求的资源不存在,例如,输入了错误的URL
500 Internal Server Error 服务器发生不可预期的错误,导致无法完成客户端的请求。
503 Service Unavailable 服务器当前不能够处理客户端的请求,在一段时间之后,服务器可能会恢复正常。
同请求报文一样,响应头也可以用于传递一些附加信息,常见的响应Header有:
Content-Type 响应体的类型,例如:text/plain application/json
Accept 说明接收的类型,可以多个值,用,(半角逗号)分开
Content-Length 响应体的长度,单位字节
Content-Encoding 响应体的编码格式,如gzip,deflate
Accept-Encoding 告知对方我方接受的Content-Encoding
ETag 给当前资源的标识,和Last-Modified、If-None-Match、If-Modified-Since配合,用于缓存控制
Cache-Control 取值为一般为no-cache或max-age=XX,XX为个整数,表示该资源缓存有效期(秒)
Date 服务器的日期
Last-Modified 该资源最后被修改时间
Transfer-Encoding 取值为一般为chunked,出现在Content-Length不能确定的情况下,表示服务器不知道响应版体的数据大小,一般同时还会出现Content-Encoding响应头
Set-Cookie 设置Cookie
Location 重定向到另一个URL,如输入浏览器就输入baidu.com回车,会自动跳到 https://www.baidu.com ,就是通过这个响应头控制的
Server 后台服务器
常见请求报文
根据应用场景的不同,HTTP请求的请求报文有三种不同的形式:
(1)使用URL中的Query String(常见于GET方法)表示参数
根据HTTP规范,GET用于信息获取,而且应该是安全的和幂等的。
所谓安全的意味着该操作用于获取信息而非修改信息,而幂等的意味着对同一URL的多个请求应该返回同样的结果。
因此,不推荐在GET请求中在body部分添加数据,应该采用在url中附加query string的方法。
query string中多个键值对之间用&连接,键与值之前用=连接,且只能用ASCII字符,非ASCII字符需使用UrlEncode编码。
注意,GET 可提交的数据量受到URL长度的限制,HTTP 协议规范没有对 URL 长度进行限制。这个限制是特定的浏览器及服务器对它的限制。
(2)Post数据(如Post JSON)
POST提交的数据必须在 body 部分中,但是协议中没有规定数据使用哪种编码方式或者数据格式。
开发者完全可以自己决定消息主体的格式,只要最后发送的 HTTP 请求满足上面的格式就可以。
但是,数据发送出去,还要服务端解析成功才有意义。一般服务端语言如 php、python 等,以及它们的 framework,都内置了自动解析常见数据格式的功能。
服务端通常是根据请求头(headers)中的 Content-Type 字段来获知请求中的消息主体是用何种方式编码,再对主体进行解析。
所以说到 POST 提交数据方案,包含了 Content-Type 和消息主体编码方式两部分。
需要配合特定的Content-Type请求头 Content-Type: application/x-www-form-urlencoded
这是最常见的 POST 数据提交方式。浏览器的原生 <form> 表单,如果不设置 enctype 属性,那么最终就会以 application/x-www-form-urlencoded 方式提交数据。
(3)通过Post方法上传文件
我们使用表单上传文件时,必须让 <form> 表单的 enctype 等于 multipart/form-data。
需要配合特定的Content-Type请求头 Content-Type: multipart/form-data; boundary=xxxxx
在这种情况下每个字段/文件都被boundary(Content-Type中指定)分成单独的段,每段以-- 加 boundary开头,然后是该段的描述头,描述头之后空一行接内容,请求结束的标制为boundary后面加--
例如:
POST http://www.example.com HTTP/1.1
Content-Type:multipart/form-data; boundary=----WebKitFormBoundaryrGKCBY7qhFd3TrwA
------WebKitFormBoundaryrGKCBY7qhFd3TrwA
Content-Disposition: form-data; name="text"
title
------WebKitFormBoundaryrGKCBY7qhFd3TrwA
Content-Disposition: form-data; name="file"; filename="chrome.png"
Content-Type: image/png
PNG ... content of chrome.png ...
------WebKitFormBoundaryrGKCBY7qhFd3TrwA--
以上报文传递了一个text字段和一个图片文件,生成了一个 boundary 用于分割不同的字段,为了避免与正文内容重复,boundary 很长很复杂。
然后 Content-Type 里指明了数据是以 multipart/form-data 来编码,本次请求的 boundary 是什么内容。
body里按照字段个数又分为多个结构类似的部分,每部分都是以 --boundary 开始,紧接着是内容描述信息,然后是回车,最后是字段具体内容(文本或二进制)。
如果传输的是文件,还要包含文件名和文件类型信息。消息主体最后以 --boundary-- 标示结束。
其他
条件GET - 利用缓存减少带宽开销
客户端向服务器发送一个包询问是否在上一次访问网站的时间后是否更改了页面,如果服务器没有更新,显然不需要把整个网页传给客户端,客户端只要使用本地缓存即可,如果服务器对照客户端给出的时间已经更新了客户端请求的网页,则发送这个更新了的网页给用户。
例如:客户端
GET / HTTP/1.1
Host: www.sina.com.cn:80
If-Modified-Since:Thu, 18 Jan 2018 20:39:13 GMT
Connection: Close
第一次请求时,服务器端返回请求数据,之后的请求,服务器根据请求中的 If-Modified-Since 字段判断响应文件没有更新,如果没有更新,服务器返回一个 304 Not Modified响应,告诉浏览器请求的资源在浏览器上没有更新,可以使用已缓存的上次获取的文件。例如 服务端返回:
HTTP/1.0 304 Not Modified
Date: Thu, 04 Feb 2010 12:38:41 GMT
Content-Type: text/html
Expires: Thu, 04 Feb 2010 12:39:41 GMT
Last-Modified: Thu, 04 Feb 2010 12:29:04 GMT
Age: 28
X-Cache: HIT from sy32-21.sina.com.cn
Connection: close
如果服务器端资源已经更新的话,就返回正常的响应。
持久连接:避免重复创建TCP连接
HTTP 协议采用“请求-应答”模式,当使用普通模式,即非 Keep-Alive 模式时,每个请求/应答客户和服务器都要新建一个连接,完成之后立即断开连接(HTTP协议为无连接的协议);当使用 Keep-Alive 模式(又称持久连接、连接重用)时,Keep-Alive 功能使客户端到服务器端的连接持续有效,当出现对服务器的后继请求时,Keep-Alive 功能避免了建立或者重新建立连接。
在 HTTP 1.0 版本中,并没有官方的标准来规定 Keep-Alive 如何工作,因此实际上它是被附加到 HTTP 1.0协议上,如果客户端浏览器支持 Keep-Alive ,那么就在HTTP请求头中添加一个字段 Connection: Keep-Alive,当服务器收到附带有 Connection: Keep-Alive 的请求时,它也会在响应头中添加一个同样的字段来使用 Keep-Alive 。这样一来,客户端和服务器之间的HTTP连接就会被保持,不会断开(超过 Keep-Alive 规定的时间,意外断电等情况除外),当客户端发送另外一个请求时,就使用这条已经建立的连接。
在 HTTP 1.1 版本中,默认情况下所有连接都被保持,如果加入 "Connection: close" 才关闭。目前大部分浏览器都使用 HTTP 1.1 协议,也就是说默认都会发起 Keep-Alive 的连接请求了,所以是否能完成一个完整的 Keep-Alive 连接就看服务器设置情况。
无状态协议的会话跟踪
浏览器与服务器之间的通信是通过HTTP协议进行通信的,而HTTP协议是”无状态”的协议,它不能保存客户的信息,即一次响应完成之后连接就断开了,下一次的请求需要重新连接,这样就需要判断是否是同一个用户,所以才有会话跟踪技术来实现这种要求。
Cookie
Cookie是Web服务器发送给客户端的一小段信息,客户端请求时可以读取该信息发送到服务器端,进而进行用户的识别。对于客户端的每次请求,服务器都会将Cookie发送到客户端,在客户端可以进行保存,以便下次使用。
客户端可以采用两种方式来保存这个Cookie对象,一种方式是保存在客户端内存中,称为临时Cookie,浏览器关闭后这个Cookie对象将消失。另外一种方式是保存在客户机的磁盘上,称为永久Cookie。以后客户端只要访问该网站,就会将这个Cookie再次发送到服务器上,前提是这个Cookie在有效期内,这样就实现了对客户的跟踪。需要注意的是,Cookie是可以被禁止的。
Session
每一个用户都有一个不同的session,各个用户之间是不能共享的,是每个用户所独享的,在session中可以存放信息。
在服务器端会创建一个session对象,产生一个sessionID来标识这个session对象,然后将这个sessionID放入到Cookie中发送到客户端,下一次访问时,sessionID会发送到服务器,在服务器端进行识别不同的用户。
Session的实现依赖于Cookie,如果Cookie被禁用,那么session也将失效。由于只需要传递Id,Session实际上把存储任务交给了服务器端。
伪造请求攻击
由于GET方法的请求头的所有参数都暴露在URL中,因此可以伪造请求,冒充用户在站内的正常操作。
例如一个发帖请求通过GET方法调用:
http://www.suiseiseki.cn/create_post.php?title=标题&content=内容
那么在论坛中发一帖,包含一个超链接:
http://www.suiseiseki.cn/create_post.php?title=我是傻逼&content=谁进来谁傻逼
只要有用户点击了这个链接,那么他们的帐户就会在不知情的情况下发布了这一帖子。可能这只是个恶作剧,但是,如果涉及到危险操作,这个故事就不那么好笑了。那么一般应该如何防止伪造请求呢?
(1)关键操作只接受POST请求
POST请求的数据都在body里,看起来安全了一点,不过还是有很大风险。
(2)验证码
关键操作都需要验证码,防止伪装的操作。而且验证码的水平也不要太次....
(3)Token
目前主流的做法是使用 Token 抵御 伪造请求 攻击。
攻击要成功的条件在于攻击者能够预测所有的参数从而构造出合法的请求。所以根据不可预测性原则,我们可以对参数进行加密从而防止伪造请求攻击。
另一个更通用的做法是保持原有参数不变,另外添加一个参数Token,其值是随机的。这样攻击者因为不知道Token而无法构造出合法的请求进行攻击。
以上就是我对HTTP协议的简单总结,在未来,我们将在实践中不断接触它们。