BaseHTTPServer模組分析
阿新 • • 發佈:2019-02-01
因為BaseHTTPServer是個基礎模組,僅提供了對HTTP請求的解析和響應框架。通過對該模組的瞭解,可以熟悉如何解析HTTP請求,窺探web服務的內部基礎。
該模組理解起來很容易,所以就沒寫例子,可以直接看註釋的原始碼。但在這之前,推薦先了解一下HTTP協議,《HTTP協議詳解》是一篇非常經典的文章。
##
# @file $python27$\Lib\BaseHTTPServer.py
# @brief 解析HTTP請求,但未提供相應響應,是SimpleHTTPServer基礎模組
# @date 2015-02-11
# @remark 為減少字元量,檔案中的註釋和對理解無關的內容都被我刪除了,想詳細瞭解請見原檔案
__version__ = "0.3"
# 出錯時響應給客戶端的預設html模板,%(***)s、%(***)d為字典型式
DEFAULT_ERROR_MESSAGE = """\
<head>
<title>Error response</title>
</head>
<body>
<h1>Error response</h1>
<p>Error code %(code)d.
<p>Message: %(message)s.
<p>Error code explanation: %(code)s = %(explain)s.
</body>
"""
DEFAULT_ERROR_CONTENT_TYPE = "text/html"
# 字串到html的轉義
def _quote_html(html):
return html.replace("&", "&").replace("<", "<").replace(">", ">")
class HTTPServer(SocketServer.TCPServer):
allow_reuse_address = 1 # 允許socket埠重用
# 重寫了基類TCPServer的函式,多儲存了計算機名稱
def server_bind(self):
SocketServer.TCPServer.server_bind(self)
host, port = self.socket.getsockname()[:2]
self.server_name = socket.getfqdn(host) # 獲得計算機名稱
self.server_port = port
class BaseHTTPRequestHandler(SocketServer.StreamRequestHandler):
default_request_version = "HTTP/0.9" # 預設時HTTP請求的版本
# 解析HTTP請求的第一行,以及HTTP請求頭(該項由mimetools.Message類完成)
def parse_request(self):
self.command = None # 儲存了HTTP請求方式,GET、POST...
self.close_connection = 1
requestline = self.raw_requestline.rstrip('\r\n')
words = requestline.split() # HTTP請求第一行由三部分組成:方式、路徑、協議,如GET /index HTTP/1.1
if len(words) == 3:
command, path, version = words
if version[:5] != 'HTTP/':
self.send_error(400, "Bad request version (%r)" % version)
return False
try:
base_version_number = version.split('/', 1)[1]
version_number = base_version_number.split(".")
if len(version_number) != 2:
raise ValueError
version_number = int(version_number[0]), int(version_number[1])
except (ValueError, IndexError):
self.send_error(400, "Bad request version (%r)" % version)
return False
# HTTP1.0需要"Connection:keep-alive"才能支援KeepAlive,而HTTP1.1預設支援
if version_number >= (1, 1) and self.protocol_version >= "HTTP/1.1":
self.close_connection = 0
if version_number >= (2, 0):
self.send_error(505,
"Invalid HTTP Version (%s)" % base_version_number)
return False
elif len(words) == 2: # 應該是為了支援HTTP/0.9吧
...
elif not words:
return False
else:
self.send_error(400, "Bad request syntax (%r)" % requestline)
return False
# 依次為:請求方式、請求相對路徑、請求協議
self.command, self.path, self.request_version = command, path, version
# 採用mimetools.Message類解析HTTP請求頭
self.headers = self.MessageClass(self.rfile, 0)
# 處理Connection,這在HTTP/1.0和HTTP/1.1上有區別
conntype = self.headers.get('Connection', "")
if conntype.lower() == 'close':
self.close_connection = 1
elif (conntype.lower() == 'keep-alive' and
self.protocol_version >= "HTTP/1.1"):
self.close_connection = 0
return True
# 處理單次HTTP請求
def handle_one_request(self):
try:
# 讀取HTTP請求的第1行
self.raw_requestline = self.rfile.readline(65537)
if len(self.raw_requestline) > 65536:
...
self.send_error(414)
return
if not self.raw_requestline:
self.close_connection = 1
return
if not self.parse_request(): # 處理第1行請求,並處理HTTP請求頭
return
# 通過HTTP請求方式對映到上層相應的處理函式,如GET -> do_GET(),POST -> do_POST()
mname = 'do_' + self.command
if not hasattr(self, mname):
...
method = getattr(self, mname)
method()
self.wfile.flush() # 將socket寫快取中的資料都發送出去
except socket.timeout, e:
self.log_error("Request timed out: %r", e)
self.close_connection = 1
return
# serve_forever()中accept到socket後會丟到這個函式處理請求
def handle(self):
self.close_connection = 1
# 非常奇怪的設計,因為現在HTTP請求版本為1.1,Connection: keep-alive,如果HTTP響應
# 版本為1.1,則self.close_connection = 0,每個請求需要等待一會才結束
self.handle_one_request()
while not self.close_connection:
self.handle_one_request()
# 向客戶端響應請求錯誤
def send_error(self, code, message=None):
try:
short, long = self.responses[code]
except KeyError:
short, long = '???', '???'
if message is None:
message = short
explain = long
self.log_error("code %d, message %s", code, message)
content = (self.error_message_format %
{'code': code, 'message': _quote_html(message), 'explain': explain})
self.send_response(code, message)
self.send_header("Content-Type", self.error_content_type)
self.send_header('Connection', 'close')
self.end_headers()
if self.command != 'HEAD' and code >= 200 and code not in (204, 304):
self.wfile.write(content)
error_message_format = DEFAULT_ERROR_MESSAGE
error_content_type = DEFAULT_ERROR_CONTENT_TYPE
# 進行HTTP響應,傳送響應第1行,包括三部分:版本、狀態碼、狀態訊息,例如“HTTP/1.0 200 OK”
# 還發送部分響應頭,Server和Data
def send_response(self, code, message=None):
self.log_request(code)
if message is None:
if code in self.responses:
message = self.responses[code][0]
else:
message = ''
if self.request_version != 'HTTP/0.9':
self.wfile.write("%s %d %s\r\n" %
(self.protocol_version, code, message))
# print (self.protocol_version, code, message)
self.send_header('Server', self.version_string())
self.send_header('Date', self.date_time_string())
# 傳送單個HTTP響應報頭
def send_header(self, keyword, value):
if self.request_version != 'HTTP/0.9':
self.wfile.write("%s: %s\r\n" % (keyword, value))
if keyword.lower() == 'connection':
if value.lower() == 'close':
self.close_connection = 1
elif value.lower() == 'keep-alive':
self.close_connection = 0
# 傳送HTTP響應報頭結束後的空行
def end_headers(self):
if self.request_version != 'HTTP/0.9': self.wfile.write("\r\n")
# 返回服務端版本號
def version_string(self):
return self.server_version + ' ' + self.sys_version
protocol_version = "HTTP/1.0" # 服務端響應時的HTTP協議
MessageClass = mimetools.Message # 解析HTTP請求報頭資訊類
responses = {
100: ('Continue', 'Request received, please continue'),
101: ('Switching Protocols',
'Switching to new protocol; obey Upgrade header'),
...
}
# '__main__'
server_address = ('', 8000)
httpd = HTTPServer(server_address, BaseHTTPRequestHandler)
sa = httpd.socket.getsockname()
print "Serving HTTP on", sa[0], "port", sa[1], "..."
httpd.serve_forever()