1. 程式人生 > >BaseHTTPServer模組分析

BaseHTTPServer模組分析

因為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("&", "&amp;").replace("<", "&lt;").replace(">", "&gt;") 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()