Django之web框架原理

Web框架原理

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

先写一个

原始的web框架

import socket

sk = socket.socket()
sk.bind(('127.0.0.1', 8080))
sk.listen(5)

while True:
    conn, addr = sk.accept()
    data = conn.recv(1024)
    print(data)  # 打印浏览器发过来的消息并分析
    conn.send(b'ok')
    conn.close()

可以说Web服务本质上都是在这几行代码基础上扩展出来的。这段代码就是它们的祖宗。

用户的浏览器一输入网址,会给服务端发送数据,那浏览器会发送什么数据?怎么发?这个谁来定? 你这个网站是这个规定,他那个网站按照他那个规定,这互联网还能玩么?

所以,必须有一个统一的规则,让大家发送消息、接收消息的时候有个格式依据,不能随便写。

这个规则就是HTTP协议,以后浏览器发送请求信息也好,服务器回复响应信息也罢,都要按照这个规则来。

HTTP协议主要规定了客户端和服务器之间的通信格式,那HTTP协议是怎么规定消息格式的呢?

运行上面的代码,在浏览器输入服务器的地址和端口

得到浏览器发过来的data的打印结果:

# data结果
'''
1.请求首行:
b'GET / HTTP/1.1\r\n
2.请求体:一大堆K:V键值对
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 6.3; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/77.0.3865.120 Safari/537.36 chrome-extension\r\n
Sec-Fetch-Mode: navigate\r\n
Sec-Fetch-User: ?1\r\n
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3\r\n
Sec-Fetch-Site: cross-site\r\n
Accept-Encoding: gzip, deflate, br\r\n
Accept-Language: zh-CN,zh;q=0.9,en-GB;q=0.8,en;q=0.7\r\n
\r\n
3.请求体:
    这里是请求数据,get请求没有,post请求才有
'''

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

1.数据格式
get请求格式
请求首行(请求方式,协议版本。。。)
请求头(一大堆k:v键值对)
\r\n
请求体(真正的数据 发post请求的时候才有 如果是get请求不会有)
响应格式
响应首行
响应头
\r\n
响应体

HTTP GET请求的格式:

img

HTTP响应的格式:

img

这里就需要了解一下HTTP协议了。

HTTP协议介绍

HTTP协议对收发消息的格式要求

每个HTTP请求和响应都遵循相同的格式,一个HTTP包含Header和Body两部分,其中Body是可选的。 HTTP响应的Header中有一个 Content-Type表明响应的内容格式。如 text/html表示HTML网页。

HTTP协议特点:
超文本传输协议

1.四大特性
1.基于TCP/IP之上作用于应用层
2.基于请求响应
3.无状态 cookie session token...
4.无连接

2.响应状态码
用特定的数字表示一些意思
1XX:服务端已经成功接收到了你的数据 正在处理 你可以继续提交其他数据
2XX:服务端成功响应(200请求成功)
3XX:重定向
4XX:请求错误(404 请求资源不存在 403 拒绝访问)
5XX:服务器内部错误(500 )

自定义web框架完整版

如果我们想要自己写的web server服务端真正运行起来,达到一种请求与响应的对应关系,我们必须要在sercer服务端给客户端回复消息的时候,按照HTTP协议的规则加上响应状态行 ,就是 协议版本+状态码+状态描述符:b'HTTP/1.1 200 OK\r\n\r\n'

如下例子:

import socket

sk = socket.socket()
sk.bind(('127.0.0.1', 8080))
sk.listen(5)

while True:
    conn, addr = sk.accept()
    data = conn.recv(1024)
    print(data)

    # 需要向客服端发送响应头,客户端才能正常显示信息
    conn.send(b'HTTP/1.1 200 OK\r\n\r\n')
    conn.send(b'hello world')
    conn.close()

根据不同的路径返回不同的内容的Web服务端

如果我们在浏览器客户端输入:http://127.0.0.1:8080/home,浏览器的页面显示就为home,那么可以这样做:

import socket

sk = socket.socket()
sk.bind(('127.0.0.1', 8080)) # 绑定IP和端口
sk.listen(5) # 监听

while True:
    # 等待连接
    conn, addr = sk.accept()
    # 接收客户端返回的信息
    data = conn.recv(1024)
    # print(data)

    # 从data中取到路径 并将收到的字节类型的数据转换成字符串
    # data = str(data,encoding='utf8')
    data = data.decode('utf8')

    # 按\r\n分割
    data1 = data.split('\r\n')[0]
    # print(data1)
    # 请求首行的进行切割拿到url url是我们从浏览器发来的消息分离出来的访问路径
    url=data1.split(' ')[1]
    # print(url)

    # 必须遵循HTTP协议,需要向客服端发送状态行,客户端才能正常显示信息
    conn.send(b'HTTP/1.1 200 OK\r\n\r\n')

    # 根据不同的路径返回不同内容
    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(5) # 监听

# 将返回不同的内容部分封装成函数
def index(url):
    res = f'这是{url}页面|'
    return bytes(res,encoding='gbk')
    # return res.encode('utf-8')

def home(url):
    res = f'这是{url}页面|'
    return bytes(res,encoding='gbk')
    # return res.encode('utf-8')

while True:
    # 等待连接
    conn, addr = sk.accept()
    # 接收客户端返回的信息
    data = conn.recv(1024)
    # print(data)

    # 从data中取到路径 并将收到的字节类型的数据转换成字符串
    # data = str(data,encoding='utf8')
    data = data.decode('utf8')

    # 按\r\n分割
    data1 = data.split('\r\n')[0]
    # print(data1)
    # 请求首行的进行切割拿到url url是我们从浏览器发来的消息分离出来的访问路径
    url=data1.split(' ')[1]
    # print(url)



    # 根据不同的路径返回不同内容
    if url == '/index':
        response = index(url)
    elif url == '/home':
        response = home(url)
    else:
        response = b'404 not found!!!'

    # 必须遵循HTTP协议,需要向客服端发送状态行,客户端才能正常显示信息
    conn.send(b'HTTP/1.1 200 OK\r\n\r\n')
    conn.send(response)
    conn.close()

不同路径不同内容-函数进阶版

import socket

sk = socket.socket()
sk.bind(('127.0.0.1', 8080)) # 绑定IP和端口
sk.listen(5) # 监听

# 定义一个url和实际要执行的函数的对应关系

def home(url):
    res = bytes(url, encoding='utf8')
    return res

def index(url):
    res = bytes(url, encoding='utf8')
    return res


# 定义一个url和要执行函数对应关系的字典
dt = {
    '/index':index,
    '/home':home
}

while True:
    # 等待连接
    conn, addr = sk.accept()
    # 接收客户端返回的信息
    data = conn.recv(1024)
    # print(data)

    # 从data中取到路径 并将收到的字节类型的数据转换成字符串
    # data = str(data,encoding='utf8')
    data = data.decode('utf8')

    # 按\r\n分割
    data1 = data.split('\r\n')[0]
    # print(data1)
    # 请求首行的进行切割拿到url url是我们从浏览器发来的消息分离出来的访问路径
    url=data1.split(' ')[1]
    # print(url)

    func = None
    # 根据不同的路径返回不同内容
    for k,v in dt.items():
        print(k,v)
        if url == k:
            func = v
            break
    if func:
        response = func(url)
    else:
        response = b'404 not found!!!'

    # 必须遵循HTTP协议,需要向客服端发送状态行,客户端才能正常显示信息
    conn.send(b'HTTP/1.1 200 OK\r\n\r\n')
    conn.send(response)
    conn.close()

返回具体的HTML页面

首先创建我们需要的html页面,然后把在代码里面以rb模式读取出来,发送到浏览器

import socket

sk = socket.socket()
sk.bind(('127.0.0.1', 8080)) # 绑定IP和端口
sk.listen(5) # 监听

def home(url):
    with open('home页面.html','rb') as fr:
        res = fr.read()
    return res

def index(url):
    with open('index页面.html', 'rb') as fr:
        res = fr.read()
    return res


# 定义一个url和实际要执行的函数的对应关系
dt = {
    '/index':'index',
    '/home':'home'
}

while True:
    # 等待连接
    conn, addr = sk.accept()
    # 接收客户端返回的信息
    data = conn.recv(1024)
    # print(data)

    # 从data中取到路径 并将收到的字节类型的数据转换成字符串
    # data = str(data,encoding='utf8')
    data = data.decode('utf8')

    # 按\r\n分割
    data1 = data.split('\r\n')[0]
    # print(data1)
    # 请求首行的进行切割拿到url url是我们从浏览器发来的消息分离出来的访问路径
    url=data1.split(' ')[1]
    # print(url)

    func = None
    # 根据不同的路径返回不同内容
    for k,v in dt.items():
        print(k,v)
        if url == k:
            func = v
            break
    if func:
        response = func(url)
    else:
        response = b'404 not found!!!'

    # 必须遵循HTTP协议,需要向客服端发送状态行,客户端才能正常显示信息
    conn.send(b'HTTP/1.1 200 OK\r\n\r\n')
    conn.send(response)
    conn.close()

返回动态的网页

上面的网页是不会变化的,如何实现得到一个动态的网站呢?下面做个例子:每次刷新都在获取新的时间,模拟动态的数据

import socket
import datetime

sk = socket.socket()
sk.bind(('127.0.0.1', 8080)) # 绑定IP和端口
sk.listen(5) # 监听

def home(url):
    with open('get_time.html', 'r',encoding='utf8') as fr:
        res = fr.read()
        now = datetime.datetime.now().strftime("%Y-%m-%d %X")
        # 在网页中定义好特殊符号,用动态的数据替换特殊字符
        res = res.replace('*time*',now).encode('utf8')
    return res

def index(url):
    with open('index页面.html', 'rb') as fr:
        res = fr.read()
    return res


# 定义一个url和实际要执行的函数的对应关系
dt = {
    '/index': index,
    '/home': home
}

while True:
    # 等待连接
    conn, addr = sk.accept()
    # 接收客户端返回的信息
    data = conn.recv(1024)
    # print(data)

    # 从data中取到路径 并将收到的字节类型的数据转换成字符串
    # data = str(data,encoding='utf8')
    data = data.decode('utf8')

    # 按\r\n分割
    data1 = data.split('\r\n')[0]
    # print(data1)
    # 请求首行的进行切割拿到url url是我们从浏览器发来的消息分离出来的访问路径
    url=data1.split(' ')[1]
    # print(url)

    func = None
    # 根据不同的路径返回不同内容
    for k,v in dt.items():
        print(k,v)
        if url == k:
            func = v
            break
    if func:
        response = func(url)
    else:
        response = b'404 not found!!!'

    # 必须遵循HTTP协议,需要向客服端发送状态行,客户端才能正常显示信息
    conn.send(b'HTTP/1.1 200 OK\r\n\r\n')
    conn.send(response)
    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模块创建web服务器


from wsgiref.simple_server import make_server

def run(environ,response):
    # 当客户发送请求过来时,会先调用wsgi内部接口,然后调用run函数,并且携带了两个参数给run函数
    # environ:一个包含所有HTTP请求信息的dict对象;
    # response:一个发送HTTP响应的函数。

    # 向客户端发送的状态码和头信息
    response('200 OK',[('content-type','text/html; charset=utf-8'),])

    # 返回的是一个列表,内容是发送给客户端展示的内容
    return ['hello world'.encode('utf-8')]


if __name__ == '__main__':
    # 相当于socket绑定ip和端口
    server = make_server('127.0.0.1',8080,run)
    # 实时监听地址,等待客户端连接,有连接来了就调用run函数
    server.serve_forever()


wsgiref模块实现上所有述功能的服务端

from wsgiref.simple_server import make_server
import datetime

def index(url):
    with open('index页面.html', 'rb') as fr:
        data = fr.read()
    return data

def home(url):
    with open('home页面.html', 'rb') as fr:
        data = fr.read()
    return data

def get_time(url):
    
    now = datetime.datetime.now().strftime('%Y-%m-%d %X')
    with open('get_time.html','r',encoding='utf-8') as fr:
        data = fr.read()
    data = data.replace('*time*',now)
    return data.encode('utf-8')

dic={
    '/index':index,
    '/home':home,
    '/get_time':get_time
}

def run(env,response):
    # 发送状态码和头信息到客户端
    response('200 ok',[('content-type','text/html;charset=utf-8'),])

    # print(env)
    # 因为env就是客户端发过来的请求信息(k:v键值对形式),
    # 通过打印信息得出PATH_INFO就是请求的url,
    url = env.get('PATH_INFO')
    print(url)

    func = None
    if url in dic:
        func =  dic.get(url)

    if func:
        res = func(url)
    else:
        res = b'404 not found!!!'

    return [res]

if __name__ == '__main__':
    server = make_server('127.0.0.1',8080,run)
    server.serve_forever()

jinja2模块

jinja2模块,跟上面的用特殊符号去替换需要展示的内容的原理是一样的,jinja2他将html页面封装成一个可以渲染数据的模板,然后得到我们真正想要返回给浏览器的html页面。

例子:从数据库中获取数据展示到浏览器。

1.创建一张user表:

2.创建html文件

<!DOCTYPE html>
<html lang="en">
<head>
        <meta charset="UTF-8">
    <title>Title</title>
    <script src="https://cdn.bootcss.com/jquery/3.4.1/jquery.min.js"></script>
    <link href="https://cdn.bootcss.com/twitter-bootstrap/3.3.1/css/bootstrap.min.css" rel="stylesheet">
    <script src="https://cdn.bootcss.com/twitter-bootstrap/3.3.1/js/bootstrap.min.js"></script>
</head>
<body>
<div class="container">
    <div class="row">
        <div class="col-md-8 col-md-offset-2">
            <h1 class="text-center">用户列表</h1>
            <table class="table table-bordered table-striped table-hover">
                <thead>
                    <tr>
                        <th>id</th>
                        <th>name</th>
                        <th>pwd</th>
                    </tr>
                </thead>
                <tbody>   
                    				<!--user_list是渲染的数据 -->
                    {% for user_dict in user_list %}   
                    <tr>
                        <td>{{ user_dict.id }}</td>
                        <td>{{ user_dict.name }}</td>
                        <td>{{ user_dict.hobby}}</td>
                    </tr>
                    {% endfor %}
                </tbody>
            </table>
        </div>
    </div>
</div>

</body>
</html>

3.使用jinja2渲染html文件。

from wsgiref.simple_server import make_server
from jinja2 import Template
import pymysql


# 从数据库中获取,并使用jinja2将数据渲染到html
def get_db(url):
    conn = pymysql.connect(
        host='127.0.0.1',
        port=3306,
        user='tomjoy',
        password='123456',
        database='user_info',
        charset='utf8',
        autocommit=True
    )
    cursor = conn.cursor(cursor=pymysql.cursors.DictCursor)
    sql = "select * from user"
    cursor.execute(sql)

    # 1.从数据库中获取数据
    res = cursor.fetchall()
    with open('get_db.html','r',encoding='utf8') as fr:
        data = fr.read()

    # 2.生成html渲染模板对象
    temp = Template(data)

    # 3.将数据库中获取回来的数据,传到html模板对象进行渲染,
    # 返回一个我们真正想要展示的html页面
    ret = temp.render(user_list=res)
    return ret.encode('utf8')



dic = {
    '/get_db' : get_db
}

def run(env,response):
    response('200 ok',[('content-type','text/html;charset=utf-8'),])
    func = None
    url = env.get('PATH_INFO')
    if url in dic:
        func = dic.get(url)
    if func:
        res = func(url)
    else:
        res = b'404 not found!!!'

    return [res]



if __name__ == '__main__':
    server = make_server('127.0.0.1',8080,run)
    server.serve_forever()

jinja2模板语法(极其贴近python后端语法)

<p>{{ user }}</p>
		<p>{{ user.name }}</p>
		<p>{{ user['pwd'] }}</p>
		<p>{{ user.get('hobby') }}</p>
		
		
		{% for user_dict in user_list %}
			<tr>
				<td>{{ user_dict.id }}</td>
				<td>{{ user_dict.name }}</td>
				<td>{{ user_dict.pwd }}</td>
			</tr>
		{% endfor %}

模板渲染:利用模板语法 实现后端传递数据给前端html页面

模板语法书写格式:
变量相关:{{}}
逻辑相关:{%%}
注意:Django的模板语法由于是自己封装好的,只支持 点.取值

注:模板渲染的原理就是字符串替换,我们只要在HTML页面中遵循jinja2的语法规则写上,其内部就会按照指定的语法进行相应的替换,从而达到动态的返回内容。

效果如下:

Django

1.安装django

pip3 install django==1.11.11

2.创建django项目
在cmd命令行下创建一个名为mysite的Django项目

django-admin startproject mysite

3.目录介绍

mysite
├── manage.py  # Django入口管理文件
└── templates  # 存放html文件
└── mysite  # 项目目录
    ├── __init__.py
    ├── settings.py  # 配置
    ├── urls.py  # 路由 --> URL和函数的对应关系
    └── wsgi.py  # runserver命令就使用wsgiref模块做简单的web server

4.模板文件配置
使用命令行创建django项目 不会自动帮你创建templates文件夹, 只能自己创建
在.settings文件中 需要你手动在TEMPLATES的DIRS写配置
[os.path.join(BASE_DIR, 'templates')]

TEMPLATES = [
    {
        'BACKEND': 'django.template.backends.django.DjangoTemplates',
        'DIRS': [os.path.join(BASE_DIR, 'templates')], # templates 文件夹位置
        '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',
            ],
        },
    },
]

5.启动django项目

python manage.py runserver

6.创建应用app01

python manage.py startapp app01

7.app应用目录:

└── app01  # 项目目录
    ├── migrations文件夹  # 存放数据库迁移记录
    ├── __init__.py
    ├── admin.py    # django后台管理
    └── apps.py     # 注册相关
    └── models.py   # 模型类 
    └── tests.py    # 测试文件 
    └── views.py    # 存放视图函数 

注意:如果是在命令行下创建app后,需要你去settings配置文件中注册添加app名字。这样django项目才能识别到你这个app

8.静态文件配置:

静态文件配置官方文档

什么是静态文件?

静态文件就是在打开网页时所用到的 图片、 js、css以及第三方的框架bootstrap、fontawesome、sweetalert

通常情况下 网站所用到的静态文件资源 统一都放在static文件夹下,为了方便识别

STATIC_URL = '/static/'  # 是访问静态资源的接口前缀,并不是存放静态文件的文件夹
"""只要你想访问静态资源 你就必须以static开头"""


# 手动在settings最底下添加配置静态文件访问资源
# 下面都是存放静态文件的文件夹的路径
# 从上往下找静态文件,找不到就报错
STATICFILES_DIRS = [
    os.path.join(BASE_DIR,'static'), 
    os.path.join(BASE_DIR,'static1'), 
    os.path.join(BASE_DIR,'static2'),
]

图解:img

9.禁用中间件:

前期为了方便表单提交测试。在settings配置文件中暂时禁用csrf中间件

10.重定向:

​ 重定向的意思就是,我访问的链接不是我刚刚输入的那个链接,而是我一输入他就跳转到了另外一个链接,这就是重定向

最后注意事项:
1.计算机的名称不能有中文
2.一个pycharm窗口就是一个项目
3.项目名里面尽量不要用中文

django版本问题
1.X 2.X 现在市面上用的比较多的还是1.X
推荐使用1.11.9~1.11.13

django安装
pip3 install django==1.11.11

如何验证django是否安装成功
命令行直接敲django-admin

一个django项目就类似于是一所大学,而app就类似于大学里面的学院
django其实就是用来一个个应用的
一个app就相当于一块独立的功能
用户功能
管理功能
.........

​ django支持任意多个app

posted @ 2019-10-20 18:29  tomjoy  阅读(1789)  评论(2编辑  收藏  举报