总结
:BS本质上也是CS架构
一、web应用程序是什么?
Web应用程序是一种可以通过Web访问的应用程序
,程序的最大好处是用户很容易访问应用程序,用户只需要有浏览器即可,不需要再安装其他软件
应用程序有两种模式C/S、B/S。C/S是客户端/服务器端程序,也就是说这类程序一般独立运行。而B/S就是浏览器端/服务器端应用程序,这类应用程序一般借助IE等浏览器来运行。
WEB应用程序一般是B/S模式
。Web应用程序首先是“应用程序”,和用标准的程序语言,如C、C++等编写出来的程序没有什么本质上的不同。然而Web应用程序又有自己独特的地方,就是它是基于Web的,而不是采用传统方法运行的。换句话说,它是典型的浏览器/服务器架构的产物。
1.web应用的优点
-
网络应用程序不需要任何复杂的“展开”过程,你所需要的只是一个适用的浏览器;
-
网络应用程序通常耗费很少的用户硬盘空间,或者一点都不耗费;
-
它们不需要更新,因为所有新的特性都在服务器上执行,从而自动传达到用户端;
-
网络应用程序和服务器端的网络产品都很容易结合,如email功能和搜索功能;
-
因为它们在网络浏览器窗口中运行,所以大多数情况下它们是通过跨平台使用的 (例如Windows,Mac,Linux等等)
2.web应用程序的缺点
-
网络应用程序强调浏览器的适用性。如果浏览器方没有提供特定的功能,或者弃用特定的平台或操作系统版本(导致不适用),就会影响大量用户;
-
网络应用依靠互联网远程服务器端的应用文件。因此,当连接出问题时,应用将不能正常使用。
-
许多网络应用程序不是开源的,只能依赖第三方提供的服务,因此不能针对用户定制化、个性化,而且大多数情况下用户不能离线使用,因而损失了很多灵活性;
-
它们完全依赖应用服务商的可及性。如果公司倒闭,服务器停止使用,用户也无法追索以前的资料。对比而看,即使软件制造商倒闭了,传统的安装软件也可以继续运行,尽管不能再更新或有其他用户服务;
-
相似地,提供方公司对软件和其功能有了更大的控制权。只要他们愿意就能为软件添加新特性,即使用户想等bugs先被解决再更新。跳过较差的软件版本也不可能了。公司可以强加不受欢迎的特性给用户,也可以随意减少带宽来削减开支。
-
公司理论上可以检索任何的用户行为。这有可能引起隐私安全问题。
3.B/S架构优点
浏览器/服务器架构(Browser/Server,简称B/S)能够很好地应用在广域网上,成为越来越多的企业的选择。浏览器/服务器架构相对于其他几种应用程序体系结构,有如下3方面的优点:
-
这种架构采用Internet上标准的通信协议(通常是TCP/IP协议)作为客户机同服务器通信的协议。这样可以使位于Internet任意位置的人都能够正常访问服务器。对于服务器来说,通过相应的Web服务和数据库服务可以对数据进行处理。对外采用标准的通信协议,以便共享数据。
-
在服务器上对数据进行处理,就处理的结果生成网页,以方便客户端直接下载。
-
在客户机上对数据的处理被进一步简化,将浏览器作为客户端的应用程序,以实现对数据的显示。不再需要为客户端单独编写和安装其他类型的应用程序。这样,在客户端只需要安装一套内置浏览器的操作系统,直接安装一套浏览器,就可以实现服务器上数据的访问。而浏览器是计算机的标准设备
总结一下,本质上:浏览器是一个socket客户端,服务器是一个socket服务端
二、web框架
1.web框架介绍
Web框架(Web framework)是一种开发框架,用来支持动态网站、网络应用和网络服务的开发。这大多数的web框架提供了一套开发和部署网站的方式,也为web行为提供了一套通用的方法。web框架已经实现了很多功能,开发人员使用框架提供的方法并且完成自己的业务逻辑,就能快速开发web应用了。浏览器和服务器的是基于HTTP协议进行通信的。也可以说web框架就是在以上十几行代码基础张扩展出来的,有很多简单方便使用的方法,大大提高了开发的效率。
2.web框架的本质
所有的Web应用其实就是一个socket服务端, 而用户使用的浏览器就是一个socket客户端程序, 明白了Web框架的本质, 我们就可以实现自己的Web框架了
半成品自定义web框架
import socket
server = socket.socket()
server.bind(("127.0.0.1",8080))
server.listen(5)
while 1:
conn,addr = server.accept()
data = conn.recv(1024)
print(data)
conn.send(b"HTTP?1.1 200 OK\r\n\nLike a child, always believe in hope, I believe the dream")
conn.close()
将服务端运行,客户端访问服务端,运行结果如下:
由上一小节可知,HTTP协议规定了让大家发送消息、接收消息的时候有个格式依据以后浏览器发送请求信息也好,服务器回复响应信息也罢,都要按照这个规则来
然后pycharm输出结果如下:
b'GET / HTTP/1.1\r\n
Host: 127.0.0.1:8080\r\n
Connection: keep-alive\r\n
Cache-Control: max-age=0\r\n
Upgrade-Insecure-Requests: 1\r\n
User-Agent: Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.25 Safari/537.36 Core/1.70.3823.400 QQBrowser/10.7.4307.400\r\n
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8\r\n
Accept-Encoding: gzip, deflate, br\r\n
Accept-Language: zh-CN,zh;q=0.9\r\n
Cookie: csrftoken=WCzjKvmjOSdJbYKs0uIfPtiFfLl04FENb6p9CjypP7ZObcUpydaQPLZN0qPOVqwj\r\n
\r\n'
可以说web服务的本质都是基于这简单的套接字程序扩展出来的。
三、根据不同的路径返回不同的内容
通过以上,我们是实现了一个简易版的web框架
但是存在以下问题:
在用户访问不同网页时候,如何让我们的Web服务根据用户请求的URL不同而返回不同的内容呢?
其实很简单,我们可以从请求相关数据里面拿到请求URL的路径,然后拿路径做一个判断…
根据不同的URL返回不同的内容
import socket
server = socket.socket()
server.bind(('127.0.0.1',8080))
server.listen(5)
while True:
conn, addr = server.accept()
data = conn.recv(1024)
res = data.decode('utf8')
conn.send(b'HTTP/1.1 200 OK\r\n\r\n')
path = res.split(' ')[1]
if path == '/index':
with open(r'liko.html','rb') as f:
data = f.read()
conn.send(data)
elif path == '/login':
conn.send(b'login')
else:
conn.send(b'404 error')
conn.close()
liko.html
内容如下:
<!DOCTYPE html>
<html lang="en">
<meta charset="UTF-8">
<title>时间因你而美好</title>
</head>
<h1 style="color: #31b0d5">嗨,朋友!你来了,你好啊!</h1>
</body>
</html>
接下来我们来测试一下:
服务器和应用程序
对于真实开发中的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模块撸
1.wsgiref 模块的作用
- swgiref模块帮助我们封装了socket 代码
2.便利之处
- 请求来的时候帮助你自动拆分http格式数据并封装成非常方便处理的数据格式(类似于字典)
- 响应走的时候帮你将数据再打包成符合http格式的数据
3.实现代码
from wsgiref.simple_server import make_server
def index_func(request):
return 'index'
def login_func(request):
return 'login'
def error(request):
return '404 Not found'
urls = [
('/index', index_func),
('/login', login_func)
def run_server(request, response):
函数名定义什么都无所谓,这里我们使用run_server
:param request:请求相关的所有数据,一个类似字典的形式,"PATH_INFO"正好就是我们要找的地址
wsgiref模块帮我们处理好HTTP格式的数据,封装成了字典让你更加方便的操作
:param response:响应相关的所有数据
:return:返回给浏览器的数据,返回格式必须是'return [二进制格式的数据]' 这种样式
response('200 OK', [])
current_path = request.get("PATH_INFO")
func = None
for url in urls:
if current_path == url[0]:
func = url[1]
break
if func:
data = func(request)
else:
data = error(request)
return [data.encode('utf-8')]
if __name__ == '__main__':
server = make_server('127.0.0.1', 8080, run_server)
会实时监听127.0.0.1:8080地址,只要客户端来了
都会交给run函数处理(加括号触发run函数的运行)
flask启动源码
make_server('127.0.0.1',8080,obj)
__call__
server.serve_forever()
效果展示如下:
- 网址很多的情况下如何匹配
- 网址多匹配如何解决
- 功能复杂代码块如何解决
看起来上面的代码还是要挨个写if判断,怎么办?我们还是有办法!
五、封装处理
随着业务越来越多, 功能越来越多, 将所有代码放在同一个文件会带来很多不必要的麻烦, 于是就需要我们分文件放置相关的代码
1.views.py : 只放功能代码
def index_func(request):
return 'index'
def login_func(request):
return 'login'
def error(request):
return '404 errors'
def xxx(request):
2.urls.py : 存放路径与功能的对应关系
from views import *
urls = [
('/index',index_func),
('/login',login_func)
3.run.py : 只放请求与相应处理代码
from wsgiref.simple_server import make_server
from urls import urls
from views import *
def run_server(request,response):
:param request:请求相关的所有数据,一个类似字典的形式,"PATH_INFO"正好就是我们要找的地址
:param response:响应相关的所有数据
:return:
response('200 OK',[])
current_path = request.get("PATH_INFO")
func = None
for url in urls:
if current_path == url[0]:
func = url[1]
break
if func:
data = func(request)
else:
data = error(request)
return [data.encode('utf-8')]
if __name__ == '__main__':
server = make_server('127.0.0.1', 8080, run_server)
server.serve_forever()
六、返回静态页面
静态页面:数据都是写死的,固定不变的。
解决了不同URL返回不同内容的问题, 但是我不想仅仅返回几个字符串, 我想给浏览器返回完整的HTML内容, 对此我们只需要通过 open 打开 HTML文件将内容读出来再发送给浏览器就行了
def index_func(request):
return 'index'
def login_func(request):
with open(r"./login.html", "r", encoding="utf-8")as f:
res = f.read()
return res
def error(request):
return '404 errors'
def xxx(request):
七、返回动态页面
动态页面 : 数据来源于后端 (代码或者数据库)
1.示例1 : 访问网址展示当前时间
'''定义变量使用双花括号'''
{{ user_list }}
'''for 循环使用双花括号+百分号'''
{{% for user_dict in user_list %}}
{{ user_dict.id}}
{{% endfor %}}
下载安装jinja2模块
pip3 install jinji2
豆瓣源 : http://pypi.douban.com/simple/
清华源: https://pypi.tuna.tsinghua.edu.cn/simple
使用方法 : pip install -i https://pypi.tuna.tsinghua.edu.cn/simple jinja2
ps:该模块是flask框架必备的模块,所以下载flask也会自动下载该模块。
2.2 用wsgiref
再通过jinja2
优化上面的replace,再通过pymysql
实现后端获取数据库中数据展示到前端页面
首先准备一张数据表
mysql> drop database db666;
Query OK, 0 rows affected (0.58 sec)
mysql> create database db666 charset utf8;
Query OK, 1 row affected (0.00 sec)
mysql> use db666;
Database changed
mysql> create table user(
-> id int primary key auto_increment,
-> username varchar(25) not null,
-> sex enum('man','woman'),
-> password varchar(255) not null,
-> hobbies set('吃螃蟹','吃生蚝','肯德基','大闸蟹')
-> );
Query OK, 0 rows affected (0.86 sec)
mysql> insert into user(username,sex,password,hobbies) values
-> ('lili','man','li1314420','吃虾米'),
-> ('shawn','woman','s1314420','大闸蟹,肯德基'),
-> ('hxx','woman','h1314420','大闸蟹,吃生蚝'),
-> ('lxx','man','l1314420','大闸蟹,吃虾米'),
-> ('zxx','man','z1314420','吃虾米,肯德基'),
-> ('mxx','man','m1314420','肯德基,吃生蚝');
Query OK, 6 rows affected, 3 warnings (0.12 sec)
Records: 6 Duplicates: 0 Warnings: 3
mysql> select * from user;
+----+----------+-------+-----------+---------------------+
| id | username | sex | password | hobbies |
+----+----------+-------+-----------+---------------------+
| 1 | lili | man | li1314420 | |
| 2 | shawn | woman | s1314420 | 肯德基,大闸蟹 |
| 3 | hxx | woman | h1314420 | 吃生蚝,大闸蟹 |
| 4 | lxx | man | l1314420 | 大闸蟹 |
| 5 | zxx | man | z1314420 | 肯德基 |
| 6 | mxx | man | m1314420 | 吃生蚝,肯德基 |
+----+----------+-------+-----------+---------------------+
6 rows in set (0.00 sec)
def index_func(request):
return 'index'
def login_func(request):
from datetime import datetime
now_time = datetime.now().strftime("%Y-%m-%d %X")
with open(r"./login.html", "r", encoding="utf-8")as f:
res = f.read().replace("datetime1", now_time)
return res
def get_db_func(request):
from jinja2 import Template
import pymysql
conn = pymysql.connect(host='127.0.0.1',
port=3306,
user='root',
password='123',
db='db666',
charset='utf8',
autocommit=True)
cursor = conn.cursor(cursor=pymysql.cursors.DictCursor)
sql = 'select * from user'
rows = cursor.execute(sql)
user_list = cursor.fetchall()
with open(r'get_db.html', 'r', encoding='utf-8')as f:
data = f.read()
temp = Template(data)
res = temp.render(data_list=user_list)
return res
def error(request):
return '404 errors'
from views import *
urls = [
('/index', index_func),
('/login', login_func),
('/get_db',get_db_func)
from wsgiref.simple_server import make_server
from urls import urls
from views import *
def run_server(request,response):
:param request:请求相关的所有数据,一个类似字典的形式,"PATH_INFO"正好就是我们要找的地址
:param response:响应相关的所有数据
:return:
response('200 OK',[])
current_path = request.get("PATH_INFO")
func = None
for url in urls:
if current_path == url[0]:
func = url[1]
break
if func:
data = func(request)
else:
data = error(request)
return [data.encode('utf-8')]
if __name__ == '__main__':
server = make_server('127.0.0.1', 8080, run_server)
server.serve_forever()
<!DOCTYPE html>
<html lang="en">
<meta charset="UTF-8">
<title>Title</title>
<script src="https://cdn.bootcdn.net/ajax/libs/jquery/3.4.1/jquery.min.js"></script>
<link href="https://cdn.bootcdn.net/ajax/libs/twitter-bootstrap/3.4.1/css/bootstrap.min.css" rel="stylesheet">
<script src="https://cdn.bootcdn.net/ajax/libs/twitter-bootstrap/3.4.1/js/bootstrap.min.js"></script>
</head>
<div class="container">
<div class="row">
<h2 class="text-center">用户信息表</h2>
<table class="table table-hover table-striped table-bordered">
<thead>
<th>ID</th>
<th>username</th>
<th>sex</th>
<th>password</th>
<th>hobbies</th>
</thead>
<tbody>
{% for user_dict in data_list %} {# 从列表中循环取出字典 #}
<td>{{ user_dict.id }}</td>
{# 以类似Python中字典的方式取值 #}
<td>{{ user_dict.username }}</td>
<td>{{ user_dict.sex }}</td>
<td>{{ user_dict.password }}</td>
<td>{{ user_dict.hobbies }}</td>
{% endfor %}
</tbody>
</table>
</div>
</div>
</body>
</html>
效果展示:
然后向数据库添加一条记录。
mysql> insert user(username,sex,password,hobbies) values('www','man','123456','吃生蚝,大闸蟹');
Query OK, 1 row affected (1.86 sec)
mysql> select * from user;
+
| id | username | sex | password | hobbies |
+
| 1 | lili | man | li1314420 | |
| 2 | shawn | woman | s1314420 | 肯德基,大闸蟹 |
| 3 | hxx | woman | h1314420 | 吃生蚝,大闸蟹 |
| 4 | lxx | man | l1314420 | 大闸蟹 |
| 5 | zxx | man | z1314420 | 肯德基 |
| 6 | mxx | man | m1314420 | 吃生蚝,肯德基 |
| 7 | www | man | 123456 | 吃生蚝,大闸蟹 |
+
7 rows in set (0.00 sec)
然后刷新也页面。
八、自定义版本的web框架流程图
1. 流程图流程:
浏览器客户端
wsgiref模块
请求来:处理浏览器请求,解析浏览器HTTP格式的数据,封装成大字典(PATH_INFO中存放的用户访问资源的路径)
响应去:将数据打包成符合HTTP格式,在返回给浏览器
urls.py:找出用户输入的路径有么有与视图层的对应关系,如果有则取到views.py找对应的视图函数。
view.py:
功能1(静态):视图函数找templates中的html文件,返回给wsgiref做HTTP格式的封装处理,再返回给浏览器.
功能2(动态):视图函数通过pymysql链接数据库, 通过jinja2模板语法将数据库中取出的数据在tmpelates文件夹下的html文件做一个数据的动态渲染, 最后返回给wsgiref做HTTP格式的封包处理, 再返回给浏览器.
功能3(动态):也可以通过jinja2模板语法对tmpelates文件夹下的html文件进行数据的动态渲染, 渲染完毕, 再经过wsgiref做HTTP格式的封包处理, 再返回给浏览器.
templates:html文件
2. 基本使用流程
from wsgiref.simple_server import make_server
def run_server(env, response):
函数名定义什么都无所谓, 我们这里就用run_server.
:param env: 请求相关的所有数据.
是一个大字典, wsgiref模块帮你处理好http格式的数据 封装成了字典让你更加方便的操作
:param response: 响应相关的所有数据.
:return: 返回给浏览器的数据, 返回个格式必须是'return [二进制格式的数据]' 这种样式
response('200 OK', [])
return [二进制格式的数据]
if __name__ == '__main__':
server = make_server(host, port, app)
server.serve_forever()
urls = [(路由, 视图函数), ]
def 视图函数():
import pymysql
conn = pymysql.connection(host, port, user, password, database, charset='utf8')
cursor = conn.cursor(cursor=pymysql.cursors.DictCursor)
affected_rows = cursor.execute(sql);
cursor.fetchall()
cursor.close()
conn.close()
from jinja2 import Template
temp = Template(data)
res = temp.render(user=user_date)
html中使用jinja2模板语法:
定义变量: {{ user }}
for循环: {% for xxx in user %} ... {% endfor %} 如果xxx是一个对象, 可以`xxx.id`或者`xxx['id']`取值
3. 自定义web框架步骤总结
手写web框架
wsgiref模块
- 封装处理了socket代码
- 处理了HTTP数据格式
根据不同的功能拆分不同文件
"urls.py" : 路由与视图函数对应关系
"views.py" : 视图函数
"templates" : 模板文件夹(存放HTML文件)
1.第一步添加路由与视图函数的对应关系
2.去views中书写功能代码
3.如果需要使用到html则去模板文件夹中操作
jinja2模板语法
// 定义变量, 双花括号
{{ user_list }}
// for 循环, 花括号 + 百分号
{% for user_dict in user_list %}
{{ user_dict.id }}
{% endfor %}
九、python三大主流web框架
1. 三大主流web框架
Django框架:
- 特点:大而全,自带的功能组件非常多,类似于航空母舰。
- 不足:有时候过于笨重
flask框架:
- 特点:小儿精,自带功能特别特别特别特别少,类似于游骑兵,但是第三方模块非常之多,如果把第三方模块全部叠加起来完全可以盖过django
- 不足:比较依赖于第三方的开发者,有时候也会受限于第三方模块
PS :三行代码就可以启动一个flask后端服务
tornado框架
- 特点: 异步非阻塞 速度非常的快 快到可以开发游戏服务器
ps:Sanic、FastAPI…
2. web框架三部分
- A:socket部分
- B:路由与视图匹配部分
- C:模板语法部分
3. 三种主流框架三部分的使用情况
Django
A:用的是别人的(wsgiref模块)
B:用的是自己的
C:用的是自己的
flask
A:用的是别人的(werkzeug(内部还是wsgiref模块))
B:自己写的
C:用的是别人的(jinja2)
tornado
A,B,C都是自己写的
Web应用程序框架——XWAF简介
XWAF是一个基于java反射和Servlet 技术的Web应用程序框架。 XWAF框架自带类包扫描器、注解解析器、URL适配器和访问控制器。能够自动扫描用户指定的Handler(处理器)和Interceptor(拦截器)类包,并解析其中所包含的类和方法的注解,创建客户请求URL与Interceptor 类和Handler方法适配器,控制 Interceptor 拦截器与Handler 处理器的代码执行顺序。XWAF框架还提供了大量基于自主核心技术的基础功能代码包(分为Java和JavaScript代码包)。使用XWAF框架,用户可以轻松实现面向对象和面向切面的开发。业务功能的添加和删减就像插拔功能芯片一样,有助于推进企业级Web应用程序的模块化和标准化,降低项目代码的整体耦合度和复杂度,方便项目的部署、运营、维护和业务功能扩展。同时,能够大大减轻程序员的编码工作量,缩短项目开发周期,提高开发效率。