Http & Web Servers Unit 2 Lesson 1 Python's 'http.server'

Python’s http.server

上一节课,我们使用Python的 http.server 模块内置的示例服务器。但这个示例服务器也就那样 - 只是证明了模块的能力。除了提供一个目录下的静态文件,你还能用HTTP做好多其他的事情。在本节课,你会用 http.server 创建不同的网络服务,并同时学习更多关于HTTP的知识。你还会使用另一个模块, requests ,写代码充当一个HTTP的客户端。

这些模块是用面向对象编程语言Python写的。你应该已经熟悉创建类实例,定义子类和定义类的方法。如果你需要复习面向对象操作的Python语法,你可以浏览Python类教程

在本节课的练习中,你将写代码运行在自己的电脑上。起始代码即上节课下载的,应该在 course-ud303 目录下。用你最喜欢的文本编辑器开始练习吧!

Servers and handlers

使用 http.server 的网络服务器由两部分组成:HTTPServer类和请求处理器类。第一部分,HTTPServer 类,和所有的网络服务一样,是内建在模块内的。它知道如何监听端口,接收来自客户端的请求。每当它接收到一个请求时,它就将请求交到第二部分 - 请求处理器 - 每个网络服务器的请求处理器都不一样。

为了运行一个网络服务,你的Python代码需要做到下面的几点:

  • 引入 http.server 模块,至少是你需要的那一部分。
  • 创建一个 http.server.BaseHTTPRequestHandler 子类。这就是你的处理器类。
  • 在处理器类为每一个你想要处理的 HTTP verb 定义方法。(在这个课程中,我们唯一见到的 HTTP verb就是 GET ,但之后我们会见到更多其他的。)
    • GET 请求的方法必须叫做 do_GET
    • 在方法内,调用处理器类的内置方法读取HTTP请求并写响应。
  • 创建一个 http.server.HTTPServer 的实例,将你的处理器类和服务器信息给它 - 特别是你的端口号。
  • 调取这个 HTTPServer 实例的 run_forever 方法。

一旦你调取 HTTPServer 实例的 run_forever 方法,它就会永远运行,除非停止。就像上一节课那样,如果你的Python服务器在运行中,你想要让它停止运行,就在它运行的终端键入 Ctrl-C。(有时候你需要键入两到三次。)

Exercise: The hello server

我们来快速看一个例子。从你的终端,进入到你之前下载的 course-ud303 目录下,在 Lesson-2 子目录下,你可以看到 0_HelloServer 子目录。里面有一个Python程序叫做 HelloServer.py 。在你到文本编辑器打开它看看。然后在你的终端运行这个程序: python3 HelloServer.py 。它什么都不会做,直到你通过浏览器访问 http://localhost:8000/

A tour of the hello server

在文本编辑器打开 HelloServer.py ,我们来一行一行地看看这段代码的每一个部分。

1
from http.server import HTTPServer, BaseHTTPRequestHandler

http.server模块里面包含很多部分内容。现在,这个程序只需要其中两个部分。我们现在使用 importfrom 语法,这样我们就不用在代码里一遍又一遍打 http.server

1
2
class HelloHandler(BaseHTTPRequestHandler):
def do_GET(self):

这就是处理器类。从在 http.server 中定义的 BaseHTTPRequestHandler 父类继承下来。我已经定义了一个方法,do_Get ,用来处理 HTTP GET 请求。当这个网络服务器接收到一个 GET 请求,它就会调取这个方法响应。

在之前的课程中我们了解到,服务器在一个 HTTP 响应中需要发送3样东西:一个状态码,一些头信息,和一个响应主体。处理器的父类对处理这些事情都有方法。在 do_Get 中,我只需要依次调取它们即可。

1
2
# First, send a 200 OK response
self.send_response(200)

服务器需要要做的就是发送一个 200 OK 状态码; send_response 方法做这个。我不需要告知它200意味着OK;父类已经知道这个了。

1
2
3
# Then send headers.
self.send_header('Content-type','text/plain;charset=utf-8')
self.end_headers()

接下来服务器需要做的就是发送 HTTP 头。 父类提供 send_headerend_headers 方法来做这些。目前,我只让服务器发送了一个单行的头 - Content-type 头告知客户端响应主体会是 UTF-8 编码的纯文本。

1
2
# Now,write the response body.
self.wfile.write("Hello, Http!\n".encode())

do_GET 方法的最后一部分写响应主体。

父类给了我们一个叫做 self.wfile 的变量来发送响应。 wfile 代表着 writeable file 。Python和很多其他的编程语言一样,将打开文件和网络连接类比起来:它们都是你能读取的数据。有些文件对象是只读的;有些是只写的;有些是既能读也能写的。

self.wfile 代表着服务器到客户端的连接;如名字所示,它是只读的。它的 write 方法将任意的二进制数据写到发送到客户端的响应中。

.encode() 是干嘛的?之后我们再看。现在让我们看看剩下的其他代码。

1
2
3
4
if __name__ == '__main__':
server_address = ('', 8000) # Serve on all addresses, port 8000.
httpd = HTTPServer(server_address, HelloHandler)
httpd.serve_forever()

如果我们将该模块当作Python程序运行,这段代码才会运行。如果只是引入,则不会运行。HTTPServer 构造函数需要知道它监听的是哪个地址和端口;当我调用 server_address 的时候,将地址和端口作为元组传入。我也给了它 HelloHandler 类,它会用这个来处理来自客户端的请求。

在文件的最后,我在 HTTPServer 调取了 server_forever ,让它开始处理HTTP请求。服务器开始运行了。

End of the tour

用Python写最基本的HTTP服务器就是这些内容了。但是这个hello server不怎么有趣。甚至做的还没有示例服务器多。不管你查询什么,它都只会说hello。

余下的课程,我们会建造不只是说hello的服务器。