python搭建簡單的靜態web服務器
[TOC]
儲備知識
- 一丟丟的python(io和多線程的知識)
- 一丟丟的http協議
- 一丟丟的tcp/ip協議(當然不了解也沒關系)
- 一丟丟的正則表達式知識
web服務器基本原理
-
當在瀏覽器的地址欄輸入一個ip與端口之后,瀏覽器就會通過tcp/ip協議與相應的主機端口進程建立聯系。經歷過三次握手之后就會將http請求發送到相應的服務器進程去,之前我們了解的http協議在服務進程收到的其實就是一串有特殊格式的字符串。
當我們瀏覽器輸入localhost:9876 后服務進程實際收到的如下:
大致流程
-
在服務端建立tcp服務進程,為了保證服務端可以同時處理多個請求,我們需要在每接受一個請求后為其單獨使用一個線程(或者進程)為其進行服務。
server = socket(AF_INET, SOCK_STREAM) server.setsockopt(SOL_SOCKET, SO_REUSEADDR, 1) address = ('', 9876) server.bind(address) server.listen(10) try: while True: print("-------等待接受服務----------") client, client_address = server.accept() print("-------接受服務成功----------") # 如果使用進程服務,可以在在后面把client關閉。 p = Thread(target=deal_socket, args=(client,)) p.start() except Exception as e: print(e) finally: server.close() print("-------服務結束----------")
這里的deal_socket函數就是我們為一個請求服務的函數,在一個單獨的線程中運行。
-
在處理url請求的函數中,我們需要讀取出客戶端的http請求。
def deal_socket(client): print("-------開啟新的線程----------") try: data = client.recv(1024) if len(data) > 0: fileName = get_request_name_from_http(data.decode("utf-8")) writeHtml(client, fileName) finally: client.close() print("-------關閉新的線程----------")
- 在這里的data就是我們讀取到的http服務請求,當其長度等于0時代表客戶端已經關閉了tcp連接。
- 這里的get_request_name_from_http()需要我們解析出請求的靜態資源
- 這里的writeHtml()將靜態文本寫回到客戶端。
-
在get_request_name_from_http函數中我們需要從原始的url請求中解析出http請求中我們需要的請求資源部分,這里我們可以通過正則表達式完成簡單的完成解析。
def get_request_name_from_http(http): # 注意這里通過非貪婪模式匹配 r = re.search(r"GET /(.+?) ", http) fileName = "" if r != None: fileName = r.group(1) return fileName
-
請求的http大概格式是這樣(當我們訪問http://localhost:9876/html/index.html時)
GET /html/index.html HTTP/1.1 Host: localhost:9876 Connection: keep-alive Cache-Control: max-age=0 Upgrade-Insecure-Requests: 1 User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/59.0.3071.109 Safari/537.36
第一行就是我們請求的靜態資源,我們將其通過非貪婪模式的正則表達式扣出來。
-
-
在解析到請求的靜態地址后就是簡單的讀取請求的文件,然后已http協議的格式返回回去就行了。
def writeHtml(client, fileName): rspHead = None rspBody = None if not os.path.exists(fileName): rspHead = "HTTP/1.1 404 error\r\nServer: foreverServer\r\n\r\n" rspBody = "file not found" else: rspHead = "HTTP/1.1 200 OK\r\nServer: foreverServer\r\n\r\n" html = open(fileName, 'r', encoding='UTF-8') rspBody = html.read() client.send((rspHead + rspBody).encode("utf-8"))
當請求的靜態文件不存在時,將返回給客戶端文件不存在。
-
上面的相應格式是根據http相應報文的格式而定的,否則瀏覽器會不識別:
?
在Windows中\r\n分別代表回車和換行,而現在在unix系統中\n就代表了回車換行。
?
完整代碼
-
下面是完整的服務代碼,不到60行就可以完成一個簡單的靜態web服務器,這就是python的魅力:
from socket import * from threading import Thread import os import re def get_request_name_from_http(http): r = re.search(r"GET /(.+?) ", http) fileName = "" if r != None: fileName = r.group(1) return fileName def writeHtml(client, fileName): rspHead = None rspBody = None if not os.path.exists(fileName): rspHead = "HTTP/1.1 404 error\r\nServer: foreverServer\r\n\r\n" rspBody = "file not found" else: rspHead = "HTTP/1.1 200 OK\r\nServer: foreverServer\r\n\r\n" html = open(fileName, 'r', encoding='UTF-8') rspBody = html.read() client.send((rspHead + rspBody).encode("utf-8")) def deal_socket(client): print("-------開啟新的線程----------") try: data = client.recv(1024) if len(data) > 0: fileName = get_request_name_from_http(data.decode("utf-8")) writeHtml(client, fileName) finally: client.close() print("-------關閉新的線程----------") def main(): server = socket(AF_INET, SOCK_STREAM) server.setsockopt(SOL_SOCKET, SO_REUSEADDR, 1) address = ('', 9876) server.bind(address) server.listen(10) try: while True: print("-------等待接受服務----------") client, client_address = server.accept() print("-------接受服務成功----------") # 就這里和多線程不同,并且千萬不能把client關掉 p = Thread(target=deal_socket, args=(client,)) p.start() except Exception as e: print(e) finally: server.close() print("-------服務結束----------") if name == "main": main()
-
到此就用python構建了史上最挫的靜態wen服務器了,直接在瀏覽其輸入靜態html請求就可以顯示網頁了:
雖然很挫,不過web服務器的基本原理就是如此,牛逼的服務器也只是在這之上做了很多完善,下一篇我們將采用python提供的WSGI標準完成一個動態的web框架。