PythonWeb框架的本质
简单描述就是:浏览器通过你输入的网址给你的socket服务端发送请求,服务端接受到请求给其回复一个对应的html页面,这就是web项目。所有的Web应用本质上就是一个socket服务端,而用户的浏览器就是一个socket客户端,基于请求做出响应,客户都先请求,服务端做出对应的响应,按照http协议的请求协议发送请求,服务端按照http协议的响应协议来响应请求,这样的网络通信,我们就可以自己实现Web框架了。
什么是web框架?这就好比建设房子,房子主体钢结构等都为我们搭建好了,我们就是抹抹墙面,添加一些装饰,修饰一下即可。Django框架就是已经为我们搭建好的主题钢结构,剩下的根据不同的业务自定制即可。我们先自己搭建一个web框架,再对Django框架就会比较好理解了。
构建Socket服务器
import socket
server = socket.socket()
server.bind(('127.0.0.1', 8002))
server.listen()
while 1:
conn, addr = server.accept()
while 1:
client_data = conn.recv(1024).decode('utf-8')
print(client_data)
# 服务端与客户端建立联系必须要遵循一个协议,此时我们用http协议示例。
conn.send('HTTP/1.1 200 OK \r\n\r\n'.encode('utf-8'))
conn.send('hello
'.encode('utf-8'))
conn.close()
我们通过浏览器请求服务端,服务端给我们返回一个hello标签,客户请求过来之后,要想让服务端给客户端返回消息,必须基于一个协议,我们用http协议示例,那么服务端如果如何返回给浏览器一个页面尼?
构建一个html页面返回
你好,世界
此时我们的服务端必须将html页面返回给客户端,可以通过读取文件然后发送bytes数据
服务端
import socket
server = socket.socket()
server.bind(('127.0.0.1',8002))
server.listen()
while 1:
conn,addr = server.accept()
while 1:
client_data = conn.recv(1024).decode('utf-8')
print(client_data)
# 服务端与客户端建立联系必须要遵循一个协议,此时我们用http协议示例
conn.send('HTTP/1.1 200 OK \r\n\r\n'.encode('utf-8'))
with open('Day11/index.html',mode='rb') as f1:
conn.send(f1.read())
conn.close()
我们浏览器访问服务端会发现我们服务器会接受以下信息
GET / HTTP/1.1
Host: 127.0.0.1:8002
Connection: keep-alive
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/80.0.3987.162 Safari/537.36
Sec-Fetch-Dest: document
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9
Sec-Fetch-Site: none
Sec-Fetch-Mode: navigate
Sec-Fetch-User: ?1
Accept-Encoding: gzip, deflate, br
Accept-Language: zh-CN,zh;q=0.9,en;q=0.8
http协议工作原理
以下是HTTP请求/响应的步骤
# 1. 客户端连接到Web服务器
# 一个HTTP客户端,通常是浏览器,与Web服务器的HTTP端口(默认为80)建立一个TCP套接字连接。例如,http://www.baidu.com
# 2. 发送HTTP请求
# 通过TCP套接字,客户端向Web服务器发送一个文本的请求报文,一个请求报文由请求行、请求头部、空行和请求数据4部分组成。
# 3. 服务器接收请求并返回HTTP响应
# Web服务器解析请求,定位请求资源。服务器将资源复本写到TCP套接字,由客户端读取。一个响应由状态行、响应头部、空行和响应数据4部分组成。
# 4. 释放连接TCP连接
# 若connection 模式为close,则服务器主动关闭TCP连接,客户端被动关闭连接,释放TCP连接;若connection 模式为keepalive,则该连接会保持一段时间,在该时间内可以继续接收请求;
# 5. 客户端浏览器解析HTML内容
# 客户端浏览器首先解析状态行,查看表明请求是否成功的状态代码。然后解析每一个响应头,响应头告知以下为若干字节的HTML文档和文档的字符集。客户端浏览器读取响应数据HTML,根据HTML的语法对其进行格式化,并在浏览器窗口中显示。
# 如果问浏览器输入URL,按下回车经历以下流程
# 1. 浏览器向 DNS 服务器请求解析该 URL 中的域名所对应的 IP 地址;
# 2. 解析出 IP 地址后,根据该 IP 地址和默认端口 80,和服务器建立TCP连接;
# 3. 浏览器发出读取文件(URL 中域名后面部分对应的文件)的HTTP 请求,该请求报文作为 TCP 三次握手的第三个报文的数据发送给服务器;
# 4. 服务器对浏览器请求作出响应,并把对应的 html 文本发送给浏览器;
# 5. 释放 TCP连接;
# 6. 浏览器将该 html 文本并显示内容;
常用的HTTP方法
HTTP/1.1协议中共定义了八种方法(也叫“动作”)来以不同方式操作指定的资源:
# GET
# 向指定的资源发出“显示”请求。使用GET方法应该只用在读取数据,而不应当被用于产生“副作用”的操作中,例如在Web Application中。其中一个原因是GET可能会被网络蜘蛛等随意访问。
# HEAD
# 与GET方法一样,都是向服务器发出指定资源的请求。只不过服务器将不传回资源的本文部分。它的好处在于,使用这个方法可以在不必传输全部内容的情况下,就可以获取其中“关于该资源的信息”(元信息或称元数据)。
# POST
# 向指定资源提交数据,请求服务器进行处理(例如提交表单或者上传文件)。数据被包含在请求本文中。这个请求可能会创建新的资源或修改现有资源,或二者皆有。
# PUT
# 向指定资源位置上传其最新内容。
# DELETE
# 请求服务器删除Request-URI所标识的资源。
# TRACE
# 回显服务器收到的请求,主要用于测试或诊断。
# OPTIONS
# 这个方法可使服务器传回该资源所支持的所有HTTP请求方法。用'*'来代替资源名称,向Web服务器发送OPTIONS请求,可以测试服务器功能是否正常运作。
# CONNECT
# HTTP/1.1协议中预留给能够将连接改为管道方式的代理服务器。通常用于SSL加密服务器的链接(经由非加密的HTTP代理服务器)。
# 注意事项:
# 方法名称是区分大小写的。当某个请求所针对的资源不支持对应的请求方法的时候,服务器应当返回状态码405(Method Not Allowed),当服务器不认识或者不支持对应的请求方法的时候,应当返回状态码501(Not Implemented)。
# HTTP服务器至少应该实现GET和HEAD方法,其他方法都是可选的。当然,所有的方法支持的实现都应当匹配下述的方法各自的语义定义。此外,除了上述方法,特定的HTTP服务器还能够扩展自定义的方法。例如PATCH(由 RFC 5789 指定的方法)用于将局部修改应用到资源。
# 请求方式: get与post请求(通过form表单我们自己写写看)
# GET提交的数据会放在URL之后,也就是请求行里面,以?分割URL和传输数据,参数之间以&相连,如EditBook?name=test1&id=123456.(请求头里面那个content-type做的这种参数形式,后面讲) POST方法是把提交的数据放在HTTP包的请求体中.
# GET提交的数据大小有限制(因为浏览器对URL的长度有限制),而POST方法提交的数据没有限制.
# GET与POST请求在服务端获取请求数据方式不同,就是我们自己在服务端取请求数据的时候的方式不同了,这句废话昂。
HTTP状态码
# 状态代码的第一个数字代表当前响应的类型:
# 1xx消息——请求已被服务器接收,继续处理
# 2xx成功——请求已成功被服务器接收、理解、并接受
# 3xx重定向——需要后续操作才能完成这一请求
# 4xx请求错误——请求含有词法错误或者无法被执行
# 5xx服务器错误——服务器在处理某个正确请求时发生错误
构建Web框架
简单版Web框架
test1.html
欢迎访问xx商城
- aa
- bb
- cc
test1.py
import socket
server = socket.socket()
server.bind(('127.0.0.1',8001))
server.listen()
while 1:
conn,addr = server.accept()
client_data = conn.recv(1024).decode('utf-8')
print(client_data)
# 服务端与客户端建立联系必须要遵循一个协议,此时我们用http协议示例
conn.send('HTTP/1.1 200 OK \r\nk1:v1\r\n\r\n'.encode('utf-8'))
with open('test1.html',mode='rb') as f1:
conn.send(f1.read())
conn.close()
升级版Web框架
html
欢迎访问和软商城
- aa
- bb
- cc
浏览器执行到link标签时,href会发出一个请求,请求你当前根目录下面的test2.css文件,其实完整url也就是http://127.0.0.1:8001/test2.css,你引入js同理,此过程是异步的,html代码不会等你引入完css之后再执行下面代码
test2.css
div {
background-color: #7fb8ff;
font-size: 16px;
}
test2.js
alert('未满18岁禁止入内');
test2.py
import socket
server = socket.socket()
server.bind(('127.0.0.1',8001))
server.listen()
while 1:
conn,addr = server.accept()
client_data = conn.recv(1024)
request_path = client_data.decode('utf-8').split('\r\n')[0].split()[1]
# 服务端与客户端建立联系必须要遵循一个协议,此时我们用http协议示例
conn.send('HTTP/1.1 200 OK \r\nk1:v1\r\n\r\n'.encode('utf-8'))
if request_path == '/':
with open('test2.html',mode='rb') as f1:
conn.send(f1.read())
elif request_path == '/test2.css':
with open('test2.css',mode='rb') as f1:
conn.send(f1.read())
elif request_path == '/2.jpg':
with open('2.jpg',mode='rb') as f1:
conn.send(f1.read())
elif request_path == '/test2.js':
with open('test2.js',mode='rb') as f1:
conn.send(f1.read())
conn.close()
函数版Web框架
上一个版本比较Low,并且代码应该整合成函数而不能使用纯面向过程方式,所以我们进行改版
test3.html
css,js,img都用上个版本的
欢迎访问和软商城
- aa
- bb
- cc
test3.py
import socket
server = socket.socket()
server.bind(('127.0.0.1', 8001))
server.listen()
def html(conn):
with open('test3.html', mode='rb') as f1:
conn.send(f1.read())
conn.close()
def css(conn):
with open('test2.css', mode='rb') as f1:
conn.send(f1.read())
conn.close()
def jpg(conn):
with open('2.jpg', mode='rb') as f1:
conn.send(f1.read())
conn.close()
def js(conn):
with open('test2.js', mode='rb') as f1:
conn.send(f1.read())
conn.close()
request_list = [
('/', html),
('/test2.css', css),
('/2.jpg', jpg),
('/test2.js', js),
]
while 1:
conn, addr = server.accept()
client_data = conn.recv(1024)
request_path = client_data.decode('utf-8').split('\r\n')[0].split()[1]
# 服务端与客户端建立联系必须要遵循一个协议,此时我们用http协议示例。
conn.send('HTTP/1.1 200 OK \r\nk1:v1\r\n\r\n'.encode('utf-8'))
for i in request_list:
if request_path == i[0]:
i[1](conn)
conn.close()
虽然这个版本简介明了,但是还不完美,现在虽然是异步处理请求,但是我们可以通过并发让异步请求用并发处理更加合理
并发版Web框架
test4.html
欢迎访问和软商城
- aa
- bb
- cc
test4.py
import socket
from threading import Thread
server = socket.socket()
server.bind(('127.0.0.1', 8001))
server.listen()
def html(conn):
with open('test3.html', mode='rb') as f1:
conn.send(f1.read())
conn.close()
def css(conn):
with open('test2.css', mode='rb') as f1:
conn.send(f1.read())
conn.close()
def jpg(conn):
with open('2.jpg', mode='rb') as f1:
conn.send(f1.read())
conn.close()
def js(conn):
with open('test2.js', mode='rb') as f1:
conn.send(f1.read())
conn.close()
request_list = [
('/', html),
('/test2.css', css),
('/2.jpg', jpg),
('/test2.js', js),
]
while 1:
conn, addr = server.accept()
client_data = conn.recv(1024)
request_path = client_data.decode('utf-8').split('\r\n')[0].split()[1]
print(request_path)
# 服务端与客户端建立联系必须要遵循一个协议,此时我们用http协议示例。
conn.send('HTTP/1.1 200 OK \r\nk1:v1\r\n\r\n'.encode('utf-8'))
for i in request_list:
if request_path == i[0]:
t = Thread(target=i[1],args=(conn,))
t.start()
# conn.close()
动态版Web框架
一般情况下,我们的数据都是在数据库中,动态获取,实时变化,但现在我们模拟一下数据,利用时间戳
test5.html
欢迎访问和软商城
- aa
- bb
- cc
test5.py
import socket
from threading import Thread
import time
server = socket.socket()
server.bind(('127.0.0.1', 8001))
server.listen()
def html(conn):
time_now = time.strftime('%Y/%m/%d %H:%M:%S',time.localtime())
with open('test5.html',encoding='utf-8') as f1:
data = f1.read().format(time_now=time_now)
conn.send(data.encode('utf-8'))
conn.close()
def css(conn):
with open('test2.css', mode='rb') as f1:
conn.send(f1.read())
conn.close()
def jpg(conn):
with open('2.jpg', mode='rb') as f1:
conn.send(f1.read())
conn.close()
def js(conn):
with open('test2.js', mode='rb') as f1:
conn.send(f1.read())
conn.close()
request_list = [
('/', html),
('/test2.css', css),
('/2.jpg', jpg),
('/test2.js', js),
]
while 1:
conn, addr = server.accept()
client_data = conn.recv(1024)
request_path = client_data.decode('utf-8').split('\r\n')[0].split()[1]
print(request_path)
# 服务端与客户端建立联系必须要遵循一个协议,此时我们用http协议示例。
conn.send('HTTP/1.1 200 OK \r\nk1:v1\r\n\r\n'.encode('utf-8'))
for i in request_list:
if request_path == i[0]:
t = Thread(target=i[1],args=(conn,))
t.start()
# conn.close()
wsgiref模板版+数据库Web框架
wsgiref模块其实就是将整个请求信息给封装了起来,就不需要你自己处理了,假如它将所有请求信息封装成了一个叫做request的对象,那么你直接request.path就能获取到用户这次请求的路径,request.method就能获取到本次用户请求的请求方式(get还是post)等,那这个模块用起来,我们再写web框架是不是就简单了好多啊
对于真实开发中的python web程序来说,一般会分为两部分:服务器程序和应用程序.
服务器程序负责对socket服务器进行封装,并在请求到来时,对请求的各种数据进行整理。
应用程序则负责具体的逻辑处理。为了方便应用程序的开发,就出现了众多的Web框架,例如:Django、Flask、web.py 等。不同的框架有不同的开发方式,但是无论如何,开发出的应用程序都要和服务器程序配合,才能为用户提供服务。
这样,服务器程序就需要为不同的框架提供不同的支持。这样混乱的局面无论对于服务器还是框架,都是不好的。对服务器来说,需要支持各种不同框架,对框架来说,只有支持它的服务器才能被开发出的应用使用。最简单的Web应用就是先把HTML用文件保存好,用一个现成的HTTP服务器软件,接收用户请求,从文件中读取HTML,返回。如果要动态生成HTML,就需要把上述步骤自己来实现。不过,接受HTTP请求、解析HTTP请求、发送HTTP响应都是苦力活,如果我们自己来写这些底层代码,还没开始写动态HTML呢,就得花个把月去读HTTP规范。
正确的做法是底层代码由专门的服务器软件实现,我们用Python专注于生成HTML文档。因为我们不希望接触到TCP连接、HTTP原始请求和响应格式,所以,需要一个统一的接口协议来实现这样的服务器软件,让我们专心用Python编写Web业务。
这时候,标准化就变得尤为重要。我们可以设立一个标准,只要服务器程序支持这个标准,框架也支持这个标准,那么他们就可以配合使用。一旦标准确定,双方各自实现。这样,服务器可以支持更多支持标准的框架,框架也可以使用更多支持标准的服务器。
WSGI(Web Server Gateway Interface)就是一种规范,它定义了使用Python编写的web应用程序与web服务器程序之间的接口格式,实现web应用程序与web服务器程序间的解耦。
常用的WSGI服务器有uwsgi、Gunicorn。而Python标准库提供的独立WSGI服务器叫wsgiref,Django开发环境用的就是这个模块来做服务器。
接下来我们先看一看wsgiref的简单用法:
from wsgiref.simple_server import make_server
# wsgiref本身就是个web框架,提供了一些固定的功能(请求和响应信息的封装,
# 不需要我们自己写原生的socket了也不需要咱们自己来完成请求信息的获取了,提取起来很方便
# 函数名字随便起
def application(environ,start_response):
'''
:param environ: 是全部加工好的请求信息,加工成了一个字典,通过字典取值的方式就能拿到很多你想要拿到的信息
:param start_response: 帮你封装响应信息的(响应行和响应头),注意下面的参数
:return:
'''
start_response('200 OK',[('k1','v1'),])
print(environ)
print(environ['PATH_INFO'])
return [b'Hello,Web!
']
# 类似socketserver
httpd = make_server('127.0.0.1',8080,application)
print('Servering HTTP on port 8080...')
# 开始监听HTTP请求
httpd.serve_forever()
这个模块就是封装好服务器程序的处理,方便你使用。
接下来我们引入数据库内容,首先创建一个数据库,然后在插入一些数据:
mysql安装请看我博客mysql标签里面,此处不做介绍
my.cnf
[root@mysql-test ~]# cat /etc/my.cnf
[mysql]
default-character-set=utf8
[mysqld]
datadir=/var/lib/mysql
socket=/var/lib/mysql/mysql.sock
symbolic-links=0
character-set-server=utf8
default-storage-engine=INNODB
max_connections=200
log-error=/var/log/mysqld.log
pid-file=/var/run/mysqld/mysqld.pid
[client]
password = ZHOUjian.24
user = root
socket服务端
from wsgiref.simple_server import make_server
import time
def html(conn):
time_now = time.strftime('%Y/%m/%d %H:%M:%S', time.localtime())
with open('test5.html', encoding='utf-8') as f1:
data = f1.read().format(time_now=time_now)
conn.send(data.encode('utf-8'))
conn.close()
def css(conn):
with open('test2.css', mode='rb') as f1:
data = f1.read()
return data
def jpg(conn):
with open('2.jpg', mode='rb') as f1:
data = f1.read()
return data
def js(conn):
with open('test2.js', mode='rb') as f1:
data = f1.read()
return data
request_list = [
('/', html),
('/test2.css', css),
('/2.jpg', jpg),
('/test2.js', js),
]
def application(environ, start_response):
start_response('200 OK', [('k1', 'v1'), ('k2', 'v2')])
print(environ)
request_path = environ['PATH_INFO']
for i in request_list:
if request_path == i[0]:
ret = i[1]()
return [ret]
else:
return [b'404.....
']
httpd = make_server('127.0.0.1', 8080, application)
print('Serving HTTP on port 8080...')
# 开始监听HTTP请求:
httpd.serve_forever()
pymysql1.py(创建数据)
import pymysql
conn = pymysql.connect("121.36.43.223", "admin", "ZHOUjian.21")
cursor = conn.cursor(pymysql.cursors.DictCursor)
# 创建 webtest数据库
cursor.execute('create database webtest;')
# 创建userinfo数据库表
cursor.execute('create table webtest.userinfo(id int,name varchar(20),age int);')
cursor.execute("""
insert into webtest.userinfo(id,name,age) values
(1,'幽梦',18),
(2,'flying',19),
(3,'渐渐',20);
"""
)
conn.commit()
cursor.close()
conn.close()
pymysql2.py(getdata)
import pymysql
def get_data():
conn = pymysql.connect("121.36.43.223", "admin", "ZHOUjian.21")
cursor = conn.cursor(pymysql.cursors.DictCursor)
cursor.execute("select * from webtest.userinfo;")
data = cursor.fetchone()
conn.commit()
cursor.close()
conn.close()
return data
wsgiref模块版+数据库+jinja2 web框架
上面的代码实现了一个简单的动态页面(字符串替换),我完全可以从数据库中查询数据,然后去替换我html中的对应内容(专业名词叫做模板渲染,你先渲染一下,再给浏览器进行渲染),然后再发送给浏览器完成渲染。 这个过程就相当于HTML模板渲染数据。 本质上就是HTML内容中利用一些特殊的符号来替换要展示的数据。 我这里用的特殊符号是我定义的,其实模板渲染有个现成的工具:
jinja2,DJango有自带的模版渲染方法,和jinja2很像,但是只能适用于Django框架,而jinja2可以适用于多种框架。
下载
pip install jinja2
getdata
import pymysql
def get_data():
conn = pymysql.connect("121.36.43.223", "admin", "ZHOUjian.21")
cursor = conn.cursor(pymysql.cursors.DictCursor)
cursor.execute("select * from webtest.userinfo;")
data = cursor.fetchone()
conn.commit()
cursor.close()
conn.close()
return data
test7.html
欢迎访问和软商城
{time_now}
- aa
- bb
- cc
socket服务端
from threading import Thread
from wsgiref.simple_server import make_server
from Day15.pymysql2 import showdata
from jinja2 import Template
def html(conn):
userinfo_data = showdata()
print(userinfo_data)
with open('test7.html', 'rb',encoding='utf-8') as f1:
data = f1.read()
temp = Template(data)
print(temp)
data = temp.reader({'userinfo':userinfo_data})
data = data.encode('utf-8')
return data
def css(conn):
with open('test2.css','rb') as f1:
data = f1.read()
return data
def jpg(conn):
with open('2.jpg','rb') as f1:
data = f1.read()
return data
def js(conn):
with open('test2.js','rb') as f1:
data = f1.read()
return data
urlpatterns = [
('/', html),
('/test2.css', css),
('/2.jpg', jpg),
('/test2.js', js),
]
def application(environ, start_response):
# print(environ)
# conn.send(b'HTTP/1.1 200 ok\r\n\r\nxxxx')
start_response('200 OK', [('k1','v1'),('k2','v2')])
# print(environ['PATH_INFO'])
path = environ['PATH_INFO']
for i in urlpatterns:
if path == i[0]:
ret = i[1]()
break
else:
ret = b'404 not found!!!!'
return [ret]
httpd = make_server('127.0.0.1', 8080, application)
print('Serving HTTP on port 8080...')
httpd.serve_forever()
此处有问题,等我大概过一遍回来再弄细节