python-web服務器


一、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()        
webStaticServer01

 

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()        
webStaticServer02

 

三、服務器動態資源請求

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響應的函數。

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     
webDynamicServer01

 

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     
ctime

 

3、運行

3.1  在腳本所在的目錄 按住shift +鼠標右鍵,右鍵菜單選擇在此處打開命令窗口

3.2  在命令窗口運行腳本(python3 webDynamicServer01.py ctime:app)

看到如下頁面即服務啟動成功

3.3  在瀏覽器中輸入http://127.0.0.1:9999/,可返回index.html

 

 

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM