Web框架本质
我们可以这样理解:所有的Web应用本质上就是一个socket服务端,而用户的浏览器就是一个socket客户端。 这样我们就可以自己实现Web框架了。
在学之前我们要复习前面的知识
"""
s = "Alex SB 哈哈\r\nx:1\r\ny:2\r\nz:3\r\n\r\n自行车"
# 问题1:如何取到["Alex SB 哈哈\r\nx:1\r\ny:2\r\nz:3", "自行车"]
s1=(s.split("\r\n\r\n"))
print(s1)
# 问题2:如何在上面结果基础上拿到["Alex", "SB", "哈哈"]?
s2=s1[0].split(‘\r\n‘)[0].split(‘ ‘)
print(s2)
# 问题3:如何在上面结果基础上拿到"SB"?
print(s2[1])
# ------------------------------------------------------------------------------------------
# 有一个列表,他的内部是一些元祖,元祖的第一个元素是姓名,第二个元素是爱好。
# 现在我给你一个姓名,如"Egon",如果有这个姓名,就打印出他的爱好,没有就打印查无此人。
list1 = [
("Alex", "烫头"),
("Egon", "街舞"),
("Yuan", "喝茶")
]
for i in list1:
if "Egon"==i[0]:
print(i[1])
break
else:
print(‘查无此人‘)
# ------------------------------------------------------------------------------------------
# 我有一个HTML文件"login.html"
# 问题1:我如何读取它的内容保存到变量html_s?
# with open(‘login.html‘,r,encoding=‘utf8‘)as f:
# html_s=f.read()
# 问题2:我如何读取它的二进制内容保存到变量html_b?
# with open(‘login.html‘,rb)as f:
# html_b=f.read()
# ------------------------------------------------------------------------------------------
s2 = "Alex 花了一百万买了辆电动车,真@@xx@@。"
# 问题1:如何把上面的s2转变成"Alex 花了一百万买了辆电动车,真SB。"
# print(s2.replace("真@@xx@@","真SB"))
半成品自定义web框架
import socketsk = socket.socket()sk.bind(("127.0.0.1", 80))sk.listen()while True: ???conn, addr = sk.accept() ???data = conn.recv(8096) ???conn.send(b"OK") ???conn.close()
可以说Web服务本质上都是在这十几行代码基础上扩展出来的。这段代码就是它们的祖宗。
用户的浏览器一输入网址,会给服务端发送数据,那浏览器会发送什么数据?怎么发?这个谁来定? 你这个网站是这个规定,他那个网站按照他那个规定,这互联网还能玩么?
所以,必须有一个统一的规则,让大家发送消息、接收消息的时候有个格式依据,不能随便写。
这个规则就是HTTP协议,以后浏览器发送请求信息也好,服务器回复响应信息也罢,都要按照这个规则来。
HTTP协议主要规定了客户端和服务器之间的通信格式,那HTTP协议是怎么规定消息格式的呢?
让我们首先打印下我们在服务端接收到的消息是什么。
然后我们再看一下我们访问博客园官网时浏览器收到的响应信息是什么。
响应相关信息可以在浏览器调试窗口的network标签页中看到。
点击view source之后显示如下图:
我们发现收发的消息需要按照一定的格式来,这里就需要了解一下HTTP协议了。
HTTP协议介绍
HTTP协议对收发消息的格式要求
每个HTTP请求和响应都遵循相同的格式,一个HTTP包含Header和Body两部分,其中Body是可选的。 HTTP响应的Header中有一个 Content-Type表明响应的内容格式。如 text/html表示HTML网页。
HTTP GET请求的格式:
HTTP响应的格式:
经过上面的补充学习,我们知道了要想让我们自己写的web server端正经起来,必须要让我们的Web server在给客户端回复消息的时候按照HTTP协议的规则加上响应状态行,这样我们就实现了一个正经的Web框架了。
import socket server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)server.bind((‘127.0.0.1‘, 8888))server.listen() while 1: ???conn, addr = server.accept() ???data = conn.recv(8096) ?# 收消息 ???print(data) ???conn.send(b‘HTTP/1.1 200 OK\r\n\r\n<h1>O98K</h1>‘) ?# 回复消息 ???conn.close()
根据不同的路径返回不同的内容
"""根据浏览器访问的路径的不同,返回不同的内容"""import socket server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)server.bind((‘127.0.0.1‘, 8888))server.listen() while 1: ???conn, addr = server.accept() ???data = conn.recv(8096) ?# 收消息 ???# print(data) ???# 从浏览器发送消息中,拿到用户访问的路径 ???data_str = str(data, encoding="utf8") ???# print(data_str) ???url = data_str.split("\r\n")[0].split(" ")[1] ???print(url) ????if url == "/home/": ??????msg = b‘<h1>home page</h1>‘ ???elif url == "/index/": ???????msg = b‘<h1>index page</h1>‘ ???else: ???????msg = b‘<h1>404</h1>‘ ????conn.send(b‘HTTP/1.1 200 OK\r\n\r\n‘) ???conn.send(msg) ???conn.close()
根据浏览器访问的路径的不同,返回不同的内容
"""根据浏览器访问的路径的不同,返回不同的内容将不同页面的处理代码封装到函数中"""import socket server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)server.bind((‘127.0.0.1‘, 8888))server.listen() def home(url): ?????return b‘<h1>home page</h1>‘ ?def index(url): ???return b‘<h1>index page</h1>‘ while 1: ???conn, addr = server.accept() ???data = conn.recv(8096) ?# 收消息 ???# print(data) ???# 从浏览器发送消息中,拿到用户访问的路径 ???data_str = str(data, encoding="utf8") ???# print(data_str) ???url = data_str.split("\r\n")[0].split(" ")[1] ???print(url) ????if url == "/home/": ??????msg = home(url) ???elif url == "/index/": ???????msg = index(url) ???else: ???????msg = b‘<h1>404</h1>‘ ????conn.send(b‘HTTP/1.1 200 OK\r\n\r\n‘) ???conn.send(msg) ???conn.close()
根据浏览器访问的路径的不同,返回不同的内容在升级
"""根据浏览器访问的路径的不同,返回不同的内容将不同页面的处理代码封装到函数中优化频繁的if判断 """import socket server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)server.bind((‘127.0.0.1‘, 8888))server.listen() def home(url): ???s = "this is {} page!".format(url) ???return bytes(s, encoding="utf8") ?def index(url): ???return b‘<h1>index page</h1>‘ ?def user(url): ???return b‘hehe‘ while 1: ???conn, addr = server.accept() ???data = conn.recv(8096) ?# 收消息 ???# print(data) ???# 从浏览器发送消息中,拿到用户访问的路径 ???data_str = str(data, encoding="utf8") ???# print(data_str) ???url = data_str.split("\r\n")[0].split(" ")[1] ???print(url) ????url2func = [ ???????("/index/", index), ???????("/home/", home), ???????("/user/", user), ???] ???func = None ???for i in url2func: ???????if url == i[0]: ???????????func = i[1] ?# 拿到将要执行的函数 ???????????break ???else: ???????func = None ????if func: ???????msg = func(url) ?# 执行对应的函数 ???else: ???????msg = b‘<h1>404</h1>‘ ?# 找不到要执行的函数就返回404 ?????conn.send(b‘HTTP/1.1 200 OK\r\n\r\n‘) ???conn.send(msg) ???conn.close()
返回具体的HTML文件
"""根据浏览器访问的路径的不同,返回不同的内容将不同页面的处理代码封装到函数中优化频繁的if判断返回具体的HTML文件 """import socket server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)server.bind((‘127.0.0.1‘, 8888))server.listen() ??def home(url): ???s = "this is {} page!".format(url) ???return bytes(s, encoding="utf8") ?def index(url): ???return b‘<h1>index page</h1>‘ ?def user(url): ???return b‘hehe‘ def login(url): ???with open("login.html", "rb") as f: ???????return f.read() # url和将要执行的函数的对应关系url2func = [ ???("/index/", index), ???("/home/", home), ???("/user/", user), ???("/login/", login),] while 1: ???conn, addr = server.accept() ???data = conn.recv(8096) ?# 收消息 ???# print(data) ???# 从浏览器发送消息中,拿到用户访问的路径 ???data_str = str(data, encoding="utf8") ???# print(data_str) ???url = data_str.split("\r\n")[0].split(" ")[1] ???print(url) ?????func = None ???for i in url2func: ???????if url == i[0]: ???????????func = i[1] ?# 拿到将要执行的函数 ???????????break ???else: ???????func = None ????if func: ???????msg = func(url) ?# 执行对应的函数 ???else: ???????msg = b‘<h1>404</h1>‘ ?# 找不到要执行的函数就返回404 ????# 按照HTTP协议的格式要求 回复消息 ???conn.send(b‘HTTP/1.1 200 OK\r\n\r\n‘) ?# 发送状态行 ???conn.send(msg) ?# 发送响应体 ???conn.close()
返回动态的HTML文件
"""根据浏览器访问的路径的不同,返回不同的内容将不同页面的处理代码封装到函数中优化频繁的if判断返回具体的HTML文件实现不同的用户得到不同的HTML页面 """import socketimport time server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)server.bind((‘127.0.0.1‘, 8888))server.listen() ??def home(url): ???s = "this is {} page!".format(url) ???return bytes(s, encoding="utf8") ?def index(url): ???return b‘<h1>index page</h1>‘ ?def user(url): ???# 不同的用户得到的页面上显示不同的时间 ???c_time = str(time.time()) ???with open("user.html", "r") as f: ???????data_s = f.read() ???????data_s = data_s.replace("@@xx@@", c_time) ?# 替换后的字符串数据 ???????return bytes(data_s, encoding="utf8") ?def login(url): ???with open("login.html", "rb") as f: ???????return f.read() # url和将要执行的函数的对应关系url2func = [ ???("/index/", index), ???("/home/", home), ???("/user/", user), ???("/login/", login),] while 1: ???conn, addr = server.accept() ???data = conn.recv(8096) ?# 收消息 ???# print(data) ???# 从浏览器发送消息中,拿到用户访问的路径 ???data_str = str(data, encoding="utf8") ???# print(data_str) ???url = data_str.split("\r\n")[0].split(" ")[1] ???print(url) ?????func = None ???for i in url2func: ???????if url == i[0]: ???????????func = i[1] ?# 拿到将要执行的函数 ???????????break ???else: ???????func = None ????if func: ???????msg = func(url) ?# 执行对应的函数 ???else: ???????msg = b‘<h1>404</h1>‘ ?# 找不到要执行的函数就返回404 ????# 按照HTTP协议的格式要求 回复消息 ???conn.send(b‘HTTP/1.1 200 OK\r\n\r\n‘) ?# 发送状态行 ???conn.send(msg) ?# 发送响应体 ???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
我们利用wsgiref模块来替换我们自己写的web框架的socket server部分:
"""
根据浏览器访问的路径的不同,返回不同的内容
将不同页面的处理代码封装到函数中
优化频繁的if判断
返回具体的HTML文件
实现不同的用户得到不同的HTML页面
""" import timefrom wsgiref.simple_server import make_server ?def home(url): ???s = "this is {} page!".format(url) ???return bytes(s, encoding="utf8") ?def index(url): ???return b‘<h1>index page</h1>‘ ?def user(url): ???# 不同的用户得到的页面上显示不同的时间 ???c_time = str(time.time()) ???with open("user.html", "r") as f: ???????data_s = f.read() ???????data_s = data_s.replace("@@xx@@", c_time) ?# 替换后的字符串数据 ???????return bytes(data_s, encoding="utf8") ?def login(url): ???with open("login.html", "rb") as f: ???????return f.read() # url和将要执行的函数的对应关系url2func = [ ???("/index/", index), ???("/home/", home), ???("/user/", user), ???("/login/", login),] # 按照wsgiref的要求定义一个run_server函数def run_server(environ, start_response): ???""" ????:param environ: 跟请求相关的参数 ???:param start_response: ???:return: ???""" ???# 设置HTTP响应的状态码和头信息 ???start_response(‘200 OK‘, [(‘Content-Type‘, ‘text/html;charset=utf8‘), ]) ???url = environ[‘PATH_INFO‘] ?# 取到用户输入的url ????func = None ???for i in url2func: ???????if url == i[0]: ???????????func = i[1] ?# 拿到将要执行的函数 ???????????break ???else: ???????func = None ????if func: ???????msg = func(url) ?# 执行对应的函数 ???else: ???????msg = b‘<h1>404</h1>‘ ?# 找不到要执行的函数就返回404 ????return [msg, ] if __name__ == ‘__main__‘: ???httpd = make_server(‘127.0.0.1‘, 8090, run_server) ???print("我在8090等你哦...") ???httpd.serve_forever()
jinja2
上面的代码实现了一个简单的动态,我完全可以从数据库中查询数据,然后去替换我html中的对应内容,然后再发送给浏览器完成渲染。 这个过程就相当于HTML模板渲染数据。 本质上就是HTML内容中利用一些特殊的符号来替换要展示的数据。 我这里用的特殊符号是我定义的,其实模板渲染有个现成的工具: jinja2
下载jinja2:
pip install jinja2
"""
根据浏览器访问的路径的不同,返回不同的内容
将不同页面的处理代码封装到函数中
优化频繁的if判断
返回具体的HTML文件
实现不同的用户得到不同的HTML页面
""" import timefrom wsgiref.simple_server import make_serverfrom jinja2 import Templateimport pymysql ?def home(url): ???s = "this is {} page!".format(url) ???return bytes(s, encoding="utf8") ?def index(url): ???return b‘<h1>index page</h1>‘ ?def user(url): ???# 从数据库里面去到所有的用户信息, ???conn = pymysql.connect( ???????host=‘127.0.0.1‘, ???????port=3306, ???????user=‘root‘, ???????password=‘123‘, ???????database=‘day61‘, ???????charset=‘utf8‘ ???) ???cursor = conn.cursor(cursor=pymysql.cursors.DictCursor) ???cursor.execute(‘select * from user‘) ???ret = cursor.fetchall() ???print(ret) ???# 在页面上显示出来 ????with open("user.html", "r", encoding="utf8") as f: ???????data_s = f.read() ????????template = Template(data_s) ?# 生成一个模板文件实例 ????????msg = template.render({"user_list": ret}) ?# 把数据填充到模板里面 ?????????return bytes(msg, encoding="utf8") ?def login(url): ???with open("login.html", "rb") as f: ???????return f.read() # url和将要执行的函数的对应关系url2func = [ ???("/index/", index), ???("/home/", home), ???("/user/", user), ???("/login/", login),] # 按照wsgiref的要求定义一个run_server函数def run_server(environ, start_response): ???""" ????:param environ: 跟请求相关的参数 ???:param start_response: ???:return: ???""" ???# 设置HTTP响应的状态码和头信息 ???start_response(‘200 OK‘, [(‘Content-Type‘, ‘text/html;charset=utf8‘), ]) ???url = environ[‘PATH_INFO‘] ?# 取到用户输入的url ????func = None ???for i in url2func: ???????if url == i[0]: ???????????func = i[1] ?# 拿到将要执行的函数 ???????????break ???else: ???????func = None ????if func: ???????msg = func(url) ?# 执行对应的函数 ???else: ???????msg = b‘<h1>404</h1>‘ ?# 找不到要执行的函数就返回404 ????return [msg, ] if __name__ == ‘__main__‘: ???httpd = make_server(‘127.0.0.1‘, 8090, run_server) ???print("我在8090等你哦...") ???httpd.serve_forever()
创建一个测试的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的语法规则写上,其内部就会按照指定的语法进行相应的替换,从而达到动态的返回内容。
Django
Django官网下载页面
安装(安装最新LTS版):
pip3 install django==1.11.9
创建一个django项目:
下面的命令创建了一个名为"mysite"的Django 项目:
django-admin startproject mysite
目录介绍:
mysite/
├── manage.py # 管理文件
└── mysite # 项目目录
├── __init__.py
├── settings.py # 配置
├── urls.py # 路由 --> URL和函数的对应关系
└── wsgi.py # runserver命令就使用wsgiref模块做简单的web server
运行Django项目:
python manage.py runserver 127.0.0.1:8000
模板文件配置:
TEMPLATES = [
{
‘BACKEND‘: ‘django.template.backends.django.DjangoTemplates‘,
‘DIRS‘: [os.path.join(BASE_DIR, "template")], # template文件夹位置
‘APP_DIRS‘: True,
‘OPTIONS‘: {
‘context_processors‘: [
‘django.template.context_processors.debug‘,
‘django.template.context_processors.request‘,
‘django.contrib.auth.context_processors.auth‘,
‘django.contrib.messages.context_processors.messages‘,
],
},
},
]
静态文件配置:
STATIC_URL = ‘/static/‘ # HTML中使用的静态文件夹前缀
STATICFILES_DIRS = [
os.path.join(BASE_DIR, "static"),# 静态文件存放位置
]
看不明白?有图有真相:
刚开始学习时可在配置文件中暂时禁用csrf中间件,方便表单提交测试。
MIDDLEWARE = [
‘django.middleware.security.SecurityMiddleware‘,
‘django.contrib.sessions.middleware.SessionMiddleware‘,
‘django.middleware.common.CommonMiddleware‘,
# ‘django.middleware.csrf.CsrfViewMiddleware‘,
‘django.contrib.auth.middleware.AuthenticationMiddleware‘,
‘django.contrib.messages.middleware.MessageMiddleware‘,
‘django.middleware.clickjacking.XFrameOptionsMiddleware‘,
]
Django基础必备三件套:
fromdjango.shortcuts import HttpResponse, render, redirect
HttpResponse
内部传入一个字符串参数,返回给浏览器。
例如:
defindex(request):
# 业务逻辑代码
return HttpResponse("OK")
render
除request参数外还接受一个待渲染的模板文件和一个保存具体数据的字典参数。
将数据填充进模板文件,最后把结果返回给浏览器。(类似于我们上面用到的jinja2)
例如:
defindex(request):
# 业务逻辑代码
return render(request, "index.html", {"name": "alex", "hobby": ["烫头", "泡吧"]})
redirect
接受一个URL参数,表示跳转到指定的URL。
例如:
defindex(request):
# 业务逻辑代码
return redirect("/home/")
重定向是怎么回事?
课后练习:
Django版登录
启动Django报错:
Django 启动时报错 UnicodeEncodeError ...
报这个错误通常是因为计算机名为中文,改成英文的计算机名重启下电脑就可以了。
web框架和第一个Django实例
原文地址:https://www.cnblogs.com/maojiang/p/9167484.html