15 . PythonWeb框架本质

PythonWeb框架的本质

简单描述就是:浏览器通过你输入的网址给你的socket服务端发送请求,服务端接受到请求给其回复一个对应的html页面,这就是web项目。所有的Web应用本质上就是一个socket服务端,而用户的浏览器就是一个socket客户端,基于请求做出响应,客户都先请求,服务端做出对应的响应,按照http协议的请求协议发送请求,服务端按照http协议的响应协议来响应请求,这样的网络通信,我们就可以自己实现Web框架了。

5

什么是web框架?这就好比建设房子,房子主体钢结构等都为我们搭建好了,我们就是抹抹墙面,添加一些装饰,修饰一下即可。Django框架就是已经为我们搭建好的主题钢结构,剩下的根据不同的业务自定制即可。我们先自己搭建一个web框架,再对Django框架就会比较好理解了。

构建Socket服务器
import socket
server = socket.socket()
server.bind(('127.0.0.1', 8002))
server.listen()

while 1:
    conn, addr = server.accept()
    while 1:
        client_data = conn.recv(1024).decode('utf-8')
        print(client_data)
        # 服务端与客户端建立联系必须要遵循一个协议,此时我们用http协议示例。
        conn.send('HTTP/1.1 200 OK \r\n\r\n'.encode('utf-8'))
        conn.send('

hello

'.encode('utf-8')) conn.close()

我们通过浏览器请求服务端,服务端给我们返回一个hello标签,客户请求过来之后,要想让服务端给客户端返回消息,必须基于一个协议,我们用http协议示例,那么服务端如果如何返回给浏览器一个页面尼?

构建一个html页面返回


    
        
        
        

        
    


    
你好,世界

此时我们的服务端必须将html页面返回给客户端,可以通过读取文件然后发送bytes数据

服务端

import socket
server = socket.socket()
server.bind(('127.0.0.1',8002))
server.listen()

while 1:
    conn,addr = server.accept()
    while 1:
        client_data = conn.recv(1024).decode('utf-8')
        print(client_data)

        # 服务端与客户端建立联系必须要遵循一个协议,此时我们用http协议示例
        conn.send('HTTP/1.1 200 OK \r\n\r\n'.encode('utf-8'))

        with open('Day11/index.html',mode='rb') as f1:
            conn.send(f1.read())
    conn.close()

我们浏览器访问服务端会发现我们服务器会接受以下信息

GET / HTTP/1.1
Host: 127.0.0.1:8002
Connection: keep-alive
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/80.0.3987.162 Safari/537.36
Sec-Fetch-Dest: document
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9
Sec-Fetch-Site: none
Sec-Fetch-Mode: navigate
Sec-Fetch-User: ?1
Accept-Encoding: gzip, deflate, br
Accept-Language: zh-CN,zh;q=0.9,en;q=0.8
http协议工作原理

以下是HTTP请求/响应的步骤

# 1. 客户端连接到Web服务器
	# 一个HTTP客户端,通常是浏览器,与Web服务器的HTTP端口(默认为80)建立一个TCP套接字连接。例如,http://www.baidu.com
    
# 2. 发送HTTP请求
	# 通过TCP套接字,客户端向Web服务器发送一个文本的请求报文,一个请求报文由请求行、请求头部、空行和请求数据4部分组成。
    
# 3. 服务器接收请求并返回HTTP响应
	# Web服务器解析请求,定位请求资源。服务器将资源复本写到TCP套接字,由客户端读取。一个响应由状态行、响应头部、空行和响应数据4部分组成。
    
# 4. 释放连接TCP连接
	# 若connection 模式为close,则服务器主动关闭TCP连接,客户端被动关闭连接,释放TCP连接;若connection 模式为keepalive,则该连接会保持一段时间,在该时间内可以继续接收请求;
    
# 5. 客户端浏览器解析HTML内容
	# 客户端浏览器首先解析状态行,查看表明请求是否成功的状态代码。然后解析每一个响应头,响应头告知以下为若干字节的HTML文档和文档的字符集。客户端浏览器读取响应数据HTML,根据HTML的语法对其进行格式化,并在浏览器窗口中显示。
    
    
# 如果问浏览器输入URL,按下回车经历以下流程
# 1. 浏览器向 DNS 服务器请求解析该 URL 中的域名所对应的 IP 地址;
# 2. 解析出 IP 地址后,根据该 IP 地址和默认端口 80,和服务器建立TCP连接;
# 3. 浏览器发出读取文件(URL 中域名后面部分对应的文件)的HTTP 请求,该请求报文作为 TCP 三次握手的第三个报文的数据发送给服务器;
# 4. 服务器对浏览器请求作出响应,并把对应的 html 文本发送给浏览器;
# 5. 释放 TCP连接;
# 6. 浏览器将该 html 文本并显示内容;  
常用的HTTP方法

HTTP/1.1协议中共定义了八种方法(也叫“动作”)来以不同方式操作指定的资源:

# GET
# 向指定的资源发出“显示”请求。使用GET方法应该只用在读取数据,而不应当被用于产生“副作用”的操作中,例如在Web Application中。其中一个原因是GET可能会被网络蜘蛛等随意访问。

# HEAD
# 与GET方法一样,都是向服务器发出指定资源的请求。只不过服务器将不传回资源的本文部分。它的好处在于,使用这个方法可以在不必传输全部内容的情况下,就可以获取其中“关于该资源的信息”(元信息或称元数据)。

# POST
# 向指定资源提交数据,请求服务器进行处理(例如提交表单或者上传文件)。数据被包含在请求本文中。这个请求可能会创建新的资源或修改现有资源,或二者皆有。

# PUT
# 向指定资源位置上传其最新内容。

# DELETE
# 请求服务器删除Request-URI所标识的资源。

# TRACE
# 回显服务器收到的请求,主要用于测试或诊断。

# OPTIONS
# 这个方法可使服务器传回该资源所支持的所有HTTP请求方法。用'*'来代替资源名称,向Web服务器发送OPTIONS请求,可以测试服务器功能是否正常运作。

# CONNECT
# HTTP/1.1协议中预留给能够将连接改为管道方式的代理服务器。通常用于SSL加密服务器的链接(经由非加密的HTTP代理服务器)。

# 注意事项:
# 方法名称是区分大小写的。当某个请求所针对的资源不支持对应的请求方法的时候,服务器应当返回状态码405(Method Not Allowed),当服务器不认识或者不支持对应的请求方法的时候,应当返回状态码501(Not Implemented)。

# HTTP服务器至少应该实现GET和HEAD方法,其他方法都是可选的。当然,所有的方法支持的实现都应当匹配下述的方法各自的语义定义。此外,除了上述方法,特定的HTTP服务器还能够扩展自定义的方法。例如PATCH(由 RFC 5789 指定的方法)用于将局部修改应用到资源。
 

# 请求方式: get与post请求(通过form表单我们自己写写看)

# GET提交的数据会放在URL之后,也就是请求行里面,以?分割URL和传输数据,参数之间以&相连,如EditBook?name=test1&id=123456.(请求头里面那个content-type做的这种参数形式,后面讲) POST方法是把提交的数据放在HTTP包的请求体中.

# GET提交的数据大小有限制(因为浏览器对URL的长度有限制),而POST方法提交的数据没有限制.
# GET与POST请求在服务端获取请求数据方式不同,就是我们自己在服务端取请求数据的时候的方式不同了,这句废话昂。
 
HTTP状态码
# 状态代码的第一个数字代表当前响应的类型:

# 1xx消息——请求已被服务器接收,继续处理
# 2xx成功——请求已成功被服务器接收、理解、并接受
# 3xx重定向——需要后续操作才能完成这一请求
# 4xx请求错误——请求含有词法错误或者无法被执行
# 5xx服务器错误——服务器在处理某个正确请求时发生错误

构建Web框架

简单版Web框架

test1.html




    
    

    

欢迎访问xx商城
  • aa
  • bb
  • cc

test1.py

import socket
server = socket.socket()
server.bind(('127.0.0.1',8001))
server.listen()

while 1:
    conn,addr = server.accept()
    client_data = conn.recv(1024).decode('utf-8')
    print(client_data)
    # 服务端与客户端建立联系必须要遵循一个协议,此时我们用http协议示例
    conn.send('HTTP/1.1 200 OK \r\nk1:v1\r\n\r\n'.encode('utf-8'))
    with open('test1.html',mode='rb') as f1:
        conn.send(f1.read())
    conn.close()
升级版Web框架

html




    
    
    


欢迎访问和软商城
  • aa
  • bb
  • cc
美女

浏览器执行到link标签时,href会发出一个请求,请求你当前根目录下面的test2.css文件,其实完整url也就是http://127.0.0.1:8001/test2.css,你引入js同理,此过程是异步的,html代码不会等你引入完css之后再执行下面代码

test2.css

div {
    background-color: #7fb8ff;
    font-size: 16px;
}

test2.js

alert('未满18岁禁止入内');

test2.py

import socket
server = socket.socket()
server.bind(('127.0.0.1',8001))
server.listen()

while 1:
    conn,addr = server.accept()
    client_data = conn.recv(1024)
    request_path = client_data.decode('utf-8').split('\r\n')[0].split()[1]
    # 服务端与客户端建立联系必须要遵循一个协议,此时我们用http协议示例
    conn.send('HTTP/1.1 200 OK \r\nk1:v1\r\n\r\n'.encode('utf-8'))
    if request_path == '/':
        with open('test2.html',mode='rb') as f1:
            conn.send(f1.read())
    elif request_path == '/test2.css':
        with open('test2.css',mode='rb') as f1:
            conn.send(f1.read())
    elif request_path == '/2.jpg':
        with open('2.jpg',mode='rb') as f1:
            conn.send(f1.read())
    elif request_path == '/test2.js':
        with open('test2.js',mode='rb') as f1:
            conn.send(f1.read())
    conn.close()
函数版Web框架

上一个版本比较Low,并且代码应该整合成函数而不能使用纯面向过程方式,所以我们进行改版

test3.html

css,js,img都用上个版本的




    
    
    


欢迎访问和软商城
  • aa
  • bb
  • cc
美女

test3.py

import socket

server = socket.socket()
server.bind(('127.0.0.1', 8001))
server.listen()


def html(conn):
    with open('test3.html', mode='rb') as f1:
        conn.send(f1.read())
        conn.close()


def css(conn):
    with open('test2.css', mode='rb') as f1:
        conn.send(f1.read())
        conn.close()


def jpg(conn):
    with open('2.jpg', mode='rb') as f1:
        conn.send(f1.read())
    conn.close()


def js(conn):
    with open('test2.js', mode='rb') as f1:
        conn.send(f1.read())
        conn.close()


request_list = [
    ('/', html),
    ('/test2.css', css),
    ('/2.jpg', jpg),
    ('/test2.js', js),
]

while 1:
    conn, addr = server.accept()
    client_data = conn.recv(1024)
    request_path = client_data.decode('utf-8').split('\r\n')[0].split()[1]
    # 服务端与客户端建立联系必须要遵循一个协议,此时我们用http协议示例。
    conn.send('HTTP/1.1 200 OK \r\nk1:v1\r\n\r\n'.encode('utf-8'))
    for i in request_list:
        if request_path == i[0]:
            i[1](conn)
    conn.close()

虽然这个版本简介明了,但是还不完美,现在虽然是异步处理请求,但是我们可以通过并发让异步请求用并发处理更加合理

并发版Web框架

test4.html




    
    
    


欢迎访问和软商城
  • aa
  • bb
  • cc
美女

test4.py

import socket
from threading import  Thread

server = socket.socket()
server.bind(('127.0.0.1', 8001))
server.listen()


def html(conn):
    with open('test3.html', mode='rb') as f1:
        conn.send(f1.read())
        conn.close()


def css(conn):
    with open('test2.css', mode='rb') as f1:
        conn.send(f1.read())
        conn.close()


def jpg(conn):
    with open('2.jpg', mode='rb') as f1:
        conn.send(f1.read())
    conn.close()


def js(conn):
    with open('test2.js', mode='rb') as f1:
        conn.send(f1.read())
        conn.close()


request_list = [
    ('/', html),
    ('/test2.css', css),
    ('/2.jpg', jpg),
    ('/test2.js', js),
]

while 1:
    conn, addr = server.accept()
    client_data = conn.recv(1024)
    request_path = client_data.decode('utf-8').split('\r\n')[0].split()[1]
    print(request_path)
    # 服务端与客户端建立联系必须要遵循一个协议,此时我们用http协议示例。
    conn.send('HTTP/1.1 200 OK \r\nk1:v1\r\n\r\n'.encode('utf-8'))
    for i in request_list:
        if request_path == i[0]:
            t = Thread(target=i[1],args=(conn,))
            t.start()
    # conn.close()
动态版Web框架

一般情况下,我们的数据都是在数据库中,动态获取,实时变化,但现在我们模拟一下数据,利用时间戳

test5.html




    
    
    


欢迎访问和软商城
  • aa
  • bb
  • cc
美女

test5.py

import socket
from threading import  Thread
import time

server = socket.socket()
server.bind(('127.0.0.1', 8001))
server.listen()


def html(conn):
    time_now = time.strftime('%Y/%m/%d %H:%M:%S',time.localtime())
    with open('test5.html',encoding='utf-8') as f1:
        data = f1.read().format(time_now=time_now)
    conn.send(data.encode('utf-8'))
    conn.close()


def css(conn):
    with open('test2.css', mode='rb') as f1:
        conn.send(f1.read())
        conn.close()


def jpg(conn):
    with open('2.jpg', mode='rb') as f1:
        conn.send(f1.read())
    conn.close()


def js(conn):
    with open('test2.js', mode='rb') as f1:
        conn.send(f1.read())
        conn.close()


request_list = [
    ('/', html),
    ('/test2.css', css),
    ('/2.jpg', jpg),
    ('/test2.js', js),
]

while 1:
    conn, addr = server.accept()
    client_data = conn.recv(1024)
    request_path = client_data.decode('utf-8').split('\r\n')[0].split()[1]
    print(request_path)
    # 服务端与客户端建立联系必须要遵循一个协议,此时我们用http协议示例。
    conn.send('HTTP/1.1 200 OK \r\nk1:v1\r\n\r\n'.encode('utf-8'))
    for i in request_list:
        if request_path == i[0]:
            t = Thread(target=i[1],args=(conn,))
            t.start()
    # conn.close()
wsgiref模板版+数据库Web框架

wsgiref模块其实就是将整个请求信息给封装了起来,就不需要你自己处理了,假如它将所有请求信息封装成了一个叫做request的对象,那么你直接request.path就能获取到用户这次请求的路径,request.method就能获取到本次用户请求的请求方式(get还是post)等,那这个模块用起来,我们再写web框架是不是就简单了好多啊

对于真实开发中的python web程序来说,一般会分为两部分:服务器程序和应用程序.

服务器程序负责对socket服务器进行封装,并在请求到来时,对请求的各种数据进行整理。

  应用程序则负责具体的逻辑处理。为了方便应用程序的开发,就出现了众多的Web框架,例如:Django、Flask、web.py 等。不同的框架有不同的开发方式,但是无论如何,开发出的应用程序都要和服务器程序配合,才能为用户提供服务。

  这样,服务器程序就需要为不同的框架提供不同的支持。这样混乱的局面无论对于服务器还是框架,都是不好的。对服务器来说,需要支持各种不同框架,对框架来说,只有支持它的服务器才能被开发出的应用使用。最简单的Web应用就是先把HTML用文件保存好,用一个现成的HTTP服务器软件,接收用户请求,从文件中读取HTML,返回。如果要动态生成HTML,就需要把上述步骤自己来实现。不过,接受HTTP请求、解析HTTP请求、发送HTTP响应都是苦力活,如果我们自己来写这些底层代码,还没开始写动态HTML呢,就得花个把月去读HTTP规范。

  正确的做法是底层代码由专门的服务器软件实现,我们用Python专注于生成HTML文档。因为我们不希望接触到TCP连接、HTTP原始请求和响应格式,所以,需要一个统一的接口协议来实现这样的服务器软件,让我们专心用Python编写Web业务。

  这时候,标准化就变得尤为重要。我们可以设立一个标准,只要服务器程序支持这个标准,框架也支持这个标准,那么他们就可以配合使用。一旦标准确定,双方各自实现。这样,服务器可以支持更多支持标准的框架,框架也可以使用更多支持标准的服务器。

  WSGI(Web Server Gateway Interface)就是一种规范,它定义了使用Python编写的web应用程序与web服务器程序之间的接口格式,实现web应用程序与web服务器程序间的解耦。

  常用的WSGI服务器有uwsgi、Gunicorn。而Python标准库提供的独立WSGI服务器叫wsgiref,Django开发环境用的就是这个模块来做服务器。

接下来我们先看一看wsgiref的简单用法:

from wsgiref.simple_server import make_server

# wsgiref本身就是个web框架,提供了一些固定的功能(请求和响应信息的封装,
# 不需要我们自己写原生的socket了也不需要咱们自己来完成请求信息的获取了,提取起来很方便
# 函数名字随便起

def application(environ,start_response):
    '''
    :param environ: 是全部加工好的请求信息,加工成了一个字典,通过字典取值的方式就能拿到很多你想要拿到的信息
    :param start_response: 帮你封装响应信息的(响应行和响应头),注意下面的参数
    :return:
    '''
    start_response('200 OK',[('k1','v1'),])
    print(environ)
    print(environ['PATH_INFO'])
    return [b'

Hello,Web!

'] # 类似socketserver httpd = make_server('127.0.0.1',8080,application) print('Servering HTTP on port 8080...') # 开始监听HTTP请求 httpd.serve_forever()

这个模块就是封装好服务器程序的处理,方便你使用。

接下来我们引入数据库内容,首先创建一个数据库,然后在插入一些数据:

mysql安装请看我博客mysql标签里面,此处不做介绍

my.cnf

[root@mysql-test ~]# cat /etc/my.cnf
[mysql]
default-character-set=utf8

[mysqld]
datadir=/var/lib/mysql
socket=/var/lib/mysql/mysql.sock
symbolic-links=0

character-set-server=utf8
default-storage-engine=INNODB

max_connections=200
log-error=/var/log/mysqld.log
pid-file=/var/run/mysqld/mysqld.pid

[client]
password = ZHOUjian.24
user = root

socket服务端

from wsgiref.simple_server import make_server

import time

def html(conn):
    time_now = time.strftime('%Y/%m/%d %H:%M:%S', time.localtime())
    with open('test5.html', encoding='utf-8') as f1:
        data = f1.read().format(time_now=time_now)
    conn.send(data.encode('utf-8'))
    conn.close()


def css(conn):
    with open('test2.css', mode='rb') as f1:
        data = f1.read()
    return data


def jpg(conn):
    with open('2.jpg', mode='rb') as f1:
        data = f1.read()
    return data


def js(conn):
    with open('test2.js', mode='rb') as f1:
        data = f1.read()
    return data


request_list = [
    ('/', html),
    ('/test2.css', css),
    ('/2.jpg', jpg),
    ('/test2.js', js),
]


def application(environ, start_response):
    start_response('200 OK', [('k1', 'v1'), ('k2', 'v2')])
    print(environ)
    request_path = environ['PATH_INFO']
    for i in request_list:
        if request_path == i[0]:
            ret = i[1]()
            return [ret]
    else:

        return [b'

404.....

'] httpd = make_server('127.0.0.1', 8080, application) print('Serving HTTP on port 8080...') # 开始监听HTTP请求: httpd.serve_forever()

pymysql1.py(创建数据)

import pymysql

conn = pymysql.connect("121.36.43.223", "admin", "ZHOUjian.21")

cursor = conn.cursor(pymysql.cursors.DictCursor)

# 创建 webtest数据库
cursor.execute('create database webtest;')

# 创建userinfo数据库表
cursor.execute('create table webtest.userinfo(id int,name varchar(20),age int);')

cursor.execute("""
        insert into webtest.userinfo(id,name,age) values
        (1,'幽梦',18),
        (2,'flying',19),
        (3,'渐渐',20);
        """
               )

conn.commit()
cursor.close()
conn.close()

pymysql2.py(getdata)

import pymysql

def get_data():
    conn = pymysql.connect("121.36.43.223", "admin", "ZHOUjian.21")
    cursor = conn.cursor(pymysql.cursors.DictCursor)
    cursor.execute("select * from webtest.userinfo;")
    data = cursor.fetchone()
    conn.commit()
    cursor.close()
    conn.close()
    return data
wsgiref模块版+数据库+jinja2 web框架

上面的代码实现了一个简单的动态页面(字符串替换),我完全可以从数据库中查询数据,然后去替换我html中的对应内容(专业名词叫做模板渲染,你先渲染一下,再给浏览器进行渲染),然后再发送给浏览器完成渲染。 这个过程就相当于HTML模板渲染数据。 本质上就是HTML内容中利用一些特殊的符号来替换要展示的数据。 我这里用的特殊符号是我定义的,其实模板渲染有个现成的工具: jinja2,DJango有自带的模版渲染方法,和jinja2很像,但是只能适用于Django框架,而jinja2可以适用于多种框架。

下载

pip install jinja2

getdata

import pymysql

def get_data():
    conn = pymysql.connect("121.36.43.223", "admin", "ZHOUjian.21")
    cursor = conn.cursor(pymysql.cursors.DictCursor)
    cursor.execute("select * from webtest.userinfo;")
    data = cursor.fetchone()
    conn.commit()
    cursor.close()
    conn.close()
    return data

test7.html




    
    
    


欢迎访问和软商城
{time_now}
  • aa
  • bb
  • cc
美女

socket服务端

from threading import Thread
from wsgiref.simple_server import make_server
from Day15.pymysql2 import showdata
from jinja2 import Template


def html(conn):
    userinfo_data = showdata()
    print(userinfo_data)
    with open('test7.html', 'rb',encoding='utf-8') as f1:
        data = f1.read()
    temp = Template(data)
    print(temp)
    data = temp.reader({'userinfo':userinfo_data})
    data = data.encode('utf-8')
    return data

def css(conn):
    with open('test2.css','rb') as f1:
        data = f1.read()
    return data


def jpg(conn):
    with open('2.jpg','rb') as f1:
        data = f1.read()
    return data


def js(conn):
    with open('test2.js','rb') as f1:
        data = f1.read()
    return data


urlpatterns = [
    ('/', html),
    ('/test2.css', css),
    ('/2.jpg', jpg),
    ('/test2.js', js),
]


def application(environ, start_response):
    # print(environ)
    # conn.send(b'HTTP/1.1 200 ok\r\n\r\nxxxx')
    start_response('200 OK', [('k1','v1'),('k2','v2')])
    # print(environ['PATH_INFO'])
    path = environ['PATH_INFO']

    for i in urlpatterns:
        if path == i[0]:
            ret = i[1]()
            break
    else:
        ret = b'404 not found!!!!'
    return [ret]

httpd = make_server('127.0.0.1', 8080, application)

print('Serving HTTP on port 8080...')
httpd.serve_forever()

此处有问题,等我大概过一遍回来再弄细节

你可能感兴趣的:(15 . PythonWeb框架本质)