[TOC] >[success] # 尝试搭建框架 ~~~ 我们所要做的工程目录划分 1.一个db 文件夹用来存储数据 2.一个models 文件夹用来操作数据 3.一个static文件夹用来保存静态文件 4.一个templates 用来保存html 5.一个routes.py 用来做路由映射 6.一个server.py 用来做启动文件 7.一个utils.py 用来配置log ~~~ >[info] ## 编写记录log -- utils.py ~~~ import time def log(*args, **kwargs): # time.time() 返回 unix time format = '%Y/%m/%d %H:%M:%S' value = time.localtime(int(time.time())) dt = time.strftime(format, value) print(dt, *args, **kwargs) ~~~ >[info] ## 编写服务器入口文件--server.py ~~~ 1.Request 类进行,保存不同路由映射函数信息 2.response_for_path 处理run中获取的地址进行路由映射 3.run 方法用来启动server服务器 ~~~ >[danger] ##### Request 保存路由映射对应请求信息类 ~~~ 1.初始化参数 method 记录每一个映射路由的请求 2.path 记录每一个路由的地址路径 3.body 保存每一个路由的body 内容主要针对post 4.query 记录每一个get请求链接上的参数 配合parsed_path 函数 5.form 用来处理post 请求参数urllib.parse .unquote(v) 处理v 是因为post 的时候空格是+ ~~~ ~~~ class Request: def __init__(self): self.method = "GET" self.path = "" self.body = "" self.query = {} def form(self): args = self.body.split("&") f = {} for arg in args: k, v = arg.split("=") f[k] = urllib.parse .unquote(v) return f # 实例化对象 request = Reuqest() ~~~ >[danger] ##### parsed_path 处理get 请求 ~~~ 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 ~~~ >[danger] ##### 编写一个异常处理的erro文件返回404 ~~~ def error(request, code=404): e = { 404: b'HTTP/1.1 404 NOT FOUND\r\n\r\n<h1>NOT FOUND</h1>', } return e.get(code, b'') ~~~ >[danger] ##### response_for_path 处理路由映射关系 ~~~ 1.parsed_path用来处理 get 请求方法,将get 请求方法清洗,返回地址和请求参数 2.return 返回的是将路由映射保存的request 对象传入到对应的映射方法 ~~~ ~~~ def response_for_path(path): # 如果你是get path, query = parsed_path(path) request.path = path request.query = query r = { "/static":route_static, } r.update(urls) response = r.get(path, error) return response(request) ~~~ >[danger] ##### run 启动服务器文件 ~~~ def run(host='', port=3000): # 在控制台打印端口,和服务器启动时间 log('start at', '{}:{}'.format(host, port)) # 使用 with 可以保证程序中断的时候正确关闭 socket 释放占用的端口 with socket.socket() as s: s.bind((host, port)) while True: s.listen(5) connection, address = s.accept() r = b"" buffer_size = 1024 while True: r_connection = connection.recv(buffer_size) r += r_connection if len(r_connection) <= 1024: break r = r.decode('utf-8') if len(r.split())<2: continue # 拆分原始数据 获取 请求方法和路径 GET / HTTP/1.1 path = r.split()[1] request.method = r.split()[0] # 利用"\r\n\r\n" 是将请求体分割格式,获取请求体 request.body = r.split("\r\n\r\n", 1)[1] # 路由映射 response = response_for_path(path) connection.sendall(response) # 处理完请求, 关闭连接 connection.close() ~~~ >[info] ## 路由映射文件 -- routes.py ~~~ 1.这个文件主要处理每一个,映射函数的请求 2.核对templates 文件的搭配 3.以及处理一些简单的模板 ~~~ >[danger] ##### 处理读取HTML函数的 -- template ~~~ 1.注意一定要对open 中的encoding 进行编码,win默认是gbk模式 ~~~ ~~~ def template(name): path = "template/" + name with open(path, "r",encoding='utf-8') as f: return f.read() ~~~ >[danger] ##### 配置url 参数映射字典 ~~~ urls = { '/': route_index, '/login': route_login, '/register': route_register, '/messages': route_message, } ~~~ >[danger] ##### 编写静态文件的映射函数 -- route_static ~~~ def route_static(request): """ 两种情况的处理 path, query = response_for_path('/static?file=doge.gif') path '/static' """ filename = request.query.get('file', 'doge.gif') path = 'static/' + filename with open(path, 'rb') as f: header = b'HTTP/1.1 200 OK\r\nContent-Type: image/gif\r\n' img = header + b'\r\n'+ f.read() return img ~~~ >[danger] ##### 编写第一个主页映射函数 -- route_index ~~~ 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(encoding='utf-8') ~~~ >[danger] ##### 编写登录逻辑处理的---route_login ~~~ 1.传的参数中的request 保存的是每一个函数的,请求时候的处理信息 2.如果是post 请求处理创建的Request 类中form 方法保存的body中的内容 3.利用replace 替代我们在html 中特殊格式化的数据,这个页面最后渲染不应该属于任何请求,而是所有请求处理的展示内 容改变 ~~~ ~~~ 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 return r.encode(encoding='utf-8') ~~~ >[danger] ##### 编写注册处理函数 --- route_register ~~~ def route_register(request): header = 'HTTP/1.1 210 VERY OK\r\nContent-Type: text/html\r\n' if request.method == 'POST': # HTTP BODY 如下 # username=gw123&password=123 # 经过 request.form() 函数之后会变成一个字典 form = request.form() u = User.new(form) if u.validate_register(): u.save() 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') ~~~ >[danger] ##### 模拟简单的psot get请求 ---route_message ~~~ # message_list 存储了所有的 message message_list = [] def route_message(request): log('本次请求的 method', request.method) if request.method == 'POST': form = request.form() msg = Message.new(form) log('post', form) message_list.append(msg) # 应该在这里保存 message_list header = 'HTTP/1.1 200 OK\r\nContent-Type: text/html\r\n' body = template('html_basic.html') # '#'.join(['a', 'b', 'c']) 的结果是 'a#b#c' msgs = '<br>'.join([str(m) for m in message_list]) body = body.replace('{{messages}}', msgs) r = header + '\r\n' + body return r.encode(encoding='utf-8') ~~~ >[info] ## 编写数据处理文件夹 model ~~~ 1.我们处理数据库的连接模块进行拆分,成三个小py文件 2.__init__.py用来编写model 的父类和数据存放方法 3.user.py 用来处理用户信息判断验证 4.message.py 用来处理信息验证 ~~~ >[danger] # 编写__init__文件 ~~~ 1.class Model 类用来处理文件的保存和读取 --- 注意这个类几点说明 --- db_path 中使用了cls.__name__ 的方法,可以获取使用类的类名,因为类名和对象不同点在于,类名是唯一的,做成 父类后所有子类就变成唯一的 --- new 相当于创建了对应的类 --- all 是获取了所有储存文件的内容 --- save 将所有内容保存到文件目录中 2.save 方法用来保存文件信息 3.load 用来加载文件信息 ~~~ ~~~ def save(data, path): """ 本函数把一个 dict 或者 list 写入文件 data 是 dict 或者 list path 是保存文件的路径 indent 是缩进 ensure_ascii=False 用于保存中文 """ s = json.dumps(data, indent=2, ensure_ascii=False) with open(path, 'w+', encoding='utf-8') as f: log('save', path, s, data) f.write(s) def load(path): """ 本函数从一个文件中载入数据并转化为 dict 或者 list path 是保存文件的路径 """ with open(path, 'r', encoding='utf-8') as f: s = f.read() log('load', s) return json.loads(s) # Model 是用于存储数据的基类 class Model(object): @classmethod def db_path(cls): classname = cls.__name__ path = 'db/{}.txt'.format(classname) return path @classmethod def new(cls, form): # 下面一句相当于 User(form) 或者 Msg(form) m = cls(form) return m @classmethod def all(cls): """ 得到一个类的所有存储的实例 """ path = cls.db_path() models = load(path) log('log', models) ms = [cls.new(m) for m in models] return ms def save(self): """ save 函数用于把一个 Model 的实例保存到文件中 """ models = self.all() log('models', models) models.append(self) # __dict__ 是包含了对象所有属性和值的字典 l = [m.__dict__ for m in models] log("lmodel", l) path = self.db_path() save(l, path) def __repr__(self): """ 对象的显示形式,__dict__ 返回的是初始化 参数 """ classname = self.__class__.__name__ properties = ['{}: ({})'.format(k, v) for k, v in self.__dict__.items()] s = '\n'.join(properties) return '< {}\n{} >\n'.format(classname, s) ~~~ >[danger] 用来进行用户注册登录判断的 user.py ~~~ from models import Model class User(Model): def __init__(self, form): self.username = form.get('username', '') self.password = form.get('password', '') def validate_login(self): return self.username == 'gua' and self.password == '123' def validate_register(self): return len(self.username) > 2 and len(self.password) > 2 ~~~ >[danger] 用来进行简单的post,get message.py ~~~ from models import Model # 定义一个 class 用于保存 message class Message(Model): def __init__(self, form): self.author = form.get('author', '') self.message = form.get('message', '') ~~~ >[info] ## html 文件 >[danger] ##### login.html ~~~ <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>注册登录页面</title> </head> <body> <h1>登录</h1> <form action="/login" method="post"> <input type="text" name="username" placeholder="请输入用户名"> <br> <input type="text" name="password" placeholder="请输入密码"> <br> <button type="submit">登录</button> </form> <h3>{{result}}</h3> </body> </html> ~~~ >[danger] ##### register.html ~~~ <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>注册页面</title> </head> <body> <h1>注册</h1> <form action="/register" method="post"> <input type="text" name="username" placeholder="请输入用户名"> <br> <input type="text" name="password" placeholder="请输入密码"> <br> <button type="submit">注册</button> </form> <h3>{{result}}</h3> </body> </html> ~~~ >[danger] ##### index.html ~~~ <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>吃瓜主页</title> </head> <body> <h1>吃瓜</h1> <a href="/login">Login</a> <img src="/static?file=doge.gif"/> <img src="/static?file=doge1.jpg"/> <img src="/static?file=doge2.gif"/> </body> </html> ~~~ >[danger] ##### html_basic.html ~~~ <!DOCTYPE html> <!-- 注释是这样的, 不会被显示出来 --> <!-- html 格式是浏览器使用的标准网页格式 简而言之就是 标签套标签 --> <!-- html 中是所有的内容 --> <html> <!-- head 中是放一些控制信息, 不会被显示 --> <head> <!-- meta charset 指定了页面编码, 否则中文会乱码 --> <meta charset="utf-8"> <!-- title 是浏览器显示的页面标题 --> <title>例子 1</title> </head> <!-- body 中是浏览器要显示的内容 --> <body> <!-- html 中的空格是会被转义的, 所以显示的和写的是不一样的 --> <!-- 代码写了很多空格, 显示的时候就只有一个 --> 很 好普通版 <h1>很好 h1 版</h1> <h2>很好 h2 版</h2> <h3>很好 h3 版</h3> <!-- form 是用来给服务器传递数据的 tag --> <!-- action 属性是 path --> <!-- method 属性是 HTTP方法 一般是 get 或者 post --> <!-- get post 的区别上课会讲 --> <form action="/messages" method="post"> <!-- textarea 是一个文本域 --> <!-- name 属性, 用处上课讲 --> <textarea name="message"></textarea> <textarea name="author"></textarea> <!-- button type=submit 才可以提交表单 --> <button type="submit">POST 提交</button> </form> <form action="/messages" method="get"> <textarea name="message"></textarea> <button type="submit">GET 提交</button> </form> {{messages}} </body> </html> ~~~