python实现一个http服务器
最近在学习Flask源码的时候,发现了python有一个内置的http,可以用来搭建http服务器,所以花时间研究了一番。httpserver基于python的http包构建一个简易http服务器。使用到的两个类from http.server import BaseHTTPRequestHandler, HTTPServer源码首先查看BaseHTTPRequestHandler处理请求的部分的
最近在学习Flask源码的时候,发现了python有一个内置的http,可以用来搭建http服务器,所以花时间研究了一番。
httpserver
基于python的http包构建一个简易http服务器。
使用到的两个类
from http.server import BaseHTTPRequestHandler, HTTPServer
源码
首先查看BaseHTTPRequestHandler
处理请求的部分的源码,才知道继承它后要怎么样才能接收处理请求。
# 跳过前面负责的逻辑,直接来到
# handle_one_request
# 这个方法就是BaseHTTPRequestHandler处理一个请求的方法。
def handle_one_request(self):
try:
# 读取请求
self.raw_requestline = self.rfile.readline(65537)
if len(self.raw_requestline) > 65536:
self.requestline = ''
self.request_version = ''
self.command = ''
self.send_error(HTTPStatus.REQUEST_URI_TOO_LONG)
return
if not self.raw_requestline:
self.close_connection = True
return
# 这一句是解析http头的(这时的http请求在raw_requestline中,也就是解析raw_requestline)
# 就是将类似b'GET /test-request HTTP/1.1\r\n...'的请求头解释出来
# An error code has been sent, just exit
if not self.parse_request():
return
# self.command 是解释出来的command,如'GET', 'POST'
# 注意这里没有使用lower函数
mname = 'do_' + self.command
if not hasattr(self, mname):
self.send_error(
HTTPStatus.NOT_IMPLEMENTED,
"Unsupported method (%r)" % self.command)
return
method = getattr(self, mname)
# 所以这个do_command需要是一个可以调用的对象,可以是一个方法。
# 每次有相应的请求到来,就会调用相应的do_command()
# 所以我们实现这些方法就可以了:
# 如果想要处理get,就在继承中实现do_GET方法, 如果处理post就实现do_POST方法等等。
method()
self.wfile.flush() #actually send the response if not already done.
except socket.timeout as e:
#a read or a write timed out. Discard this connection
self.log_error("Request timed out: %r", e)
self.close_connection = True
return
读了源码之后就大概知道如何实现自己的Http服务器了:
思路是继承BaseHTTPRequestHandler
写一个处理类,然后用它初始化一个HTTPServer
继承BaseHTTPRequestHandler
继承BaseHTTPRequestHandler
,然后实现相应的方法如do_GET
,do_POST
,do_DELETE
等。(注意大小写一定要对)
class MyHttpRequestHandler(BaseHTTPRequestHandler):
def do_GET(self):
# 处理逻辑
pass
def do_POST(self):
pass
...
创建Server
创建一个server,这里的server是HTTPServer
类,设置监听地址和端口,将MyHttpRequestHandler
传递给它。
ts = HTTPServer(('127.0.0.1', 8899), MyHttpRequestHandler)
然后需要让服务器开始工作
ts.serve_forever()
这时服务器就开始工作了。
所以我们该在BaseHTTPRequestHandler
中准备一些简单的逻辑,否则无输出的话我们也不知道服务器是否在运行中。
do_GET
因为GET
方法容易测试,所以选择GET
方法测试。
在do_GET
中添加一些逻辑,在do_GET
返回响应头和数据。
def do_GET(self):
# 响应头
headers = """HTTP/1.1 200 OK
Server: YouFather
Accept-Ranges: bytes
Content-Length: {data-length}
Vary: Accept-Encoding
Content-Type: text/html
""".replace('\n', '\r\n') + '\r\n'
# 响应数据
data = "<a href='http://www.baidu.com'>百度</a>".encode('gbk')
# 设置一下这个Content-Length参数,告诉客户端数据的长度。
headers = headers.format_map({'data-length': len(data)})
# 写入响应头和数据
# 这里的wfile是HTTPServer的基类TCPServer为我们准备的一个写入对象。
# wfile = socket.make_file('wb')
# 和open(filename,'w')是同一类型。
self.wfile.write(headers.encode())
self.wfile.write(data)
执行一下
如果方法命名不对如do_get
,或请求到没有的方法如没有do_GET
,都会引发异常
控制台:
127.0.0.1 - - [05/Aug/2021 17:35:52] code 501, message Unsupported method ('GET')
127.0.0.1 - - [05/Aug/2021 17:35:52] "GET / HTTP/1.1" 501 -
127.0.0.1 - - [05/Aug/2021 17:35:52] code 501, message Unsupported method ('GET')
127.0.0.1 - - [05/Aug/2021 17:35:52] "GET /favicon.ico HTTP/1.1" 501 -
浏览器端:
获取Http的请求信息
在do_GET
中可能要用到Http
相关的请求信息,这些大部分都由BaseHTTPRequestHandler
为我们准备好了。
来看看BaseHTTPRequestHandler
封装了哪些消息给我们:
# 修改MyHttpRequestHandler
class MyHttpRequestHandler(BaseHTTPRequestHandler):
is_first_request = True
def do_GET(self):
if self.is_first_request:
self.is_first_request = False
for k in dir(self):
v = getattr(self, k)
# 魔术方法,私有变量和大部分的callable并不是为我们提供的,所以排除
if k.startswith('_') or callable(v):
continue
print('---arg: ', k, ' type:', v.__class__.__name__, '\n', v, '\n\n', sep='')
headers = """HTTP/1.1 200 OK
Server: YouFather
Accept-Ranges: bytes
Content-Length: {data-length}
Vary: Accept-Encoding
Content-Type: text/html
""".replace('\n', '\r\n') + '\r\n'
data = "<a href='http://www.baidu.com'>百度</a>".encode('gbk')
headers = headers.format_map({'data-length': len(data)})
self.wfile.write(headers.encode())
self.wfile.write(data)
再运行,浏览器访问一下
---arg: client_address type:tuple
('127.0.0.1', 59205)
---arg: close_connection type:bool
True
---arg: command type:str
GET
---arg: connection type:socket
<socket.socket fd=888, family=AddressFamily.AF_INET, type=SocketKind.SOCK_STREAM, proto=0, laddr=('127.0.0.1', 8899), raddr=('127.0.0.1', 59205)>
---arg: default_request_version type:str
HTTP/0.9
---arg: disable_nagle_algorithm type:bool
False
---arg: error_content_type type:str
text/html;charset=utf-8
---arg: error_message_format type:str
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN"
"http://www.w3.org/TR/html4/strict.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html;charset=utf-8">
<title>Error response</title>
</head>
<body>
<h1>Error response</h1>
<p>Error code: %(code)d</p>
<p>Message: %(message)s.</p>
<p>Error code explanation: %(code)s - %(explain)s.</p>
</body>
</html>
---arg: headers type:HTTPMessage
Host: localhost:8899
Connection: keep-alive
Cache-Control: max-age=0
sec-ch-ua: "Chromium";v="92", " Not A;Brand";v="99", "Google Chrome";v="92"
sec-ch-ua-mobile: ?0
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/92.0.4515.107 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9
Sec-Fetch-Site: none
Sec-Fetch-Mode: navigate
Sec-Fetch-User: ?1
Sec-Fetch-Dest: document
Accept-Encoding: gzip, deflate, br
Accept-Language: zh-CN,zh;q=0.9
---arg: is_first_request type:bool
False
---arg: monthname type:list
[None, 'Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']
---arg: path type:str
/
---arg: protocol_version type:str
HTTP/1.0
---arg: raw_requestline type:bytes
b'GET / HTTP/1.1\r\n'
---arg: rbufsize type:int
-1
---arg: request type:socket
<socket.socket fd=888, family=AddressFamily.AF_INET, type=SocketKind.SOCK_STREAM, proto=0, laddr=('127.0.0.1', 8899), raddr=('127.0.0.1', 59205)>
---arg: request_version type:str
HTTP/1.1
---arg: requestline type:str
GET / HTTP/1.1
---arg: responses type:dict
{<HTTPStatus.CONTINUE: 100>: ('Continue', 'Request received, please continue'), <HTTPStatus.SWITCHING_PROTOCOLS: 101>: ('Switching Protocols', 'Switching to new protocol; obey Upgrade header'), <HTTPStatus.PROCESSING: 102>: ('Processing', ''), <HTTPStatus.OK: 200>: ('OK', 'Request fulfilled, document follows')...省略节省篇幅}
---arg: rfile type:BufferedReader
<_io.BufferedReader name=888>
---arg: server type:HTTPServer
<http.server.HTTPServer object at 0x0000022A1A1011C0>
---arg: server_version type:str
BaseHTTP/0.6
---arg: sys_version type:str
Python/3.8.6
---arg: timeout type:NoneType
None
---arg: wbufsize type:int
0
---arg: weekdayname type:list
['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun']
---arg: wfile type:_SocketWriter
<socketserver._SocketWriter object at 0x0000022A335CCE20>
更多推荐
所有评论(0)