網路編成-2 === ### 簡單複習 - 四層 - 應用層: 傳什麼數據 - 傳輸層: - 如何傳輸數據tcp/udp - 可以理解為『快遞公司』 - 網絡層: IP, 可以理解為地理位置 - 鏈路層: 具體的傳輸工具 - Client & Server - C: socket>bind>listen>accept>send>recv>close - S: socket>connect>send>recv>close - connect與accept返回的Socket進行通訊 - connect後開始執行三次握手 - Q. Code沒有三次握手, 那誰完成的呢? - OS完成的, 其依照協議完成後封裝成Socket, 故我們使用Socket時會完成自動完成這些工作 - Q. 為何必須使用accept返回的socket來與client通訊?能自己寫嗎? - 如果只是寫簡單的socket、bind是無法的, 還有許多os寫好的, 如三次揮手的數據(seq, ack)等 - close後開始四次揮手 - 應用層處理的事send<>recv這段, 當傳輸內容不僅是簡單的文檔時, 應按照特定協議來完成各式功能 - ftp: 傳輸文件 - ssh: 遠程連接 - smyp,pop3: 郵件 - Http HTTP --- ### HTML: 整體網頁框架 `$ vi index.html` ```htmlembedded <htm> <head> <title>包軌</title> </head> <body> <h1>Toyz</h1> <p>喊在</p> </body> </htm> ``` ### CSS: 更改標籤的默認行為 ### JavaScript: 腳本 - 與瀏覽器互動 - 過去JS因為解析器運行效率太低而快淘汰, 直到[V8引擎](https://zh.wikipedia.org/wiki/V8_(JavaScript%E5%BC%95%E6%93%8E))誕生 ### 瀏覽器工作? - 依照HTML語法將HTML文本顯示 - 各瀏覽器的區別在於其解析算法 - 主流核心 - IE 6/7/8/9/10 - FireFox(Chrome) - Opera - Safari - Edge - 不同核心支持的內容或顯示方式可能不同 - JavaScript解析器 - Socket - 當我們打開瀏覽器進到一個網站, 意即瀏覽器是客戶端, 而socket,connect由瀏覽器完成 > \# Request Headers > > GET / HTTP/1.1 >- HTTP請求方式 > - GET: 請求數據 > - POST: 修改數據 > - PUT: 保存數據 > - DELETE: 刪除 > - OPTION: 詢問特性 > - HEAD: 返回報文頭 >- 請求方式(method)+路徑(path)+傳輸協定(protocol) >- 第一行稱為起始行(請求行), 必備的一行 > > Host: www.baidu.com > Connection: keep-alive > Upgrade-Insecure-Requests: 1 > User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/75.0.3770.100 Safari/537.36 > Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3 > Referer: http://baidu.com/ > Accept-Encoding: gzip, deflate, br > Accept-Language: en-US,en;q=0.9,zh-TW;q=0.8,zh;q=0.7 > Cookie: ZD_ENTRY=google; BAIDUID=1319AEEE912E7B348E22D51093F80C28:FG=1; BIDUPSID=1319AEEE912E7B348E22D51093F80C28; PSTM=1561451361; delPer=0; BD_HOME=0; BD_UPN=123253; BDORZ=B490B5EBF6F3CD402E515D22BCDA1598; BDRCVFR[QxxZVyx49rf]=I67x6TjHwwYf0; BD_CK_SAM=1; PSINO=7; H_PS_PSSID=1459_21123_29135_29237_28518_29098_29131_28836_29220 >- 後面整段稱為[協議頭](https://zh.wikipedia.org/wiki/HTTP%E5%A4%B4%E5%AD%97%E6%AE%B5)(請求頭): 描述本次請求的相關訊息 >- 整個數據為一般的文本, 以\r\n換行 - Q. 傳資料(ex.圖片, 帳密)給服務器的運作方式? > H_PS_PSSID=1459_21123_29135_29237_28518_29098_29131_28836_29220\r\n > Content-Length: xxx\r\n >> 傳數據時, 在協議頭會告訴服務器傳多少數據 > \r\n > 01010100010 > password: > - 在協議頭的後面以\r\n分開後傳輸資訊 > - 此處稱為請求體 ### 服務器開發: - recv<>send 之間 - 收到數據後處理發回 > HTTP/1.1 200 OK >> [HTTP狀態碼](https://zh.wikipedia.org/wiki/HTTP%E7%8A%B6%E6%80%81%E7%A0%81) > > Bdpagetype: 1 > Bdqid: 0xa7bfeeaa003fc2a1 > Cache-Control: private > Connection: Keep-Alive > Content-Encoding: gzip > Content-Type: text/html > Cxy_all: baidu+dfde13153c0ed9b0cdfae51b54b6724a > Date: Wed, 26 Jun 2019 03:05:44 GMT > Expires: Wed, 26 Jun 2019 03:05:00 GMT > Server: BWS/1.1 > Set-Cookie: delPer=0; path=/; domain=.baidu.com > Set-Cookie: BDSVRTM=0; path=/ > Set-Cookie: BD_HOME=0; path=/ > Set-Cookie: H_PS_PSSID=1459_21123_29135_29237_28518_29098_29131_28836_29220; path=/; domain=.baidu.com > Strict-Transport-Security: max-age=172800 > Vary: Accept-Encoding > X-Ua-Compatible: IE=Edge,chrome=1 > Transfer-Encoding: chunked\r\n > \r\n > <html\> > <title\><title\\> > <html\\> >> 服務器發回來也在\r\n之後(請求體)發送數據 >> 瀏覽器接收數據後顯示出來 - HTTP就是規定這些數據如何收發 - [URI](https://zh.wikipedia.org/wiki/%E7%BB%9F%E4%B8%80%E8%B5%84%E6%BA%90%E6%A0%87%E5%BF%97%E7%AC%A6) - [URL](https://zh.wikipedia.org/wiki/%E7%BB%9F%E4%B8%80%E8%B5%84%E6%BA%90%E5%AE%9A%E4%BD%8D%E7%AC%A6) - [URN](https://zh.wikipedia.org/wiki/%E7%BB%9F%E4%B8%80%E8%B5%84%E6%BA%90%E5%90%8D%E7%A7%B0) - 當我搜尋時, 會返回搜索頁面, 由於**標準的GET不會有請求體**, 而此時搜索頁面的關鍵詞描述訊息會放到URL中 > https://www.baidu.com/s?&wd=64&ie=utf-8 >- https://www.baidu.com/s為搜索頁面 >- 後面用?來傳遞參數 >- Query String Parameters: &wd=64&ie=utf-8 >- 其他(ex.POST,PUT)除了用這方式,還可以用請求體傳送 - 查詢字符串QueryString - ?起始 - &為下一個參數 - 格式: ?key1=value1&key2=value2 - 此str是有大小限制 - HTTP是[無狀態協議](https://zh.wikipedia.org/wiki/%E6%97%A0%E7%8A%B6%E6%80%81%E5%8D%8F%E8%AE%AE) - HTTP舊版是短鏈接, 傳送一次即關閉 - 未來會往長鏈接發展, 但HTTP依舊是無狀態 - 有些APP也是使用HTTP協議 ### 搜索引擎公司 #### 爬蟲 ```sequence 爬蟲->Google: HTTP GET Google-->爬蟲: Index.html Note left of 爬蟲: 拿到頁面資源後開始解析後\n主動對頁面中的鏈結發送請求 爬蟲->A:HTTP GET A-->爬蟲: xxx.html Note left of 爬蟲: 不僅主動訪問, \n並會將頁面訊息存至數據庫中 ``` ## 靜態server #### 構建架構 ```python # coding=utf-8 from socket import * from multiprocessing import Process def handleClient(newSocket): request_data = newSocket.recv(1024) print("request data:", request_data) # newSocket.send(("HTTP/1.1 200 OK\r\n\r\nGodJJ").encode("utf-8")) newSocket.send(bytes("HTTP/1.1 200 OK\r\n\r\nGodJJ", "utf-8")) newSocket.close() def main(): # AF_INET > 常量 # 通常使用大寫字母表示常量 s = socket(AF_INET, SOCK_STREAM) s.setsockopt(SOL_SOCKET, SO_REUSEADDR, 1) s.bind(("", 5566)) s.listen(5) while True: newSocket, cliInfo = s.accept() # print("[%s,%s]連上了"%(cliInfo[0], cliInfo[1])) print("[%s,%s]連上了"%cliInfo) p = Process(target=handleClient, args=(newSocket,)) p.start() newSocket.close() if __name__ == "__main__": main() ``` ##### [常量](https://zh.wikibooks.org/zh-tw/Java/%E5%8F%98%E9%87%8F%E4%B8%8E%E5%B8%B8%E9%87%8F) > 127.0.0.1:5566 >> HTTP/1.1 200 OK >> Server: Toyz >> GodJJ >> #### 解析request_data ```python In [1]: s = "GET / HTTP/1.1\r\nHost: 127.0.0.1:5566\r\nCon" In [2]: s.splitlines() Out[2]: ['GET / HTTP/1.1', 'Host: 127.0.0.1:5566', 'Con'] In [1]: import re In [2]: re.match(r"\w+ +/[^ ]* ", 'GET / HTTP/1.1') Out[2]: <re.Match object; span=(0, 6), match='GET / '> In [3]: re.match(r"\w+ +(/[^ ]*) ", 'GET / HTTP/1.1').group(1) Out[3]: '/' In [4]: re.match(r"\w+ +(/[^ ]*) ", 'GET /abc.py HTTP/1.1').group(1) Out[4]: '/abc.py' ``` ```python # coding=utf-8 import re from socket import * from multiprocessing import Process # 設置靜態文件根目錄 HTML_ROOT_DIR = "./html" def handle_client(newSocket): # 獲取客戶端請求數據 request_data = newSocket.recv(1024) # print(request_data) request_lines = request_data.splitlines() # for line in request_lines: # print(line) # 解析請求報文 request_start_line = request_lines[0] # 提取請求文件名 # 'GET / HTTP/1.1' request_file_name = re.match(r"\w+ +(/[^ ]*) ", request_start_line.decode("utf-8")).group(1) # if request_file_name == "/" # 通常會反過來寫, 避免少寫一個=而沒發現 # ""不可變, 一個等號會有返回值, 此時解析器就會報錯而發現 # 如果是 if xx = "", xx僅為變量, 不會有報錯 # 所以判斷式通常會把變量寫到右邊 if "/" == request_file_name: request_file_name = "/index.html" try: # 檔案不一定是純文檔, 用二進制開最保險 file = open(HTML_ROOT_DIR + request_file_name, "rb") except IOError: response_start_line = "HTTP/1.1 404 Not Found\r\n" response_headers = "Server: My Server\r\n" response_body = "The file is Not Found" else: file_data = file.read() file.close() response_start_line = "HTTP/1.1 200 OK\r\n" response_headers = "Server: My Server\r\n" response_body = file_data.decode("utf-8") finally: response_data = response_start_line + response_headers + "\r\n" + response_body newSocket.send(bytes(response_data, "utf-8")) newSocket.close() def main(): s = socket(AF_INET, SOCK_STREAM) # set socket option # SOL > Socket option level # REUSEADDR > re use addr s.setsockopt(SOL_SOCKET, SO_REUSEADDR, 1) s.bind(("", 5566)) s.listen(5) while True: newSocket, cliInfo = s.accept() # print("[%s,%s]連上了"%(cliInfo[0], cliInfo[1])) print("[%s,%s]連上了"%cliInfo) p = Process(target=handle_client, args=(newSocket,)) p.start() newSocket.close() if __name__ == "__main__": main() ``` #### 整理一下 ```python # coding=utf-8 import re from socket import * from multiprocessing import Process # 設置靜態文件根目錄 HTML_ROOT_DIR = "./html" class HTTPServer(object): """""" def __init__(self): self.s = socket(AF_INET, SOCK_STREAM) self.s.setsockopt(SOL_SOCKET, SO_REUSEADDR, 1) def start(self): self.s.listen(5) while True: newSocket, cliInfo = self.s.accept() print("[%s,%s]連上了"%cliInfo) p = Process(target=self.handle_client, args=(newSocket,)) p.start() newSocket.close() def handle_client(self ,newSocket): # 獲取客戶端請求數據 request_data = newSocket.recv(1024) request_lines = request_data.splitlines() for line in request_lines: print(line) # 解析請求報文 request_start_line = request_lines[0] # 提取請求文件名 request_file_name = re.match(r"\w+ +(/[^ ]*) ", request_start_line.decode("utf-8")).group(1) if "/" == request_file_name: request_file_name = "/index.html" try: file = open(HTML_ROOT_DIR + request_file_name, "rb") except IOError: response_start_line = "HTTP/1.1 404 Not Found\r\n" response_headers = "Server: My Server\r\n" response_body = "The file is Not Found" else: file_data = file.read() file.close() response_start_line = "HTTP/1.1 200 OK\r\n" response_headers = "Server: My Server\r\n" response_body = file_data.decode("utf-8") finally: response_data = response_start_line + response_headers + "\r\n" + response_body newSocket.send(bytes(response_data, "utf-8")) newSocket.close() def bind(self, port): self.s.bind(("", port)) def main(): http_server = HTTPServer() http_server.bind(5566) http_server.start() if __name__ == "__main__": main() ``` ### 補充 - `open(xx, "rb")` 文本與二進制有何區別? - 對於文本\r\n 為換行 - linux, unix > \n - 新mac > \n, 舊mac > \r - windows > \r\n - 對於二進制, 純粹為\r\n `CMD` ```python >>> python >>> f = open("wb", "wb") >>> f.write(b"ha\nha") 5 >>> f.close() >>> >>> f = open("w", "w") >>> f.write("ha\nha") 5 >>> f.close() >>> >>> f = open("wb", "rb") >>> f.read() b'ha\nha' >>> f.close() >>> >>> f = open("w", "rb") >>> f.read() b'ha\r\nha' >>> # 系統在解析文本時, 會自動轉換 >>>f.close() ``` - Terminal > CTRL Z: 把程序放到背景,而非殺死 ``` $ python3 a.py ^Z [1]+ Stopped python3 a.py # 查看背景 bg $ bg [1]+ python3 a.py & # 叫出來 fg $ fg python3 a.py ``` ## 動態Server ### WSGI - 需有一個同名函數或類能夠被調用, 以下用application說明 - class > `def __call__` - 該對象應該產生一個響應值, 返回給服務器接收 - 服務器就可以返回給前端 - WSGI要求return只能傳響應體(body) - 執行時, 需傳兩個參數 `application(environ, start_response)` - environ - 在執行application時, 可能需要用到客戶端傳回來的相關信息 - 故程序就需要有個變量來接收 - {} - `def start_response(status_code, [("k1","v1"),("k2","v2"),...])` - start_response是一個函數 - 由於return 只能傳 body, 而響應頭即在此函數處理 - start_response傳兩個參數 - status_code - 200 404 ... - [("K1", "V1"),("K2", "V2")] - 響應頭可能會有好幾個,分別放入元組 ```python # 判斷 In [1]: a = "fajdlfksjd" In [5]: a.endswith("jd") Out[5]: True In [6]: a.endswith("a") Out[6]: False In [7]: a.startswith("a") Out[7]: False In [8]: a.startswith("fa") Out[8]: True # 切片 In [2]: s = "/time.py" In [3]: s[1:-3] Out[3]: 'time' # 搜索路徑 In [4]: import sys In [5]: sys.path Out[5]: ['/Library/Frameworks/Python.framework/Versions/3.7/bin', ... 'packages/IPython/extensions', '/Users/haha/.ipython'] ``` `vi server.py` ```python # coding=utf-8 import re import sys from socket import * from multiprocessing import Process HTML_ROOT_DIR = "./html" # WSGI路徑 WSGI_PYTHON_DIR = "./wsgipython" class HTTPServer(object): """""" def __init__(self): self.s = socket(AF_INET, SOCK_STREAM) self.s.setsockopt(SOL_SOCKET, SO_REUSEADDR, 1) def start(self): self.s.listen(5) while True: newSocket, cliInfo = self.s.accept() print("[%s,%s]連上了"%cliInfo) p = Process(target=self.handle_client, args=(newSocket,)) p.start() newSocket.close() # 處理狀態碼跟響應頭, 需有兩個參數來接收 def start_response(self, status, headers): """從time.py接收了兩個東西 status = "200 OOK" headers = [ ("Content-Type", "text/plain") ] """ response_headers = "HTTP/1.1 " + status + "\r\n" for header in headers: response_headers += "%s: %s\r\n" % header self.response_headers = response_headers def handle_client(self ,newSocket): request_data = newSocket.recv(1024) request_lines = request_data.splitlines() #for line in request_lines: # print(line) request_start_line = request_lines[0] request_file_name = re.match(r"\w+ +(/[^ ]*) ", request_start_line.decode("utf-8")).group(1) print(request_file_name) # /ctime.py if request_file_name.endswith(".py"): # 導入模塊, 當模塊經常變換時,可以使用__import__(module_name) # 使用切片取出模塊名[1:-3] m = __import__(request_file_name[1:-3]) env = {} response_body = m.application(env, self.start_response) response_data = self.response_headers + "\r\n" + response_body else: if "/" == request_file_name: request_file_name = "/index.html" try: file = open(HTML_ROOT_DIR + request_file_name, "rb") except IOError: response_start_line = "HTTP/1.1 404 Not Found\r\n" response_headers = "Server: My Server\r\n" response_body = "The file is Not Found" else: file_data = file.read() file.close() response_start_line = "HTTP/1.1 200 OK\r\n" response_headers = "Server: My Server\r\n" response_body = file_data.decode("utf-8") response_data = response_start_line + response_headers + "\r\n" + response_body print(response_data) newSocket.send(bytes(response_data, "utf-8")) newSocket.close() def bind(self, port): self.s.bind(("", port)) def main(): sys.path.insert(1, WSGI_PYTHON_DIR) #print(sys.path) http_server = HTTPServer() http_server.bind(5566) http_server.start() if __name__ == "__main__": main() ``` `mkdir wsgipython` `vi ./wsgipython/ctime.py` ```python #coding:utf-8 import time def application(env, start_response): # 請求有可能有參數, 此時就可以從env取得來處理 # env.get("METHOD") # env.get("PATH_INFO) status = "200 OK" headers = [ ("Content-Type", "text/plain") ] start_response(status, headers) return time.ctime() ``` #### 講解 > Q. 為何響應頭要在application裡面完成, 不能在服務器中完成嗎? - 當然可以在服務器中完成, - 但動態網頁的成功失敗或回傳的類型為何等, 通常是在appli中決定的 - 故邏輯上應該是在appli中告訴服務器stuts, type...等 > 流程 ```sequence note left of server.py: 接收cli請求 note left of server.py: accept() note left of server.py: handle_cli() note left of server.py: rq_data = recv() note left of server.py: f_name = re.match() note left of server.py: path = re.match() note left of server.py: f_name.endswith(.py) note left of server.py: m = __import__(m_name) note left of server.py: env={}, def s(status,headers) note right of ctime.py: def application(env, s) note left of server.py: m.application(env, s) server.py->ctime.py: env, s note right of ctime.py: status = "200 OK" note right of ctime.py: headers = [("K1","V1"),(K2,V2)] note right of ctime.py: s(status, headers) ctime.py->server.py: s,h note left of server.py: def s(status, headers) note left of server.py: self.respon = s+h server.py->ctime.py: note right of ctime.py: return time.ctime() ctime.py->server.py: ctime() note left of server.py: body = m.appli(env,s) note left of server.py: send(self.respon+body) ``` ## 框架 ` vi framework.py` ```python #coding:utf-8 import time from WebServer import HTTPServer HTML_ROOT_DIR = "./html" # 思考: 框架是要給別人使用, # 要如何讓別人在不變動框架的前提下傳入urls> class class application(object): """框架核心""" def __init__(self, urls): self.urls = urls # 讓類可以被調用, # 調用時, 相當於調用__call__ def __call__(self, env, start_response): path = env.get("PATH_INFO", "/") # 有傳就取, 沒傳默認/ # 框架規定靜態語言以/static/ 開頭 > 與urls區分 # /ststic/index.html if path.startswith("/static/"): request_file_name = path[7:] try: file = open(HTML_ROOT_DIR + request_file_name, "rb") except IOError: status = "404 Not Found" headers = [] start_response(status, headers) return "Not Found" else: file_data = file.read() file.close() status = "200 OK" headers = [] start_response(status, headers) return file_data.decode("utf-8") for url, handler in self.urls: # ("/ctime", handle_ctime) if path == url: return handler(env, start_response) status = "404 Not Found" headers = [] start_response(status, headers) return "Not Found" def handle_ctime(env, start_response): status = "200 OK" headers = [ ("Content-Type", "text/plain") ] start_response(status, headers) return time.ctime() def handle_haha(env, start_response): status = "200 OK" headers = [ ("Content-Type", "text/plain") ] start_response(status, headers) return "haha" def main(): urls = [ ("/", handle_ctime), ("/ctime", handle_ctime), ("/haha", handle_haha) ] app = application(urls) http_server = HTTPServer(app) http_server.bind(5566) http_server.start() if __name__ == "__main__": main() ``` `vi WebServer` ```python # coding=utf-8 import re import sys from socket import * from multiprocessing import Process HTML_ROOT_DIR = "./html" # WSGI路徑 WSGI_PYTHON_DIR = "./wsgipython" class HTTPServer(object): """""" def __init__(self, application): """application指向框架app""" self.s = socket(AF_INET, SOCK_STREAM) self.s.setsockopt(SOL_SOCKET, SO_REUSEADDR, 1) self.app = application def start(self): self.s.listen(5) while True: newSocket, cliInfo = self.s.accept() print("[%s,%s]連上了"%cliInfo) p = Process(target=self.handle_client, args=(newSocket,)) p.start() newSocket.close() # 處理狀態碼跟響應頭, 需有兩個參數來接收 def start_response(self, status, headers): response_headers = "HTTP/1.1 " + status + "\r\n" for header in headers: response_headers += "%s: %s\r\n" % header self.response_headers = response_headers def handle_client(self ,newSocket): request_data = newSocket.recv(1024) request_lines = request_data.splitlines() request_start_line = request_lines[0] request_file_name = re.match(r"\w+ +(/[^ ]*) ", request_start_line.decode("utf-8")).group(1) print(request_file_name) env = { "PATH_INFO": request_file_name, } response_body = self.app(env, self.start_response) response_data = self.response_headers + "\r\n" + response_body print(response_data) newSocket.send(bytes(response_data, "utf-8")) newSocket.close() def bind(self, port): self.s.bind(("", port)) ``` ```python # [] vs get() In [1]: a = {"a":2, "b":4} # 如果{}裡面沒有相應K, get()不會報錯 In [3]: a.get("a") Out[3]: 2 In [4]: a.get("c") # {} 沒有相應K, []會報錯 In [5]: a["a"] Out[5]: 2 In [6]: a["c"] KeyError: 'c' # get()默認值 # 如果找無k, 默認cc In [8]: a.get("c", "cc") Out[8]: 'cc' In [9]: a.get("a", "cc") Out[9]: 2 ``` > - 一般來說應該是WebServer去對接框架. 亦即, WebServer導入framework, 以WebServer啟動 > - 惟, 以導入的方式不方便, 因為如果要對接別的框架, 那還要再改服務器代碼 > - 需求: 直接在命令行中指定對接 > - 即$ python3 Webserver.py xx:xxx #### 說明 ```python import sys print(sys.argv) ``` ``` $ python3 arg.py a b c ['arg.py', 'a', 'b', 'c'] # 可知如果要取a 應該是sys.argv[1] , 以此類推 ``` ```python # split In [1]: a = "kkbox:pikachu" In [2]: a.split(":") Out[2]: ['kkbox', 'pikachu'] In [3]: b,c = a.split(":") In [4]: b Out[4]: 'kkbox' In [5]: c Out[5]: 'pikachu' # getattr In [1]: cat test1.py class xxx(object): def __call__(self): return "xxx" In [2]: import test1 In [3]: c = getattr(test1, "xxx") In [4]: c Out[4]: test1.xxx In [5]: d = c() In [10]: c() Out[10]: <test1.xxx at 0x10bceec50> In [11]: d() Out[11]: 'xxx' ``` `$ vi WebServer.py` ```python3 # coding=utf-8 import re import sys from socket import * from multiprocessing import Process class HTTPServer(object): """""" def __init__(self, application): """application指向框架app""" self.s = socket(AF_INET, SOCK_STREAM) self.s.setsockopt(SOL_SOCKET, SO_REUSEADDR, 1) self.app = application def start(self): self.s.listen(128) while True: newSocket, cliInfo = self.s.accept() print("[%s,%s]連上了"%cliInfo) p = Process(target=self.handle_client, args=(newSocket,)) p.start() newSocket.close() # 處理狀態碼跟響應頭, 需有兩個參數來接收 def start_response(self, status, headers): response_headers = "HTTP/1.1 " + status + "\r\n" for header in headers: response_headers += "%s: %s\r\n" % header self.response_headers = response_headers def handle_client(self ,newSocket): request_data = newSocket.recv(1024) request_lines = request_data.splitlines() request_start_line = request_lines[0] request_file_name = re.match(r"\w+ +(/[^ ]*) ", request_start_line.decode("utf-8")).group(1) print(request_file_name) env = { "PATH_INFO": request_file_name, } response_body = self.app(env, self.start_response) response_data = self.response_headers + "\r\n" + response_body print(response_data) newSocket.send(bytes(response_data, "utf-8")) newSocket.close() def bind(self, port): self.s.bind(("", port)) def main(): # $ python3 WebServer.py FrameWork:app # IndexError: list index out of range if len(sys.argv) < 2: sys.exit("python3 WebServer.py FrameWork:app") module_name, app_name = sys.argv[1].split(":") # module_name = "FrameWork" # app_name = "app" m = __import__(module_name) # m.app_name()? > FrameWork沒有app_name這東西 app = getattr(m, app_name) # "m.application" http_server = HTTPServer(app) http_server.bind(5566) http_server.start() if __name__ == "__main__": main() ``` `$ vi FrameWork.py` ```python #coding:utf-8 import time HTML_ROOT_DIR = "./html" # 思考: 框架是要給別人使用, 要如何讓別人在不變動框架的前提下傳入data> class class application(object): """框架核心""" def __init__(self, urls): self.urls = urls # 讓類可以被調用, # 調用時, 相當於調用了類中的__call__ def __call__(self, env, start_response): path = env.get("PATH_INFO", "/") # 有傳就取, 沒傳默認/ # 框架規定靜態語言以/static/ 開頭 # /ststic/index.html if path.startswith("/static/"): request_file_name = path[7:] try: file = open(HTML_ROOT_DIR + request_file_name, "rb") except IOError: status = "404 Not Found" headers = [] start_response(status, headers) return "Not Found" else: file_data = file.read() file.close() status = "200 OK" headers = [] start_response(status, headers) return file_data.decode("utf-8") for url, handler in self.urls: # ("/ctime", handle_ctime) if path == url: return handler(env, start_response) status = "404 Not Found" headers = [] start_response(status, headers) return "Not Found" def handle_ctime(env, start_response): status = "200 OK" headers = [ ("Content-Type", "text/plain") ] start_response(status, headers) return time.ctime() def handle_haha(env, start_response): status = "200 OK" headers = [ ("Content-Type", "text/plain") ] start_response(status, headers) return "haha" urls = [ ("/", handle_ctime), ("/ctime", handle_ctime), ("/haha", handle_haha) ] app = application(urls) ``` ```$ $ python3 WebServer.py python3 WebServer.py FrameWork:app $ python3 WebServer.py FrameWork:app [127.0.0.1,60784]連上了 [127.0.0.1,60785]連上了 /static/ HTTP/1.1 404 Not Found Not Found [127.0.0.1,60814]連上了 / HTTP/1.1 200 OK Content-Type: text/plain Thu Jul 18 13:18:44 2019 ```