This article was last updated on <span id="expire-date"></span> days ago, the information described in the article may be outdated.
现代互联网的很多流量都是承载在 HTTP 协议之上的,伟大的工程师前辈们制作了许多非常优秀的框架/协议,在我们的开发过程中帮助我们减轻了很多的工作,所以在业余时间,我想我们应该更加了解这些框架/协议的工作原理。
因此我构建了这个系列文章,以及 Flaks 项目(没错就是 Flaks – 高射炮),它模仿了一些 Flask 框架的特性(路由、可配置、…)并添加了一个简单的 并行/异步 HTTP 服务器与 CGI 支持;在这个系列文章中会较为详细的讲解该框架的构建流程以及思路,希望大家喜欢。
这是这个系列的第三篇文章,本篇文章我们构建 HTTP 响应类。
本人才疏学浅,如果在文章中有任何错误,还请大家不吝指正。
这个系列文章将会由以下几篇文章组成:
- 从
socket
到selectors
选择器 -
HTTP
请求解析 -
WSGI
与CGI
支持 - 生成
HTTP
响应 - 视图函数与路由
- 尝试
asyncio
的协程异步 I/O
Response 类的构建
上一篇文章中我们介绍了 WSGI
与 CGI
的支持,在结尾小结处的框架图中我们可以看到,在应用层处理完毕之后我们需要对应用层的结果进行包装,生成HTTP
响应,进而交予底层的 socket
连接处理数据发送。
回忆一下,在第一篇文章中,我们使用了一个固定的 Hello World 字符串来向客户端返回一个简单的问候;借此,我们来分析一下生成 HTTP
响应所需要的信息,以及思考如何构建我们的响应类。
1 | RESPONSE = \ |
可以看到,如同 HTTP
请求一样,响应遵循着大致相同的格式:
- 首行的第一个字段表示
HTTP
版本号 - 首行的第二个字段表示响应的状态,也就是我们常说的
HTTP
状态码 - 第三个字段用于表示返回状态的说明
- 下面的任意多行表示响应头
- 在一个
CR-LF
字符之后添加需要的相应数据
总结起来大致如此:
1 | [HTTP Version] [Response Code] [Response Message] |
因为我们这里只做 HTTP/1.1
版本的支持,而响应消息由响应代码可以确定,所以我们的响应类应该具有如下的功能:
- 接收一个响应代码 -
int
- 可选的接收响应头 -
dict
- 可选的接收响应数据 -
bytes/str
- 将上面的信息打包成为完整的响应 -
bytes
响应头的补充规定
在 RFC2616 中已经详细的规定了 HTTP
响应头的相关规范,我们在 Flaks
项目的服务器部分不强制指定缓存与认证相关的功能,而是交由更上层的应用部分去处理;由此,根据标准,在头部必须指定的两个信息分别为:
-
Content-Type
- 指定响应内容类型 -
Content-Length
- 指定响应的长度
因此,这两个信息我们单独将处理:
- 内容类型指定一个默认值,当应用层没有给定时防止出现错误语义
- 长度由给定的数据进行计算,不需要应用层进行管理
结合上面所得到的功能,可以给出如下实现:
1 | class Response: |
在这里,我们在程序内部的常量定义中实现了几乎所有可能的 HTTP
状态码及其说明消息,所以在类的构造函数中可以对传入的状态码进行检查;当状态码不正确/未知时,向应用层掷出一个 InvalidHTTPResponseCode
错误。
Response
类的构造函数允许仅仅传入单个的状态码,这是因为在任何非 2xx 的响应中,根据约定,不应该有任何的返回数据被传输。
在最后,我们的 Response
类提供了一个 done
方法,该方法的返回值是字节流 - 也就是打包好之后的响应,它将被交付给 socket
层进行客户端传输:
1 | def done(self): |
小结
到此为止我们这个简单 HTTP
服务器的所有基础架构都已经完成。现在,它可以实现以下功能:
- 接受来自用户的连接请求 - 使用
socket
- 对请求的数据进行解析 - 构建
Request
类实例 - 生成一个请求返回给用户 - 构建
Response
类实例 -
CGI
与WSGI
应用支持
但是需要注意的是,除了第四点,剩下的三个部分是相互分离的的。这也就意味着,我们最重要的一个部分还没有完成 - Application
应用层,它将负责把以上的所有功能进行耦合,并提供给上层用户一个接口,用以实现“复杂的” Web
应用程序构建。
换句话说,我们已经实现了一个功能完备的 HTTP
伺服器。在下一篇文章中,我们将对前三篇文章的所有产出结果进行调用,并增加最重要的路由绑定与视图函数处理功能。
Comments