原创仅供学习,转载请注明出处
在前面的功能开发中,我已经写道了Python web框架开发 - 路由功能,此时已经基本讲述了web框架如何控制访问过来的http请求路由到相应的处理方法。
那么下一步就是要考虑如何使用框架,从数据库中查询数据,然后呈现到html页面中。 例如:准备要呈现一个table表格,此时需要从数据库中读取数据来呈现,那么该怎么做呢?
这个功能在框架中定义为模板功能,例如PHP就有smarty这样的模板引擎,当然python的django也有这样的模板引擎。
那么下面先准备一下准备测试的html代码以及mysql数据。
<!DOCTYPE html> <html> <meta charset="utf-8"> <body> <table border="1px"> <thead> <th>肥仔id</th> <th>肥仔名称</th> <th>肥仔爱好</th> <th>肥仔职业</th> <th>肥仔居住地</th> </thead> <tbody> <tr> <td>1</td> <td>胖子老板</td> <td>斗地主</td> <td>小卖部老板</td> <td>西九龙</td> </tr> <tr> <td>2</td> <td>肥仔白</td> <td>吃槟榔、打dota</td> <td>挨踢男</td> <td>铜锣湾</td> </tr> </tbody> </table> </body> </html>
将html保存到项目当中,页面打开呈现如下:
好了,上面准备了一个超级简单的table页面。
这上面的每一行数据暂时是通过html里面写死的,那么下面就要准备一些mysql数据,用于后面从mysql中读取,然后再在页面中呈现。
test_db
CREATE DATABASE IF NOT EXISTS test_db CHARACTER SET utf8 COLLATE utf8_general_ci;
fatboy_hobby
create table fatboy_hobby( id int unsigned primary key auto_increment not null, name varchar(40) default null COMMENT "肥仔名称", hobby varchar(40) default null COMMENT "肥仔爱好", professional varchar(40) default null COMMENT "肥仔职业", address varchar(40) default null COMMENT "肥仔居住地" );
insert into fatboy_hobby VALUES(null,"胖子老板","斗地主","小卖铺老板","西九龙"); insert into fatboy_hobby VALUES(null,"肥仔白","打dota、吃槟榔","挨踢攻城狮","铜锣湾"); insert into fatboy_hobby VALUES(null,"肥仔超","吃槟榔、养养猫","挨踢攻城狮","大上海");
好了,创建有了一个表的数据了,那么下面可以使用pymysql来进行增删查改的操作。
另外,在进行数据查询之前,首先将fat_boy.html
放入web项目中,先展示看看。
fat_boy.html
虽然已经将html文件放入项目中,但是还是不能直接访问的,需要编写访问fat_boy.html
的路由方法。
运行测试如下:
好了,页面也有了。下面就是考虑如何实现模板功能。
其实模板功能就是根据html页面的表格,在处理方法的时候,对html里面的数据进行处理。 例如:将mysql查询出来的数据,拼接成html内容,重新返回页面,这时候页面呈现的就是myslq查询出来并且拼接好的html内容。
那么有了大概的理解之后,就来看看实现的思路:
安装pymysql
pip3 install pymysql
在fatboy处理方法中,将fatboy表的数据查询出来
首先将数据查询出来看看先,测试如下:
好了,这样已经可以查询出数据了。
测试一下看看:
将拼接好的content内容替换到返回浏览器的内容中
首先来测试写写html的替换语句:
In [1]: import re In [2]: html_content = """""" In [3]: html_content = """<!DOCTYPE html> ...: <html> ...: <meta charset="utf-8"> ...: <body> ...: <table border="1px"> ...: <thead> ...: <th>肥仔id</th> ...: <th>肥仔名称</th> ...: <th>肥仔爱好</th> ...: <th>肥仔职业</th> ...: <th>肥仔居住地</th> ...: </thead> ...: <tbody> ...: <{content}> ...: </tbody> ...: </table> ...: </body> ...: </html>""" In [4]: content = """<tr> ...: <td>1</td> ...: <td>胖子老板</td> ...: <td>斗地主</td> ...: <td>小卖铺老板</td> ...: <td>西九龙</td> ...: </tr> ...: """ In [5]: ret = re.sub(r"<{content}>",content,html_content) In [6]: print(ret) <!DOCTYPE html> <html> <meta charset="utf-8"> <body> <table border="1px"> <thead> <th>肥仔id</th> <th>肥仔名称</th> <th>肥仔爱好</th> <th>肥仔职业</th> <th>肥仔居住地</th> </thead> <tbody> <tr> <td>1</td> <td>胖子老板</td> <td>斗地主</td> <td>小卖铺老板</td> <td>西九龙</td> </tr> </tbody> </table> </body> </html> In [7]:
从上面的测试结果来看,使用ret = re.sub(r"<{content}>",content,html_content)
就可以匹配替换后,返回正常的html内容了。
同时,要注意,因为这次匹配后是字符串,需要进行utf-8
编码后,才能返回浏览器。
例如:ret.encode("utf-8")
好了,下面实现一下代码看看:
测试一下看看:
可以看出,查询出来的结果编码是正常的,但是原来HTML的内容就出现编码错误的情况了。 那么这种情况该怎么处理呢?
这里就涉及一个接受HTML内容使用的bytes类型转化的问题。
html
读取内容的时候,数据类型是bytes
型,此时可以认为以及默认使用了encode("utf-8")
编码,所以需要进行反编码decode("utf-8")
。str()
转为字符串。utf-8
编码返回(encode("utf-8")
)。测试结果如下:
好了,基本上大致功能就是这样的了。肯定有很多其他地方可以优化,但是目前本篇只要介绍大概实现功能就好了。
fat_boy.html
<!DOCTYPE html> <html> <meta charset="utf-8"> <body> <table border="1px"> <thead> <th>肥仔id</th> <th>肥仔名称</th> <th>肥仔爱好</th> <th>肥仔职业</th> <th>肥仔居住地</th> </thead> <tbody> <{content}> </tbody> </table> </body> </html>
webserver.py
#coding=utf-8 from socket import * import re import multiprocessing import time import framework import sys class WebServer: def __init__(self,server_port): # 创建套接字 self.server_socket = socket(AF_INET, SOCK_STREAM) # 设置当服务器先close 即服务器端4次挥手之后资源能够立即释放,这样就保证了,下次运行程序时 可以立即绑定7788端口 self.server_socket.setsockopt(SOL_SOCKET, SO_REUSEADDR, 1) # 设置服务端提供服务的端口号 self.server_socket.bind(('', server_port)) # 使用socket创建的套接字默认的属性是主动的,使用listen将其改为被动,用来监听连接 self.server_socket.listen(128) #最多可以监听128个连接 def start_http_service(self): # 开启while循环处理访问过来的请求 while True: # 如果有新的客户端来链接服务端,那么就产生一个新的套接字专门为这个客户端服务 # client_socket用来为这个客户端服务 # self.server_socket就可以省下来专门等待其他新的客户端连接while True: client_socket, clientAddr = self.server_socket.accept() # handle_client(client_socket) # 设置子进程 new_process = multiprocessing.Process(target=self.handle_client,args=(client_socket,)) new_process.start() # 开启子进程 # 因为子进程已经复制了父进程的套接字等资源,所以父进程调用close不会将他们对应的这个链接关闭的 client_socket.close() def handle_client(self,client_socket): """为一个客户端服务""" # 接收对方发送的数据 recv_data = client_socket.recv(1024).decode("utf-8") # 1024表示本次接收的最大字节数 # 打印从客户端发送过来的数据内容 #print("client_recv:",recv_data) request_header_lines = recv_data.splitlines() for line in request_header_lines: print(line) # 返回浏览器数据 # 设置内容body # 使用正则匹配出文件路径 print("------>",request_header_lines[0]) print("file_path---->","./html/" + re.match(r"[^/]+/([^\s]*)",request_header_lines[0]).group(1)) ret = re.match(r"[^/]+/([^\s]*)",request_header_lines[0]) if ret: file_path = "./html/" + ret.group(1) if file_path == "./html/": file_path = "./html/index.html" print("file_path *******",file_path) # 判断file_path是否py文件后缀,如果是则请求动态资源,否则请求静态资源 if file_path.endswith(".py"): # framework.application(client_socket) # 支撑WGSI协议的调用方式 environ = {} environ['REQUEST_URI'] = file_path # 设置需要打开的文件路径 response_body = framework.application(environ, self.start_response) # 设置返回的头信息header # 1.拼接第一行HTTP/1.1 200 OK + 换行符内容 response_headers = "HTTP/1.1 " + self.application_header[0] + "\r\n" # 2.循环拼接第二行或者多行元组内容:Content-Type:text/html for var in self.application_header[1]: response_headers += var[0]+":"+var[1] + "\r\n" # 3.空一行与body隔开 response_headers += "\r\n" # 4.打印看看header的内容信息 print("response_header=") print(response_headers) # 设置返回的浏览器的内容 client_socket.send(response_headers.encode("utf-8")) client_socket.send(response_body) else: # 请求静态资源 try: # 设置返回的头信息 header response_headers = "HTTP/1.1 200 OK\r\n" # 200 表示找到这个资源 response_headers += "\r\n" # 空一行与body隔开 # 读取html文件内容 file_name = file_path # 设置读取的文件路径 f = open(file_name,"rb") # 以二进制读取文件内容 response_body = f.read() f.close() # 返回数据给浏览器 client_socket.send(response_headers.encode("utf-8")) #转码utf-8并send数据到浏览器 client_socket.send(response_body) #转码utf-8并send数据到浏览器 except: # 如果没有找到文件,那么就打印404 not found # 设置返回的头信息 header response_headers = "HTTP/1.1 404 not found\r\n" # 200 表示找到这个资源 response_headers += "\r\n" # 空一行与body隔开 response_body = "<h1>sorry,file not found</h1>" response = response_headers + response_body client_socket.send(response.encode("utf-8")) def start_response(self,status,header): self.application_header = [status,header] print("application_header=",self.application_header) def main(): # 通过sys.argv来获取服务端的端口号 # server_port = int(sys.argv[1]) server_port = 7788 webserver = WebServer(server_port) webserver.start_http_service() if __name__ == "__main__": main()
framework.py
import re from pymysql import * # 设置路由对应的字典 route_dict = dict() """ route_dict = { "index" : index, "page_about" : page_about, } """ def route(url): def set_route(func): # 设置字典映射 route_dict[url] = func def call_func(file_path): return func(file_path) return call_func return set_route # index页面 @route("index") def index(file_path): file_path = re.sub(r".py", ".html", file_path) with open(file_path,"rb") as f: return f.read() # page_about页面 @route("page_about") def page_about(file_path): file_path = re.sub(r".py", ".html", file_path) with open(file_path,"rb") as f: return f.read() # fat_boy页面 @route("fat_boy") def fat_boy(file_path): # 创建Connection连接 conn = connect(host='localhost', port=3306, user='root', password='', database='test_db', charset='utf8') # 获得Cursor对象 cs1 = conn.cursor() # 执行select语句,并返回受影响的行数:查询一条数据 count = cs1.execute('select * from fatboy_hobby;') # 打印受影响的行数 print("查询到%d条数据:" % count) # 需要拼接的HTML内容 """ <tr> <td>1</td> <td>胖子老板</td> <td>斗地主</td> <td>小卖部老板</td> <td>西九龙</td> </tr> <tr> <td>2</td> <td>肥仔白</td> <td>吃槟榔、打dota</td> <td>挨踢男</td> <td>铜锣湾</td> </tr> """ content = "" for i in range(count): # 获取查询的结果 result = cs1.fetchone() # 打印查询的结果 print(result) # 获取查询的结果 print("肥仔名称=%s" % result[0]) print("肥仔爱好=%s" % result[1]) print("肥仔职业=%s" % result[2]) print("肥仔居住地=%s" % result[3]) content += """ <tr> <td>%s</td> <td>%s</td> <td>%s</td> <td>%s</td> <td>%s</td> </tr> """ % (result[0],result[1],result[2],result[3],result[4]) print("content",content.encode("utf-8")) # 关闭Cursor对象 cs1.close() conn.close() file_path = re.sub(r".py", ".html", file_path) with open(file_path,"rb") as f: # html_content = f.read() html_content = str(f.read().decode("utf-8")) # 使用正则匹配替换<{content}> ret = re.sub(r"<{content}>", content, html_content) print("html_content=",ret) # 返回html内容 return ret.encode("utf-8") # 支撑WGSI协议 def application(environ, start_response): start_response('200 OK', [('Content-Type', 'text/html;charset=UTF-8')]) # 接受需要打开的文件路径 print("动态file_path= ", environ['REQUEST_URI']) # 使用正则匹配路径中的函数方法 try: func_name = re.match(r"\./html/([^.]+)",environ['REQUEST_URI']).group(1) except Exception as e: print("匹配不上,%s" % e) else: response_body = route_dict[func_name](environ['REQUEST_URI']) return response_body
本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。
我来说两句