WSGI协议

1. WSGI

  1. WSGI:是一种规定了web服务器和web应用程序之间协作关系的协议
      主流的python web框架和web服务器都支持WSGI协议
      在支持WSGI协议的情况下 web服务器和web应用框架的开发可以各司其职专注于自己的领域
      WSGI允许开发者将选择web框架和web服务器分开。可以混合匹配web服务器 和web框架,选择一个适合的配对
  2. 正常情况下,Web服务器会接受到两种请求资源:动态和静态
      静态资源:一般不需要经常变化的资源 – 文件系统
      动态资源需要经常变化的数据 – 数据库
  3. 当接受静态资源请求时候,它可以自己处理,当收到动态资源请求时候,为了使其压力不必过大,Web服务器会将其交给web框架来处理。他们之间的通信就是用 的 WSGI协议
  4. 当动态数据要更改的时候,要用到模板的替换
      用户需要的不是模板文件 需要价格模板文件中的模板变量替换为用户关心的数据
  5. web服务器必须具备WSGI接口,所有的现代Python Web框架都已具备WSGI接口, 它让你不对代码作修改就能使服务器和特点的web框架协同工作。

2. 最简单的Web版本的“Hello World!”:

def application(environ, start_response):
    start_response('200 OK', [('Content-Type', 'text/html')])
    return 'Hello World!'
  • environ:一个包含所有HTTP请求信息的dict对象;
  • start_response:一个发送HTTP响应的函数

3. WSGI 通信中,最终要的两个函数

  • 一个是从框架定义的application(environ,start_response)
  • 一个是需要在服务器 中定义的 start_response(status, header_list)

application的实现

import time


# 指定模板文件资源 所在的根目录
TEMPLATE_ROOT = "./template"


def gettime(path_info):
    """当用户请求/gettime.py的时候执行该函数代码"""
return time.ctime()  # 显示当前的时间


def app(environ, start_response):
    # 这个函数是应用框架提供给web服务器调用
    # web服务器在接收到动态资源请求的时候调用这个函数

    # 参数1(environ) 是一个含有HTTP请求相关信息的字典
    #     字典中的'PATH_INFO': '/index.html'就是用户请求的路径

    # 参数2(start_response) 是一个web提供的函数的引用
    # start_response函数功能主要是用以设置 str响应状态和响应头部
    # start_response参数1 是需要设置的响应状态
    # start_response参数2 是一个响应头信息的列表 列表中每个元素是一个元组
    #                               元组前者是响应头名称 后者是响应头对应的值

    # 获取到用户请求路径
    # 设置前面的路径
path_info = environ['PATH_INFO']

    # 当用户访问已经实现了功能的路径  就返回200 OK 其余的返回404 Not Found
    if path_info == '/gettime.py':
        start_response('200 OK', [('Content-Type', 'text/html'),
                                  ("Server", "PythonWebServer")])
        return gettime(path_info)
    else:
        # app函数的返回值就是响应体
        start_response('404 Not Found', [('Content-Type', 'text/html'),
                                  ("Server", "PythonWebServer")])
        return 'Hello World!!!!!' + str(environ)



def start_response(self,status, header_list):
        """设置响应状态和响应头部 web服务器提供给web应用程序调用的"""
        # 参数1 是一个状态-str类型
        # 参数2 是多个响应头构成的列表<顺序>  [(名,值),(),()]
        response_line = "HTTP/1.1 %s\r\n" % status
        self.status_headers = response_line
        for header_name, header_value in header_list:
            self.status_headers += "%s: %s\r\n"%(header_name, header_value)

以下例中,用到的函数解析:
Argv函数,是指在命令行输入的时候可以取出他们的参数

import sys
Data = sys.argv[1], 表示将命令行索引坐标为1的数赋值给Data

#  __import__是import函数的底层实现
    # 参数是 模块的名称 返回值就是模块对象
# 重点理解 -- import threading 等价于 threading = __import__("threading")

# getattr(对象,属性名) 返回值就是属性的引用
# 重点理解 -- Application.app  等价于 app = getattr(Application, "app")

4. 实现:web服务器

from gevent import monkey
monkey.patch_all()  # recv recvfrom accept time.sleep()在本应该阻塞的地方能够被gevent自动切换
import gevent
import socket
import re
import sys
"""
a 创建TCP套接字 绑定和监听
b 接受客户端的连接请求
c 接收客户端的HTTP请求报文数据
d 解析 ------> 发送HTTP响应报文数据
"""


class HTTPServer(object):
    def __init__(self,port,app):
        """初始化"""
        # a 创建TCP套接字 绑定和监听
        self.tcp_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)

        # 设置套接字选项 忽略等待2MSL时间-----> 立即重新绑定该端口
        self.tcp_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)

        # 绑定端口 -- 固定端口
        self.tcp_socket.bind(('', port))

        # 设置为被动监听模式
        self.tcp_socket.listen(128)

        # 在web服务器中保存一个应用程序提供的函数的引用
        self.app = app

    def start(self):
        while True:
            # b 接受客户端的连接请求
            client_socket, client_addr = self.tcp_socket.accept()
            print("接受来自%s的连接请求" % str(client_addr))
            # 调用函数  完成对客户端的响应
            # client_handler(client_socket)

            # 在接收一个用户请求的时候  创建一个协程并且启动 为一个客户服务
            gevent.spawn(self.client_handler, client_socket)

            # 在start方法中  while True不会退出

    def client_handler(self, client_socket):
        """处理客户端请求的函数"""
        # c 接收客户端的HTTP请求报文数据
        # 接收请求数据
        request_data = client_socket.recv(4096)
        if not request_data:
            print("客户端已经断开连接")
            client_socket.close()

        # print(request_data)
        # 解码
        request_str_data = request_data.decode()
        data_list = request_str_data.split("\r\n")
        # for line in data_list:
        #     print(line)

        # 获取到请求行 GET /a/b/c/index.html HTTP/1.1
        request_line = data_list[0]
        result = re.match(r"\w+\s+(\S+)", request_line)
        if not result:
            print("格式错误")
            client_socket.close()
            return

        # 格式正确
        # 用户的请求路径信息
        path_info = result.group(1)
        print("收到用户的请求:" + path_info)

        # web服务器的默认规则
        # 当用户访问/目录的时候  默认使其能够访问一个网页
        if path_info == '/':
            path_info = "/index.html"

        # 用户请求的路径信息 相关的文件数据当做响应体发送给客户

        # d 解析 ------> 发送HTTP响应报文数据
        """
        HTTP/1.1 200 OK\r\nServer: PythonWebServer6.0\r\n\r\n文件数据
        HTTP/1.1 404 Not Found\r\nServer: PythonWebServer6.0\r\n\r\nerror
        """
        # .jsp .php .aspx
        # 首先判断用户资源请求类型是静态.css .png .jpg 的还是动态.py

        env = {
            "PATH_INFO": path_info
        }
        # 字典是一种散列结构  -- 无序
        if path_info.endswith(".py"):
            # 动态资源请求

            # 调用应用程序提供的一个函数 函数的返回值就是响应体
            response_body = self.app(env, self.start_response)

            # 将web服务器中已经保存的响应状态和头  + 响应体
            response_data = self.status_headers + "\r\n" + response_body
            client_socket.send(response_data.encode())
            client_socket.close()
        else:
            # 服务器指定路径 用户请求路径
            # static       /index.html  ---》 /home/it/123.txt

            try:
                # file = open("static"+ path_info, 'rb')
                #
                # # 如果文件很大 会有隐患  rb读取的文件 就是Bytes类型
                # file_data = file.read()
                # file.close()
                with open("static" + path_info, 'rb') as file:
                    file_data = file.read()

            except Exception as e:
                print(e)
                # 出现异常  路径没有相关的文件数据
                # 响应行
                response_line = "HTTP/1.1 404 Not Found\r\n"
                # 响应头
                response_header = "Server: PythonWebServer6.0\r\n"
                # 下面这个头 会让浏览器将响应体数据 当做utf-8编码处理
                response_header += "Content-Type: text/html;charset=utf-8\r\n"
                # 响应体
                response_body = "ERROR!!!!!你所请求的资源没有找到"

                response_data = response_line + response_header + "\r\n" + response_body
                client_socket.send(response_data.encode())
            else:
                # 响应行
                response_line = "HTTP/1.1 200 OK\r\n"
                # 响应头
                response_header = "Server: PythonWebServer6.0\r\n"
                # 响应体 Bytes类型
                response_body = file_data
                # 将相应数据拼接
                response_data = (response_line + response_header + "\r\n").encode() + response_body
                # send函数的返回值表示 发送数据的字节数

                # send_len = client_socket.send(response_data)
                # print("数据总大小%d 已经发送%d" % (len(response_data), send_len))
                client_socket.sendall(response_data)
            finally:
                # 关闭套接字
                client_socket.close()

    def start_response(self,status, header_list):
        """设置响应状态和响应头部 web服务器提供给web应用程序调用的"""
        # 参数1 是一个状态-str类型
        # 参数2 是多个响应头构成的列表<顺序>  [(名,值),(),()]
        response_line = "HTTP/1.1 %s\r\n" % status
        self.status_headers = response_line

        for header_name, header_value in header_list:
            self.status_headers += "%s: %s\r\n"%(header_name, header_value)


def main():
    # python3 webxxxx.py 9999
    # 创建一个实例对象
    # 获取到操作系统给我传递的 命令行参数列表
    # print(sys.argv)

    # 第三个版本 通过命令行参数指定 应用程序所在模块:函数
    if len(sys.argv) != 3:
        print("使用方式 python3 webxxxx.py 9999 应用模块名:函数名")
        return
    data = sys.argv[1]
    # print(sys.argv[2])

    mod_name_app_name = sys.argv[2]
    data_list = mod_name_app_name.split(":")
    if len(data_list) != 2:
        print("使用方式 python3 webxxxx.py 9999 应用模块名:函数名")
        return

    # 模块名 “Application”
    module_name = data_list[0]

    # 函数名 "app"
    app_name = data_list[1]

    # __import__是import函数的底层实现
    # 参数是 模块的名称 返回值就是模块对象
    # 重点理解 -- import threading 等价于 threading = __import__("threading")
    Application = __import__(module_name)

    # getattr(对象,属性名) 返回值就是属性的引用
    # 重点理解 -- Application.app  等价于 app = getattr(Application, "app")
    app = getattr(Application, "app")

    if data.isdigit():
        port = int(data)

        http_server = HTTPServer(port, app)
        # 启动HTTP服务器的运行
        http_server.start()


if __name__ == '__main__':
    main()

5. 实现:web框架 Application.py

import time
import re


# 指定模板文件资源 所在的根目录
TEMPLATE_ROOT = "./template"


def gettime(path_info):
    """当用户请求/gettime.py的时候执行该函数代码"""
    return time.ctime()


def center(path_info):
    """当用户请求/index.py的时候执行该函数代码"""
    # 读取模板文件
    # /index.py ----》 /index.html
    path_info = path_info.replace(".py", ".html")

    with open(TEMPLATE_ROOT + path_info) as file:
        html = file.read()

    # 将模板文件的数据进行 替换 --正则替换 {%content%}
    data_from_mysql = "这是从数据库中查询出来的相关信息"
    html = re.sub(r"\{%content%\}",data_from_mysql,html)

    return html

def app(environ, start_response):
    # 这个函数是应用框架提供给web服务器调用
    # web服务器在接收到动态资源请求的时候调用这个函数

    # 参数1 是一个含有HTTP请求相关信息的字典
    #     字典中的'PATH_INFO': '/index.html'就是用户请求的路径

    # 参数2 是一个web提供的函数的引用
    # start_response函数功能主要是用以设置 str响应状态和响应头部
    # start_response参数1 是需要设置的响应状态
    # start_response参数2 是一个响应头信息的列表 列表中每个元素是一个元组
    #                                元组前者是响应头名称 后者是响应头对应的值

    # 获取到用户请求路径
    path_info = environ['PATH_INFO']

    # 当用户访问已经实现了功能的路径  就返回200 OK 其余的返回404 Not Found
    if path_info == '/gettime.py':
        start_response('200 OK', [('Content-Type', 'text/html'),
                                  ("Server", "PythonWebServer")])
        return gettime(path_info)
    elif path_info == '/index.py':
        # 读取模板文件  并且进行替换 然后通过返回值返回给web服务器
        start_response('200 OK', [('Content-Type', 'text/html'),
                                  ("Server", "PythonWebServer")])
        return index(path_info)
    else:
        # app函数的返回值就是响应体
        start_response('404 Not Found', [('Content-Type', 'text/html'),
                                  ("Server", "PythonWebServer")])
        return 'Hello World!!!!!' + str(environ)

6. 图片展示访问交互过程

主要是为了解耦合
流程
流程详解
实现

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值