zoukankan      html  css  js  c++  java
  • 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
    
    2.请求体:一大堆K:V键值对
    Host: 127.0.0.1:8080
    
    Connection: keep-alive
    
    Cache-Control: max-age=0
    
    Upgrade-Insecure-Requests: 1
    
    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
    
    Sec-Fetch-Mode: navigate
    
    Sec-Fetch-User: ?1
    
    Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3
    
    Sec-Fetch-Site: cross-site
    
    Accept-Encoding: gzip, deflate, br
    
    Accept-Language: zh-CN,zh;q=0.9,en-GB;q=0.8,en;q=0.7
    
    
    
    3.请求体:
        这里是请求数据,get请求没有,post请求才有
    '''
    

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

    1.数据格式
    get请求格式
    请求首行(请求方式,协议版本。。。)
    请求头(一大堆k:v键值对)

    请求体(真正的数据 发post请求的时候才有 如果是get请求不会有)
    响应格式
    响应首行
    响应头

    响应体

    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 '

    如下例子:

    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
    
    ')
        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')
    
        # 按
    分割
        data1 = data.split('
    ')[0]
        # print(data1)
        # 请求首行的进行切割拿到url url是我们从浏览器发来的消息分离出来的访问路径
        url=data1.split(' ')[1]
        # print(url)
    
        # 必须遵循HTTP协议,需要向客服端发送状态行,客户端才能正常显示信息
        conn.send(b'HTTP/1.1 200 OK
    
    ')
    
        # 根据不同的路径返回不同内容
        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')
    
        # 按
    分割
        data1 = data.split('
    ')[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
    
    ')
        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')
    
        # 按
    分割
        data1 = data.split('
    ')[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
    
    ')
        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')
    
        # 按
    分割
        data1 = data.split('
    ')[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
    
    ')
        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')
    
        # 按
    分割
        data1 = data.split('
    ')[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
    
    ')
        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

  • 相关阅读:
    洛谷P2602 [ZJOI2010]数字计数 题解
    数位DP模板
    The Meaningless Game 思维题
    CF55D Beautiful numbers 数位DP
    NOIP 2016 洛谷 P2827 蚯蚓 题解
    弹性碰撞问题:Ants+Linear world
    BZOJ1294 洛谷P2566 状态压缩DP 围豆豆
    朋友HDU
    树的深度———树形DP
    CF1292C Xenon's Attack on the Gangs 题解
  • 原文地址:https://www.cnblogs.com/guapitomjoy/p/11708467.html
Copyright © 2011-2022 走看看