標籤:
由於要做自動化和效能測試,工作中需要有一個能夠控制返回訊息資料的web伺服器,所以用python初步實現了一個簡單的web伺服器,能夠處理HTTP的請求(GET,POST,PUT),並完成響應。
先簡單說明下原理,python中實現web伺服器大概分兩個步驟:
1. 建立一個通訊端,綁定到指定的IP和連接埠,保持監聽
2. 建立一個handle類,當收到請求訊息時,作出響應
主要使用的類有兩個:
HTTPServer:HTTP伺服器的基類,提供了HTTP伺服器的常用方法,建立伺服器時可以直接聲明這個類,也可以根據需要從這個類繼承。
CGIHTTPRequestHandler:處理請求的handle類,繼承自BaseHTTPRequestHandler類,在python文檔中說明該類可以處理GET和POST請求,自己試了一下發現也可以處理PUT請求。
有關這兩個類的詳細描述可以參考python的標準類庫說明:https://docs.python.org/2/library/
源碼如下:
import socket
from os import curdir,sep
from BaseHTTPServer import HTTPServer
from CGIHTTPServer import CGIHTTPRequestHandler
import json
class HttpHandler(CGIHTTPRequestHandler):
def do_GET(self):
try:
f = open(curdir + sep + self.path)
print self.path
print ‘This is a GET method!‘
self.send_response(200)
self.send_header(‘Content-type‘,
‘text/html‘)
self.end_headers()
self.wfile.write(f.read())
f.close()
except IOError:
self.send_error(404,‘File Not Found:%s‘ % self.path)
def do_POST(self):
try:
print self.path
print ‘This is a POST method!‘
self.data = self.request.recv(1024).strip()
self.data = json.loads(self.data)
print self.data
sendData = {}
if self.path == ‘/getfile‘:
f = open(curdir + sep + ‘/test_body.txt‘)
fileContent = f.read()
sendData[‘content‘] = fileContent
f.close()
self.send_response(200)
self.send_header(‘Content-type‘,
‘text/html‘)
self.end_headers()
self.wfile.write(sendData)
except:
self.send_error(404,‘Invalid request:%s!‘ % self.path)
def do_PUT(self):
try:
print self.path
print ‘This is a PUT method!‘
self.data = self.request.recv(1024).strip()
print self.data
sendData = {}
if self.path == ‘/getfile‘:
f = open(curdir + sep + ‘/test_body.txt‘)
fileContent = f.read()
sendData[‘content‘] = fileContent
f.close()
self.send_response(200)
self.send_header(‘Content-type‘,
‘text/html‘)
self.end_headers()
self.wfile.write(sendData)
except:
self.send_error(404,‘Invalid request:%s!‘ % self.path)
def main():
try:
localIp = socket.gethostbyname(socket.gethostname())
server_address = (localIp,8080)
http_server = HTTPServer(server_address,HttpHandler)
sa = http_server.socket.getsockname()
print "Serving HTTP on", sa[0], "port", sa[1], "..."
print ‘Press ^C once or twice to quit.‘
http_server.serve_forever()
except KeyboardInterrupt:
print ‘^C received,shutting down server.‘
http_server.socket.close()
if __name__ == ‘__main__‘:
main()
在main()函數中建立了一個HTTPServer類,綁定到原生8080連接埠,並保持監聽。當收到請求時,根據請求的方法類型進入不同的do_*函數。函數的處理邏輯很簡單,如果發送的URL合法,設定返回碼為200,然後讀取一個檔案的內容,並寫入到返回訊息中。這樣一個簡單的web伺服器就完成了,可以用RESTClient測試一下。
HTTPS相對於HTTP增加了SSL通道,用戶端和伺服器端必須有相互信任的認證才可以通訊。自己對這方面的內容之前完全不瞭解,費了很大的功夫才初步把HTTPS的請求調通。
相比於HTTP的伺服器,HTTPS改動的地方主要是建立伺服器的時候:
class SecureHTTPServer(HTTPServer):
def __init__(self, server_address, HandlerClass):
BaseServer.__init__(self, server_address, HandlerClass)
self.socket = ssl.SSLSocket(socket.socket(self.address_family,self.socket_type),keyfile=‘./server.key‘, certfile=‘./server.crt‘, server_side=True)
self.server_bind()
self.server_activate()
這裡新聲明了一個SecureHTTPServer類,繼承自HTTPServer類,在這個類中重新定義了建構函式,主要是新建立了一個SSLSocket類型的通訊端,並指定了這個通訊端的keyfile和certfile。keyfile和certfile這兩個檔案是安裝了OpenSSL後手動產生的,執行如下openssl命令在本地產生pem檔案:
req -new -x509 -keyout server.pem -out server.pem -day 365 -nodes
將server.pem檔案中CERTIFICATE部分拷貝出來另存新檔server.crt。執行如下命令在本地產生或者刪除認證:
keytool -import -alias certificatekey -file server.crt -keystore server.jks
keytool -delete -alias certificatekey -keystore server.jks
將server.jk上傳到發送請求的系統作為認證庫。
新聲明一個handler類來處理收到的請求,並添加了多線程機制:
class SecureHTTPHandler(CGIHTTPRequestHandler):
# def setup(self):
# self.connection = self.request
# # self.rbufsize = 8192
# # self.wbufsize = 8192
# self.rfile = socket._fileobject(self.request, "rb", self.rbufsize)
# self.wfile = socket._fileobject(self.request, "wb", self.wbufsize)
def commonProcess(self):
#處理請求的通用函數
try:
#設定不阻塞
self.request.setblocking(0)
#設定逾時時間為5秒
self.request.settimeout(5)
#自己的處理代碼
f = open(curdir + sep + self.path)
print self.path
print ‘This is a GET method!‘
self.send_response(200)
self.send_header(‘Content-type‘,
‘text/html‘)
self.end_headers()
self.wfile.write(f.read())
f.close()
except:
logging.error(‘Invalid request:%s!‘ % self.path)
self.send_error(404,‘Invalid request:%s!‘ % self.path)
def do_GET(self):
logging.info(‘Received url is %s‘ % self.path)
logging.info(‘This is a GET method!‘)
#self.commonProcess()
t = threading.Thread(target = self.commonProcess,args = ())
t.start()
t.join(5)
def do_POST(self):
logging.info(‘Received url is %s‘ % self.path)
logging.info(‘This is a POST method!‘)
#self.commonProcess()
t = threading.Thread(target = self.commonProcess,args = ())
t.start()
t.join(5)
def do_PUT(self):
logging.info(‘Received url is %s‘ % self.path)
logging.info(‘This is a PUT method!‘)
#self.commonProcess()
t = threading.Thread(target = self.commonProcess,args = ())
t.start()
t.join(5)
在main()函數中要聲明這個類的對象來建立伺服器:
https_server = SecureHTTPServer(server_address,SecureHTTPHandler)
以下是用戶端的測試代碼:
import httplib
def main():
conn = httplib.HTTPSConnection(host = "127.0.0.1", port = 8443, key_file=‘./server.key‘, cert_file=‘./server.crt‘ , source_address = (‘127.0.0.1‘,8080))
print "requesting..."
conn.request(‘PUT‘, ‘/test_body.txt‘)
r1 = conn.getresponse()
#列印響應碼和響應狀態資訊
print r1.status, r1.reason
另外,python中的twisted架構在處理web伺服器時似乎功能很強大,後續可以深入研究一下。
python搭建簡單的web伺服器