本文介绍: 在Python我们可以使用别人开发的、功能更强大的Web框架,如djangotornado、flask等,三种区别如下1、django框架实现了上述功能1、2、3。django框架自定义server基于wsgiref+socketserver模块实现),但这只是提供给开发测试使用server,并不能在生产环境应用,生产环境部署djangoserver通常采用uwsgidjango自定义视图系统,即实现功能2django自定义模板系统,即实现了功能32、tornado框架

Web框架与Django简介

我们为了开发一款Web软件首先要了解什么才是Web应用软件呢?

对于传统的应用软件来说,基本都是部署单机使用,而Web应用软件就不一样,Web应用软件是基于B/S架构的,B和S都在不同的计算机上,并且基于网络通信,所以B和S的本质就是套接字,B指的是浏览器并且无需开发,我们需要开发的是S端。

我们在开发套接字服务端S的思路应该是这样:

1.接收客户端B发来的信息并且加以解析

2. 根据解析后的结果,加以判断,生成/获取用户想要的数据

3.返回数据给套接字客户端B

1.Sever:称为服务器程序,指的是套接字的通信相关事宜,包含1和3

2.application应用程序,指的是应用程序逻辑,包含2

综上所述,一个完整的Web应用如下图所示

  

1、S端的简单开发与Http协议 

# S端
import socket

def make_server(ip, port, app):  # 代表server
    # 处理套接字通信相关事宜
    sock = socket.socket()
    sock.bind((ip, port))
    sock.listen(5)
    print('Starting development server at http://%s:%s/' %(ip,port))
    while True:
        conn, addr = sock.accept()

        # 1、接收浏览器发来的请求信息
        recv_data = conn.recv(1024)
        # print(recv_data.decode('utf-8'))

        # 2、将请求信息直接转交给application处理,得到返回res = app(recv_data)

        # 3、向浏览器返回消息(此处并没有按照http协议返回conn.send(res)
        
        conn.close()

def app(environ):  # 代表application
    # 处理业务逻辑
    return b'hello world'

if __name__ == '__main__':
    make_server('127.0.0.1', 8008, app)  # 在客户端浏览器输入http://127.0.0.1:8008 会报错(注意:请使用谷歌浏览器

处理HTTP协议的请求消息,并按照HTTP协议的格式回复消息

# S端
import socket

def make_server(ip, port, app): # 代表server
    # 处理套接字通信相关事宜
    sock = socket.socket()
    sock.bind((ip, port))
    sock.listen(5)
    print('Starting development server at http://%s:%s/' %(ip,port))
    while True:
        conn, addr = sock.accept()

        # 1、接收并处理浏览器发来的请求信息
        # 1.1 接收浏览器发来的http协议的消息
        recv_data = conn.recv(1024)

        # 1.2 对http协议的消息加以处理简单示范如下
        ll=recv_data.decode('utf-8').split('rn')
        head_ll=ll[0].split(' ')
        environ={}
        environ['PATH_INFO']=head_ll[1]
        environ['method']=head_ll[0]

        # 2:将请求信息处理后的结果environ交给application,这样application便无需再关注请求信息的处理,可以更加专注于业务逻辑的处理
        res = app(environ)

        # 3:按照http协议向浏览器返回消息
        # 3.1 返回响应首行
        conn.send(b'HTTP/1.1 200 OKrn')
        # 3.2 返回响应头(可以省略)
        conn.send(b'Content-Type: text/htmlrnrn')
        # 3.3 返回响应conn.send(res)

        conn.close()

def app(environ): # 代表application
    # 处理业务逻辑
    return b'hello world'

if __name__ == '__main__':
    make_server('127.0.0.1', 8008, app)

此时,重启S端后,再在客户端浏览器输入http://127.0.0.1:8008 便可以看到正常结果hello world了。

# S端
import socket

def make_server(ip, port, app): 
    sock = socket.socket()
    sock.bind((ip, port))
    sock.listen(5)
    print('Starting development server at http://%s:%s/' %(ip,port))
    while True:
        conn, addr = sock.accept()
        
        recv_data = conn.recv(1024)
        ll=recv_data.decode('utf-8').split('rn')
        head_ll=ll[0].split(' ')
        environ={}
        environ['PATH_INFO']=head_ll[1]
        environ['method']=head_ll[0]

        res = app(environ)

        conn.send(b'HTTP/1.1 200 OKrn')
        conn.send(b'Content-Type: text/htmlrnrn')
        conn.send(res)

        conn.close()

def app(environ):
    # 返回html标签
    return b'<h1>hello web</h1><img src="https://www.baidu.com/img/bd_logo1.png"></img>'

if __name__ == '__main__':
    make_server('127.0.0.1', 8008, app)
<!DOCTYPE html>
<html>
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<h2>当前时间为:2020-02-02 20:20:20</h2>
</body>
</html>
S端程序如下
# S端
import socket

def make_server(ip, port, app): # 代表server
    sock = socket.socket()
    sock.bind((ip, port))
    sock.listen(5)
    print('Starting development server at http://%s:%s/' %(ip,port))
    while True:
        conn, addr = sock.accept()

        recv_data = conn.recv(1024)
        ll=recv_data.decode('utf-8').split('rn')
        head_ll=ll[0].split(' ')
        environ={}
        environ['PATH_INFO']=head_ll[1]
        environ['method']=head_ll[0]

        res = app(environ)

        conn.send(b'HTTP/1.1 200 OKrn')
        conn.send(b'Content-Type: text/htmlrnrn')
        conn.send(res)

        conn.close()

def app(environ):
    # 处理业务逻辑打开文件读取文件内容并返回
    with open('timer.html', 'r', encoding='utf-8') as f:
        data = f.read()
    return data.encode('utf-8')

if __name__ == '__main__':
    make_server('127.0.0.1', 8008, app)

 上述S端为浏览器返回的都是静态页面内容都固定的),要想返回动态页面内容是变化的),那timer.html中的内容就不能写死,可以定义一个特殊的符号(类似于变量名),然后每次用得到的值覆盖符号即可(类似于为变量赋值),于是timer.html内容修改如下

<!DOCTYPE html>
<html>
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<h2>当前时间为:{{ xxx }}</h2>
</body>
</html>
S端修改如下
# S端
import socket

def make_server(ip, port, app): # 代表server
    sock = socket.socket()
    sock.bind((ip, port))
    sock.listen(5)
    print('Starting development server at http://%s:%s/' %(ip,port))
    while True:
        conn, addr = sock.accept()

        recv_data = conn.recv(1024)
        ll=recv_data.decode('utf-8').split('rn')
        head_ll=ll[0].split(' ')
        environ={}
        environ['PATH_INFO']=head_ll[1]
        environ['method']=head_ll[0]

        res = app(environ)

        conn.send(b'HTTP/1.1 200 OKrn')
        conn.send(b'Content-Type: text/htmlrnrn')
        conn.send(res)

        conn.close()

def app(environ):
    # 处理业务逻辑
    import time
    now = time.strftime("%Y-%m-%d %H:%M:%S", time.localtime())
    
    with open('timer.html', 'r', encoding='utf-8') as f:
        data = f.read()
        
    data = data.replace('{{ xxx }}', now)  # 字符串替换
    
    return data.encode('utf-8')

if __name__ == '__main__':
    make_server('127.0.0.1', 8008, app) # 在浏览器输入http://127.0.0.1:8008,每次刷新都会看到不同的时间

4、jinja2模块

承接上例我们返回动态页面解决方案,思考一个问题,如果页面需要引入特殊符号/“变量”过多,那么函数app中要写一大堆字符串替换代码,相当麻烦,有一个模块jinja2很好地帮我们解决了这个问题,本质原理也就是字符串替换,我们用它就好 

<!DOCTYPE html>
<html>
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<h2>当前时间为:{{ xxx }}</h2>
<h2>当前用户为:{{ user }}</h2>
<h2>当前角色:{{ role }}</h2>
</body>
</html>
S端内容如下
# S端
import socket
from jinja2 import Template # pip3 install jinja2

def make_server(ip, port, app): # 代表server
    sock = socket.socket()
    sock.bind((ip, port))
    sock.listen(5)
    print('Starting development server at http://%s:%s/' %(ip,port))
    while True:
        conn, addr = sock.accept()

        recv_data = conn.recv(1024)
        ll=recv_data.decode('utf-8').split('rn')
        head_ll=ll[0].split(' ')
        environ={}
        environ['PATH_INFO']=head_ll[1]
        environ['method']=head_ll[0]

        res = app(environ)

        conn.send(b'HTTP/1.1 200 OKrn')
        conn.send(b'Content-Type: text/htmlrnrn')
        conn.send(res)

        conn.close()

def app(environ):
    # 处理业务逻辑
    import time
    now = time.strftime("%Y-%m-%d %H:%M:%S", time.localtime())
    
    with open('timer.html', 'r', encoding='utf-8') as f:
        data = f.read()
        template=Template(data)

    # data = data.replace('{{ xxx }}', now)  # 字符串替换
    data=template.render({'xxx':now,'user':'ly','role':'大总管'})

    return data.encode('utf-8')

if __name__ == '__main__':
    make_server('127.0.0.1', 8008, app) # 在浏览器输入http://127.0.0.1:8008,每次刷新都会看到不同的时间

 

timer.html已经存在了,新增的index.html页面内容如下:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
    <h1>主页</h1>
</body>
</html>

 上述案例中app在处理业务逻辑时需要根据不同的url地址返回不同的页面内容,当url地址越来越多,需要写一堆if判断,代码不够清晰,耦合程度高,所以我们做出以下优化

# 处理业务逻辑的函数
from jinja2 import Template

def index(environ):
    with open('index.html', 'r', encoding='utf-8') as f:
        data = f.read()
    return data.encode('utf-8')


def timer(environ):
    import time
    now = time.strftime("%Y-%m-%d %H:%M:%S", time.localtime())
    
    with open('timer.html', 'r', encoding='utf-8') as f:
        data = f.read()
        template=Template(data)

    data=template.render({'xxx':now,'user':'ly','role':'大总管'})
    return data.encode('utf-8')


# 路径函数映射关系
url_patterns = [
    ('/index', index),
    ('/timer', timer),
]


from wsgiref.simple_server import make_server 

def app(environ, start_response):
    start_response('200 OK', [('Content-Type', 'text/html')])

    # 拿到请求的url并根据映射关系url_patters执行相应的函数
    reuqest_url = environ.get('PATH_INFO')
    for url in url_patterns:
        if url[0] == reuqest_url:
            data = url[1](environ)
            break
    else:
        data = b'404'

    return [data]


if __name__ == '__main__':
    s = make_server('', 8011, app)
    print('监听8011')
    s.serve_forever()

 2、简单Web框架实现

views.py 内容如下:

# 处理业务逻辑的函数
from jinja2 import Template

def index(environ):
    with open('index.html', 'r', encoding='utf-8') as f:
        data = f.read()
    return data.encode('utf-8')


def timer(environ):
    import time
    now = time.strftime("%Y-%m-%d %H:%M:%S", time.localtime())
    
    with open('timer.html', 'r', encoding='utf-8') as f:
        data = f.read()
        template=Template(data)

    data=template.render({'xxx':now,'user':'ly','role':'大总管'})
    return data.encode('utf-8')

urls.py内容如下:
# 路径函数映射关系
from app01.views import * # 需要导入views中的函数

url_patterns = [
    ('/index', index),
    ('/timer', timer),
]

main.py 内容如下:
from wsgiref.simple_server import make_server
from mysite.urls import url_patterns  # 需要导入urls中的url_patterns


def app(environ, start_response):
    start_response('200 OK', [('Content-Type', 'text/html')])

    # 拿到请求的url并根据映射关系url_patters执行相应的函数
    reuqest_url = environ.get('PATH_INFO')
    for url in url_patterns:
        if url[0] == reuqest_url:
            data = url[1](environ)
            break
    else:
        data = b'404'

    return [data]


if __name__ == '__main__':
    s = make_server('', 8011, app)
    print('监听8011')
    s.serve_forever()

3、简单web框架的使用

在框架的基础上具体开发步骤如下:

步骤一:在templates文件夹下新增home.html

步骤二:在urls.py的url_patterns中新增一条映射关系

url_patterns = [
    ('/index', index),
    ('/timer', timer),
    ('/home', home), # 新增的映射关系
]

步骤三:在views.py中新增一个名为home的函数 

def home(environ):
    with open('templates/home.html', 'r',encoding='utf-8') as f: 
        data = f.read()
    return data.encode('utf-8')

 4、三大web框架简介与wsgi协议

3、flask框架
只实现了上述功能2

其实wsgiref、uwsgi等服务器程序server都是遵循wsgi协议的,有了这套协议/标准,server与application的开发就完全解开了耦合,一批程序员可以专注于开发不同的server,一批程序员(就是我们自己)则专注于开发不同的application,只要二者都遵循wsgi协议,则开发的程序可以完美整合,这跟谈恋爱是一个道理,定好你对老婆的要求/标准,只要符合这个标准的女人都可以做你的老婆,反之也一样,所以,找到单身的原因没有

web框架的出现是为了让我们把精力更多地放在开发application上,有的web框架自己实现了高性能的server(比如tornado),有的web框架需要借助别人开发的高性能server=>uwsgi(比如django),之所以可以这么灵活,都要归功于wsgi协议

在开发过程中我们也无需关系wsgi协议,web框架都会按照wsgi协议为我们定制好application的基本功能,所以我们只需要关心最上层的应用程序的开发即可,有了web框架真是省了我们不少事,下面我们就来详细介绍一下django框架吧

 5 主要几个版本的支持时间

 django的版本我们推荐2.2.9LTS,所以搭配的python解释器版本,我们应该选择python3.5之后的解释器

pip3 install django==2.2.9 # 在命令执行命令
查看
pip3 show django
# 在命令执行以下指令,会在当前目录生成一个名为mysite的文件夹,该文件夹中包含Django框架的一系列基础文件
django-admin startproject mysite
cd mysite # 切换到mysite目录下,执行以下命令
python manage.py startapp app01 # 创建功能模块app01,此处的startapp代表创建application下的一个功能模块。例如我们要开发application是京东商城,京东商城这个大项目下有一个订单管理模块,我们可以将其命名为app01
python manage.py runserver 8001 # 在浏览器输入:http://127.0.0.1:8001 会看到Django的欢迎页面

 之前我们提过,django有多种版本,如果我们想不同版本的django都体验一下,怎么做呢,可以创建虚拟环境,在虚拟环境安装想要使用的django版本,如下

创建成功后,在虚拟环境中安装最新3.0.2版本的django即可 

mysite # 文件夹
*  ├── app01 # 文件夹
*  │  └── migrations # 文件夹**
*  │  └── admin.py
*  │  └── apps.py
*  │  └── models.py
*  │  └── tests.py
*  │  └── views.py
*  ├── mysite # 文件夹**
*  │  └── settings.py
*  │  └── urls.py
*  │  └── wsgi.py
*  └── templates # 文件夹

├── manage.py

 关键文件介绍

-manage.py---项目入口,执行一些命令
-项目名
*  -settings.py  全局配置信息
*  -urls.py    总路由,请求地址视图函数的映射关系
-app名字
*  -migrations  数据库迁移记录
*  -models.py   数据库模型
*  -views.py   处理业务逻辑的函数,简称视图函数

 4、基于Django实现的一个简单示例

(1)url.py 
from django.contrib import admin
from django.urls import path,re_path # 导入re_path
#导入views模块
from app01 import views

urlpatterns = [
    path('admin/', admin.site.urls), # 该行代码是新建项目时django框架自动生成的,暂且忽略,暂时注释掉也可以,随着一步步深入介绍,都会讲到它的专门用法
    
    # 新增地址http://127.0.0.1:8001/index/与index函数映射关系
    re_path(r'^index/$',views.index), # r'^index/$'会匹配url地址的路径部分
]
(2)视图
from django.shortcuts import render

# 必须定义一个request形参,request相当于我们自定义框架时的environ参数
def index(request):
    import datetime
    now=datetime.datetime.now()
    ctime=now.strftime("%Y-%m-%d %X")

    return render(request,"index.html",{"ctime":ctime}) # render会读取templates目录下的index.html文件的内容并且用字典中的ctime的值替换模版中的{{ ctime }}
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<h4>当前时间:{{ ctime }}</h4>

</body>
</html>
python manage.py runserver 8001 # 在浏览器输入:http://127.0.0.1:8001/index/ 会看到当前时间。
# M:Model模型负责业务对象数据库的映射(ORM),用于数据库打交道
# V:view视图负责用户的交互(html页面)
# C:Controller控制器接受用户的输入调用模型视图完成用户的请求

# Model(模型):负责业务对象数据库的对象(ORM)

# Template(模版):负责如何把页面展示用户

# View(视图):负责业务逻辑,并在适当的时候调用Model和Template

此外,Django还有一个urls分发器,它的作用是将一个个URL的页面请求分发给不同的view处理,
view再调用相应的Model和Template

 MTV模式与MVC本质上都是一样的,都是为了各组件保持松耦合关系,如下所示

 七、Django框架的分层与请求生命周期        

 

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注