WSGI-mini-web框架服务器
前期准备:
安装python环境
安装pycharm
安装MySQL数据库
安装pymsql
创建一个学生表,存入数据
我们只是实现一个非常简单的web服务,前端页面不会专门做页面文件,会在代码中以具体命令的形式形成文件样式。服务器的功能:
1、show.html显示student表中的数据,点击页面内的修改按钮,会跳转到change.html页面修改内容
2、change.html修改student表中数据,将学生姓名由“张三”改为“李四”,点击页面内的”show page”会重新跳转到显示学生信息页面重新显示修改后的学生信息
创建表
create table student( id int primary key, sname varchar(50) not null, sex char(5) not null );
原始表数据
一、创建tcp服务器tcpWebServer.py
服务器需要实现多线程的访问,采用面向对象的思想,创建一个服务器类。采用wsgi的思想,需要在服务器类中预先设置一个set_response方法,供框架代码中application的调用
Tcp服务器类创建流程如下:
因为多线程中都需要使用到套接字创建、ip和port的绑定,以及监听(listen),所以我们设计类的时候将这几部分设置为对象属性,直接在初始化方法中创建
创建__init__方法
- 创建套接字绑定ip和port:使用命令bind()
- Listen使套接字变为可以被动链接:使用命令listen()
创建服务器处理请求/发送请求方法
- 接收客户端传送的request数据
- 因为前端发送的数据是url地址所以需要处理url地址的代码
- 设置伪静态的url地址
- 发送response返回给前端
创建set_response函数
创建运行函数
1 import multiprocessing 2 import re 3 import socket 4 from 简单web服务实现 import web_frame 5 6 7 class MSGIServer: 8 def __init__(self): 9 """初始化方法,完成套接字创建、ip和port绑定、listen监听""" 10 # 创建套接字 11 self.tcp_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) 12 self.tcp_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) 13 14 # 绑定ip和端口,使用方法bind((ip, 端口)),注意bind方法需要入参一个元组 15 # 自己编写的代码不要使用1024以内的端口号 16 self.tcp_socket.bind(('', 7890)) 17 18 #监听 19 self.tcp_socket.listen(128) 20 21 22 def client_server(self, new_socket): 23 """处理和客户端之间数据的交互:接收recv/发送send""" 24 # 接收客户端发送过来的请求 25 # 使用方法recv(),数字参数是指定长度 26 # decode()方法实现解码,参数为编码格式,因为为使用的window系统,所以使用gbk格式 27 request = new_socket.recv(1024).decode('gbk') 28 print(str(request).splitlines()[0]) 29 # 使用正则提取请求url地址的后缀名 30 # group()表示分组匹配 31 file_name = re.match('.*/.*\.([a-z]*)\sH', str(request).splitlines()[0]).group(1) 32 # 使用正则提取请求的url地址,用于后面代码中进行匹配 33 page_name = re.match('.*/(.*)\sH', str(request).splitlines()[0]).group(1) 34 # 返回数据给客户端 35 # 根据请求的url地址的不同,返回不同的数据,伪静态(.html)内容是目标内容,否则一律返回404 36 if not file_name.endswith('html'): 37 response = "HTTP/1.1 200 OK \r\n" 38 response += '\r\n' 39 response += "<h1>404 NOT FOUND</h1>" 40 new_socket.send(response.encode('gbk')) 41 else: 42 # 创建字典,后面调用application函数作为参数传递 43 env = dict() 44 env['path'] = page_name 45 print('--->page_name=%s<---' % page_name) 46 # 调用框架模块中的application方法,传入url信息,确定执行的命令 47 body = web_frame.application(env, self.set_response) 48 # print('--->body=%s<---' % body) 49 # response = "HTTP/1.1 200 OK \r\n" 50 header = 'HTTP/1.1 %s\r\n' % self.status 51 for temp in self.header: 52 header += '%s:%s' % (temp[0], temp[1]) 53 header += '\r\n' 54 response = header + '\r\n' + body 55 56 new_socket.send(response.encode('utf-8')) 57 new_socket.close() 58 59 # 设置请求头 60 def set_response(self, status, headers): 61 self.status = status 62 self.header = headers 63 64 65 def run_forver(self): 66 """实现web服务器""" 67 while True: 68 # 4、等待新客户端的链接 accept 69 new_socket, socket_address = self.tcp_socket.accept() 70 # 5、为新客户端服务,独立为服务的方法处理接收和发送数据(request\response) 71 p = multiprocessing.Process(target=self.client_server, args=(new_socket,)) 72 # client_server(new_socket) 73 p.start() 74 new_socket.close() 75 self.tcp_socket.close() 76 77 78 def main(): 79 msgi_server = MSGIServer() 80 msgi_server.run_forver() 81 82 if __name__ == '__main__': 83 main()
二、创建框架web_frame.py
- 创建闭包
- 创建两个函数show(显示学生信息)、change(修改学生信息)
- 创建application函数
1 import time 2 import pymysql 3 import logging 4 5 URL_FUNC_DICT = dict() 6 func_list = list() 7 8 9 def route(url): 10 """创建路由闭包: 11 实现url地址的路由,即根据装饰器填入的url信息执行装饰器所装饰的方法 12 """ 13 def set_func(func): 14 URL_FUNC_DICT[url] = func 15 16 def call_func(): 17 func() 18 return call_func 19 return set_func 20 21 22 @route('show.html') 23 def show(): 24 """登录网页""" 25 sql = "select * from student;" 26 # 链接MySQL服务器 27 conn = pymysql.connect(host='127.0.0.1', user='wang', password='123456', database='jingdong', port=3306, charset='utf8') 28 cur = conn.cursor() 29 cur.execute(sql) 30 # 获取查询到的数据 31 data = cur.fetchone() 32 print(data) 33 cur.close() 34 conn.close() 35 # 构建一个网页内容 36 table = """<html> 37 <head> 38 <meta charset="utf-8"> 39 <title>学生信息</title> 40 </head> 41 <body> 42 <tr> 43 <td>%s</td> 44 <td>%s</td> 45 <td>%s</td> 46 <td> 47 <a href="/change.html"><input type="button" value="修改"></a> 48 </td> 49 </tr> 50 </body> 51 </html>""" 52 # 将数据库查询到的数据拼接到网页内容中 53 res = table % (str(data[0]), data[1], data[2]) 54 # return cur.fetchone()[0]+str 55 return res 56 57 58 @route('change.html') 59 def change(): 60 """修改数据页面""" 61 # 链接MySQL服务器 62 sql = "update student set sname='李四';" 63 # 创建数据库链接对象 64 conn = pymysql.connect(host='127.0.0.1', user='wang', password='123456', database='jingdong', port=3306, 65 charset='utf8') 66 # 创建游标 67 cur = conn.cursor() 68 # 执行sql命令 69 lines = cur.execute(sql) 70 # 提交数据 71 conn.commit() 72 cur.close() 73 conn.close() 74 return "<a href=\"show.html\">show page</a>" 75 76 77 # 服务器代码调用执行的代码 78 # 遵照wsgi协议,框架中需要存在一个application(字典, 函数引用)函数 79 # 字典,用来传入前端所提交的信息 引用的函数时服务器创建的封装响应头的函数 80 def application(environ, start_response): 81 # 调用tcpWebServer模块中的设置相应函数,设置响应头 82 start_response('200 OK', [('Context-Type', 'text/html')]) 83 # 获取前端传入的字典中的路径,即url地址 84 page_name = environ['path'] 85 # 设置日志 86 logging.basicConfig(level=logging.INFO, 87 filename='./log.txt', 88 filemode='a', 89 # format表示日志文件中显示的格式 90 # astime时间 写入文件名 第几行的日志信息 日志等级 打印的信息 91 format='%(asctime)s-%(filename)s[line:%(lineno)d]-%(levelname)s:%(message)s' 92 ) 93 logging.info('访问的是:%s' % page_name) 94 if page_name in URL_FUNC_DICT.keys(): 95 return URL_FUNC_DICT[page_name]() 96 else: 97 return 'not Found' 98
运行效果
代码运行,浏览器输入”localhost:7890/show.html”查看学生的信息
点击“修改”按钮会跳转到change.html页面
再点击“show page”链接会重新跳转回show.html页面,显示修改后的学生信息
数据库中的数据也已经同步更新
代码目录下已经生成了日志文件
内容为我们设定的显示内容
以上是一个非常粗糙的web服务,后续我们会使用到Django框架来详细设计一个完整的web服务