13.6. http.server — 实现 web 服务器的基础类 | 互联网数据处理 |《python 3 标准库实例教程》| python 技术论坛-金年会app官方网

未匹配的标注

目标:利用 http.server 中的一些类可以实现一个基本的网络服务器

http.server 使用 中的一些类来创建用于实现 http 服务器的基类。httpserver 可以直接拿来用,而 basehttprequesthandler 的目的则是提供一个可供扩展的基础,以便处理各项协议 ( get,post 等)。

http get

在处理请求的类中,要添加对 http 方法的支持,就需要实现 do_method() 方法,且将 method 替换成 http 方法的名字。(比如  do_get()do_post() 等)。为了保持一致,处理请求的方法一律没有参数。请求的所有参数由 basehttprequesthandler来解析,并且作为一个对象保存在一个请求对象的属性中。

下面这个处理请求的例子展示了如何向客户返回一个答复,其中一些本地属性可以被用来构建回复。

http_server_get.py

from http.server import basehttprequesthandler
from urllib import parse
class gethandler(basehttprequesthandler):
    def do_get(self):
        parsed_path = parse.urlparse(self.path)
        message_parts = [
            'client values:',
            'client_address={} ({})'.format(
                self.client_address,
                self.address_string()),
            'command={}'.format(self.command),
            'path={}'.format(self.path),
            'real path={}'.format(parsed_path.path),
            'query={}'.format(parsed_path.query),
            'request_version={}'.format(self.request_version),
            '',
            'server values:',
            'server_version={}'.format(self.server_version),
            'sys_version={}'.format(self.sys_version),
            'protocol_version={}'.format(self.protocol_version),
            '',
            'headers received:',
        ]
        for name, value in sorted(self.headers.items()):
            message_parts.append(
                '{}={}'.format(name, value.rstrip())
            )
        message_parts.append('')
        message = '\r\n'.join(message_parts)
        self.send_response(200)
        self.send_header('content-type',
                         'text/plain; charset=utf-8')
        self.end_headers()
        self.wfile.write(message.encode('utf-8'))
if __name__ == '__main__':
    from http.server import httpserver
    server = httpserver(('localhost', 8080), gethandler)
    print('starting server, use  to stop')
    server.serve_forever()

所有文本信息先被组装起来再被写到 wfile 中,文件处理器则将回复包装到 socket 里。每个回复都需要一个回复代码,由 send_response() 设定。如果使用了一个错误代码( 404,501 等),一个合适的默认错误信息应该包含在头部信息中,或者包含在某个可以传递错误代码的信息中。

要运行一个服务器的请求处理器,需要将它传给 httpserver 构建函数,就如 __main__ 部分脚本所示处理。

然后开启服务器:

$ python3 http_server_get.py
starting server, use  to stop

再另开一个终端,用 curl 来访问它:

$ curl -v -i http://127.0.0.1:8080/?foo=bar
*   trying 127.0.0.1...
* connected to 127.0.0.1 (127.0.0.1) port 8080 (#0)
> get /?foo=bar http/1.1
> host: 127.0.0.1:8080
> user-agent: curl/7.43.0
> accept: */*
>
http/1.0 200 ok
content-type: text/plain; charset=utf-8
server: basehttp/0.6 python/3.5.2
date: thu, 06 oct 2016 20:44:11 gmt
client values:
client_address=('127.0.0.1', 52934) (127.0.0.1)
command=get
path=/?foo=bar
real path=/
query=foo=bar
request_version=http/1.1
server values:
server_version=basehttp/0.6
sys_version=python/3.5.2
protocol_version=http/1.0
headers received:
accept=*/*
host=127.0.0.1:8080
user-agent=curl/7.43.0
* connection #0 to host 127.0.0.1 left intact

注意

由不同版本的 curl 输出可能不好。如果运行例子产生不同的输出,就检查一下 curl 的版本号。

http post

要支持 post 请求需要更多一点的工作,因为提供的基类不能自动分析表单数据。不过,如果给定的输入是正确的,那么 cgi 模块提供的 fieldstorage 类却可以用来分析表单。

http_server_post.py

import cgi
from http.server import basehttprequesthandler
import io
class posthandler(basehttprequesthandler):
    def do_post(self):
        # 分析提交的表单数据
        form = cgi.fieldstorage(
            fp=self.rfile,
            headers=self.headers,
            environ={
                'request_method': 'post',
                'content_type': self.headers['content-type'],
            }
        )
        # 开始回复
        self.send_response(200)
        self.send_header('content-type',
                         'text/plain; charset=utf-8')
        self.end_headers()
        out = io.textiowrapper(
            self.wfile,
            encoding='utf-8',
            line_buffering=false,
            write_through=true,
        )
        out.write('client: {}\n'.format(self.client_address))
        out.write('user-agent: {}\n'.format(
            self.headers['user-agent']))
        out.write('path: {}\n'.format(self.path))
        out.write('form data:\n')
        # 表单信息内容回放
        for field in form.keys():
            field_item = form[field]
            if field_item.filename:
                # 字段中包含的是一个上传文件
                file_data = field_item.file.read()
                file_len = len(file_data)
                del file_data
                out.write(
                    '\tuploaded {} as {!r} ({} bytes)\n'.format(
                        field, field_item.filename, file_len)
                )
            else:
                # 通常形式的值
                out.write('\t{}={}\n'.format(
                    field, form[field].value))
        # 将编码 wrapper 到底层缓冲的连接断开, 
        # 使得将 wrapper 删除时, 
        # 并不关闭仍被服务器使用 socket 。
        out.detach()
if __name__ == '__main__':
    from http.server import httpserver
    server = httpserver(('localhost', 8080), posthandler)
    print('starting server, use  to stop')
    server.serve_forever()

在一个窗口运行服务器

$ python3 http_server_post.py
starting server, use  to stop

使用 -f 选项, curl 的参数可以包含要提交给服务器的表单数据。最后一个参数 -fdatafile=@http_server_get.py ,将文件 http_server_get.py 的内容用表单提交,展示了如何利用表单来读取一个文件数据。

$ curl -v http://127.0.0.1:8080/ -f name=dhellmann -f foo=bar\
-f datafile=@http_server_get.py
*   trying 127.0.0.1...
* connected to 127.0.0.1 (127.0.0.1) port 8080 (#0)
> post / http/1.1
> host: 127.0.0.1:8080
> user-agent: curl/7.43.0
> accept: */*
> content-length: 1974
> expect: 100-continue
> content-type: multipart/form-data;
boundary=------------------------a2b3c7485cf8def2
>
* done waiting for 100-continue
http/1.0 200 ok
content-type: text/plain; charset=utf-8
server: basehttp/0.6 python/3.5.2
date: thu, 06 oct 2016 20:53:48 gmt
client: ('127.0.0.1', 53121)
user-agent: curl/7.43.0
path: /
form data:
    name=dhellmann
    uploaded datafile as 'http_server_get.py' (1612 bytes)
    foo=bar
* connection #0 to host 127.0.0.1 left intact

threading 和 forking

httpserver 是 socketserver.tcpserver 的一个简单自子类,它并不使用多线程或多进程来处理请求。要添加 threading 或 forking ,需要从   中使用一个合适的 mix-in 来创建一个新的类。

http_server_threads.py

from http.server import httpserver, basehttprequesthandler
from socketserver import threadingmixin
import threading
class handler(basehttprequesthandler):
    def do_get(self):
        self.send_response(200)
        self.send_header('content-type',
                         'text/plain; charset=utf-8')
        self.end_headers()
        message = threading.currentthread().getname()
        self.wfile.write(message.encode('utf-8'))
        self.wfile.write(b'\n')
class threadedhttpserver(threadingmixin, httpserver):
    """在一个新的线程中处理请求。"""
if __name__ == '__main__':
    server = threadedhttpserver(('localhost', 8080), handler)
    print('starting server, use  to stop')
    server.serve_forever()

和其他例子一样,以同样的方式运行服务器

$ python3 http_server_threads.py
starting server, use  to stop

每当服务器接收一个请求,它就创建一个新的线程或进程来处理它:

$ curl http://127.0.0.1:8080/
thread-1
$ curl http://127.0.0.1:8080/
thread-2
$ curl http://127.0.0.1:8080/
thread-3

用 forkingmixin 替换 threadingmixin 可以达到类似的效果,只不过这时创建的是一个新的进程,而不是线程。

处理错误

传递一个合适的错误代码以及可选的错误信息,调用 send_error()来处理错误,将自动生成整个回复(包括头部,状态代码和信息体)。

http_server_errors.py

from http.server import basehttprequesthandler
class errorhandler(basehttprequesthandler):
    def do_get(self):
        self.send_error(404)
if __name__ == '__main__':
    from http.server import httpserver
    server = httpserver(('localhost', 8080), errorhandler)
    print('starting server, use  to stop')
    server.serve_forever()

在这个例子中,总是返回一个 404 错误。

$ python3 http_server_errors.py
starting server, use  to stop

错误发生时,返回信息在头部指明错误代码,并回传一个 html 文件将该错误报告给客户。

$ curl -i http://127.0.0.1:8080/
http/1.0 404 not found
server: basehttp/0.6 python/3.5.2
date: thu, 06 oct 2016 20:58:08 gmt
connection: close
content-type: text/html;charset=utf-8
content-length: 447
    
        
        error response
    
    
        
        

error code: 404

message: not found.

error code explanation: 404 - nothing matches the given uri.

设定头部

使用 send_header 方法可添加头数据到 http 回复中。该方法需要两个参数:头的名称和相应的值。

http_server_send_header.py

from http.server import basehttprequesthandler
import time
class gethandler(basehttprequesthandler):
    def do_get(self):
        self.send_response(200)
        self.send_header(
            'content-type',
            'text/plain; charset=utf-8',
        )
        self.send_header(
            'last-modified',
            self.date_time_string(time.time())
        )
        self.end_headers()
        self.wfile.write('response body\n'.encode('utf-8'))
if __name__ == '__main__':
    from http.server import httpserver
    server = httpserver(('localhost', 8080), gethandler)
    print('starting server, use  to stop')
    server.serve_forever()

在这个例子中,我们用当前的时间戳来给头 last-modified 赋值,并将其格式化为符合 rfc 7231 的形式。

$ curl -i http://127.0.0.1:8080/
http/1.0 200 ok
server: basehttp/0.6 python/3.5.2
date: thu, 06 oct 2016 21:00:54 gmt
content-type: text/plain; charset=utf-8
last-modified: thu, 06 oct 2016 21:00:54 gmt
response body

如同其他例子一样,服务器在终端记录请求。

$ python3 http_server_send_header.py
starting server, use  to stop
127.0.0.1 - - [06/oct/2016 17:00:54] "get / http/1.1" 200 -

使用命令行

http.server 內建有一个用于服务本地文件系统文件的服务器。 使用 python 解释器的  -m  选项可以从命令行运行它。

$ python3 -m http.server 8080
serving http on 0.0.0.0 port 8080 ...
127.0.0.1 - - [06/oct/2016 17:12:48] "head /index.rst http/1.1" 200 -

服务器的根目录即当前运行服务器的工作目录。

$ curl -i http://127.0.0.1:8080/index.rst
http/1.0 200 ok
server: simplehttp/0.6 python/3.5.2
date: thu, 06 oct 2016 21:12:48 gmt
content-type: application/octet-stream
content-length: 8285
last-modified: thu, 06 oct 2016 21:12:10 gmt

参考

  •  -- socketserver 模块提供了处理原始未加工的 socket 连接的基类。
  •  -- "hypertext transfer protocol (http/1.1): semantics and content" 含有一份关于 http 头和日期时间格式的说明。

本文章首发在 金年会app官方网 网站上。

本译文仅用于学习和交流目的,转载请务必注明文章译者、出处、和本文链接
我们的翻译工作遵照 cc 协议,如果我们的工作有侵犯到您的权益,请及时联系金年会app官方网。

原文地址:https://learnku.com/docs/pymotw/httpserv...

译文地址:https://learnku.com/docs/pymotw/httpserv...

上一篇 下一篇
讨论数量: 0



暂无话题~
网站地图