06 | python HTTPServer(入门)
目标:理解 httpserver 开发的流程 ,为后面学习成熟的框架打好基础。理解配置文件这种与用户交互的方式。理解 httpserver 和 webframe 两部分的作用。 复习 多线程并发 和 IO 多路复用。理解 功能类的封装。理解 网站请求有 页面和数据两类。
功能 :
httpserver部分
获取http请求
解析http请求
将请求发送给WebFrame
从WebFrame接收反馈数据
将数据组织为Response格式发送给客户端
WebFrame部分
从httpserver接收具体请求
根据请求进行逻辑处理和数据处理
将需要的数据反馈给httpserver
特点
采用httpserver和应用处理分离的模式,降低了耦合度
采用了用户配置文件的思路
webframe部分采用了模拟后端框架的处理方法
技术点
httpserver部分需要与两端建立通信
webFrame部分采用多路复用接收并发请求
数据传递使用json格式
项目结构:
|--httpserver --HttpServer.py (主程序)
| --config (httpserver配置)
project--|
|
|
|--WebFrame --WebFrame.py (主程序代码)
--static (存放静态网页)
--views.py ( 应用处理程序)
--urls.py (存放路由)
--settings (框架配置)
交互数据格式协议
httpserver-->webframe {method:'GET',info:'/'}
webframe-->httpserver {status:'200',data:'ccccc'}
🔧HTTPserver代码
"""
config.py
http server 相关配置
需要使用者提供的内容写在配置文件
"""
# [http server address]
HOST = '0.0.0.0'
PORT = 8000
# [debug]
DEBUG = True
# web frame地址
frame_ip = '127.0.0.1'
frame_port = 8080
"""
httpserver.py
httpserver 3.0
获取http请求
解析http请求
将请求发送给WebFrame
从WebFrame接收反馈数据
将数据组织为Response格式发送给客户端
"""
from socket import *
import sys
from threading import Thread
import json,re
from config import * # 导入配置文件内容
# 负责和webframe交互, socket客户端
def connect_frame(env):
s = socket()
try:
s.connect((frame_ip,frame_port))
except Exception as e:
print(e)
return
# 将env转换为json发送
data = json.dumps(env)
s.send(data.encode())
# 接收webframe反馈的数据
data = s.recv(1024 * 1024 * 10).decode()
return json.loads(data)
# httpserver功能
class HTTPServer:
def __init__(self):
self.host = HOST
self.port = PORT
self.create_socket()
self.bind()
# 创建套接字
def create_socket(self):
self.sockfd = socket()
self.sockfd.setsockopt(SOL_SOCKET, # 是否端口立即重用
SO_REUSEADDR,
DEBUG)
# 绑定地址
def bind(self):
self.address = (self.host,self.port)
self.sockfd.bind(self.address)
# 启动服务
def serve_forever(self):
self.sockfd.listen(5)
print("Start the http server:%d"%self.port)
while True:
connfd,addr = self.sockfd.accept()
client = Thread(target=self.handle,
args=(connfd,))
client.setDaemon(True)
client.start()
# 具体处理客户端请求
def handle(self,connfd):
request = connfd.recv(4096).decode()
pattern=r'(?P<method>[A-Z]+)\s+(?P<info>/\S*)'
try:
env = re.match(pattern,request).groupdict() # match 是从开头开始匹配防止后面也有类似内容
except:
connfd.close()
return
else:
# data就是从webframe得到的数据
data = connect_frame(env)
if data:
self.response(connfd,data)
def response(self,connfd,data):
# data-->{'status':'200','data':'xxx'}
if data['status'] == '200':
responseHeaders = "HTTP/1.1 200 OK\r\n"
responseHeaders += 'Content-Type:text/html\r\n'
responseHeaders += '\r\n'
responseBody = data['data']
elif data['status'] == '404':
responseHeaders = "HTTP/1.1 404 Not Found\r\n"
responseHeaders += 'Content-Type:text/html\r\n'
responseHeaders += '\r\n'
responseBody = data['data']
elif data['status'] == '302':
pass
# 将数据发送给浏览器
data = responseHeaders+responseBody
connfd.send(data.encode())
if __name__ == '__main__':
httpd = HTTPServer()
httpd.serve_forever() # 启动服务
#server_test.py
# 用户测试httpserver
from socket import *
import json
s = socket()
s.bind(('127.0.0.1',8080))
s.listen(5)
while True:
c,addr = s.accept()
data = c.recv(1024)
print(data)
data = json.dumps({'status':'200','data':'ccccccc'})
c.send(data.encode())
🔧webframe 代码
"""
settings.py
web frame 部分的配置文件
"""
# [frame address]
frame_ip = '127.0.0.1'
frame_port = 8080
# [debug]
DEBUG = True
# [static]
STATIC_DIR = "./static"
"""
url.py
声明客户端能够请求的数据
"""
from views import *
urls = [
('/time',show_time),
('/guonei',guonei),
('/guoji',guoji)
]
"""
views.py
数据处理的具体函数
"""
import time
def show_time():
return time.ctime()
def guonei():
return "你看到了国内新闻"
def guoji():
return "你看到了国际新闻"
"""
webframe.py
模拟网站的后端应用
从httpserver接收具体请求
根据请求获取网页
将需要的数据反馈给httpserver
"""
from socket import *
import json
from settings import *
from select import *
from urls import *
# 应用类,实现具体的后端功能
class Application:
def __init__(self):
self.host = frame_ip
self.port = frame_port
self.fdmap = {} # 查找地图
self.ep = epoll() # epoll对象
self.sockfd = socket()
self.sockfd.setsockopt(SOL_SOCKET,
SO_REUSEADDR,
DEBUG)
self.sockfd.bind((self.host,self.port))
# 用于服务启动
def start(self):
self.sockfd.listen(5)
print("Listen the port %d"%self.port)
# 关注sockfd
self.ep.register(self.sockfd,EPOLLIN)
self.fdmap[self.sockfd.fileno()] = self.sockfd
while True:
events = self.ep.poll()
for fd,event in events:
if fd == self.sockfd.fileno():
connfd,addr = self.fdmap[fd].accept()
self.ep.register(connfd)
self.fdmap[connfd.fileno()] = connfd
else:
self.handle(self.fdmap[fd])
self.ep.unregister(fd)
del self.fdmap[fd]
# 具体处理请求
def handle(self,connfd):
request = connfd.recv(1024).decode()
request = json.loads(request)
# request -> {'method':'GET','info':'/'}
if request['method'] == 'GET':
if request['info'] == '/' or \
request['info'][-5:] == '.html':
response = self.get_html(request['info'])
else:
response = self.get_data(request['info'])
elif request['method'] == 'POST':
pass
# 将数据发送给HTTPserver
response = json.dumps(response)
connfd.send(response.encode())
connfd.close()
# 网页处理函数
def get_html(self,info):
if info == '/':
filename = STATIC_DIR+'/index.html'
else:
filename = STATIC_DIR+info
try:
fd = open(filename)
except:
f = open(STATIC_DIR+'/404.html')
return {'status':'404','data':f.read()}
else:
return {'status':'200','data':fd.read()}
# 处理数据
def get_data(self,info):
for url,func in urls:
if url == info:
return {'status':'200','data':func()}
return {'status':'404','data':'Sorry...'}
if __name__ == '__main__':
app = Application()
app.start()