两个亮点:
- 将 request 传递进路由函数(以及表驱动法)
- 多线程
server.py
import socket
import urllib.parse
from utils import log
from routes import route_static
from routes import route_dict
class Request(object):
def __init__(self):
self.method = 'GET'
self.path = ''
self.query = {}
self.header = ''
self.body = ''
def form(self):
body = urllib.parse.unquote(self.body)
args = body.split('&')
f = {}
for arg in args:
k, v = arg.split('=')
f[k] = v
log('form() 字典', f)
return f
def header_dict(self):
header = self.header
header_list = header.split('\r\n')
h = {}
for ele in header_list:
(k, v) = ele.split(':')
h[k] = v
return h
解析请求行,存储请求数据
- request 类解析请求行,用属性存储请求行中的数据(方法、路径、get 方法提交的数据);
- from 方法存储表单提交的数据(因为表单提交的数据存储在 body 中);
- urllib.parse.uniquote_plus() 能够处理汉字和特殊字符
- header_dict 方法存储请求首部字段的建和值;
def error(code=404):
e = {
404: b'HTTP/1.1 404 NOT FOUND\r\n\r\n<h1>NOT FOUND</h1>',
}
return e.get(code, b'')
def parsed_path(path):
index = path.find('?')
if index == -1:
return path, {}
else:
path, query_string = path.split('?', 1)
args = query_string.split('&')
query = {}
for arg in args:
k, v = arg.split('=')
query[k] = v
return path, query
def response_for_path(path):
path, query = parsed_path(path)
request.path = path
request.query = query
r = {
'/static': route_static,
}
r.update(route_dict)
response = r.get(path, error)
return response(request)
这个是根据解析的路径分发路由
- 字典的键一般不用数字,但是 HTTP 状态码就是键
- 字符串的 find() 方法,找不到,返回 -1
- 表驱动发分发路由
- 字典的 update() 方法能将两个字典合并,参数为另一个字典
- 将 request 传递进路由函数
import _thread
def run(host='', port=3000):
with socket.socket() as s:
s.bind((host, port))
s.listen()
while True:
connection, address = s.accept()
_thread.start_new_thread(process_request, (address, connection))
def process_request(address, connection):
r = connection.recv(1024)
r = r.decode()
if len(r) > 0:
request = Request()
header, request.body = r.split('\r\n\r\n', 1)
h = header.split('\r\n')
parts = h[0].split()
request.header = h[1:]
path = parts[1]
request.method = parts[0]
response = response_for_path(path)
connection.sendall(response)
else:
log('接收到了一个空请求')
connection.close()
if __name__ == '__main__':
config = dict(
host='127.0.0.1',
port=3000,
)
run(**config)
routes.py
from utils import log
from models import Message
from models import User
message_list = []
def template(name):
path = 'templates/' + name
with open(path, 'r', encoding='utf-8') as f:
return f.read()
def route_index(request):
header = 'HTTP/1.1 210 VERY OK\r\nContent-Type: text/html\r\n'
body = template('index.html')
r = header + '\r\n' + body
return r.encode()
def route_login(request):
header = 'HTTP/1.1 210 VERY OK\r\nContent-Type: text/html\r\n'
if request.method == 'POST':
form = request.form()
u = User.new(form)
if u.validate_login():
result = '登录成功'
else:
result = '用户名或者密码错误'
else:
result = ''
body = template('login.html')
body = body.replace('{{result}}', result)
r = header + '\r\n' + body
# 有中文的时候最好用 utf-8 进行编码
return r.encode(encoding='utf-8')
def route_register(request):
header = 'HTTP/1.1 210 VERY OK\r\nContent-Type: text/html\r\n'
if request.method == 'POST':
form = request.form()
# 将表单提交的数据数实例化,也就是将字典变为属性
u = User.new(form)
if u.validate_register():
u.save()
#log('u的id为:', u.id)
result = '注册成功<br> <pre>{}</pre>'.format(User.all())
else:
result = '用户名或者密码长度必须大于2'
else:
result = ''
body = template('register.html')
body = body.replace('{{result}}', result)
r = header + '\r\n' + body
return r.encode(encoding='utf-8')
def route_message(request):
log('本次请求的 method', request.method)
if request.method == 'POST':
data = request.form()
else:
data = request.query
if len(data) > 0:
msg = Message.new(data)
log('post', data)
# 应该在这里保存 message_list
message_list.append(msg)
header = 'HTTP/1.1 200 OK\r\nContent-Type: text/html\r\n'
body = template('html_basic.html')
ms = '<br>'.join([str(m) for m in message_list])
body = body.replace('{{messages}}', ms)
r = header + '\r\n' + body
return r.encode()
def route_static(request):
filename = request.query.get('file', 'doge.gif')
path = 'static/' + filename
# 通过Content-Type指定传输文件类型(比较松散)
with open(path, 'rb') as f:
header = b'HTTP/1.1 200 OK\r\nContent-Type: image/gif\r\n'
r = header + b'\r\n' + f.read()
return r
route_dict = {
'/': route_index,
'/login': route_login,
'/register': route_register,
'/messages': route_message,
}
多线程
单线程:不处理完当前请求,就无法处理下一个请求,也就是一次处理一个链接。
每个程序(qq/知乎)是一个进程,相当于一条大马路,这个大马路有很多车道,那么就是每个进程有很多线程,进程的子线程崩溃了不会导致这个主进程崩溃。
网友评论