完整源代碼 https://github.com/skyerhxx/HttpServer
簡易HTTP服務器開發項目
開發環境
Python 3.7
IDE:Pycharm
功能
- 能訪問 127.0.0.1:9999
- 能訪問 127.0.0.1:9999/index.html
- 能夠用戶名和密碼登錄127.0.0.1:9999/index.html,然後頁面會自動跳轉到另一個頁面
最終項目目錄結構
http服務器是用到了TCP協議和HTTP協議
http server是在TCP server的基礎上加了一些功能,這也正好對應了HTTP協議是TCP協議的上層
我們說的Web服務器就是HTTP服務器
傳輸層TCP協議
面向TCP協議的套接字服務端編程
實現TCPServer和StreamRequestHandler
實現網絡服務器TCPServer類
初始化 服務器地址、處理請求類、套接字
啓動服務器
接受請求
處理請求
關閉連接關閉服務器
socket_server.py
#實現TCPServer類 import socket class TCPServer: def __init__(self,server_address,handler_class): self.server_address = server_address self.HandlerClass = handler_class #處理請求的類 self.socket = socket.socket(socket.AF_INET,socket.SOCK_STREAM) self.is_shutdown = False #服務器的啓動函數 def serve_forever(self): self.socket.bind(self.server_address) #綁定服務器地址 self.socket.listen(10) #啓動並監聽 #while True: while not self.is_shutdown: #1.接收請求 request, client_address = self.get_request() #2.處理請求 try: self.process_request(request, client_address) except Exception as e: print(e) finally: #3.關閉連接 self.close_request(request) #接受請求 def get_request(self): return self.socket.accept() #處理請求 def process_request(self, request, client_address): handler = self.HandlerClass(request, client_address) handler.handle() #關閉請求 def close_request(self,request): request.shutdown() request.close() #關閉服務器 def shutdown(self): self.is_shutdown = True
實現網絡請求處理器Handler類
轉換字節碼用一個bytes()就行了
緩存是一個自己定義的list
base_handler.py
# -*- encoding=utf-8 -*- class BaseRequestHandler: def __init__(self, server, request, client_address): self.server = server self.request = request self.client_address = client_address def handle(self): #不需要做任何工作,主要由後面繼承它的類去做相關工作 pass #功能:編碼、解碼、讀寫消息 class StreamRequestHandler(BaseRequestHandler): def __init__(self, server, request, client_address): BaseRequestHandler.__init__(self, server, request, client_address) self.rfile = self.request.makefile('rb') self.wfile = self.request.makefile('wb') self.wbuf = [] # 編碼 # 字符串—>字節碼 def encode(self, msg): if not isinstance(msg, bytes): #如果不是字節碼就編碼成字節碼 msg = bytes(msg, encoding='utf-8') return msg # 解碼 # 字節碼—>字符串 def decode(self, msg): if isinstance(msg, bytes): msg = msg.decode() return msg # 讀消息 def read(self, length): msg = self.rfile.read(length) return self.decode(msg) # 讀取一行消息 def readline(self, length=65536): #65536是http請求報文的最大長度 msg = self.rfile.readline(length).strip() return self.decode(msg) # 寫消息 #接受內容,然後寫到緩存裏面 def write_content(self, msg): msg = self.encode(msg) #把字符串轉成字節碼 self.wbuf.append(msg) # 發送消息 def send(self): for line in self.wbuf: self.wfile.write(line) self.wfile.flush() self.wbuf = [] #發送完之後清空緩衝區 def close(self): self.wfile.close() self.rfile.close()
編寫網絡服務器的測試用例
測試前面實現的TCPServer和Handler是否可以正常工作
編寫客戶端來連接服務端,測試服務端是否可以正常工作
改進成多線程
此時項目結構
應用層HTTP協議
HTTP服務器實現
實現一個支持HTTP協議的Web服務器
Handler纔是真正處理HTTP請求的處理的類
實現BaseHTTPRequestHandler
繼承base_handler
HTTP請求報文格式
base_http_handler.py
import logging from handler.base_handler import StreamRequestHandler logging.basicConfig(level=logging.DEBUG, format='%(asctime)s - %(filename)s[line:%(lineno)d] - %(levelname)s: %(message)s') class BaseHTTPRequestHandler(StreamRequestHandler): def __init__(self, server, request, client_address): self.method = None self.path = None self.version = None self.headers = None self.body = None StreamRequestHandler.__init__(self,server, request, client_address) #請求的處理 def handle(self): try: #1、解析請求 if not self.parse_request(): return #2、方法執行(GET、POST) method_name = 'do_' + self.method #自檢判斷方法是否存在 if not hasattr(self,method_name): #發送錯誤 return method = getattr(self,method_name) method() #應答報文的封裝 #3、發送結果 self.send() except Exception as e: logging.exception(e) #解析請求頭 def parse_headers(self): #請求頭是以key:value的形式存在的 headers = {} while True: line = self.readline() #如果是空行,表示請求頭已經結束 if line: key,value = line.split(":",1) #這個1是分割次數,就是指分割一次 key = key.strip() value = value.strip() #將key,value保存到map中 headers[key] = value else: break return headers #解析請求 def parse_request(self): #1、解析請求行 #讀取第一行————請求行 first_line = self.readline() words = first_line.split() #請求方法、請求地址、請求的HTTP版本 self.method, self.path, self.version = words #2、解析請求頭 self.headers = self.parse_headers() #3、解析請求內容 #如果請求頭有內容,那麼它的長度將會保存在headers裏面 key = 'Content-Length' if key in self.headers.keys(): #請求內容的長度 body_length = int(self.headers[key]) self.body = self.read(body_length) return True
HTTP應答報文
編寫基礎HTTP服務器測試工作,測試其是否正常工作
新建base_http_server.py
在原來的test.py的基礎上
此時,用瀏覽器訪問localhost:9999
添加了個do_GET方法
可以看到這些我們寫的內容都能正常返回
到目前爲止,我們已經把一個基礎的web服務器搭建起來了,這個服務器可以
具備這三個功能,一個基礎的web服務器就已經實現了,我們項目的目的也已經達到了
此時的目錄結構
即HttpServer4就是一個可以實現基本功能的http服務器了
-----------------------------------------------------------------------------------------------------------------------
但是爲了更好的理解GET方法以及POST方法的工作原理
我們來編寫自定義HTTP應用之GET方法和POST方法
編寫自定義HTTP應用之GET方法
web服務器是怎樣把html頁面以及圖片返回給瀏覽器的,以及怎樣進行賬號和密碼的校驗
我們來看webserver是怎樣把資源/數據返回給瀏覽器的
新建simple_http_handler.py
編寫測試用例,看是否可用
新建simple_http_server.py
如果請求一個不存在的資源
GET方法
首先判斷資源是否存在
不存在直接返回404,存在的話,首先把資源的文件打開,讀取文件的長度,寫到頭部,再把內容讀取出來,寫到緩衝區。就是一行一行的讀。返回瀏覽器之所以能成網頁的界面是因爲要讀取的那個文件就是html的,http服務器並沒有怎麼處理
編寫自定義HTTP應用之POST方法
POST就是提交數據到服務器端進行處理,這裏以賬號密碼爲例
來看數據提交到後臺之後,後臺是怎麼來處理的
提交是用js來提交的
正確的用戶名密碼是 hxx 123456
給他設置的是登錄成功後會自動跳轉到我的博客
如果是錯誤的
至此,項目完結
此時項目結構
未來可以增添的功能
功能方面
①更完善的異常處理機制
狀態碼只利用了404狀態碼,還有很多其他的沒有用
②更豐富的功能支持
日誌、長連接、https安全協議
性能方面
①引入線程池
否則頻繁創建和銷燬線程會對性能造成一定的影響
②不使用多線程
用事件驅動引擎(epoll、select),改造成事件驅動的web服務器
參考: