一、HTTP協議
1、HTTP協議簡介
在Web應用中,服務器把網頁傳給瀏覽器,實際上就是把網頁的HTML代碼發送給瀏覽器,讓瀏覽器顯示出來。而瀏覽器和服務器之間的傳輸協議是HTTP,所以:
-
HTML是一種用來定義網頁的文本,會HTML,就可以編寫網頁;
-
HTTP是在網絡上傳輸HTML的協議,用於瀏覽器和服務器的通信。
Chrome瀏覽器提供了一套完整地調試工具,非常適合Web開發。
2. http協議的分析
當我們在地址欄輸入www.baidu.com時,瀏覽器將顯示新浪的首頁。在這個過程中,瀏覽器都干了哪些事情呢?通過Network的記錄,我們就可以知道。在Network中,找到www.baidu.com那條記錄,點擊,右側將顯示Request Headers,點擊右側的view source,我們就可以看到瀏覽器發給新浪服務器的請求:
2.1 瀏覽器請求
說明
最主要的頭兩行分析如下,第一行:
GET / HTTP/1.1
GET表示一個讀取請求,將從服務器獲得網頁數據,/表示URL的路徑,URL總是以/開頭,/就表示首頁,最后的HTTP/1.1指示采用的HTTP協議版本是1.1。目前HTTP協議的版本就是1.1,但是大部分服務器也支持1.0版本,主要區別在於1.1版本允許多個HTTP請求復用一個TCP連接,以加快傳輸速度。
從第二行開始,每一行都類似於Xxx: abcdefg:
Host: www.baidu.com
表示請求的域名是www.baidu.com。如果一台服務器有多個網站,服務器就需要通過Host來區分瀏覽器請求的是哪個網站。
2.2 服務器響應
HTTP響應分為Header和Body兩部分(Body是可選項),我們在Network中看到的Header最重要的幾行如下:
HTTP/1.1 200 OK
200表示一個成功的響應,后面的OK是說明。
如果返回的不是200,那么往往有其他的功能,例如
- 失敗的響應有404 Not Found:網頁不存在
- 500 Internal Server Error:服務器內部出錯
- 302,重定向
Content-Type: text/html
Content-Type指示響應的內容,這里是text/html表示HTML網頁。
請注意,瀏覽器就是依靠Content-Type來判斷響應的內容是網頁還是圖片,是視頻還是音樂。瀏覽器並不靠URL來判斷響應的內容,所以,即使URL是
http://www.baidu.com/meitu.jpg
,它也不一定就是圖片。
HTTP響應的Body就是HTML源碼,我們在菜單欄選擇“視圖”,“開發者”,“查看網頁源碼”就可以在瀏覽器中直接查看HTML源碼:
瀏覽器解析過程
當瀏覽器讀取到新浪首頁的HTML源碼后,它會解析HTML,顯示頁面,然后,根據HTML里面的各種鏈接,再發送HTTP請求給新浪服務器,拿到相應的圖片、視頻、Flash、JavaScript腳本、CSS等各種資源,最終顯示出一個完整的頁面。所以我們在Network下面能看到很多額外的HTTP請求
3. 總結
3.1 HTTP請求
跟蹤了百度的首頁,我們來總結一下HTTP請求的流程:
3.1.1 步驟1:瀏覽器首先向服務器發送HTTP請求,請求包括:
方法:GET還是POST,GET僅請求資源,POST會附帶用戶數據;
路徑:/full/url/path;
域名:由Host頭指定:Host: www.baidu.com
以及其他相關的Header;
如果是POST,那么請求還包括一個Body,包含用戶數據
3.1.1 步驟2:服務器向瀏覽器返回HTTP響應,響應包括:
響應代碼:200表示成功,3xx表示重定向,4xx表示客戶端發送的請求有錯誤,5xx表示服務器端處理時發生了錯誤;
響應類型:由Content-Type指定;
以及其他相關的Header;
通常服務器的HTTP響應會攜帶內容,也就是有一個Body,包含響應的內容,網頁的HTML源碼就在Body中。
3.1.1 步驟3:如果瀏覽器還需要繼續向服務器請求其他資源,比如圖片,就再次發出HTTP請求,重復步驟1、2。
Web采用的HTTP協議采用了非常簡單的請求-響應模式,從而大大簡化了開發。當我們編寫一個頁面時,我們只需要在HTTP請求中把HTML發送出去,不需要考慮如何附帶圖片、視頻等,瀏覽器如果需要請求圖片和視頻,它會發送另一個HTTP請求,因此,一個HTTP請求只處理一個資源(此時就可以理解為TCP協議中的短連接,每個鏈接只獲取一個資源,如需要多個就需要建立多個鏈接)
HTTP協議同時具備極強的擴展性,雖然瀏覽器請求的是http://www.baidu.com
的首頁,但是新浪在HTML中可以鏈入其他服務器的資源,比如<img src="http://i1.baiduimg.cn/home/2013/1008/U84123.png">
,從而將請求壓力分散到各個服務器上,並且,一個站點可以鏈接到其他站點,無數個站點互相鏈接起來,就形成了World Wide Web,簡稱WWW。
3.2 HTTP格式
每個HTTP請求和響應都遵循相同的格式,一個HTTP包含Header和Body兩部分,其中Body是可選的。
HTTP協議是一種文本協議,所以,它的格式也非常簡單。
3.2.1 HTTP GET請求的格式:
GET /path HTTP/1.1 Header1: Value1 Header2: Value2 Header3: Value3
......
每個Header一行一個,換行符是\r\n。
3.2.2 HTTP POST請求的格式:
POST /path HTTP/1.1
Header1: Value1
Header2: Value2
Header3: Value3
body data goes here...
當遇到連續兩個\r\n時,Header部分結束,后面的數據全部是Body。
3.2.3 HTTP響應的格式:
200 OK
Header1: Value1
Header2: Value2
Header3: Value3
body data goes here...
HTTP響應如果包含body,也是通過\r\n\r\n來分隔的。
請再次注意,Body的數據類型由Content-Type頭來確定,如果是網頁,Body就是文本,如果是圖片,Body就是圖片的二進制數據。
當存在Content-Encoding時,Body數據是被壓縮的,最常見的壓縮方式是gzip,所以,看到Content-Encoding: gzip時,需要將Body數據先解壓縮,才能得到真正的數據。壓縮的目的在於減少Body的大小,加快網絡傳輸。
二 、案列
Web靜態服務器-1

1 #coding-=utf-8 2 ''' 3 Created on 2019年8月9日 4 5 @author: Administrator 6 7 #Web靜態服務器-1-顯示固定的頁面 8 ''' 9 import socket 10 from multiprocessing import Process 11 12 def handleClient(clientSocket): 13 '用一個新進程為一個客戶端進行服務' 14 recvData = clientSocket.recv(2014) 15 requestHeaderLines = recvData.splitlines() 16 for line in requestHeaderLines: 17 print(line) 18 19 'response header ' 20 responseHeaderLines = 'HTTP/1.1 200 OK\r\n' 21 responseHeaderLines += '\r\n' 22 23 responseBody = 'hello world' 24 25 response = responseHeaderLines + responseBody 26 clientSocket.send(response.encode()) 27 clientSocket.close() 28 29 30 def main(): 31 '作為程序入口' 32 serverSocket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) 33 #serverSocket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) 34 serverSocket.bind(('',9999)) 35 serverSocket.listen() 36 37 while True: 38 clientSocket,clientAddr = serverSocket.accept() 39 p = Process(target=handleClient, args=(clientSocket,)) 40 p.start() 41 42 if __name__ == '__main__': 43 main()
Web靜態服務器-2

1 #coding-=utf-8 2 ''' 3 Created on 2019年8月9日 4 5 @author: Administrator 6 7 #Web靜態服務器-2-顯示需要的頁面 8 ''' 9 import socket 10 from multiprocessing import Process 11 import re 12 13 14 def handleClient(clientSocket): 15 '用一個新進程為一個客戶端進行服務' 16 recvData = clientSocket.recv(2014) 17 requestHeaderLines = recvData.splitlines() 18 for line in requestHeaderLines: 19 print(line) 20 21 #httpRequestMethodLine = requestHeaderLines[0].decode('utf-8') 22 #getFileName = re.match("[^/]+(/[^ ]*)", httpRequestMethodLine).group(1) 23 #print('getFileName:%s'%getFileName) 24 filename = requestHeaderLines[0].decode('utf-8').split(' ')[1] 25 print('filename:%s'%filename) 26 27 if filename == '/': 28 filename = documentRoot + "/index.html"#默認返回的頁面 29 else: 30 filename = documentRoot + filename#請求的頁面 31 32 print('request-filename:%s'%filename) 33 34 try: 35 f = open(filename,'r',encoding='UTF-8') 36 responseHeaderLines = 'HTTP/1.1 200 OK\r\n' 37 responseHeaderLines += '\r\n' 38 responseBody = f.read() 39 f.close()#close file 40 except Exception as e: 41 print('[error]Exception:%s'%e) 42 responseHeaderLines = 'HTTP/1.1 404 NOT FOUND\r\n' 43 responseHeaderLines += '\r\n' 44 responseBody = '=====sorry, page not found=====' 45 finally: 46 response = responseHeaderLines + responseBody 47 clientSocket.send(response.encode()) 48 clientSocket.close() 49 50 def main(): 51 '作為程序入口' 52 serverSocket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) 53 #serverSocket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) 54 serverSocket.bind(('',9999)) 55 serverSocket.listen() 56 57 while True: 58 clientSocket,clientAddr = serverSocket.accept() 59 p = Process(target=handleClient, args=(clientSocket,)) 60 p.start() 61 62 #配置 63 documentRoot = './html' 64 65 if __name__ == '__main__': 66 main()
三、服務器動態資源請求
1. 瀏覽器請求動態頁面過程
1.1 WSGI介紹
PythonWeb服務器網關接口(Python Web Server Gateway Interface,縮寫為WSGI)是Python應用程序或框架和Web服務器之間的一種接口,
WSGI接口定義非常簡單,它只要求Web開發者實現一個函數,就可以響應HTTP請求。
# WSGI 規范的函數
def application(environ, start_response): start_response('200 OK', [('Content-Type', 'text/html')]) return '<h1>Hello, Se7eN_HOU!</h1>'
上面的application()
函數就是符合WSGI標准的一個HTTP處理函數,它接收兩個參數:
-
- environ:一個包含所有HTTP請求信息的
dict
對象; - start_response:一個發送HTTP響應的函數。
- environ:一個包含所有HTTP請求信息的
在application()
函數中,調用:
start_response('200 OK', [('Content-Type', 'text/html')])
1.2 運行WSGI服務
1、 Python內置了一個WSGI服務器,這個模塊叫wsgiref,首先我們先實現一個hello.py文件,實現Web應用程序的WSGI處理函數
def application(environ, start_response): start_response("200 OK",[("Content-Type","text/html")]) return "<h1>Hello,Se7eN_HOU!</h1>"
2、然后,再編寫一個server.py
,負責啟動WSGI服務器,加載application()
函數:
#coding:utf-8 # 導入wsgiref模塊 from wsgiref.simple_server import make_server from hello import application # 創建一個服務器,IP地址為空,端口號為7788,處理的函數是application httpServer = make_server("", 7788, application) # 開始監聽HTTP請求 httpServer.serve_forever()
3、 確保以上兩個文件在同一個目錄下,然后使用命令行輸入python server.py
來啟動WSGI服務器:
houleideMacPro:WSGI Se7eN_HOU$ python server.py 127.0.0.1 - - [19/Jun/2019 15:52:37] "GET / HTTP/1.1" 200 24 127.0.0.1 - - [19/Jun/2019 15:52:37] "GET /favicon.ico HTTP/1.1" 200 24
4、 按Ctrl+C
終止服務器。如果你覺得這個Web應用太簡單了,可以稍微改造一下,從environ
里讀取PATH_INFO
,這樣可以顯示更加動態的內容:
def application(environ, start_response): start_response("200 OK",[("Content-Type","text/html")]) return "<h1>Hello,%s</h1>"%(environ["PATH_INFO"][1:] or "Se7eN_HOU")
5、 你可以在地址欄輸入用戶名作為URL的一部分,

四、案列
1、web動態服務器-1

1 #coding=utf-8 2 ''' 3 Created on 2019年8月9日 4 5 @author: Administrator 6 ''' 7 import socket 8 from multiprocessing import Process 9 import re 10 import sys 11 12 class WSGIServer(object): 13 14 addressFamily = socket.AF_INET 15 socketType = socket.SOCK_STREAM 16 requestQueuesize = 5 17 18 def __init__(self, serverAddress): 19 #創建tcp套接字 20 #self.listenSocket = socket(self.addressFamily, self.socketType) 21 self.listenSocket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) 22 #允許重復使用上次的套接字綁定的port 23 self.listenSocket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) 24 #綁定 25 self.listenSocket.bind(serverAddress) 26 #被動監聽,並制定隊列長度 27 self.listenSocket.listen(self.requestQueuesize) 28 29 #self.serverName = 'localhost' 30 #self.serverPort = serverAddress[1] 31 32 def serverForever(self): 33 #'循環運行web服務器,等待客戶端的連接並為客戶端服務' 34 while True: 35 #等待新客戶端 36 self.clientSocket, client_address = self.listenSocket.accept() 37 38 #為每一個客戶端創建新進程, 39 newClientProcess = Process(target=self.handleClient) 40 newClientProcess.start() 41 #處理完之后,關閉 42 self.clientSocket.close() 43 44 def setApp(self, application): 45 '設置此WSGI服務器調用的應用程序入口函數' 46 self.application = application 47 48 def handleClient(self): 49 self.recvData = self.clientSocket.recv(2048) 50 print('self.recvData:%s'%self.recvData) 51 if self.recvData:#過濾空 52 requestHeaderLines = self.recvData.splitlines() 53 54 for line in requestHeaderLines: 55 print(line) 56 57 print('requestHeaderLines[0]:%s'%requestHeaderLines[0]) 58 filename = requestHeaderLines[0].decode('utf-8').split(' ')[1] 59 print('filename:%s'%filename) 60 61 if filename[-3:] != '.py': 62 if filename == '/': 63 filename = documentRoot + "/index.html"#默認返回的頁面 64 else: 65 filename = documentRoot + filename#請求的頁面 66 67 print('request-filename:%s'%filename) 68 69 try: 70 f = open(filename,'r',encoding='UTF-8') 71 responseHeaderLines = 'HTTP/1.1 200 OK\r\n' 72 responseHeaderLines += '\r\n' 73 responseBody = f.read() 74 f.close()#close file 75 except Exception as e: 76 print('[error]Exception:%s'%e) 77 responseHeaderLines = 'HTTP/1.1 404 NOT FOUND\r\n' 78 responseHeaderLines += '\r\n' 79 responseBody = '=====sorry, page not found=====' 80 finally: 81 response = responseHeaderLines + responseBody 82 self.clientSocket.send(response.encode()) 83 self.clientSocket.close() 84 else: 85 #處理接收到的請求頭 86 self.parseRequest() 87 #根據接收到的請求頭構造環境變量字典 88 env = self.getEnviron() 89 #調用應用的相應方法,完成動態數據的獲取 90 bodyContent = self.application(env, self.startResponse) 91 #組織數據發送給客戶端 92 self.finishResponse(bodyContent) 93 94 def parseRequest(self): 95 '提取出客戶端發送的request' 96 requestLine = self.recvData.splitlines()[0] 97 requestLine = requestLine.rstrip(b'\r\n') 98 print('requestLine:%s'%requestLine) 99 self.requestMethod, self.path, self.requestVersion = requestLine.split(b" ") 100 101 def getEnviron(self): 102 env = {} 103 env['wsgi.version'] = (1, 0) 104 env['wsgi.input'] = self.recvData 105 env['REQUEST_METHOD'] = self.requestMethod # GET 106 env['PATH_INFO'] = self.path # /index.html 107 return env 108 109 #def startResponse(self, status, response_headers, exc_info=None) 110 def startResponse(self, status, response_headers, exc_info=None): 111 serverHeaders = [ 112 ('Date', 'Fri, 9 Augs 2019 14:00:00 GMT'), 113 ('Server', 'WSGIServer 0.2'), 114 ] 115 self.headers_set = [status, response_headers+serverHeaders] 116 117 def finishResponse(self,bodyContent): 118 try: 119 status, response_headers = self.headers_set 120 #response第一行 121 response = 'HTTP/1.1 {status}\r\n'.format(status=status) 122 #其他頭信息 123 for header in response_headers: 124 response += '{0}: {1}\r\n'.format(*header) 125 #添加一個換行,與body進行分開來 126 response += '\r\n' 127 #添加發送的數據 128 for data in bodyContent: 129 response += data 130 131 self.clientSocket.send(response.encode()) 132 133 finally: 134 self.clientSocket.close() 135 136 137 138 #設定服務器的端口 139 serverAddr = (HOST, PORT) = '',9999 140 #設置服務器靜態資源的路徑 141 documentRoot = './html' 142 #設置服務器胴體資源路徑 143 pythonRoot = './wsgiPy' 144 145 def makeServer(serverAddr, application): 146 server = WSGIServer(serverAddr) 147 server.setApp(application) 148 return server 149 150 def main(): 151 if len(sys.argv) < 2: 152 sys.exit('請按照要求,制定模塊名稱:應用名稱,例如:module:callable') 153 #獲取module:callable 154 appPath = sys.argv[1] 155 print('appPath:%s'%appPath) 156 #根據冒號切割為module和callable 157 module,application = appPath.split(':') 158 #添加路徑到 sys.path 159 sys.path.insert(0,pythonRoot) 160 #動態導入module變量中的指定模塊 161 module = __import__(module) 162 #獲取module變量中指定的模塊,application變量指定的屬性 163 application = getattr(module, application) 164 httpd = makeServer(serverAddr, application) 165 print('WSGIServer: Serving HTTP on port %d ...\n'%PORT) 166 httpd.serverForever() 167 168 if __name__ == '__main__': 169 main() 170 171
2、應用程序代碼ctime.py

1 #coding=utf-8 2 ''' 3 Created on 2019年8月12日 4 5 @author: Administrator 6 ''' 7 import time 8 9 def app(environ, start_response): 10 status = '200 OK' 11 12 response_headers = [ 13 ("Content-Type", "text/plain") 14 ] 15 16 start_response(status, response_headers) 17 18 return [str(environ)+'--->%s\n'%time.ctime()] 19
3、運行
3.1 在腳本所在的目錄 按住shift +鼠標右鍵,右鍵菜單選擇在此處打開命令窗口
3.2 在命令窗口運行腳本(python3 webDynamicServer01.py ctime:app)
看到如下頁面即服務啟動成功
3.3 在瀏覽器中輸入http://127.0.0.1:9999/,可返回index.html: