Python HTTP项目实战丨【生长吧!Python】
【摘要】 Python HTTP项目实战
推荐书籍
- 日本人写的 “图解Http"
- 图解系列严重推荐
HTTP项目实战
- 深入理解HTTP协议
- 模拟后台服务程序基本流程和大致框架
- 每一个步骤一个文件夹
- 图解http协议, 图解tcp/ip协议
v01-验证技术
- 验证socket-tcp技术,看能否走通流程
- 使用浏览器发送消息,访问地址
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()
V02-解析传入http协议
- 根据http协议格式,逐行读取信息
- 按行读取后的信息,需要进行拆解
import socket def getHttpHeader(skt): ''' 得到传入socket的http请求头 :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协议封装返回内容
- 返回头: “HTTP/1.1 200 OK\r\n”
- 首部行:
- “Content-Length: xxx\r\n”
- “Date: 20180616\r\n”
- 空行:
- “\r\n”
- 返回内容:
- “I love beijign tulingxueyuan”
- 例子v03
import socket def getHttpHeader(skt): ''' 得到传入socket的http请求头 :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-面向对象重构
- 两个对象:
- 一个负责监听接受传入socket, WebServer
- 一个负责通讯, SocketHandler
- 参看例子v04
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类型的页面
- 把html文件作为文件读入内容
- 作为结果反馈回去
- 静态文件存放再: webapp文件夹下
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()
v07-添加路由功能和404
- 路由: 能够理解请求并按照请求调用相应处理函数的模块
- 理解请求内容
- 能够调用或者指定相应业务处理模块
- 算法:
- 按行读取传入报文
- 假如报文能用空格分割成三段,则是请求行
- 否则,是首部行,首部行必须要能够用冒号空格分割成两段
- 首部行是天然的键值对
- 请求行需要自行增加键
- 404代表访问的资源不存在
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()
v08-添加静态文件
- 静态文件: 在web后台,一般把图片,音频,视频,附件等很少需要更改内容的文件成为静态文件
- 新建文件夹static用来存放静态文件
【生长吧!Python】有奖征文火热进行中:https://bbs.huaweicloud.com/blogs/278897
【版权声明】本文为华为云社区用户原创内容,转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息, 否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱:
cloudbbs@huaweicloud.com
- 点赞
- 收藏
- 关注作者
评论(0)