前端Web框架的实现过程
一、Web框架的本质:
我们可以这样理解:所有的Web应用本质上就是一个socket服务端,而用户的浏览器就是一个socket客户端。 这样我们就可以自己实现Web框架了。
半成品自定义web框架
import socket server = socket.socket()
server.bind(("127.0.0.1", 8080))
server.listen() while True:
conn, addr = server.accept()
data = conn.recv(8096)
conn.send(b"OK")
conn.close()可以说Web服务本质上都是在这十几行代码基础上扩展出来的。这段代码是web服务的核心。
那么用户的浏览器输入一个网址,会给服务端发送数据,那么服务端会发送什么格式的数据来进行浏览器与服务端直接的收发通信呢?
必须有一个统一的规则,让双方发送消息、接收消息的时候有个格式依据,这样就不会出乱子。
这个规则就是HTTP-超文本传输协议
先看看浏览器访问服务端会发生什么:import socket server = socket.socket()
server.bind(("127.0.0.1", 8080))
server.listen() while True:
conn, addr = server.accept()
data = conn.recv(8096)
print(data)
conn.send(b"OK")
conn.close()输出:
b'GET / HTTP/1.1\r\nHost: 127.0.0.1:8080\r\nConnection: keep-alive\r\nUpgrade-Insecure-Requests: 1\r\nUser-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/64.0.3282.186 Safari/537.36\r\nAccept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8\r\nDNT: 1\r\nAccept-Encoding: gzip, deflate, br\r\nAccept-Language: zh-CN,zh;q=0.9\r\nCookie: csrftoken=RKBXh1d3M97iz03Rpbojx1bR6mhHudhyX5PszUxxG3bOEwh1lxFpGOgWN93ZH3zv\r\n\r\n'
这就是浏览器给服务端发送的请求数据,接下来得知道,HTTP请求和相应的格式:
数据格式之GET请求:
请求首行
请求头(一堆k,v键值对)请求体(post请求携带的数据)
数据格式之响应:
响应首行
响应头(一堆k,v键值对)响应体(post请求携带的数据)
接下来继续完善web框架:(根据不同路径返回不同的内容):
'''
根据URL中不同的路径返回不同的内容
''' import socket # 创建server服务连接
server = socket.socket()
server.bind(('127.0.0.1', 8080)) # 绑定服务端IP和端口(本机做服务端则为127.0.0.1,这样socket就会自动识别。
server.listen(5) # 半连接池 # 下面是通信循环:
while True:
conn, addr = server.accept() # 等待客户的连接,这个地方是阻塞点
data_bytes = conn.recv(1024) # 接收客户的发来的请求数据
data = data_bytes.decode('utf-8') # 解码收到的数据
data1 = data.split('\r\n')[0] # 分离出第一行数据(第一个\r\n之前的数据)
url = data1.split()[1] # 继续分离,用空格来分割,然后取索引为1的元素就是请求的访问路径
conn.send(b'HTTP/1.1 200 OK\r\n\r\n') # 因为要遵循HTTP协议,所以回复的消息也要加状态行
# 根据不同的路径返回不同的内容:
if url == '/index':
response = b'index'
elif url == '/home':
response = b'home'
else:
response = b'404 not found!'
conn.send(response)
conn.close()继续优化:用函数来返回不同的内容:
import socket # 创建server服务连接
server = socket.socket()
server.bind(('127.0.0.1', 8080)) # 绑定服务端IP和端口(本机做服务端则为127.0.0.1,这样socket就会自动识别。
server.listen(5) # 半连接池 def index(url):
response = b'index'
return response def home(url):
response = b'home'
return response url_list = [
('/index', index),
('/home', home),
] # 下面是通信循环:
while True:
conn, addr = server.accept() # 等待客户的连接,这个地方是阻塞点
data_bytes = conn.recv(1024) # 接收客户的发来的请求数据
data = data_bytes.decode('utf-8') # 解码收到的数据
data1 = data.split('\r\n')[0] # 分离出第一行数据(第一个\r\n之前的数据)
url = data1.split()[1] # 继续分离,用空格来分割,然后取索引为1的元素就是请求的访问路径
conn.send(b'HTTP/1.1 200 OK\r\n\r\n') # 因为要遵循HTTP协议,所以回复的消息也要加状态行
# 根据不同的路径返回不同的内容:
func = None
for i in url_list:
if i[0] == url:
func = i[1]
break
if func:
response = func(url)
else:
response = b'404 not found!' conn.send(response)
conn.close()返回具体的HTML文件
完美解决了不同URL返回不同内容的问题。 但是我不想仅仅返回几个字符串,我想给浏览器返回完整的HTML内容,这又该怎么办呢?
没问题,不管是什么内容,最后都是转换成字节数据发送出去的。 我们可以打开HTML文件,读取出它内部的二进制数据,然后再发送给浏览器。
import socket # 创建server服务连接
server = socket.socket()
server.bind(('127.0.0.1', 8080)) # 绑定服务端IP和端口(本机做服务端则为127.0.0.1,这样socket就会自动识别。
server.listen(5) # 半连接池 def index(url):
# 读取index.html页面的内容
with open("index.html", "r", encoding="utf8") as f:
s = f.read()
# 返回字节数据
return s.encode('utf-8') def home(url):
# 读取index.html页面的内容
with open("home.html", "r", encoding="utf8") as f:
s = f.read()
# 返回字节数据
return s.encode('utf-8') url_list = [
('/index', index),
('/home', home),
] # 下面是通信循环:
while True:
conn, addr = server.accept() # 等待客户的连接,这个地方是阻塞点
data_bytes = conn.recv(1024) # 接收客户的发来的请求数据
data = data_bytes.decode('utf-8') # 解码收到的数据
data1 = data.split('\r\n')[0] # 分离出第一行数据(第一个\r\n之前的数据)
url = data1.split()[1] # 继续分离,用空格来分割,然后取索引为1的元素就是请求的访问路径
conn.send(b'HTTP/1.1 200 OK\r\n\r\n') # 因为要遵循HTTP协议,所以回复的消息也要加状态行
# 根据不同的路径返回不同的内容:
func = None
for i in url_list:
if i[0] == url:
func = i[1]
break
if func:
response = func(url)
else:
response = b'404 not found!' conn.send(response)
conn.close()让网页动态起来
这网页能够显示出来了,但是都是静态的啊。页面的内容都不会变化的,我想要的是动态网站。
没问题,我也有办法解决。我选择使用字符串替换来实现这个需求。(这里使用时间戳来模拟动态的数据)
import socket
import time # 创建server服务连接
server = socket.socket()
server.bind(('127.0.0.1', 8080)) # 绑定服务端IP和端口(本机做服务端则为127.0.0.1,这样socket就会自动识别。
server.listen(5) # 半连接池 def index(url):
with open("index.html", "r", encoding="utf8") as f:
s = f.read()
now = str(time.time())
s = s.replace("@@oo@@", now) # 在网页中定义好特殊符号,用动态的数据去替换提前定义好的特殊符号
return s.encode('utf-8') def home(url):
# 读取index.html页面的内容
with open("index.html", "r", encoding="utf8") as f:
s = f.read()
# 返回字节数据
return s.encode('utf-8') url_list = [
('/index', index),
('/home', home),
] # 下面是通信循环:
while True:
conn, addr = server.accept() # 等待客户的连接,这个地方是阻塞点
data_bytes = conn.recv(1024) # 接收客户的发来的请求数据
data = data_bytes.decode('utf-8') # 解码收到的数据
data1 = data.split('\r\n')[0] # 分离出第一行数据(第一个\r\n之前的数据)
url = data1.split()[1] # 继续分离,用空格来分割,然后取索引为1的元素就是请求的访问路径
conn.send(b'HTTP/1.1 200 OK\r\n\r\n') # 因为要遵循HTTP协议,所以回复的消息也要加状态行
# 根据不同的路径返回不同的内容:
func = None
for i in url_list:
if i[0] == url:
func = i[1]
break
if func:
response = func(url)
else:
response = b'404 not found!' conn.send(response)
conn.close()对于真实开发中的python web程序来说,一般会分为两部分:服务器程序和应用程序。
服务器程序负责对socket服务器进行封装,并在请求到来时,对请求的各种数据进行整理。
应用程序则负责具体的逻辑处理。为了方便应用程序的开发,就出现了众多的Web框架,例如:Django、Flask、web.py 等。不同的框架有不同的开发方式,但是无论如何,开发出的应用程序都要和服务器程序配合,才能为用户提供服务。
这样,服务器程序就需要为不同的框架提供不同的支持。这样混乱的局面无论对于服务器还是框架,都是不好的。对服务器来说,需要支持各种不同框架,对框架来说,只有支持它的服务器才能被开发出的应用使用。
这时候,标准化就变得尤为重要。我们可以设立一个标准,只要服务器程序支持这个标准,框架也支持这个标准,那么他们就可以配合使用。一旦标准确定,双方各自实现。这样,服务器可以支持更多支持标准的框架,框架也可以使用更多支持标准的服务器。
WSGI(Web Server Gateway Interface)就是一种规范,它定义了使用Python编写的web应用程序与web服务器程序之间的接口格式,实现web应用程序与web服务器程序间的解耦。
常用的WSGI服务器有uwsgi、Gunicorn。而Python标准库提供的独立WSGI服务器叫wsgiref,Django开发环境用的就是这个模块来做服务器。
服务器程序,正常开发环境下用wsgiref,而在正式假设服务上线用uWSGI
接下来利用wsgiref模块来替换上面写的web框架的socket server部分:import time
from wsgiref.simple_server import make_server def index(url):
with open("index.html", "r", encoding="utf8") as f:
s = f.read()
now = str(time.time())
# 在网页中定义好特殊符号,用动态的数据去替换提前定义好的特殊符号
s = s.replace("@@oo@@", now)
return s def home(url):
# 读取index.html页面的内容
with open("index.html", "r", encoding="utf8") as f:
s = f.read()
return s def err():
return '404 not found!' url_list = [
('/index', index),
('/home', home),
] def run(env, response):
''' :param env: 请求相关信息,一个大字典,里面装了一堆处理好了的键值对数据
:param response: 相应相关信息
:return:
'''
# 固定写法 后面列表里面一个个元祖会以响应头kv键值对的形式返回给客户端
response('200 ok', [('username', 'sgt'), ('password', '')])
# 获取用户访问路径:
current_path = env.get('PATH_INFO')
# 定义一个存储函数名的变量:
func = None
for i in url_list:
if i[0] == current_path:
func = i[1]
break
if func:
res = func()
else:
res = err() return [res.encode('utf-8')] if __name__ == '__main__':
server = make_server('127.0.0.1', 8080, run)
server.serve_forever()jinja2
上面的代码实现了一个简单的动态,我完全可以从数据库中查询数据,然后去替换我html中的对应内容,然后再发送给浏览器完成渲染。 这个过程就相当于HTML模板渲染数据。 本质上就是HTML内容中利用一些特殊的符号来替换要展示的数据。 我这里用的特殊符号是我定义的,其实模板渲染有个现成的工具:
jinja2
下载jinja2:
pip install jinja2
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta http-equiv="x-ua-compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Title</title>
</head>
<body>
<h1>姓名:{{name}}</h1>
<h1>爱好:</h1>
<ul>
{% for hobby in hobby_list %}
<li>{{hobby}}</li>
{% endfor %}
</ul>
</body>
</html>index2 html文件
使用jinja2渲染index2.html文件:
from wsgiref.simple_server import make_server
from jinja2 import Template def index():
with open("index2.html", "r") as f:
data = f.read()
template = Template(data) # 生成模板文件
ret = template.render({"name": "Alex", "hobby_list": ["烫头", "泡吧"]}) # 把数据填充到模板里面
return [bytes(ret, encoding="utf8"), ] def home():
with open("home.html", "rb") as f:
data = f.read()
return [data, ] # 定义一个url和函数的对应关系
URL_LIST = [
("/index/", index),
("/home/", home),
] def run_server(environ, start_response):
start_response('200 OK', [('Content-Type', 'text/html;charset=utf8'), ]) # 设置HTTP响应的状态码和头信息
url = environ['PATH_INFO'] # 取到用户输入的url
func = None # 将要执行的函数
for i in URL_LIST:
if i[0] == url:
func = i[1] # 去之前定义好的url列表里找url应该执行的函数
break
if func: # 如果能找到要执行的函数
return func() # 返回函数的执行结果
else:
return [bytes("404没有该页面", encoding="utf8"), ] if __name__ == '__main__':
httpd = make_server('', 8000, run_server)
print("Serving HTTP on port 8000...")
httpd.serve_forever()现在的数据是我们自己手写的,那可不可以从数据库中查询数据,来填充页面呢?
使用pymysql连接数据库:
conn = pymysql.connect(host="127.0.0.1", port=3306, user="root", passwd="xxx", db="xxx", charset="utf8")
cursor = conn.cursor(cursor=pymysql.cursors.DictCursor)
cursor.execute("select name, age, department_id from userinfo")
user_list = cursor.fetchall()
cursor.close()
conn.close()创建一个测试的user表:
CREATE TABLE user(
id int auto_increment PRIMARY KEY,
name CHAR(10) NOT NULL,
hobby CHAR(20) NOT NULL
)engine=innodb DEFAULT charset=UTF8;模板的原理就是字符串替换,我们只要在HTML页面中遵循jinja2的语法规则写上,其内部就会按照指定的语法进行相应的替换,从而达到动态的返回内容。
前端Web框架的实现过程的更多相关文章
- 前端技术之:常见前端Web框架
Express 声称是快速.自由.小巧的Node.js Web框架,官网地址如下: https://expressjs.com/ https://github.com/expressjs/expres ...
- 开源的前端web框架推荐
B-JUI前端框架:http://demo.b-jui.com/ gentelella :https://colorlib.com/polygon/gentelella/ admui(收费):http ...
- python 实现web框架simfish
python 实现web框架simfish 本文主要记录本人利用python实现web框架simfish的过程.源码github地址:simfish WSGI HTTP Server wsgi模块提供 ...
- Python学习 - 编写一个简单的web框架(一)
自己动手写一个web框架,因为我是菜鸟,对于python的一些内建函数不是清楚,所以在写这篇文章之前需要一些python和WSGI的预备知识,这是一系列文章.这一篇只实现了如何处理url. 参考这篇文 ...
- 二、Web框架实现
一.简单web(socket) 在前一篇WEB框架概述一文中已经详细了解了:从浏览器键入一个URL到返回HTML内容的整个过程.说到底,本质上其实就是一个socket服务端,用户的浏览器其实就是一个s ...
- 浅析Java Web框架技术
一.Java Web框架技术的概念 所谓的Java框架,简单理解是一个可复用的设计构件,它规定了应用的体系结构,阐明了整个设计.协作构件之间的依赖关系.责任分配和控制流程,表现为一组抽象类以及其实例之 ...
- 从使用传统Web框架到切换到Spring Boot后的总结
1.前言 其实我接触 Spring Boot 的时间并不长,所以还算一个初学者,这篇文章也算是我对 Spring Boot 学习以及使用过程中的复盘,如果文章出现描述错误或表达不清晰的地方,欢迎大家在 ...
- 2015年最全的移动WEB前端UI框架
目前,众多互联网公司APP都嵌入了大量的HTML5,移动端的开发越来越重视,HTML5的运用场景也越来越多了.在移动WEB开发的过程中,使用合适的移动WEB UI框架可以大大提升我们的开发效率.下面P ...
- web前端UI框架
分类:WEB前端 时间:2016年1月13日 目前,众多互联网公司APP都嵌入了大量的HTML5,移动端的开发越来越重视,HTML5的运用场景也越来越多了.在移动WEB开发的过程中,使用合适的移动WE ...
随机推荐
- PAT甲级——1123 Is It a Complete AVL Tree (完全AVL树的判断)
嫌排版乱的话可以移步我的CSDN:https://blog.csdn.net/weixin_44385565/article/details/89390802 An AVL tree is a sel ...
- Tinghua Data Mining 5
ID3 ID3算法倾向于分的很细的变量 C4.5加入分母为惩罚量
- Shortest Path Codeforces - 59E || 洛谷P1811 最短路_NOI导刊2011提高(01)
https://codeforces.com/contest/59/problem/E 原来以为不会..看了题解发现貌似自己其实是会的? 就是拆点最短路..拆成n^2个点,每个点用(i,j)表示,表示 ...
- solr亿万级索引优化实践-自动生成UUID
solr亿万级索引优化实践(三) 原创 2017年03月14日 17:03:09 本篇文章主要介绍下如何从客户端solrJ以及服务端参数配置的角度来提升索引速度. solrJ6.0提供的 ...
- 发布好的SDE 如何注册,让数据库更新 实现arcgis 服务更新
1, 打开 MXD 文件,前期已经发布的文件 右键 service peopertisers 右键 Service Property 出现如下界面: “+”号 需要需要选择SDE库 不需要 选择 ...
- react-native入门学习( 一 )
开发环境配置 因为个人电脑是windows7环境,所以在选择安装react-native 环境的时候是用的 windows+androidreact-native中文网文档地址 https://re ...
- Java基础50题test2—输出素数
[输出素数] 题目:判断 101-200 之间有多少个素数,并输出所有素数. 程序分析:判断素数的方法:用一个数分别去除 2 到 sqrt(这个数),如果能被整除,则表明此数不是素数,反之是素数 pu ...
- kafka基础六
kafka中的高可用HA 1.replication副本 同一个partition会有一个leader和多个副本,这些副本存储的内容与leader相同,可以通过 server.properties 配 ...
- 如何加快HTML页面加载速度
1. 页面减肥 a. 页面的肥瘦是影响加载速度最重要的因素. b. 删除不必要的空格.注释. c. 将inline的script和css移到外部文件. d. 可以使用HTML Tidy来给HTML减肥 ...
- GraphicsMagick安装&make命令使用
0.0本过程为GraphicsMagick Linux版安装,通过典型的make编译安装. 未了支持png和jpg格式,首先请安装依赖.执行 yum install -y libpng-devel y ...