python搭建簡(jiǎn)單的靜態(tài)web服務(wù)器
[TOC]
儲(chǔ)備知識(shí)
- 一丟丟的python(io和多線程的知識(shí))
- 一丟丟的http協(xié)議
- 一丟丟的tcp/ip協(xié)議(當(dāng)然不了解也沒(méi)關(guān)系)
- 一丟丟的正則表達(dá)式知識(shí)
web服務(wù)器基本原理
-
當(dāng)在瀏覽器的地址欄輸入一個(gè)ip與端口之后,瀏覽器就會(huì)通過(guò)tcp/ip協(xié)議與相應(yīng)的主機(jī)端口進(jìn)程建立聯(lián)系搞莺。經(jīng)歷過(guò)三次握手之后就會(huì)將http請(qǐng)求發(fā)送到相應(yīng)的服務(wù)器進(jìn)程去焚志,之前我們了解的http協(xié)議在服務(wù)進(jìn)程收到的其實(shí)就是一串有特殊格式的字符串析藕。
當(dāng)我們?yōu)g覽器輸入localhost:9876 后服務(wù)進(jìn)程實(shí)際收到的如下:
大致流程
-
在服務(wù)端建立tcp服務(wù)進(jìn)程椿肩,為了保證服務(wù)端可以同時(shí)處理多個(gè)請(qǐng)求,我們需要在每接受一個(gè)請(qǐng)求后為其單獨(dú)使用一個(gè)線程(或者進(jìn)程)為其進(jìn)行服務(wù)馏慨。
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("-------等待接受服務(wù)----------") client, client_address = server.accept() print("-------接受服務(wù)成功----------") # 如果使用進(jìn)程服務(wù)音婶,可以在在后面把client關(guān)閉。 p = Thread(target=deal_socket, args=(client,)) p.start() except Exception as e: print(e) finally: server.close() print("-------服務(wù)結(jié)束----------")
這里的deal_socket函數(shù)就是我們?yōu)橐粋€(gè)請(qǐng)求服務(wù)的函數(shù)倒谷,在一個(gè)單獨(dú)的線程中運(yùn)行蛛蒙。
-
在處理url請(qǐng)求的函數(shù)中,我們需要讀取出客戶端的http請(qǐng)求渤愁。
def deal_socket(client): print("-------開(kāi)啟新的線程----------") 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("-------關(guān)閉新的線程----------")
- 在這里的data就是我們讀取到的http服務(wù)請(qǐng)求牵祟,當(dāng)其長(zhǎng)度等于0時(shí)代表客戶端已經(jīng)關(guān)閉了tcp連接。
- 這里的get_request_name_from_http()需要我們解析出請(qǐng)求的靜態(tài)資源
- 這里的writeHtml()將靜態(tài)文本寫(xiě)回到客戶端抖格。
-
在get_request_name_from_http函數(shù)中我們需要從原始的url請(qǐng)求中解析出http請(qǐng)求中我們需要的請(qǐng)求資源部分诺苹,這里我們可以通過(guò)正則表達(dá)式完成簡(jiǎn)單的完成解析。
def get_request_name_from_http(http): # 注意這里通過(guò)非貪婪模式匹配 r = re.search(r"GET /(.+?) ", http) fileName = "" if r != None: fileName = r.group(1) return fileName
-
請(qǐng)求的http大概格式是這樣(當(dāng)我們?cè)L問(wèn)http://localhost:9876/html/index.html時(shí))
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
第一行就是我們請(qǐng)求的靜態(tài)資源雹拄,我們將其通過(guò)非貪婪模式的正則表達(dá)式扣出來(lái)筝尾。
-
-
在解析到請(qǐng)求的靜態(tài)地址后就是簡(jiǎn)單的讀取請(qǐng)求的文件,然后已http協(xié)議的格式返回回去就行了办桨。
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"))
當(dāng)請(qǐng)求的靜態(tài)文件不存在時(shí)筹淫,將返回給客戶端文件不存在。
-
上面的相應(yīng)格式是根據(jù)http相應(yīng)報(bào)文的格式而定的呢撞,否則瀏覽器會(huì)不識(shí)別:
?
在Windows中\(zhòng)r\n分別代表回車(chē)和換行损姜,而現(xiàn)在在unix系統(tǒng)中\(zhòng)n就代表了回車(chē)換行。
?
完整代碼
-
下面是完整的服務(wù)代碼殊霞,不到60行就可以完成一個(gè)簡(jiǎn)單的靜態(tài)web服務(wù)器摧阅,這就是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("-------開(kāi)啟新的線程----------") 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("-------關(guān)閉新的線程----------") 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("-------等待接受服務(wù)----------") client, client_address = server.accept() print("-------接受服務(wù)成功----------") # 就這里和多線程不同,并且千萬(wàn)不能把client關(guān)掉 p = Thread(target=deal_socket, args=(client,)) p.start() except Exception as e: print(e) finally: server.close() print("-------服務(wù)結(jié)束----------") if name == "main": main()
-
到此就用python構(gòu)建了史上最挫的靜態(tài)wen服務(wù)器了绷蹲,直接在瀏覽其輸入靜態(tài)html請(qǐng)求就可以顯示網(wǎng)頁(yè)了:
雖然很挫棒卷,不過(guò)web服務(wù)器的基本原理就是如此,牛逼的服務(wù)器也只是在這之上做了很多完善祝钢,下一篇我們將采用python提供的WSGI標(biāo)準(zhǔn)完成一個(gè)動(dòng)態(tài)的web框架比规。