从零开始制作Web框架(4) – 生成HTTP响应

现代互联网的很多流量都是承载在 HTTP 协议之上的,伟大的工程师前辈们制作了许多非常优秀的框架/协议,在我们的开发过程中帮助我们减轻了很多的工作,所以在业余时间,我想我们应该更加了解这些框架/协议的工作原理。

因此我构建了这个系列文章,以及 Flaks 项目(没错就是 Flaks – 高射炮),它模仿了一些 Flask 框架的特性(路由、可配置、…)并添加了一个简单的 并行/异步 HTTP 服务器与 CGI 支持;在这个系列文章中会较为详细的讲解该框架的构建流程以及思路,希望大家喜欢。

本人才疏学浅,如果在文章中有任何错误,还请大家不吝指正。

这个系列文章将会由以下几篇文章组成:

  1. socketselectors 选择器
  2. HTTP 请求解析
  3. WSGICGI 支持
  4. 生成 HTTP 响应
  5. 视图函数与路由
  6. 尝试 asyncio 的协程异步 I/O

Response 类的构建

上一篇文章中我们介绍了 WSGICGI 的支持,在结尾小结处的框架图中我们可以看到,在应用层处理完毕之后我们需要对应用层的结果进行包装,生成HTTP 响应,进而交予底层的 socket 连接处理数据发送。

回忆一下,在第一篇文章中,我们使用了一个固定的 Hello World 字符串来向客户端返回一个简单的问候;借此,我们来分析一下生成 HTTP 响应所需要的信息,以及思考如何构建我们的响应类。

RESPONSE = \
"""
HTTP/1.1 200 OK\r
Content-Type: text/html\r
Content-Length: 21\r
\r
<h1>Hello World!</h1>
""".encode()

可以看到,如同 HTTP 请求一样,响应遵循着大致相同的格式:

  1. 首行的第一个字段表示 HTTP 版本号
  2. 首行的第二个字段表示响应的状态,也就是我们常说的 HTTP 状态码
  3. 第三个字段用于表示返回状态的说明
  4. 下面的任意多行表示响应头
  5. 在一个 CR-LF 字符之后添加需要的相应数据

总结起来大致如此:

[HTTP Version] [Response Code] [Response Message]  
[Headers]
 
[Response data]

因为我们这里只做 HTTP/1.1 版本的支持,而响应消息由响应代码可以确定,所以我们的响应类应该具有如下的功能:

  • 接收一个响应代码 – int
  • 可选的接收响应头 – dict
  • 可选的接收响应数据 – bytes/str
  • 将上面的信息打包成为完整的响应 – bytes

响应头的补充规定

RFC2616 中已经详细的规定了 HTTP 响应头的相关规范,我们在 Flaks 项目的服务器部分不强制指定缓存与认证相关的功能,而是交由更上层的应用部分去处理;由此,根据标准,在头部必须指定的两个信息分别为:

  1. Content-Type – 指定响应内容类型
  2. Content-Length – 指定响应的长度

因此,这两个信息我们单独将处理:

  1. 内容类型指定一个默认值,当应用层没有给定时防止出现错误语义
  2. 长度由给定的数据进行计算,不需要应用层进行管理

结合上面所得到的功能,可以给出如下实现:

class Response:
    """
    Wrap an HTTP Response packet based on
    the return value generated by the Application layer.
    Requires WSGI standard server to pass in an
    Environ parameter for package generation.
    """

    def __init__(self, code: int, data='', environ=None, headers=None,
                 content_type=settings.DEFAULT_RESPONSE_CONTENT_TYPE):
        """
        Initialize a Response object.
        Parameters:
            code: int - HTTP Response code
            data: str - HTTP Response data
            environ: dict - WSGI Environ dict
            headers: Optional[dict] - Extra header information
            content_type: Optional[str] - Return type description
        """
        self.code = code
        self.data = data
        self._extra_hedaers = headers
        self._content_type = content_type

        # Try to catch environ method
        self._environ_method = None
        if environ:
            self._environ_method = environ.method

        # Make sure there's no sth weird send to client
        if not code in consts.HTTP_RESPONSE_DESCRIPTIONS.keys():
            raise errors.InvalidHTTPResponseCode(code)

        self._exception_data()

在这里,我们在程序内部的常量定义中实现了几乎所有可能的 HTTP 状态码及其说明消息,所以在类的构造函数中可以对传入的状态码进行检查;当状态码不正确/未知时,向应用层掷出一个 InvalidHTTPResponseCode 错误。

Response 类的构造函数允许仅仅传入单个的状态码,这是因为在任何非 2xx 的响应中,根据约定,不应该有任何的返回数据被传输。

在最后,我们的 Response 类提供了一个 done 方法,该方法的返回值是字节流 – 也就是打包好之后的响应,它将被交付给 socket 层进行客户端传输:

def done(self):
    """
    Form a complete HTTP Response package:
    HTTP/1.1 200 OK<CR>
    Server: Simple-Python-HTTP-Server<CR>
    Content-Type: text/plain<CR>
    Content-Length: 37<CR>
    <CR>
    {body}...
    if environ.method is HEAD - the {body} part
    will not be addin.
    """
    baseline = self._make_baseline()
    headers = {
        "Server": settings.SERVER_NAME,
        "Content-Type": self._content_type,
        "Content-Length": len(self.data)}

    if self._extra_hedaers:
        headers.update(self._extra_hedaers)

    if self._environ_method == "HEAD":
        headers.update({"Content-Length": 0})

    response = baseline + self.header_maker(headers)
    if not self._environ_method == "HEAD":
        response += "\r\n" + self.data
    return response.encode()

小结

到此为止我们这个简单 HTTP 服务器的所有基础架构都已经完成。现在,它可以实现以下功能:

  1. 接受来自用户的连接请求 – 使用 socket
  2. 对请求的数据进行解析 – 构建 Request 类实例
  3. 生成一个请求返回给用户 – 构建 Response 类实例
  4. CGIWSGI 应用支持

但是需要注意的是,除了第四点,剩下的三个部分是相互分离的的。这也就意味着,我们最重要的一个部分还没有完成 – Application 应用层,它将负责把以上的所有功能进行耦合,并提供给上层用户一个接口,用以实现“复杂的” Web 应用程序构建。

换句话说,我们已经实现了一个功能完备的 HTTP 伺服器。在下一篇文章中,我们将对前三篇文章的所有产出结果进行调用,并增加最重要的路由绑定与视图函数处理功能。

暂无评论

发送评论 编辑评论


|´・ω・)ノ
ヾ(≧∇≦*)ゝ
(☆ω☆)
(╯‵□′)╯︵┴─┴
 ̄﹃ ̄
(/ω\)
∠( ᐛ 」∠)_
(๑•̀ㅁ•́ฅ)
→_→
୧(๑•̀⌄•́๑)૭
٩(ˊᗜˋ*)و
(ノ°ο°)ノ
(´இ皿இ`)
⌇●﹏●⌇
(ฅ´ω`ฅ)
(╯°A°)╯︵○○○
φ( ̄∇ ̄o)
ヾ(´・ ・`。)ノ"
( ง ᵒ̌皿ᵒ̌)ง⁼³₌₃
(ó﹏ò。)
Σ(っ °Д °;)っ
( ,,´・ω・)ノ"(´っω・`。)
╮(╯▽╰)╭
o(*////▽////*)q
>﹏<
( ๑´•ω•) "(ㆆᴗㆆ)
😂
😀
😅
😊
🙂
🙃
😌
😍
😘
😜
😝
😏
😒
🙄
😳
😡
😔
😫
😱
😭
💩
👻
🙌
🖕
👍
👫
👬
👭
🌚
🌝
🙈
💊
😶
🙏
🍦
🍉
😣
Source: github.com/k4yt3x/flowerhd
颜文字
Emoji
小恐龙
花!
上一篇
下一篇