在Web應用中形耗,服務器把網(wǎng)頁傳給瀏覽器闻妓,實際上就是把網(wǎng)頁的HTML代碼發(fā)送給瀏覽器双谆,讓瀏覽器顯示出來。而瀏覽器和服務器之間的傳輸協(xié)議是HTTP兴枯,所以:
HTML是一種用來定義網(wǎng)頁的文本血淌,會HTML,就可以編寫網(wǎng)頁财剖;
HTTP是在網(wǎng)絡上傳輸HTML的協(xié)議悠夯,用于瀏覽器和服務器的通信。
HTTP是Hyper Text Transfer Protocol(超文本傳輸協(xié)議)的縮寫躺坟。它的發(fā)展是萬維網(wǎng)協(xié)會(World Wide Web Consortium)和Internet工作小組IETF(Internet Engineering Task Force)合作的結果沦补,(他們)最終發(fā)布了一系列的RFC,RFC 1945定義了HTTP/1.0版本咪橙。其中最著名的就是RFC 2616夕膀。RFC 2616定義了今天普遍使用的一個版本——HTTP 1.1。
HTTP協(xié)議(HyperText Transfer Protocol美侦,超文本傳輸協(xié)議)是用于從WWW服務器傳輸超文本到本地瀏覽器的傳送協(xié)議产舞。它可以使瀏覽器更加高效,使網(wǎng)絡傳輸減少音榜。它不僅保證計算機正確快速地傳輸超文本文檔,還確定傳輸文檔中的哪一部分捧弃,以及哪部分內容首先顯示(如文本先于圖形)等赠叼。
HTTP是一個基于TCP/IP通信協(xié)議來傳遞數(shù)據(jù)(HTML 文件, 圖片文件, 查詢結果等)擦囊。
HTTP是一個應用層協(xié)議,由請求和響應構成嘴办,是一個標準的客戶端服務器模型瞬场。HTTP是一個無狀態(tài)的協(xié)議。
下面寫一個web靜態(tài)頁面涧郊,顯示固定頁面贯被。代碼如下:
import socket
import multiprocessing
import time
import os
def clientP(newSocket):
recvData = newSocket.recv(1024).decode('gbk')
print(recvData)
responseLine = 'HTTP/1.1 200 OK' + os.linesep
responseHeader = 'Server: wangzy' + os.linesep
responseHeader += 'Date: %s' % time.ctime() + os.linesep
responseBody = 'Hello World!'
sendData = (responseLine + responseHeader + os.linesep + responseBody).encode('gbk')
newSocket.send(sendData)
newSocket.close()
def main():
severSocket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
severSocket.bind(('', 7777))
severSocket.listen(10)
while True:
newSocket, clientAddr = severSocket.accept()
p = multiprocessing.Process(target=clientP, args=(newSocket,))
p.start()
newSocket.close()
if __name__ == '__main__':
main()
結果如下:
第一行:
GET / HTTP/1.1
GET表示一個讀取請求,將從服務器獲得網(wǎng)頁數(shù)據(jù)妆艘,/表示URL的路徑彤灶,URL總是以/開頭,/就表示首頁批旺,最后的HTTP/1.1指示采用的HTTP協(xié)議版本是1.1幌陕。目前HTTP協(xié)議的版本就是1.1,但是大部分服務器也支持1.0版本汽煮,主要區(qū)別在于1.1版本允許多個HTTP請求復用一個TCP連接搏熄,以加快傳輸速度。
我們可以跟數(shù)據(jù)庫的CRUD增刪改查操作對應起來:
CREATE :PUT
READ:GET
UPDATE:POST
DELETE:DELETE
打開網(wǎng)頁暇赤,查看開發(fā)者工具:
200表示一個成功的響應心例,后面的OK是說明。
如果返回的不是200鞋囊,那么往往有其他的功能止后,例如
失敗的響應有404 Not Found:網(wǎng)頁不存在
500 Internal Server Error:服務器內部出錯
等等...
HTTP格式
HTTP GET請求的格式:
GET /path HTTP/1.1
Header1: Value1
Header2: Value2
Header3: Value3
每個Header一行一個,換行符是\r\n失暴。
HTTP POST請求的格式:
POST /path HTTP/1.1
Header1: Value1
Header2: Value2
Header3: Value3
body data goes here...
當遇到連續(xù)兩個\r\n時坯门,Header部分結束,后面的數(shù)據(jù)全部是Body逗扒。
HTTP響應的格式:
200 OK
Header1: Value1
Header2: Value2
Header3: Value3
body data goes here...
HTTP響應如果包含body古戴,也是通過\r\n\r\n來分隔的。
請再次注意矩肩,Body的數(shù)據(jù)類型由Content-Type頭來確定现恼,如果是網(wǎng)頁,Body就是文本黍檩,如果是圖片叉袍,Body就是圖片的二進制數(shù)據(jù)。
當存在Content-Encoding時刽酱,Body數(shù)據(jù)是被壓縮的喳逛,最常見的壓縮方式是gzip,所以棵里,看到Content-Encoding: gzip時润文,需要將Body數(shù)據(jù)先解壓縮姐呐,才能得到真正的數(shù)據(jù)。壓縮的目的在于減少Body的大小典蝌,加快網(wǎng)絡傳輸曙砂。
顯示需要的頁面
import socket
import re
from multiprocessing import Process
# 設置靜態(tài)文件根目錄
HTML_ROOT_DIR = "./html"
def handle_client(client_socket):
"""處理客戶端請求"""
# 獲取客戶端請求數(shù)據(jù)
request_data = client_socket.recv(1024)
print("request data:", request_data.decode('utf-8'))
request_lines = request_data.splitlines()
# 解析請求報文
# 'GET / HTTP/1.1'
request_start_line = request_lines[0].decode('utf-8')
# 提取用戶請求的文件名
file_name = re.match(r"\w+ +(/[^ ]*) ", request_start_line).group(1)
if "/" == file_name:
file_name = "/index.html"
# 打開文件,讀取內容
try:
file = None
file = open(HTML_ROOT_DIR + file_name, "rb")
file_data = file.read()
# 構造響應數(shù)據(jù)
response_start_line = "HTTP/1.1 200 OK\r\n"
response_headers = "Server: My server\r\n"
response_body = file_data.decode("utf-8")
except FileNotFoundError:
response_start_line = "HTTP/1.1 404 Not Found\r\n"
response_headers = "Server: My server\r\n"
response_body = "The file is not found!"
finally:
if file and (not file.closed):
file.close()
response = response_start_line + response_headers + "\r\n" + response_body
print("response data:", response)
# 向客戶端返回響應數(shù)據(jù)
client_socket.send(bytes(response, "utf-8"))
# 關閉客戶端連接
client_socket.close()
def main():
'作為程序的主控制入口'
server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
server_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
server_socket.bind(("", 8000))
server_socket.listen(128)
while True:
client_socket, client_address = server_socket.accept()
# print("[%s, %s]用戶連接上了" % (client_address[0],client_address[1]))
print("[%s, %s]用戶連接上了" % client_address)
handle_client_process = Process(target=handle_client, args=(client_socket,))
handle_client_process.start()
client_socket.close()
if __name__ == "__main__":
main()
結果如下:
使用類創(chuàng)建服務器并實現(xiàn)動態(tài)資源的申請
import socket
import multiprocessing
import re
import os
import time
class MyServer(object):
def __init__(self, pork=8888):
serverSocket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
serverSocket.bind(('', pork))
self.serverSocket = serverSocket
self.HTMLPATH = './html'
def start(self):
self.serverSocket.listen()
while True:
newSocket, clientAddr = self.serverSocket.accept()
multiprocessing.Process(target=self.clientHander, args=(newSocket,)).start()
newSocket.close()
def clientHander(self, newSocket):
try:
recvData = newSocket.recv(1024).decode('gbk')
fileName = re.split(r' +', recvData.splitlines()[0])[1]
filePath = self.HTMLPATH
if fileName.endswith('.py'):
try:
pyname = fileName[1:-3]
print(pyname)
# 導入
pyModule = __import__(pyname)
env = {}
responseBody = pyModule.application(env, self.startResponse)
responseLine = self.responseLine
responseHeader = self.responseHeader
except ImportError:
responseLine = 'HTTP/1.1 404 NOT FOUND'+os.linesep
responseHeader = 'Server: Wangzy' + os.linesep
responseBody = '<h1>找不到PY文件<h1>'
else:
if '/' == fileName:
filePath += '/index.html'
else:
filePath += fileName
try:
file = None
file = open(filePath, 'r', encoding='gbk')
responseBody = file.read()
responseLine = 'HTTP/1.1 200 OK' + os.linesep
responseHeader = 'Server: Wangzy' + os.linesep
except FileNotFoundError:
responseLine = 'HTTP/1.1 404 NOT FOUND' + os.linesep
responseHeader = 'Server: Wangzy' + os.linesep
responseBody = '找不到html文件'
finally:
if (file != None) and (not file.closed):
file.close()
except Exception:
responseLine = 'HTTP/1.1 500 ERROR' + os.linesep
responseHeader = 'Server: Wangzy' + os.linesep
responseBody = '服務器忙骏掀,稍后再試鸠澈。。截驮。'
finally:
sendData = (responseLine + responseHeader + os.linesep + responseBody).encode('gbk')
newSocket.send(sendData)
newSocket.close()
def startResponse(self, status, responseHeaders):
'''
:param self:
:param status:
:param responseHeaders:
:return:
'''
self.responseLine = status
self.responseHeader = ''
for k, v in responseHeaders:
kv = (k + ':' + v + os.linesep)
self.responseHeader += kv
if __name__ == '__main__':
server = MyServer()
server.start()
動態(tài)申請的py文件寫法:
import time
def application(env, startResponse):
'''
:param env:
:param startResponse:
:return:
'''
status = 'HTTP/1.1 200 OK'
responseHeaders = [('Server', 'Wangzy'), ('Date', 'today'), ('Content-Type', 'text/plain')]
startResponse(status, responseHeaders)
responseBody = str(time.ctime())
return responseBody
結果如下:
如果請求的路徑不存在
以上例子中就用到了WSGI接口笑陈。
上面的application()函數(shù)就是符合WSGI標準的一個HTTP處理函數(shù),它接收兩個參數(shù):
environ:一個包含所有HTTP請求信息的dict對象侧纯;
start_response:一個發(fā)送HTTP響應的函數(shù)新锈。
整個application()函數(shù)本身沒有涉及到任何解析HTTP的部分,也就是說眶熬,把底層web服務器解析部分和應用程序邏輯部分進行了分離妹笆,這樣開發(fā)者就可以專心做一個領域了
不過,等等娜氏,這個application()函數(shù)怎么調用拳缠?如果我們自己調用,兩個參數(shù)environ和start_response我們沒法提供贸弥,返回的str也沒法發(fā)給瀏覽器窟坐。
所以application()函數(shù)必須由WSGI服務器來調用。有很多符合WSGI規(guī)范的服務器绵疲。而我們此時的web服務器項目的目的就是做一個極可能解析靜態(tài)網(wǎng)頁還可以解析動態(tài)網(wǎng)頁的服務器哲鸳。
下面來編寫框架和服務器,實現(xiàn)功能的分離盔憨。
服務器代碼如下:
import socket
import multiprocessing
import re
import os
import myFramework
class MyServer(object):
def __init__(self, application):
serverSocket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
self.serverSocket = serverSocket
self.application = application
def bind(self, port=9999):
self.serverSocket.bind(('', port))
def start(self):
self.serverSocket.listen()
while True:
newSocket, clientAddr = self.serverSocket.accept()
multiprocessing.Process(target=self.clientHander, args=(newSocket,)).start()
newSocket.close()
def clientHander(self, newSocket):
recvData = newSocket.recv(1024).decode('gbk')
fileName = re.split(r' +', recvData.splitlines()[0])[1]
method = re.split(r' +', recvData.splitlines()[0])[0]
env = {
'PATH_INFO': fileName,
'METHOD': method
}
for item in recvData:
if item.count(':') != 0:
k, v = item.split(':')
env[k] = v
responseBody = self.application(env, self.startResponse)
sendData = (self.responseHeader + os.linesep + os.linesep + responseBody).encode('gbk')
newSocket.send(sendData)
newSocket.close()
def startResponse(self, status, responseHeader):
self.responseHeader = status + os.linesep
for k, v in responseHeader:
kv = (k + ':' + v + os.linesep)
self.responseHeader += kv
if __name__ == '__main__':
server = MyServer(myFramework.application)
server.bind(8888)
server.start()
框架代碼如下:
import time
class Application(object):
def __init__(self, urls):
self.urls = urls
self.filePath = './html'
def __call__(self, env, startResponse):
# 從請求頭中獲取訪問的名字
fileName = env.get('PATH_INFO')
# 判斷是靜態(tài)訪問還是動態(tài)訪問
# 靜態(tài)訪問方法
if fileName.startswith('/static'):
fileName = fileName[7:]
if '/' == fileName:
self.filePath += '/index.html'
else:
self.filePath += fileName
try:
file = None
file = open(self.filePath, 'r', encoding='gbk')
responseBody = file.read()
status = 'HTTP/1.1 200 OK'
responseHeaders = [('Server', 'Wangzy')]
except FileNotFoundError:
status = 'HTTP/1.1 404 NOT FOUND'
responseHeaders = [('Server', 'Wangzy')]
responseBody = 'HTML文件找不到徙菠!'
finally:
startResponse(status, responseHeaders)
if (file != None) and (not file.closed):
file.close()
# 動態(tài)訪問方法
else:
# 表示請求的名字是否在urls中,True:存在郁岩,F(xiàn)alse:不存在
isIn = False
for k, v in self.urls:
if k == fileName:
responseBody = v(env, startResponse)
isIn = True
break
if isIn == False:
status = 'HTTP/1.1 404 NOT FOUND'
responseHeaders = [('Server', 'Wangzy')]
responseBody = 'py文件找不到婿奔!'
startResponse(status, responseHeaders)
return responseBody
def showtime(env, startResponse):
status = 'HTTP/1.1 200 OK'
responseHeaders = [('Server', 'Wangzy')]
startResponse(status,responseHeaders)
responseBody = time.ctime()
return responseBody
def shownews(env, startResponse):
status = 'HTTP/1.1 200 OK'
responseHeaders = [('Server', 'Wangzy')]
startResponse(status, responseHeaders)
responseBody = '今天的新聞是。问慎。萍摊。。'
return responseBody
urls = [
('/showtime', showtime),
('/shownews', shownews)
]
application = Application(urls)
結果如下:
這里就實現(xiàn)了框架和服務器的功能分離如叼,但是現(xiàn)在還存在一個問題冰木,我們在服務器中導入了框架,并且把需要調用的框架對象給寫死了笼恰,如果需要應用其他框架踊沸,還需要修改服務器代碼囚衔,這就違反了代碼的開閉原則。
為了使我們的服務器具有通用性雕沿,可以匹配任何符合wsgi的框架,我們修改服務器代碼如下:
import socket
import multiprocessing
import re
import os
import sys
class MyServer(object):
def __init__(self, application):
serverSocket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
self.serverSocket = serverSocket
self.application = application
def bind(self, port=9999):
self.serverSocket.bind(('', port))
def start(self):
self.serverSocket.listen()
while True:
newSocket, clientAddr = self.serverSocket.accept()
multiprocessing.Process(target=self.clientHander, args=(newSocket,)).start()
newSocket.close()
def clientHander(self, newSocket):
recvData = newSocket.recv(1024).decode('gbk')
fileName = re.split(r' +', recvData.splitlines()[0])[1]
method = re.split(r' +', recvData.splitlines()[0])[0]
env = {
'PATH_INFO': fileName,
'METHOD': method
}
for item in recvData:
if item.count(':') != 0:
k, v = item.split(':')
env[k] = v
responseBody = self.application(env, self.startResponse)
sendData = (self.responseHeader + os.linesep + os.linesep + responseBody).encode('gbk')
newSocket.send(sendData)
newSocket.close()
def startResponse(self, status, responseHeader):
self.responseHeader = status + os.linesep
for k, v in responseHeader:
kv = (k + ':' + v + os.linesep)
self.responseHeader += kv
def main():
print(sys.argv)
moduleName, attrName = sys.argv[1].split(':')
print(moduleName)
print(attrName)
myModule = __import__(moduleName)
server = MyServer(getattr(myModule, attrName))
server.bind(8888)
server.start()
if __name__ == '__main__':
main()
框架代碼如下:
import time
class Application(object):
def __init__(self, urls):
self.urls = urls
self.filePath = './html'
def __call__(self, env, startResponse):
# 從請求頭中獲取訪問的名字
fileName = env.get('PATH_INFO')
# 判斷是靜態(tài)訪問還是動態(tài)訪問
# 靜態(tài)訪問方法
if fileName.startswith('/static'):
fileName = fileName[7:]
if '/' == fileName:
self.filePath += '/index.html'
else:
self.filePath += fileName
try:
file = None
file = open(self.filePath, 'r', encoding='gbk')
responseBody = file.read()
status = 'HTTP/1.1 200 OK'
responseHeaders = [('Server', 'Wangzy')]
except FileNotFoundError:
status = 'HTTP/1.1 404 NOT FOUND'
responseHeaders = [('Server', 'Wangzy')]
responseBody = 'HTML文件找不到猴仑!'
finally:
startResponse(status, responseHeaders)
if (file != None) and (not file.closed):
file.close()
# 動態(tài)訪問方法
else:
# 表示請求的名字是否在urls中审轮,True:存在,F(xiàn)alse:不存在
isIn = False
for k, v in self.urls:
if k == fileName:
responseBody = v(env, startResponse)
isIn = True
break
if isIn == False:
status = 'HTTP/1.1 404 NOT FOUND'
responseHeaders = [('Server', 'Wangzy')]
responseBody = 'py文件找不到辽俗!'
startResponse(status, responseHeaders)
return responseBody
def showtime(env, startResponse):
status = 'HTTP/1.1 200 OK'
responseHeaders = [('Server', 'Wangzy')]
startResponse(status, responseHeaders)
responseBody = time.ctime()
return responseBody
def shownews(env, startResponse):
status = 'HTTP/1.1 200 OK'
responseHeaders = [('Server', 'Wangzy')]
startResponse(status, responseHeaders)
responseBody = '今天的新聞是疾渣。。崖飘。榴捡。'
return responseBody
urls = [
('/showtime', showtime),
('/shownews', shownews)
]
application = Application(urls)
此時,我們如果運行服務器會報錯:
在終端用命令運行服務器