web框架的原理

web框架的原理

web框架本质

我们可以这样理解:所有的Web应用本质上就是一个socket服务端,而用户的浏览器就是一个socket客户端。 这样我们就可以自己实现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(8096) conn.send(b"OK") conn.close()

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

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

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

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

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

让我们首先打印下我们在服务端接收到的消息是什么。

 

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

while True:  
    conn, addr = sk.accept()  
    data = conn.recv(8096)  
    print(data)     #将浏览器发来的消息打印出来
    conn.send(b"OK")  
    conn.close()  

输出:

b'GET / HTTP/1.1\r\nHost: 127.0.0.1:8080\r\nConnection: keep-alive\r\nCache-Control: max-age=0\r\nUpgrade-Insecure-Requests: 1\r\nUser-Agent: Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/66.0.3355.4 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\nCookie: csrftoken=CtHePYARJOKNx5oNVwxIteOJXpNyJ29L4bW4506YoVqFaIFFaHm0EWDZqKmw6Jm8\r\n\r\n'
输出

 

我们将\r\n替换成换行看得更清晰点:

GET / HTTP/1.1  
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.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/66.0.3355.4 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  
Cookie: csrftoken=CtHePYARJOKNx5oNVwxIteOJXpNyJ29L4bW4506YoVqFaIFFaHm0EWDZqKmw6Jm8  

 

然后我们再看一下我们访问博客园官网时浏览器收到的响应信息是什么。

响应相关信息可以在浏览器调试窗口的Network标签页中看到。

点击view source之后显示如下图:

 

 

 

 HTTP GET请求的格式:

 

 

 HTTP响应的格式:

 

自定义web框架

 经过上面的学习,那我们基于socket服务端的十几行代码写一个我们自己的web框架。我们先不处理浏览器发送的请求,先让浏览器能显示我们web框架返回的信息,那我们就要按照HTTP协议的格式来发送响应。

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\r\n\r\n")    
    conn.send(b"OK")    
    conn.close()    

 

 

 根据不同的路径返回不同的内容(函数版和普通版)

这样就结束了吗? 如何让我们的Web服务根据用户请求的URL不同而返回不同的内容呢? 

小事一桩,我们可以从请求相关数据里面拿到请求URL的路径,然后拿路径做一个判断...

"""
根据URL中不同的路径返回不同的内容--函数版
"""
import socket
sk = socket.socket()
sk.bind(("127.0.0.1", 8080))  # 绑定IP和端口
sk.listen(5)  # 监听

# 将返回不同的内容部分封装成函数
def func(url):
    s = "这是{}页面!".format(url)
    return bytes(s, encoding="utf8")

while True:
    # 等待连接
    conn, add = sk.accept()
    data = conn.recv(8096)  # 接收客户端发来的消息
    # 从data中取到路径
    data = str(data, encoding="utf8")  # 把收到的字节类型的数据转换成字符串
    # 按\r\n分割
    data1 = data.split("\r\n")[0]
    url = data1.split()[1]  # url是我们从浏览器发过来的消息中分离出的访问路径
    conn.send(b'HTTP/1.1 200 OK\r\n\r\n')  # 因为要遵循HTTP协议,所以回复的消息也要加状态行
    # 根据不同的路径返回不同内容,response是具体的响应体
    if url == "/index/":
        # response = b'index' #普通版
        response = func(url)
    elif url == "/home/":
        # response = b'home' #普通版
        response = func(url)
    else:
        response = b"404 not found!"

    conn.send(response)
    conn.close()

 

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

返回具体的HTML文件

""" 
根据URL中不同的路径返回不同的内容--函数进阶版 
"""  
  
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 True:  
    # 等待连接  
    conn, add = sk.accept()  
    data = conn.recv(8096)  # 接收客户端发来的消息  
    # 从data中取到路径  
    data = str(data, encoding="utf8")  # 把收到的字节类型的数据转换成字符串  
    # 按\r\n分割  
    data1 = data.split("\r\n")[0]  
    url = data1.split()[1]  # url是我们从浏览器发过来的消息中分离出的访问路径  
    conn.send(b'HTTP/1.1 200 OK\r\n\r\n')  # 因为要遵循HTTP协议,所以回复的消息也要加状态行  
    # 根据不同的路径返回不同内容  
    func = None  # 定义一个保存将要执行的函数名的变量  
    for item in list1:  
        if item[0] == url:  
            func = item[1]  
            break  
    if func:  
        response = func(url)  
    else:  
        response = b"404 not found!"  
  
    # 返回具体的响应消息  
    conn.send(response)  
    conn.close()  

 

 让网页动态起来

 这些网页能够显示出来了,但是都是静态的,页面的内容都不会变化,我想要的是动态网站

 可以使用字符串替换来实现这个需求.(这里使用时间戳来模拟动态的数据).

""" 
根据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")  
  
  
def timer(url):  
    import time  
    with open("time.html", "r", encoding="utf8") as f:  
        s = f.read()  
        s = s.replace('@@time@@', time.strftime("%Y-%m-%d %H:%M:%S"))  
    return bytes(s, encoding="utf8")  
  
  
# 定义一个url和实际要执行的函数的对应关系  
list1 = [  
    ("/index/", index),  
    ("/home/", home),  
    ("/time/", timer),  
]  
  
while True:  
    # 等待连接  
    conn, add = sk.accept()  
    data = conn.recv(8096)  # 接收客户端发来的消息  
    # 从data中取到路径  
    data = str(data, encoding="utf8")  # 把收到的字节类型的数据转换成字符串  
    # 按\r\n分割  
    data1 = data.split("\r\n")[0]  
    url = data1.split()[1]  # url是我们从浏览器发过来的消息中分离出的访问路径  
    conn.send(b'HTTP/1.1 200 OK\r\n\r\n')  # 因为要遵循HTTP协议,所以回复的消息也要加状态行  
    # 根据不同的路径返回不同内容  
    func = None  # 定义一个保存将要执行的函数名的变量  
    for item in list1:  
        if item[0] == url:  
            func = item[1]  
            break  
    if func:  
        response = func(url)  
    else:  
        response = b"404 not found!"  
  
    # 返回具体的响应消息  
    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

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

"""  
根据URL中不同的路径返回不同的内容--函数进阶版  
返回HTML页面  
让网页动态起来  
wsgiref模块版  
"""   
     
from wsgiref.simple_server import make_server   
     
     
# 将返回不同的内容部分封装成函数   
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")   
     
     
def timer(url):   
    import time   
    with open("time.html", "r", encoding="utf8") as f:   
        s = f.read()   
        s = s.replace('@@time@@', time.strftime("%Y-%m-%d %H:%M:%S"))   
    return bytes(s, encoding="utf8")   
     
     
# 定义一个url和实际要执行的函数的对应关系   
list1 = [   
    ("/index/", index),   
    ("/home/", home),   
    ("/time/", timer),   
]   
     
     
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()  

 

 

 jinja2

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

 下载jinja2:

pip install jinja2

使用jinja2渲染index2.html文件:

from wsgiref.simple_server import make_server  
from jinja2 import Template  
   
def index(url):  
    # 读取HTML文件内容  
    with open("index2.html", "r", encoding="utf8") as f:  
        data = f.read()  
        template = Template(data)   # 生成模板文件  
        ret = template.render({'name': 'alex', 'hobby_list': ['抽烟', '喝酒', '烫头']})   # 把数据填充到模板中  
    return bytes(ret, 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()  

 

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

 

 Django

命令下载

安装(安装最新LTS版)

pip3 install django==1.11.20

 

创建一个django项目

下面的命令创建了一个名为"mysite"的Django项目:

django-admin startproject mysite

 

运行Django项目:

python manage.py runserver 127.0.0.1:8000

 

 

 python中的下载

 先打开Settings设置

 

 

 

 

 

 

 

 

 

 

目录介绍

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

 

settings.py

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

 

posted on 2019-03-26 21:50  🐳️南栀倾寒🐳️  阅读(228)  评论(0编辑  收藏  举报

导航