Web框架的本质/服务器程序和应用程序/初识Django/WSGI标准/wsgiref模块/jinjia2模块

Web框架的本质

我们可以这样理解:所有的Web应用本质上就是一个socket服务端,而用户的浏览器就是一个socket客户端。 这样我们就可以自己实现Web框架了。

简易版本的自定义web框架代码:

import socket
​
sk = 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()

总结:

用户的浏览器一输入网址,会给服务端发送数据,那浏览器会发送什么数据?怎么发?这个谁来定?所以,有一个统一的规则,让大家发送消息、接收消息的时候有个格式依据,不能随便写。这个规则就是HTTP协议,以后浏览器发送请求信息也好,服务器回复响应信息也罢,都要按照这个规则来。

HTTP协议

超文本传输协议:规定了客户端与服务端消息传输的格式

四大特性:

  • 1.基于TCP/IP协议作用于应用层的协议
  • 2.基于请求响应
  • 3.无状态
  • 4.无连接

数据格式之请求:

  • 请求首行
  • 请求头(一堆k,v键值对)
  • /r/n
  • 请求体(post请求携带的数据)

数据格式之响应:

  • 响应首行
  • 响应头(一堆k,v键值对)
  • /r/n
  • 响应体(post请求携带的数据)

响应状态码:

  • 1XX 服务器已经成功接受到你的数据正在处理,你可以继续提交其他数据
  • 2XX 请求成功,服务器已经将你请求的数据发送给你了
  • 3XX 重定向
  • 4XX 请求资源不存在
  • 5XX 服务器错误

HTTP协议主要规定了客户端和服务器之间的通信格式,让我们首先打印下我们在服务端接收到的消息是什么。

# b'GET / HTTP/1.1
Host: 127.0.0.1:8080
Connection: keep-alive
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/64.0.3282.186 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8
DNT: 1
Accept-Encoding: gzip, deflate, br
Accept-Language: zh-CN,zh;q=0.9
Cookie: csrftoken=RKBXh1d3M97iz03Rpbojx1bR6mhHudhyX5PszUxxG3bOEwh1lxFpGOgWN93ZH3zv

接收的原始数据

我们发现收发的消息需要按照一定的格式来:

'''
b'GET / HTTP/1.1

Host: 127.0.0.1:8080

Connection: keep-alive

Upgrade-Insecure-Requests: 1

User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/64.0.3282.186 Safari/537.36

Accept:text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8

DNT: 1

Accept-Encoding: gzip, deflate, br

Accept-Language: zh-CN,zh;q=0.9

Cookie: csrftoken=RKBXh1d3M97iz03Rpbojx1bR6mhHudhyX5PszUxxG3bOEwh1lxFpGOgWN93ZH3zv



'''

结论:

之前那些看起来比较杂乱的数据,变成了一排排的键值对,具体名称见上.

加强版的自定义版web框架:

import socket
​
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock.bind(('127.0.0.1', 8000))
sock.listen()
​
while True:
    conn, addr = sock.accept()
    data = conn.recv(8096)
    # 给回复的消息加上响应状态行
    conn.send(b"HTTP/1.1 200 OK

")
    conn.send(b"OK")
    conn.close()

这样我们通过十几行代码就能简单地演示web 框架的本质。

根据不同的路径返回不同的内容

import socket
sk = socket.socket()
sk.bind(("127.0.0.1", 8080))  # 绑定IP和端口
sk.listen()  # 监听
while 1:
    # 等待连接
    conn, add = sk.accept()
    data = conn.recv(8096)  # 接收客户端发来的消息
    # 从data中取到路径
    data = str(data, encoding="utf8")  # 把收到的字节类型的数据转换成字符串
    # 按
分割
    data1 = data.split("
")[0]
    url = data1.split()[1]  # url是我们从浏览器发过来的消息中分离出的访问路径
    conn.send(b'HTTP/1.1 200 OK

')  # 因为要遵循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
sk = socket.socket()
sk.bind(("127.0.0.1", 8080))  # 绑定IP和端口
sk.listen()  # 监听
# 将返回不同的内容部分封装成函数
def index(url):
    s = "这是{}页面!".format(url)
    return bytes(s, encoding="utf8")
​
def home(url):
    s = "这是{}页面!".format(url)
    return bytes(s, encoding="utf8")
​
while 1:
    # 等待连接
    conn, add = sk.accept()
    data = conn.recv(8096)  # 接收客户端发来的消息
    # 从data中取到路径
    data = str(data, encoding="utf8")  # 把收到的字节类型的数据转换成字符串
    # 按
分割
    data1 = data.split("
")[0]
    url = data1.split()[1]  # url是我们从浏览器发过来的消息中分离出的访问路径
    conn.send(b'HTTP/1.1 200 OK

')  # 因为要遵循HTTP协议,所以回复的消息也要加状态行
    # 根据不同的路径返回不同内容,response是具体的响应体
    if url == "/index/":
        response = index(url)
    elif url == "/home/":
        response = home(url)
    else:
        response = b"404 not found!"
​
    conn.send(response)
    conn.close()

根据不同的路径返回不同的内容--函数进阶版

import socket
sk = socket.socket()
sk.bind(("127.0.0.1", 8080))  # 绑定IP和端口
sk.listen()  # 监听
# 将返回不同的内容部分封装成函数
def index(url):
    s = "这是{}页面!".format(url)
    return bytes(s, encoding="utf8")
​
def home(url):
    s = "这是{}页面!".format(url)
    return bytes(s, encoding="utf8")
​
# 定义一个url和实际要执行的函数的对应关系
list1 = [
    ("/index/", index),
    ("/home/", home),
]
​
while 1:
    # 等待连接
    conn, add = sk.accept()
    data = conn.recv(8096)  # 接收客户端发来的消息
    # 从data中取到路径
    data = str(data, encoding="utf8")  # 把收到的字节类型的数据转换成字符串
    # 按
分割
    data1 = data.split("
")[0]
    url = data1.split()[1]  # url是我们从浏览器发过来的消息中分离出的访问路径
    conn.send(b'HTTP/1.1 200 OK

')  # 因为要遵循HTTP协议,所以回复的消息也要加状态行
    # 根据不同的路径返回不同内容
    func = None  # 定义一个保存将要执行的函数名的变量
    for i in list1:
        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页面
"""import socket
sk = socket.socket()
sk.bind(("127.0.0.1", 8080))  # 绑定IP和端口
sk.listen()  # 监听
# 将返回不同的内容部分封装成函数
def index(url):
    # 读取index.html页面的内容
    with open("index.html", "r", encoding="utf8") as f:
        s = f.read()
    # 返回字节数据
    return bytes(s, encoding="utf8")
​
def home(url):
    with open("home.html", "r", encoding="utf8") as f:
        s = f.read()
    return bytes(s, encoding="utf8")
​
# 定义一个url和实际要执行的函数的对应关系
list1 = [
    ("/index/", index),
    ("/home/", home),
]
​
while 1:
    # 等待连接
    conn, add = sk.accept()
    data = conn.recv(8096)  # 接收客户端发来的消息
    # 从data中取到路径
    data = str(data, encoding="utf8")  # 把收到的字节类型的数据转换成字符串
    # 按
分割
    data1 = data.split("
")[0]
    url = data1.split()[1]  # url是我们从浏览器发过来的消息中分离出的访问路径
    conn.send(b'HTTP/1.1 200 OK

')  # 因为要遵循HTTP协议,所以回复的消息也要加状态行
    # 根据不同的路径返回不同内容
    func = None  # 定义一个保存将要执行的函数名的变量
    for i in list1:
        if i[0] == url:
            func = i[1]
            break
    if func:
        response = func(url)
    else:
        response = b"404 not found!"# 返回具体的响应消息
    conn.send(response)
    conn.close()

动静态网页

静态网页:

页面上的数据都是写死的,万年不变

动态网页:

  • 页面上的数据是从后端动态获取的
  • 比如后端获取当前时间
  • 后端获取数据库数据然后传递给前端页面

制作成动态网页:

"""
根据URL中不同的路径返回不同的内容--函数进阶版
返回HTML页面
让网页动态起来
"""import socket
import time
​
sk = socket.socket()
sk.bind(("127.0.0.1", 8080))  # 绑定IP和端口
sk.listen()  # 监听
# 将返回不同的内容部分封装成函数
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 bytes(s, encoding="utf8")
​
def home(url):
    with open("home.html", "r", encoding="utf8") as f:
        s = f.read()
    return bytes(s, encoding="utf8")
​
# 定义一个url和实际要执行的函数的对应关系
list1 = [
    ("/index/", index),
    ("/home/", home),
]
​
while 1:
    # 等待连接
    conn, add = sk.accept()
    data = conn.recv(8096)  # 接收客户端发来的消息
    # 从data中取到路径
    data = str(data, encoding="utf8")  # 把收到的字节类型的数据转换成字符串
    # 按
分割
    data1 = data.split("
")[0]
    url = data1.split()[1]  # url是我们从浏览器发过来的消息中分离出的访问路径
    conn.send(b'HTTP/1.1 200 OK

')  # 因为要遵循HTTP协议,所以回复的消息也要加状态行
    # 根据不同的路径返回不同内容
    func = None  # 定义一个保存将要执行的函数名的变量
    for i in list1:
        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服务器进行封装,并在请求到来时,对请求的各种数据进行整理。

软件开发架构:

C/S架构:客户端 服务端

B/S架构:浏览器 服务器

本质上b/s架构其实也是c/s架构

应用程序则负责具体的逻辑处理。为了方便应用程序的开发,就出现了众多的Web框架,例如:Django、Flask、web.py 等。

Web框架

python三大主流web框架:

  • Django:大而全,自带了很多功能模块,类似于航空母舰 (缺点:有点笨重)
  • Flask:短小精悍,自带的功能模块特别少,大部分都是依赖于第三方模块(小而轻)
  • Tornado:异步非阻塞 主要用在处理高io 多路复用的情况 可以写游戏后端

我们根据三样元素来统计他们的特点:

  • a:socket
  • b:路由与视图函数
  • c:模板渲染

Django:

  • a用的别人的 wsgiref
  • b自己写的
  • c自己写的

Flask:

  • a用的别人的 werkzeug
  • b自己写的
  • c用的别人的 jinja2

Tornado:

a,b,c都是自己写的

简易版本的web请求流程图

关于使用Django框架的注意事项(重要):

  • 1.计算机的名称不能有中文
  • 2.一个pycharm窗口就是一个项目,不要多个项目放在一个窗口里面
  • 3.项目名不能起中文

Django

版本问题以及下载

我们通常都推荐下载1.11.11版本,在下载时也有两种方式

命令行下载:

  • pip3 install django==1.11.11
  • django-admin(验证是否下载成功)

pycharm下载:

  • pip3 install django==1.11.11
  • django-admin(验证是否下载成功)

创建Django项目的方式:

命令行创建

  • 创建django项目:

    • django-admin startproject 项目名
  • 创建app应用:

    • python3 manage.py startapp 应用名
  • 启动django项目:

    • python3 manage.py runserver

PS:用命令行创建django默认不会自动创建templates文件夹,需要你手动自己创建(注意改文件夹路径是否被添加配置文件中,如下段代码中的DIRS:[ ]中添加)

模板文件配置:

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',
]

Pycharm创建

创建django项目:

FILE >>> new project 选择第二个django 需要注意名字不能有中文,选择本地的解释器,勾选后台管理

创建app应用:

  • 1.pycharm命令行创建 >>> python3 manage.py startapp 应用名
  • 2.Tools下面run manage task功能栏

启动django项目:

点击上面绿色小箭头!

注意:

  • 1.用django一定要保证只有一个在运行状态,切记切记!
  • 2.一定记得清浏览器的缓存

app应用的概念

一个django项目就是一所大学,app就是大学里面的学院

注意新创建的app需要在配置文件中注册才能生效!

INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    'app01.apps.App01Config'  # 可以用全称
    'app01'                  # 也可以简写
]

Django各个文件的作用

应用名字

  • migrations 数据库迁移记录相关数据
  • admin.py Django后台管理相关
  • models.py 模型表相关
  • views.py 视图函数相关

项目名

  • settings.py 配置文件
  • urls.py 路由与视图函数的映射关系

templates

项目用到的所有的html文件

manage.py

Django入口文件


WSGI标准

不同的框架有不同的开发方式,但是无论如何,开发出的应用程序都要和服务器程序配合,才能为用户提供服务。

服务器程序需要为不同的框架提供不同的支持。这样混乱的局面无论对于服务器还是框架,都是不好的。对服务器来说,需要支持各种不同框架,对框架来说,只有支持它的服务器才能被开发出的应用使用。

这时候,标准化就变得尤为重要。我们可以设立一个标准,只要服务器程序支持这个标准,框架也支持这个标准,那么他们就可以配合使用。一旦标准确定,双方各自实现。这样,服务器可以支持更多支持标准的框架,框架也可以使用更多支持标准的服务器。

WSGI(Web Server Gateway Interface)就是一种规范,它定义了使用Python编写的web应用程序与web服务器程序之间的接口格式,实现web应用程序与web服务器程序间的解耦。

常用的WSGI服务器有uwsgi、Gunicorn。而Python标准库提供的独立WSGI服务器叫wsgiref,Django开发环境用的就是这个模块来做服务器。

引申出的wsgiref模块

我们利用wsgiref模块来替换我们自己写的web框架的socket server部分:

"""
根据URL中不同的路径返回不同的内容--函数进阶版
返回HTML页面
让网页动态起来
wsgiref模块版
"""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 bytes(s, encoding="utf8")
​
def home(url):
    with open("home.html", "r", encoding="utf8") as f:
        s = f.read()
    return bytes(s, encoding="utf8")
​
# 定义一个url和实际要执行的函数的对应关系
list1 = [
    ("/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 list1:
        if i[0] == url:
            func = i[1]
            break
    if func:
        response = func(url)
    else:
        response = b"404 not found!"
    return [response, ]
​
if __name__ == '__main__':
    httpd = make_server('127.0.0.1', 8090, run_server)
    print("我在8090等你哦...")
    httpd.serve_forever()

引申出的jinjia2模块

上面的代码实现了一个简单的动态,我完全可以从数据库中查询数据,然后去替换我html中的对应内容,然后再发送给浏览器完成渲染。 本质上就是HTML内容中利用一些特殊的符号来替换要展示的数据。 我这里用的特殊符号是我定义的,其实模板渲染有个现成的工具: "jinja2"

上述过程就相当于HTML模板渲染数据。

关于HTML页面渲染

后端生成的数据直接传递给前端页面使用(并且前端页面可以灵活的操作改数据) >>> 模板语法

PS:模板语法,jinja2支持前端直接使用类似于python的语法操作数据

下载jinja2:

# 终端输入即可,也可以用命令行输入
pip install jinja2

index.html文件:

<!--渲染前-->
<!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>

渲染index.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的语法规则写上,其内部就会按照指定的语法进行相应的替换,从而达到动态的返回内容。

模板替换语法(字典部分)在前端页面部分的代码:

<p>{{ user_dic }}</p>
<p>{{ user_dic.name }}</p>
<p>{{ user_dic['password'] }}</p>
<p>{{ user_dic.get('name') }}</p>
    
    {% for user in user_dict %}  <!--[{},{},{},{}]-->
        <tr>
            <td>{{ user.id }}</td>
            <td>{{ user.name }}</td>
            <td>{{ user.password }}</td>
        </tr>
    {% endfor %}

Django基础必备三件套:

from django.shortcuts import HttpResponse, render, redirect

HttpResponse

内部传入一个字符串参数,返回给浏览器。

示例:

def index(request):
    # 业务逻辑代码
    return HttpResponse("OK")

render

除request参数外还接受一个待渲染的模板文件和一个保存具体数据的字典参数。将数据填充进模板文件,最后把结果返回给浏览器。(类似于我们上面用到的jinja2)

示例(两种方式):

#第一种
def reg(request):
    user_dict = {'name':'jason','password':'123'}
    return render(request,'reg.html',{'user_dict':user_dict}
​
#第二种
def reg(request):
    user_dict = {'name':'jason','password':'123'}
    return render(request,'reg.html',locals())      

redirect

接受一个URL参数,表示跳转到指定的URL。

示例:

def index(request):
    # 业务逻辑代码
    return redirect("/home/")

关于重定向

 

原文地址:https://www.cnblogs.com/zhukaijian/p/11512712.html