Web框架的原理和Django初识

Posted 从入门到出门

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Web框架的原理和Django初识相关的知识,希望对你有一定的参考价值。

复制代码
一、Web框架的本质
1、本质
实际上Web应用本质上就是一个socket服务端,
而用户的浏览器就是一个socket客户端。 


2、最原始的web框架
socket服务端
import socket

sk = socket.socket()
sk.bind(("127.0.0.1", 80))
sk.listen()

while True:
    conn, addr = sk.accept()
    data = conn.recv(5120)
    print(data)  # 打印一下接收到的数据是什么
    conn.send(b"Hello World")
    conn.close()

上面这几行代码就是最基础的web框架的服务端,
用户使用浏览器输入网址(IP),就会给服务端发送请求消息,
比如你在浏览器输入上面服务器的ip地址   127.0.0.1:80  
浏览器就会帮你给服务端发送请求消息,服务端就可以接收到你的请求消息,
并对此可以做出相应的操作。

当然了,虽然上面的代码是web框架的本质,但是还不能进行消息的响应,为什么呢?
因为没有一个统一的规则啊,你想想如果每个人的网站都按照自己的意愿随便定制规则,
那浏览器不就崩了?

所以必须有一个统一的规则,让大家发送消息、接收消息的时候有个格式依据,不能随便写。
这个规则就是HTTP协议,以后浏览器发送请求信息也好,服务器回复响应信息也罢,都要按照这个规则来进行


现在去看一眼刚才收到的data数据是什么(客户端的请求数据)
b\'GET /index/ HTTP/1.1\\r\\nHost: 127.0.0.1:8080\\r\\nConnection: keep-alive\\r\\nUpgrade-Insecure-Requests: 1\\r\\nUser-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (Khtml, like Gecko) Chrome/69.0.3497.92 Safari/537.36\\r\\nAccept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8\\r\\nAccept-Encoding: gzip, deflate, br\\r\\nAccept-Language: zh-CN,zh;q=0.9\\r\\n\\r\\n\'


然后可以再看一眼平时我们访问百度时浏览器接收到的响应信息
HTTP/1.1 301 Moved Permanently
Date: Wed, 24 Oct 2018 07:06:00 GMT
Content-Type: text/html; charset=UTF-8
Content-Length: 147
Location: https://www.cnblogs.com/
X-UA-Compatible: IE=10
X-Frame-Options: SAMEORIGIN


这样看起来,好像找不出什么规则,那就先了解一下HTTP协议(上一篇博客),在回来看看是否发现什么规则


HTTP协议对收发消息的格式要求: 每个HTTP请求和响应都遵循相同的格式,一个HTTP包含Header和Body两部分,其中Body是可选的(就是可以不写)。 HTTP响应的Header中有一个 Content
-Type表明响应的内容格式。如 text/html表示HTML网页。 那么上面的请求数据data,我们也可以给它分出Header和Body两部分(\\r\\n代表回车换行),如下: GET /index/ 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/69.0.3497.92 Safari/537.36 Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8 Accept-Encoding: gzip, deflate, br Accept-Language: zh-CN,zh;q=0.9 请求数据 所以我们想要开始那段代码能顺利给客户端返回消息,我们也按照HTTP协议进行发送就好了 import socket sk = socket.socket() sk.bind((\'127.0.0.1\',80)) sk.listen() while True: conn,addr = sk.accept() data = conn.recv(5120) print(data) conn.send(b\'HTTP/1.1 200 OK\\r\\n\\r\\nHello World\') # HTTP/1.1 200 OK\\r\\n\\r\\n 是响应头部为空,响应正文为Hello World的HTTP响应格式 conn.close() 这个时候你在浏览器输入127.0.0.1:80就可以收到服务端给你返回的消息了 3、服务器例子 1、根据不同的路径返回不同的内容 注意:我们服务器接收到的请求数据全是bytes类型的,回车换行是用\\r\\n表示的 原始请求数据:b\'GET /index/ HTTP/1.1\\r\\nHost: 127.0.0.1:8080\\r\\nConnection: keep-alive\\r\\nUpgrade-Insecure-Requests: 1\\r\\nUser-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.92 Safari/537.36\\r\\nAccept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8\\r\\nAccept-Encoding: gzip, deflate, br\\r\\nAccept-Language: zh-CN,zh;q=0.9\\r\\n\\r\\n\' """ Web框架原理: 根据不同的URL返回不同的内容 1. 先拿到用户访问的URL是什么 2. 返回不同的内容 """ import socket sk = socket.socket() sk.bind((\'127.0.0.1\',8080)) sk.listen() while 1: conn, addr = sk.accept() # socket收到的消息是bytes类型的 data = conn.recv(5120) # 从请求的消息中拿到请求的URL是什么 data_str = data.decode(\'utf8\') # 按照\\r\\n分割字符串 list1 = data_str.split(\'\\r\\n\') # url在第一个元素里面再进行分割取到 url = list1[0].split()[1] # 对不同的url返回不同的消息 if url == \'/index/\': msg = \'This is index page\' elif url == \'/home/\': msg = \'This is home page\' else: msg = \'404 Not Found\' conn.send(b\'HTTP/1.1 200 OK\\r\\n\\r\\n\') # 先发送响应行 conn.send(msg.encode(\'utf8\')) conn.close() 此时你在浏览器输入: http://127.0.0.1:8080 ---> 404 Not Found http://127.0.0.1:8080/index/ ---> This is index page http://127.0.0.1:8080/home/ ---> This is home page 2、根据不同的路径返回不同的内容函数版 """ Web框架原理: 根据不同的URL返回不同的内容函数版 1. 先拿到用户访问的URL是什么 2. 返回不同的内容 """ import socket sk = socket.socket() sk.bind((\'127.0.0.1\',8080)) sk.listen() # 定义处理用户请求的函数 def index(url): s = \'你访问的是%s页面\' %url return s.encode(\'utf8\') def home(url): s = \'你访问的是%s页面\' %url return s.encode(\'utf8\') while 1: conn, addr = sk.accept() # socket收到的消息是bytes类型的 data = conn.recv(5120) # 从请求的消息中拿到请求的URL是什么 data_str = data.decode(\'utf8\') # 按照\\r\\n分割字符串 list1 = data_str.split(\'\\r\\n\') # url在第一个元素里面再进行分割取到 url = list1[0].split()[1] # 对不同的url返回不同的消息 if url == \'/index/\': msg = index(url) elif url == \'/home/\': msg = home(url) else: msg = b\'404 Not Found\' # 因为有中文,所有要在响应头部添加Content-Type: text/html; charset=utf-8 conn.send(b\'HTTP/1.1 200 OK\\r\\nContent-Type: text/html; charset=utf-8\\r\\n\\r\\n\') # 先发送响应行 conn.send(msg) conn.close() 3、根据不同的路径返回不同的内容函数进阶版 """ Web框架原理: 根据不同的URL返回不同的内容函数进阶版 1. 先拿到用户访问的URL是什么 2.设置一个url和函数的对应关系 3. 根据对应关系返回不同的内容 """ import socket sk = socket.socket() sk.bind((\'127.0.0.1\',8080)) sk.listen() # 定义处理用户请求的函数 def index(url): s = \'你访问的是%s页面\' %url return s.encode(\'utf8\') def home(url): s = \'你访问的是%s页面\' %url return s.encode(\'utf8\') # 定义一个用户访问的url和要执行的函数的对应关系 url_func = [ (\'/index/\', index), (\'/home/\', home) ] while 1: conn, addr = sk.accept() # socket收到的消息是bytes类型的 data = conn.recv(5120) # 从请求的消息中拿到请求的URL是什么 data_str = data.decode(\'utf8\') # 按照\\r\\n分割字符串 list1 = data_str.split(\'\\r\\n\') # url在第一个元素里面再进行分割取到 url = list1[0].split()[1] # 循环url列表,对不同的url返回不同的消息 func = None for i in url_func: if i[0] == url: func = i[1] break if func: msg = func(url) else: msg = b\'404 Not Found\' # 因为有中文,所有要在响应头部添加Content-Type: text/html; charset=utf-8 conn.send(b\'HTTP/1.1 200 OK\\r\\nContent-Type: text/html; charset=utf-8\\r\\n\\r\\n\') # 先发送响应行 conn.send(msg) conn.close() 4、返回具体的html页面 import socket sk = socket.socket() sk.bind((\'127.0.0.1\',8080)) sk.listen() # 定义处理用户请求的函数 def index(url): s = \'你访问的是%s页面\' %url return s.encode(\'utf8\') def home(url): s = \'你访问的是%s页面\' %url return s.encode(\'utf8\') def table(url): # 返回TableOp.html with open(\'TableOp.html\', mode=\'rb\') as f: return f.read() # 定义一个用户访问的url和要执行的函数的对应关系 url_func = [ (\'/index/\', index), (\'/home/\', home), (\'/table/\',table) ] while 1: conn, addr = sk.accept() # socket收到的消息是bytes类型的 data = conn.recv(5120) # 从请求的消息中拿到请求的URL是什么 data_str = data.decode(\'utf8\') # 按照\\r\\n分割字符串 list1 = data_str.split(\'\\r\\n\') # url在第一个元素里面再进行分割取到 url = list1[0].split()[1] # 循环url列表,对不同的url返回不同的消息 func = None for i in url_func: if i[0] == url: func = i[1] break if func: msg = func(url) else: msg = b\'404 Not Found\' # 因为有中文,所有要在响应头部添加Content-Type: text/html; charset=utf-8 conn.send(b\'HTTP/1.1 200 OK\\r\\nContent-Type: text/html; charset=utf-8\\r\\n\\r\\n\') # 先发送响应行 conn.send(msg) conn.close() 5、返回动态的html页面 """ 因为时间是会动的,所以这里用时间戳代表动态的事件 """
<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>

<h1>这是index页面!</h1>
<p>现在的时间是:@t@</p>

</body>
</html>
index页面代码
import time
import socket
sk = socket.socket()
sk.bind((\'127.0.0.1\',8080))
sk.listen()


# 定义处理用户请求的函数
def index(url):
    with open(\'index.html\', \'r\', encoding=\'utf8\') as f1:
        html_s = f1.read()
        # 根据用户不同,取出不同的数据
        # 用不同的数据去替换页面上的特殊符号
        now = str(time.strftime("%H:%M:%S"))
        msg = html_s.replace(\'@t@\', now)
        return msg.encode(\'utf8\')


def home(url):
    s = \'你访问的是%s页面\' %url
    return s.encode(\'utf8\')


def table(url):
    # 返回TableOp.html
    with open(\'TableOp.html\', mode=\'rb\') as f:
        return f.read()


# 定义一个用户访问的url和要执行的函数的对应关系
url_func = [
    (\'/index/\', index),
    (\'/home/\', home),
    (\'/table/\', table)
]


while 1:
    conn, addr = sk.accept()
    # socket收到的消息是bytes类型的
    data = conn.recv(5120)
    # 从请求的消息中拿到请求的URL是什么
    data_str = data.decode(\'utf8\')
    # 按照\\r\\n分割字符串
    list1 = data_str.split(\'\\r\\n\')
    # url在第一个元素里面再进行分割取到
    url = list1[0].split()[1]

    # 循环url列表,对不同的url返回不同的消息
    func = None
    for i in url_func:
        if i[0] == url:
            func = i[1]
            break

    if func:
        msg = func(url)
    else:
        msg = b\'404 Not Found\'

    # 因为有中文,所有要在响应头部添加Content-Type: text/html; charset=utf-8
    conn.send(b\'HTTP/1.1 200 OK\\r\\nContent-Type: text/html; charset=utf-8\\r\\n\\r\\n\')  # 先发送响应行
    conn.send(msg)
    conn.close()




二、服务器程序和应用程序
1、本质介绍
对于真实开发中的python web程序来说,一般会分为两部分:服务器程序和应用程序,然后通过一个协议连接起来,如下:
    收发socket消息(Web服务器程序)  --> uWsgi、Gunicorn、wsgiref (nginx和tomcat)
                        WSGI协议
    业务逻辑不同(Web应用程序)      --> Django、Flask、Webpy、bottle、Tornado
    
服务器程序负责对socket服务器进行封装,并在请求到来时,对请求的各种数据进行整理。
应用程序则负责具体的逻辑处理。为了方便应用程序的开发,就出现了众多的Web框架,例如:Django、Flask、web.py 等。不同的框架有不同的开发方式,但是无论如何,开发出的应用程序都要和服务器程序配合,才能为用户提供服务。
这样,服务器程序就需要为不同的框架提供不同的支持。这样混乱的局面无论对于服务器还是框架,都是不好的。对服务器来说,需要支持各种不同框架,对框架来说,只有支持它的服务器才能被开发出的应用使用。
这时候,标准化就变得尤为重要。我们可以设立一个标准,只要服务器程序支持这个标准,框架也支持这个标准,那么他们就可以配合使用。一旦标准确定,双方各自实现。这样,服务器可以支持更多支持标准的框架,框架也可以使用更多支持标准的服务器。
WSGI(Web Server Gateway Interface)就是一种规范,它定义了使用Python编写的web应用程序与web服务器程序之间的接口格式,实现web应用程序与web服务器程序间的解耦。
常用的WSGI服务器有uwsgi、Gunicorn。而Python标准库提供的独立WSGI服务器叫wsgiref,Django开发环境用的就是这个模块来做服务器。


Python中Web框架的分类
    a. 收发socket消息
    b. 根据不同的URL执行不同的函数(业务逻辑)
    c. 字符串替换(动态网页)

    1. 第一种分类:(按照上面的三部分功能划分)
        1. 自己实现b和c,使用第三方的a      --> Django
        2. 自己实现b,使用第三方的a和c      --> Flask
        3. 自己实现a、b、c                           --> Tornado
    2. 第二种分类:
        1. Django(大而全)
        2. 其他


        
        
2、例子
1、wsgiref版web开发
"""
利用wsgiref模块来替换我们自己写的web框架的socket server部分
"""

import time
from wsgiref.simple_server import make_server


# 定义处理用户请求的函数
def index(url):
    with open(\'index.html\', \'r\', encoding=\'utf8\') as f1:
        html_s = f1.read()
        # 根据用户不同,取出不同的数据
        # 用不同的数据去替换页面上的特殊符号
        now = str(time.strftime("%H:%M:%S"))
        msg = html_s.replace(\'@t@\', now)
        return msg.encode(\'utf8\')


def home(url):
    s = \'你访问的是%s页面\' %url
    return s.encode(\'utf8\')


def table(url):
    # 返回TableOp.html
    with open(\'TableOp.html\', mode=\'rb\') as f:
        return f.read()


# 定义一个用户访问的url和要执行的函数的对应关系
url_func = [
    (\'/index/\', index),
    (\'/home/\', home),
    (\'/table/\', table)
]

# wsgiref模块的格式要求
def run_server(environ, start_response):
    start_response(\'200 OK\', [(\'Content-Type\', \'text/html;charset=utf8\'), ])  # 设置HTTP响应的状态码和头信息
    url = environ[\'PATH_INFO\']  # 取到用户输入的url
    # 循环url列表,对不同的url返回不同的消息
    func = None
    for i in url_func:
        if i[0] == url:
            func = i[1]
            break

    if func:
        msg = func(url)
    else:
        msg = b\'404 Not Found\'

    return [msg]


if __name__ == \'__main__\':
    httpd = make_server(\'127.0.0.1\', 8080, run_server)
    httpd.serve_forever()



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

下载jinja2:在cmd命令行输入    pip install jinja2   安装第三方包
""" 利用wsgiref和jinja2模块动态渲染userinfo页面 """
<!DOCTYPE html>
<html lang="zh-CN">
<head>
  <title>用户列表</title>
</head>
<body>
    <table border="1">
        <thead>
            <tr>
                <th>id值</th>
                <th>姓名</th>
                <th>密码</th>
            </tr>
        </thead>
        <tbody>
            {% for user in user_dict %}
                <tr>
                    <td>{{user.id}}</td>
                    <td>{{user.username}}</td>
                    <td>{{user.password}}</td>
                </tr>
            {% endfor %}
        </tbody>
    </table>

</body>
</html>
userinfo代码

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


# 定义处理用户请求的函数
def index(url):
    with open(\'index.html\', \'r\', encoding=\'utf8\') as f1:
        html_s = f1.read()
        # 根据用户不同,取出不同的数据
        # 用不同的数据去替换页面上的特殊符号
        now = str(time.strftime("%H:%M:%S"))
        msg = html_s.replace(\'@t@\', now)
        return msg.encode(\'utf8\')


def home(url):
    s = \'你访问的是%s页面\' %url
    return s.encode(\'utf8\')


def table(url):
    # 返回TableOp.html
    with open(\'TableOp.html\', mode=\'rb\') as f:
        return f.read()


def userinfo(url):
    # 1. 连接数据库,把所有的用户数据拿到
    conn = pymysql.connect(
        host=\'127.0.0.1\',
        port=3306,
        user=\'root\',
        password=\'123abc\',
        database=\'userinfo\',
        charset=\'utf8\'
    )
    cursor = conn.cursor(cursor=pymysql.cursors.DictCursor)
    cursor.execute(\'select * from userinfo;\')
    # 从数据库中拿到的数据user_dict
    user_dict = cursor.fetchall()
    with open(\'userinfo.html\',\'r\',encoding=\'utf8\') as f:
        data = f.read()
    # 读取网页内容,生成jinja2模板对象
    template = Template(data)
    # 让jinja2用数据替换HTML中的特殊符号,拿到新的html内容并返回
    msg = template.render({\'user_dict\':user_dict})
    return bytes(msg, encoding="utf8")



# 定义一个用户访问的url和要执行的函数的对应关系
url_func = [
    (\'/index/\', index),
    (\'以上是关于Web框架的原理和Django初识的主要内容,如果未能解决你的问题,请参考以下文章

初识Django —Python API接口编程入门

初识Django

初识django

Django框架-Django初识

Django初识(web框架,django下载安装,http协议)

Django——纯手撸简易web框架Django初识