1. 程式人生 > >Webserver-HTTP專案(深入理解HTTP協議)

Webserver-HTTP專案(深入理解HTTP協議)

# HTTP專案實戰
- 深入理解HTTP協議
- 模擬後臺服務程式基本流程和大致框架
- 每一個步驟一個資料夾
- 圖解http協議, 圖解tcp/ip協議

# v01-驗證技術
- 驗證socket-tcp技術,看能否走通流程
- 使用瀏覽器傳送訊息,訪問地址

# V02-解析傳入http協議
- 根據http協議格式,逐行讀取資訊
- 按行讀取後的資訊,需要進行拆解,

# 推薦書籍
- 日本人寫的 圖解Http"
- 圖解系列嚴重推薦

# v03-http協議封裝返回內容
- 返回頭: "HTTP/1.1 200 OK\r\n"
- 首部行:
- "Content-Length: xxx\r\n"
- "Date: 20180616\r\n"
-
空行:
- "\r\n"
- 返回內容:
- "I love beijign tulingxueyuan"
- 例子v03

# v04-面向物件重構
- 兩個物件:
- 一個負責監聽接受傳入socketWebServer
- 一個負責通訊, SocketHandler
- 參看例子v04

# v05-使用配置檔案

# v06-返回靜態頁面
- 靜態檔案:不常編輯的檔案內容
- 靜態檔案的儲存: 一般單獨放入一共資料夾,或者靜態檔案伺服器
- 需要有一共html型別的頁面
- html檔案作為檔案讀入內容
- 作為結果反饋回去
- 靜態檔案存放再: webapp資料夾下

# v07-新增路由功能和404
- 路由: 能夠理解請求並按照請求呼叫相應處理函式的模組
- 理解請求內容
- 能夠呼叫或者指定相應業務處理模組
- 演算法:
- 按行讀取傳入報文
- 假如報文能用空格分割成三段,則是請求行
- 否則,是首部行,首部行必須要能夠用冒號空格分割成兩段
- 首部行是天然的鍵值對
- 請求行需要自行增加鍵
- 404代表訪問的資源不存在

# v08-新增靜態檔案
- 靜態檔案: web後臺,一般把圖片,音訊,視訊,附件等很少需要更改內容的檔案成為靜態檔案
- 新建資料夾static用來存放靜態檔案



======================================== TCP傳輸+HTTP協議   HTTP協議請求頭部:
  HTTP頭部解析演算法:   新增路由: 一個socket負責通道傳輸,一個socket負責監聽,其他的socket負責處理; socker併發執行。
v01-驗證技術:
import socket

# 理解兩個引數的含義
# 理解建立一個socket的過程
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)

# 注意addr的格式是tuple
# 以及tuple兩個元素的含義
sock.bind(("127.0.0.1", 7852))
print("已經繫結埠........")
# 監聽
sock.listen()
print("正在監聽......")

# 接受一個傳進來的socket
print("準備接受socket傳入....")
skt, addr = sock.accept()
print("已經接收到傳入socket {0}".format(skt))

# 讀取傳入訊息,實際上是資訊
# 需要注意讀取的資訊的長度一定要小於等於實際訊息的長度,否則會假死
msg = skt.recv(100)
print(type(msg))

# decode預設utf-8
print(msg.decode())


# 給對方一個反饋
msg = "I love only wangxiaojing"
skt.send(msg.encode())

skt.close()
sock.close()


解析傳入HTTP:
import socket



def getHttpHeader(skt):
'''
得到傳入sockethttp請求頭
:param skt: 通訊的socket
:return: 解析後的請求頭內容,字典形式
'''

# 讀取某一行
# 直到讀取的行返回空行為止

# 用來存放結果,dict型別
rst = {}

line = getLine(skt)
while line:
'''
判斷得到的行是報頭還是首部行,兩個操作方法不一樣
演算法是:
1. 利用作為分隔符,分割字串
2. 如果是首部行,則一定會把字串分成兩個子串
3. 否則就是一個字串
'''
r = line.split(r': ')

if len(r) == 2:
rst[r[0]] = r[1]
else:
r = line.split(r' ')
rst['method'] = r[0]
rst['uri'] = r[1]
rst['version'] = r[2]

line = getLine(skt)

return rst




def getLine(skt):
'''
socket中讀取某一行
:param skt: ocket
:return: 返回讀取到的一行str格式內容
'''
'''
前提:
1. http協議傳輸內容是ascii編碼
2. 真正傳輸的內容是通過網路流傳輸
3. 回車換行: b'\r', b'\n', b表示是一個bytes格式
'''
# 每次從socket讀取一個byte內容
b1 = skt.recv(1)
b2 = 0
#data用來存放讀取的行的內容
data = b''

#當確定還沒有讀到一行最後,也就是回車換行符號的時候,需要迴圈
while b2 != b'\r' and b1 != b'\n':
b2 = b1
b1 = skt.recv(1)

data += bytes(b2)

# decode 需要一個引數,即編碼,但是不給的話就採用預設utf-8解碼
return data.strip(b'\r').decode()


# 理解兩個引數的含義
# 理解建立一個socket的過程
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)

# 注意addr的格式是tuple
# 以及tuple兩個元素的含義
sock.bind(("127.0.0.1", 7852))
print("已經繫結埠........")
# 監聽
sock.listen()
print("正在監聽......")

# 接受一個傳進來的socket
print("準備接受socket傳入....")
skt, addr = sock.accept()
print("已經接收到傳入socket {0}".format(skt))

# 實際處理請求內容
http_info = getHttpHeader(skt)
print(http_info)




# 給對方一個反饋
msg = "I love only wangxiaojing"
skt.send(msg.encode())

skt.close()
sock.close()


v03-HTTP協議返回:
import socket



def getHttpHeader(skt):
'''
得到傳入sockethttp請求頭
:param skt: 通訊的socket
:return: 解析後的請求頭內容,字典形式
'''

# 讀取某一行
# 直到讀取的行返回空行為止

# 用來存放結果,dict型別
rst = {}

line = getLine(skt)
while line:
'''
判斷得到的行是報頭還是首部行,兩個操作方法不一樣
演算法是:
1. 利用作為分隔符,分割字串
2. 如果是首部行,則一定會把字串分成兩個子串
3. 否則就是一個字串
'''
r = line.split(r': ')

if len(r) == 2:
rst[r[0]] = r[1]
else:
r = line.split(r' ')
rst['method'] = r[0]
rst['uri'] = r[1]
rst['version'] = r[2]

line = getLine(skt)

return rst




def getLine(skt):
'''
socket中讀取某一行
:param skt: ocket
:return: 返回讀取到的一行str格式內容
'''
'''
前提:
1. http協議傳輸內容是ascii編碼
2. 真正傳輸的內容是通過網路流傳輸
3. 回車換行: b'\r', b'\n', b表示是一個bytes格式
'''
# 每次從socket讀取一個byte內容
b1 = skt.recv(1)
b2 = 0
#data用來存放讀取的行的內容
data = b''

#當確定還沒有讀到一行最後,也就是回車換行符號的時候,需要迴圈
while b2 != b'\r' and b1 != b'\n':
b2 = b1
b1 = skt.recv(1)

data += bytes(b2)

# decode 需要一個引數,即編碼,但是不給的話就採用預設utf-8解碼
return data.strip(b'\r').decode()



def sendRsp(skt, content):
'''
傳送返回值,利用傳入的socket
:param skt: 通訊的socket
:return:
'''

# 構建返回頭
rsp_1 = "HTTP/1.1 200 OK\r\n"
rsp_2 = "Date: 20180616\r\n"
# 求返回內容的長度
len_value= len(content)
rsp_3 = "Content-Length: {0}\r\n".format(len_value)
rsp_4 = "\r\n"
rsp_content = content
# rsp代表返回的全部資料資訊,裡面包含http協議本身的內容
rsp = rsp_1 + rsp_2 + rsp_3 + rsp_4 + rsp_content

skt.send(rsp.encode())



# 理解兩個引數的含義
# 理解建立一個socket的過程
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)

# 注意addr的格式是tuple
# 以及tuple兩個元素的含義
sock.bind(("127.0.0.1", 7852))
print("已經繫結埠........")
# 監聽
sock.listen()
print("正在監聽......")

# 接受一個傳進來的socket
print("準備接受socket傳入....")
skt, addr = sock.accept()
print("已經接收到傳入socket {0}".format(skt))

# 實際處理請求內容
http_info = getHttpHeader(skt)
print(http_info)




# 給對方一個反饋
msg = "I love only wangxiaojing"
sendRsp(skt, msg)

skt.close()
sock.close()


v04-OOP重構-面向物件重構程式碼
import socket
import threading

class SocketHandler:

def __init__(self, sock):
self.sock = sock
# 放置Http請求的頭部資訊
self.headInfo = set()

def startHandler(self):
'''
處理傳入請求做兩件事情
1. 解析http協議
2. 返回n內容
:return:
'''
self.headHandler()
self.sendRsp()
return None

def headHandler(self):
# 兩個下劃線開頭的變數是啥意思捏?
self.headInfo = self.__getAllLine()
print(self.headInfo)
return None

def sendRsp(self):
data = "HELLO WORLD"
self.__sendRspAll(data)
return None

#####################################

def __getLine(self):

b1 = self.sock.recv(1)
b2 = 0
data = b''

while b2 != b'\r' and b1 != b'\n' :
b2 = b1
b1 = self.sock.recv(1)

data += bytes(b2)

return data.strip(b'\r')


def __getAllLine(self):

data = b''
dataList = list()
data = b''

while True:
data = self.__getLine()
if data:
dataList.append(data)
else:
return dataList

return None

def __sendRspLine(self,data):

data += "\r\n"
self.sock.send(data.encode("ASCII"))
return None


def __sendRspAll(self, data):

self.__sendRspLine("HTTP/1.1 200 OK")

strRsp = "Content-Length: "
strRsp += str(len(data))
self.__sendRspLine( strRsp )

self.__sendRspLine("Content-Type: text/html")

self.__sendRspLine("")
self.__sendRspLine(data)


class WebServer():


def __init__(self, ip='127.0.0.1', port=7853):
self.ip = ip
self.port = port

self.sock = socket.socket( socket.AF_INET, socket.SOCK_STREAM)
self.sock.bind((self.ip, self.port))
self.sock.listen(1)
print("WebServer is started............................")

def start(self):
'''
伺服器程式一共永久性不間斷提供服務
:return:
'''
while True:
skt, addr = self.sock.accept()

if skt:
print("Received a socket {0} from {1} ................. ".format(skt.getpeername(), addr))
# sockHandler負責具體通訊
sockHandler = SocketHandler(skt)
thr = threading.Thread(target=sockHandler.startHandler , args=( ) )
thr.setDaemon(True)
thr.start()
thr.join()

skt.close()
print("Socket {0} handling is done............".format(addr))



if __name__ == '__main__':
ws = WebServer()
ws.start()

v05-配置檔案
class ServerContent:
ip = '127.0.0.1'
port = 9999

head_protocal = "HTTP/1.1 "
head_code_200 = "200 "
head_status_OK = "OK"

head_content_length = "Content-Length: "
head_content_type = "Content-Type: "
content_type_html = "text/html"

blank_line = ""



import socket
import threading

class SocketHandler:

def __init__(self, sock):
self.sock = sock
# 放置Http請求的頭部資訊
self.headInfo = set()

def startHandler(self):
'''
處理傳入請求做兩件事情
1. 解析http協議
2. 返回n內容
:return:
'''
self.headHandler()
self.sendRsp()
return None

def headHandler(self):
# 兩個下劃線開頭的變數是啥意思捏?
self.headInfo = self.__getAllLine()
print(self.headInfo)
return None

def sendRsp(self):
data = "HELLO WORLD"
self.__sendRspAll(data)
return None

#####################################

def __getLine(self):

b1 = self.sock.recv(1)
b2 = 0
data = b''

while b2 != b'\r' and b1 != b'\n' :
b2 = b1
b1 = self.sock.recv(1)

data += bytes(b2)

return data.strip(b'\r')


def __getAllLine(self):

data = b''
dataList = list()
data = b''

while True:
data = self.__getLine()
if data:
dataList.append(data)
else:
return dataList

return None

def __sendRspLine(self,data):

data += "\r\n"
self.sock.send(data.encode("ASCII"))
return None


def __sendRspAll(self, data):


self.__sendRspLine("HTTP/1.1 200 OK")

strRsp = "Content-Length: "
strRsp += str(len(data))
self.__sendRspLine( strRsp )

self.__sendRspLine("Content-Type: text/html")

self.__sendRspLine("")
self.__sendRspLine(data)


class WebServer():


def __init__(self, ip=ServerContent.ip, port=ServerContent.port):
self.ip = ip
self.port = port

self.sock = socket.socket( socket.AF_INET, socket.SOCK_STREAM)
self.sock.bind((self.ip, self.port))
self.sock.listen(1)
print("WebServer is started............................")

def start(self):
'''
伺服器程式一共永久性不間斷提供服務
:return:
'''
while True:
skt, addr = self.sock.accept()

if skt:
print("Received a socket {0} from {1} ................. ".format(skt.getpeername(), addr))
# sockHandler負責具體通訊
sockHandler = SocketHandler(skt)
thr = threading.Thread(target=sockHandler.startHandler , args=( ) )
thr.setDaemon(True)
thr.start()
thr.join()

skt.close()
print("Socket {0} handling is done............".format(addr))



if __name__ == '__main__':
ws = WebServer()
ws.start()

v06-返回靜態頁面-返回html頁面
class ServerContent:
ip = '127.0.0.1'
port = 9999

head_protocal = "HTTP/1.1 "
head_code_200 = "200 "
head_status_OK = "OK"

head_content_length = "Content-Length: "
head_content_type = "Content-Type: "
content_type_html = "text/html"

blank_line = ""



import socket
import threading

class SocketHandler:

def __init__(self, sock):
self.sock = sock
# 放置Http請求的頭部資訊
self.headInfo = set()

def startHandler(self):
'''
處理傳入請求做兩件事情
1. 解析http協議
2. 返回n內容
:return:
'''
self.headHandler()
self.sendRsp()
return None

def headHandler(self):
# 兩個下劃線開頭的變數是啥意思捏?
self.headInfo = self.__getAllLine()
print(self.headInfo)
return None


def sendRsp(self):
data = "HELLO WORLD"
'''
想返回一個靜態頁面,可以考慮把靜態頁面檔案讀入,作為str型別
然後作為一共長字串返回
'''

fp = r'.\webapp\hello.html'

with open(fp, mode='r', encoding='utf-8') as f:
data = f.read()
self.__sendRspAll(data)

return None

#####################################

def __getLine(self):

b1 = self.sock.recv(1)
b2 = 0
data = b''

while b2 != b'\r' and b1 != b'\n' :
b2 = b1
b1 = self.sock.recv(1)

data += bytes(b2)

return data.strip(b'\r')


def __getAllLine(self):

data = b''
dataList = list()
data = b''

while True:
data = self.__getLine()
if data:
dataList.append(data)
else:
return dataList

return None

def __sendRspLine(self,data):

data += "\r\n"
self.sock.send(data.encode())
return None


def __sendRspAll(self, data):


self.__sendRspLine("HTTP/1.1 200 OK")

strRsp = "Content-Length: "
strRsp += str(len(data))
self.__sendRspLine( strRsp )

self.__sendRspLine("Content-Type: text/html")

self.__sendRspLine("")
self.__sendRspLine(data)


class WebServer():


def __init__(self, ip=ServerContent.ip, port=ServerContent.port):
self.ip = ip
self.port = port

self.sock = socket.socket( socket.AF_INET, socket.SOCK_STREAM)
self.sock.bind((self.ip, self.port))
self.sock.listen(1)
print("WebServer is started............................")

def start(self):
'''
伺服器程式一共永久性不間斷提供服務
:return:
'''
while True:
skt, addr = self.sock.accept()

if skt:
print("Received a socket {0} from {1} ................. ".format(skt.getpeername(), addr))
# sockHandler負責具體通訊
sockHandler = SocketHandler(skt)
thr = threading.Thread(target=sockHandler.startHandler , args=( ) )
thr.setDaemon(True)
thr.start()
thr.join()

skt.close()
print("Socket {0} handling is done............".format(addr))



if __name__ == '__main__':
ws = WebServer()
ws.start()

webapp:hello.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>北京圖靈學院歡迎您</title>
</head>
<body>

<h1 style="color:blue"> 我愛北京圖靈學院劉大拿</h1>

</body>
</html>

v07-新增路由:
class ServerContent:
ip = '127.0.0.1'
port = 9999

head_protocal = "HTTP/1.1 "
head_code_200 = "200 "
head_status_OK = "OK"

head_content_length = "Content-Length: "
head_content_type = "Content-Type: "
content_type_html = "text/html"

blank_line = ""



import socket
import threading

class SocketHandler:

def __init__(self, sock):
self.sock = sock
# 放置Http請求的頭部資訊
self.headInfo = dict()

def startHandler(self):
'''
處理傳入請求做兩件事情
1. 解析http協議
2. 返回n內容
:return:
'''
self.headHandler()
self.reqRoute()
return None

def reqRoute(self):

uri = self.headInfo.get("uri")
if uri == b"/":
self.sendRsp(r"./webapp/hello.html")
return None
if uri == b"/favicon.ico":
self.sendStaticIco(r"./static/fav.jfif")
return None

self.sendRsp(r"./webapp/404.html")

def sendStaticIco(self, fp):
with open(fp, mode='rb') as f:
ico = f.read()
self.__sendRspAll(ico)

def headHandler(self):
self.headInfo = dict()
tmpHead = self.__getAllLine()
for line in tmpHead:
if b":" in line:
# split的具體含義
infos = line.split(b": ")
self.headInfo[infos[0]] = infos[1]
else:
infos = line.split(b" ")
self.headInfo["protocal"] = infos[2]
self.headInfo["method"] = infos[0]
self.headInfo["uri"] = infos[1]


def sendRsp(self, fp):
data = "HELLO WORLD"
'''
想返回一個靜態頁面,可以考慮把靜態頁面檔案讀入,作為str型別
然後作為一共長字串返回
'''

#r'.\webapp\hello.html'

with open(fp, mode='r', encoding='utf-8') as f:
data = f.read()
self.__sendRspAll(data)

return None

#####################################

def __getLine(self):

b1 = self.sock.recv(1)
b2 = 0
data = b''

while b2 != b'\r' and b1 != b'\n' :
b2 = b1
b1 = self.sock.recv(1)

data += bytes(b2)

return data.strip(b'\r')


def __getAllLine(self):

data = b''
dataList = list()
data = b''

while True:
data = self.__getLine()
if data:
dataList.append(data)
else:
return dataList

return None

def __sendRspLine(self,data):

if type(data) == bytes:
self.sock.send(data)
else:
data += "\r\n"
self.sock.send(data.encode())
return None


def __sendRspAll(self, data):


self.__sendRspLine("HTTP/1.1 200 OK")

strRsp = "Content-Length: "
strRsp += str(len(data))
self.__sendRspLine( strRsp )

self.__sendRspLine("Content-Type: text/html")

self.__sendRspLine("")
self.__sendRspLine(data)


class WebServer():


def __init__(self, ip=ServerContent.ip, port=ServerContent.port):
self.ip = ip
self.port = port

self.sock = socket.socket( socket.AF_INET, socket.SOCK_STREAM)
self.sock.bind((self.ip, self.port))
self.sock.listen(1)
print("WebServer is started............................")

def start(self):
'''
伺服器程式一共永久性不間斷提供服務
:return:
'''
while True:
skt, addr = self.sock.accept()

if skt:
print("Received a socket {0} from {1} ................. ".format(skt.getpeername(), addr))
# sockHandler負責具體通訊
sockHandler = SocketHandler(skt)
thr = threading.Thread(target=sockHandler.startHandler , args=( ) )
thr.setDaemon(True)
thr.start()
thr.join()

skt.close()
print("Socket {0} handling is done............".format(addr))



if __name__ == '__main__':
ws = WebServer()
ws.start()
           

<wiz_tmp_tag id="wiz-table-range-border" contenteditable="false" style="display: none;">

       



來自為知筆記(Wiz)