首页 > Coding > 网络协议详解之 HTTP 协议

网络协议详解之 HTTP 协议

2016年11月20日 发表评论 阅读评论

概述

HTTP(HyperText Transfer Protocal)超文本传输协议, 是一个基于请求与响应模式的、无状态的应用层协议。它是 WEB 上应用最广泛的协议。它一般基于 TCP 的连接方式。其主要特点有:

  • 支持 C/S 通信模式
  • 简单快速。HTTP 协议简单,使得 HTTP 服务器的程序规模小,因而通信快。
  • 灵活。HTTP 协议允许客户端和服务端传输任意类型任意格式的数据。不同的类型由 Content-Tyoe 标记
  • 面向无连接。无连接是指每次建立的连接只处理一个文请求。
  • 无状态。无状态是指协议对于事务处理没有记忆能力。如果后续处理需要前面的信息,则它必须重传。这样可有导致每次连接传送的数据量增大。

HTTP URL

URL(Uniform Resource Locator) 统一资源定位符,它包含了查找某个资源的信息。其格式如下:

http 指定协议的名称,表示要通过 HTTP 协议来定位网络资源。 host 是一个合法的网络域名 或 IP 地址。 port 指定使用的网络端口,缺省值为 80 。 abs_path 指定请求的资源的 URI .如果 URL 中没有给出 URI, 则必须以 "/" 符号结束(这个工作通常由浏览器完成)。

连接

浏览器与服务器联系的最常用方法是与服务器的 80 端口建立 TCP 连接。使用 TCP 的意义在于,浏览器和服务器都不需要担心如何处理长消息、可靠性与拥塞控制,这些事将由 TCP 来处理。 在早期的 HTTP1.0 中,连接建立起来后会在一个请求和一个响应后立即释放。因为那时的 HTML 很简单,基本只有文本,使用这种模式就够了。但是随着时代的发展,HTML里包含了太多的东西,使用单独的 TCP 来传递每个资源代价太大。于是 HTTP1.1诞生了,它支持持续连接(persistent connection),它可以在一个 TCP 连接上进行多次请求响应,还可以发送流水线请求。这种做法减少建立多个 TCP 连接所用的时间,减少服务器的空闲时间,提高了性能。

HTTP 协议的请求

每个 HTTP Request (请求) 由一行或多行 ASCII 文本组成。其中第一行的第一个词为请求方法的名称,然后是请求资源的 URI,再后是协议的版本。这几个部分使用空格分开:

Method 为请求方法,必须为大写。 Request-URI 是一个统一资源标识符。  HTTP-Version 表示请求的 HTTP 协议版本。 CRLF 表示换行符。 例如:

常用的请求方法如下:

名称 解释
GET 请求 Request-URI 所标识的资源
POST 在Request-URI所标识的资源后附加数据
HEAD 仅获取所标识的资源的响应消息报头
PUT 请求服务器存储一个资源,并用 Request-URL 作为其标识
DELETE 请求服务删除 Request-URI 所标识的资源
TRACE 请求服务器回传收到的请求。一般用于调试或诊断
CONNECT 保留使用
OPTIONS 请求查询服务器的性能或查询与资源相关的选项或需求

Request 从第二行开始为请求消息头,每个消息头以 CRLF 结尾。消息头在后面会单独讲。下面是一个 Request 的实例:

关于 Request 的更多内容请参考这里

HTTP 协议的响应

服务器在接收和解释请求的消息后,返回一个 HTTP Response(响应) 消息。每个 HTTP 响应由一个状态行、0到多个消息头,以及消息体组成。状态行与每个消息头均以 CRLF 结束,消息体与消息头之间以一个空行(CRLF)隔开。 状态行中包括一个 3 位数的状态码,标示请求是否被满足。状态行格式如下:

其中, HTTP-Version 表示服务器 HTTP 协议的版本.  Status-Code 表示服务器发回的响应状态代码. Reason-Phrase 表示状态码的文本描述

状态码由三位数字组成,其中第一位表示类别 。

类别 意义
1xx 信息。请求已接收,继续处理
2xx 成功。请求已被成功接收、理解、处理
3xx 重定向。要完成请求需要有进一步的操作
4xx 客户端错误。请求有语法错误或无法处理
5xx 服务端错误。服务器未能实现请求

常见的状态码:

  • 200 OK 请求成功
  • 302 重定向
  • 400 BadRequest 客户端请求语法错误
  • 401 Unauthorized 未授权
  • 403 Forbidden 服务器拒绝服务
  • 404 NotFound 错误的URL,资源不存在
  • 500 InternalServerError 服务器发生不可预期的错误
  • 503 Server Unavailable 服务器当前不能处理请求,请稍后重试

消息头(报头)将在下一节中讲到.关于 Response 的更多内容请参考这里

HTTP 协议的报头

请求行后面可能还有附加的行,其中包含了更多信息,它们被称为请求头(request header).同样的,响应消息也有响应行(response header).每个请求和响应通常具有不同的头。 下表列出了一些常用的消息头:

Header Type Content
User-Agent 请求 浏览器及平台的信息
Accept  " 客户可处理的页面类型
Accept-Charset  " 客户可处理的字符集
Accept-Encoding  " 客户可处理的页面编码
Accept-Language  " 客户可处理的自然语言
If-Modified-Since  " 检查新鲜度的时间及日期
If-None-Match  " 为检查新鲜度而发送的标签
Host  " 服务器DNS名称
Authorization  " 客户的信任凭据
Referer  " 发出请求的先前URL
Cookie  " 给服务器发回Cookie的先前URL
Set-Cookie 响应 客户存储的Cookie
Server  " 服务器信息
Content-Encoding  " 内容编码
Content-Language  " 页面的自然语言
Content-Length  " 页面长度(字节)
Content-Type  " 页面MIME类型
Last-Modified  " 页面最后个性的时间
Excepires  " 页面过期时间
Location  " 告诉客户向谁发送请求
Accept-Ranges  " 告诉客户服务器能接受的字节范围
Date 请求/响应 发送消息的时间
Range  " 标识一个页面的一部分
Cache-Control  " 指示如何处理缓存
Upgrade  " 发送方希望希望切换的协议
  • User-Agent 允许客户将它的浏览器信息告知服务器(如 Mozilla/5.0 ) 服务器可以据此给不同的浏览器发送不同的响应。毕竟不同浏览器和行为是各不一样的。
  • Accept 客户端通过 4 个 Accept 头告知服务器用户端可以接受哪些信息。分别为 MIME类型(如 text/html)、字符集(如 ISO-8859-5)、压缩方法(如 gzip)、自然语言(如 zh-cn)
  • Host 是服务器的名称,它取自URL.这个对是强制性的。因为有些 IP 地址可能对应多个 DNS 名称。
  • Referer 根据此信息服务器可以知道浏览器是从哪个页面到达此页面的
  • Last-Modified/Excepires 在页面缓存中有重要作用
  • Upgrade 用来切换到一个新的通信协议

HTTP 实践

基于 Python socket 的简单 http 服务器

这里我们将使用 Python 来完成一个简单的 http 服务器,它将用来接收浏览器发出的 Request ,进行处理并返回 Response.

1.整体流程

首先建立一个 TCP 连接,在连接中我们接收来客户端数据,如果检测到是 Http 请求,则进行处理,并返回响应

这里使用到的几个方法:

  • GetHttpRequest 对数据进行检测,如果是 HTTP Request 则返回方法与RUI,否则返回 None
  • DoGet 对于 GET ,传入 URI ,进行相应的处理
  • DoRenderPage  对根据参数对模板页面进行填充
  • SendResponse 将 Response 消息体发送回客户端

在这里我们仅处理 GET 方法。

2.检测 HTTP 请求

HTTP request 是 ACSII 协议,我们可以使用文本操作的方式来处理它。这里我们只分析第一行就够了。然后根据空格与区分请求方法(Method)与URI,最后返回Method与URI:

3.处理 GET 请求

浏览器默认的请求方法为 GET 。GET 方法在URI 后面使用问号("?") 来分隔参数。它的参数是一个或多个键值对,每个键值对之间使用 "&" 号分隔。如

HTTP协议规定URL里的 "?","=" , "&" (包括其他一些符号)作为URL中的特殊符号,不能用做其它含意,否则会出现解析错误。如果需要使用使用这些符号,则需要进行转义.这些符号将转化成 "%"后接两们十六进制符号(% HEX HEX) 的形式进行表示 。详情请参考这里

这里我们将无URI的请求转到 index.html页面。

4.返回响应消息

在处理完请求后,服务器将返回响应消息。根据协议,响应消息由三个部分组成,响应状态行在最前,消息体放在最后,与消息头之间有一个空行:

到此,一个完整的响应过程就算完成了。

5.处理请求的一些细节

我们使用 html 模板来完成 response 消息体的组装。这将使页面(view)与数据模型(Model)分开。在获取请求的URI后读取模板,然后将模板中的参数一一对应地替换掉。

这里我们实现一个简单的功能:通过页面传入一个姓名,并将它在一个新的页面展示出来。

首先定义一个页面(index.html),用于传入参数:

服务器收到请求后使用 "name" 参数对 welcome.html 进行渲染:

最终效果如图:

request

response

6.本次实践的资源

本次实践的所有代码可以在这里下载。这里仅仅是一个最简单的实现,如果有时间会考虑到它的优化,并在github上公布。


目前这一部分代码已经整理到 github 上

  1. 其于单线程的 TCP 实现简单的 GET/POST  Request 处理。
  2. 简单的 MVC 模块化。实现基本的 HTML 模板渲染。实现 200/400/404/500 等状态的 Response
  3. 实现 URL 映射
  1. 本文目前尚无任何评论.
  1. 本文目前尚无任何 trackbacks 和 pingbacks.