13.6. http.server — 实现 web 服务器的基础类 | 互联网数据处理 |《python 3 标准库实例教程》| python 技术论坛-380玩彩网官网入口
目标:利用 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 头和日期时间格式的说明。
本译文仅用于学习和交流目的,转载请务必注明文章译者、出处、和本文链接
我们的翻译工作遵照 cc 协议,如果我们的工作有侵犯到您的权益,请及时联系380玩彩网官网入口。