python中遵循WSGI协议的HTTP服务器

一、WSGI协议

WSGI:全称是Web Server Gateway Interface,WSGI不是服务器,python模块,框架,API或者任何软件,只是一种规范,描述web server如何与web application通信的规范。
WSGI协议其实是定义了一种server与application解耦的规范,即可以有多个实现WSGI server的服务器,也可以有多个实现WSGI application的框架,那么就可以选择任意的server和application组合实现自己的web应用。

1、服务器代码 web_server.py

import sys
from socket import *
import re
import multiprocessing



class WSGI_Http(object):
    """遵循WSGI协议的http服务器"""


    def __init__(self, port, static_path, function_name):
        """服务器的初始化"""

        # 创建套接字
        self.tcp_server = socket(AF_INET, SOCK_STREAM)
        # 端口重复使用
        self.tcp_server.setsockopt(SOL_SOCKET, SO_REUSEADDR, 1)

        # 绑定(bind)
        self.tcp_server.bind(("", port))

        # 监听(listen)
        self.tcp_server.listen(128)

        # 用实例属性来存储 static的路径信息 和 模块中application函数的引用
        self.static = static_path
        self.application = function_name


    def client_request(self, new):
        """浏览器的请求"""

        # 将浏览器的请求转换成列表
        request_data = new.recv(1024).decode("utf-8").splitlines()
        print(request_data)
        print("------------------------请求信息------------------------------")

        # 根据浏览器请求的路径响应对应的页面
        rul = re.match(r"[^/]+(/[^ ]*)", request_data[0])  # [^/]:从左往右取,不是/都不取。  +:必须有/。  (/[^ ]*):从/开始取到空格
        if rul:
            file_name = rul.group(1)
            if file_name == "/":    # 如果请求没有指定要访问的页面,默认是/(/默认访问的就是主页)
                file_name = "/index.html"

            return file_name


    def static_page(self,new , file_name):
        """静态页面处理"""

        try:
            f = open(self.static + file_name, "rb")
        except:
            response_header = "HTTP/1.1 404 NOT FOUND \r\n"
            response_header += "\r\n"
            response_header += "file not found"
            new.send(response_header.encode("utf-8"))
        else:
            response_body = f.read()
            f.close()

            # header:响应的是一个头部信息
            response_header = "HTTP/1.1 200 OK \r\n"
            response_header += "\r\n"

            # 将header和body响应给浏览器
            response = response_header.encode("utf-8") + response_body
            new.send(response)


    def dynamic_page(self,new ,file_name):
        """动态页面处理"""

        # 定义一个空字典来存放用户请求
        environ = dict()
        environ["PATH_NAME"] = file_name

        # (引用)调用框架中的application()函数,把 请求 和set_header()这个方法作为实参传递给application()函数
        response_body = self.application(environ, self.set_header)

        # 此时set_header()中的第一个形参(status_number)接收的是框架中传递过来的状态码
        response_header = "HTTP/1.1 %s\r\n"%self.status_number

        # 通过for  in  遍历set_header()中第二个形参(descirbe)拿到框架中传递过来的描述信息
        for demo in self.descirbe:

            response_header += "%s:%s\r\n"%(demo[0],demo[1])

        response_header += "\r\n"

        # 拼接响应头和响应体
        response = response_header + response_body

        # 响应给浏览器
        new.send(response.encode("utf-8"))


    def set_header(self, status_number, describe):  # status_number:接收状态码   decirbe:接收描述信息
        """存储框架传递过来的实参"""

        self.status_number = status_number
        self.descirbe = describe
        self.descirbe += [("server_version","ChuangXin 3.0")]


    def server_response(self, new, file_name):
        """服务器的响应:给浏览器的http格式的数据(header和body)"""

        # 如果请求的页面不是以.py结尾的(用字符串的endswith()方法获得后缀)
        if not file_name.endswith(".py"):

            # 不是以.py结尾的页面也就是执行静态页面,调用static_page()这个方法
            self.static_page(new, file_name)

        else:

            # 是以.py结尾的页面也就是执行动态页面
            self.dynamic_page(new, file_name)


    def request_response(self, new):
        """浏览器的请求和服务器的响应"""

        # 调用请求的函数
        file_name = self.client_request(new)

        # 调用响应的函数
        self.server_response(new, file_name)

        # 关闭客户端套接字
        new.close()


    def run(self):
        """"""

        while True:

            # 等待客户端的连接(accept)
            new, other = self.tcp_server.accept()

            # 创建进程
            p1 = multiprocessing.Process(target=self.request_response, args=(new,))

            # 开启进程
            p1.start()

            # 关闭主进程套接字,因为子进程执行前会复制一份主进程的代码,用fd:文件的描述符
            new.close()

        # 关闭服务器套接字
        tcp_server.close()


def control():
    """让服务器与框架之间降低耦合程度"""

    # sys.argv:输入的是一个列表  如['python3','web_server.py','7896','mini_frame','application']
    # 长度判断
    if len(sys.argv) == 3:

        # 端口判断
        try:
            port = int(sys.argv[1]) # 拿到输入的端口,如果端口占用,抛出异常
            frame_name = (sys.argv[2]) # 拿到的是框架名与框架中的函数名
        except Exception as e:
            print("端口输入有误!")
            return "0"    

    else:
        print("请按以下格式书写")
        print("python3 服务器名称.py 端口号 框架名称:application")
        return "0"

    # 用正则表达式拿到sys.argv返回的框架名与函数名
    rel = re.match(r"([^:]+):(.*)",frame_name)
    if rel:

        frame_name = rel.group(1)
        function_name = rel.group(2)

    else:

        print("请按以下格式书写")
        print("python3 服务器名称.py 端口号 框架名称:application")
        return "0"

    # 打开服务器的配置文件,用eval函数按照键值对格式读取路径信息
    with open("./web_server.conf") as f:
        conf_info = eval(f.read())

    # 添加配置文件里面的键,也就是路径    
    sys.path.append(conf_info["dynamic_path"])

    # 用内置方法__import__()导入框架这个模块,它返回的是一个对象,标记导入了这个模块
    in_frame_name = __import__(frame_name)

    #用point_function指向了/dynamic/mini_frame/application这个函数 
    point_function = getattr(in_frame_name,function_name)

    # 将多个值返回给函数的调用者
    return port,conf_info["static_path"],point_function


def main():


    # 调用control函数
    if control() in ["0"]:
        return

    port,static_path,point_function = control()

    # 实例化对象
    wsgi_http = WSGI_Http(port, static_path, point_function)

    #对象调用方法开启服务器
    wsgi_http.run()



if __name__ == '__main__':

    main()

2、框架代码 mini_frame.py


import re
from pymysql import *


URL_RESPONSE = dict()


def connect_join(sql):
    """初始化connect连接对象"""

    conn = connect(host="localhost",port=3306,user="root",password="root",database="jing_dong",charset="utf8")
    cursor = conn.cursor()
    cursor.execute(sql)
    cursor_content = cursor.fetchall()
    cursor.close()
    conn.close()
    return cursor_content


# 定义一个装饰器,把请求(flie_name)当做key,把需要装饰的函数当做value,二者进行判断
def set_url(url):   # url:请求的地址

    def set_func(func):   # func:被装饰函数的引用

        URL_RESPONSE[url] = func       # 把请求的地址(url)当做key,把需要装饰的函数当做value,然后存入字典

        def call_func(*args,**kwargs):

            return func(*args,**kwargs)

        return call_func

    return set_func


@set_url("/index.html")
def index():

    with open("./templates/index.html") as f:

        content = f.read()

    sql1 = "select * from goods_cates"
    goods_cates = connect_join(sql1)

    sql2 = "select * from goods_brands"
    goods_brands = connect_join(sql2)

    sql3 = "select id,name,price from goods"
    goods = connect_join(sql3)

    html1 = ""
    for line_info in goods_cates:

        html1 += "<tr><td>%s</td><td>%s</td></tr>" %(line_info[0],line_info[1])
    
    content = re.sub(r"\{%content1%\}",html1,content)

    html2 = ""
    for line_info in goods_brands:

        html2 += "<tr><td>%s</td><td>%s</td></tr>" %(line_info[0],line_info[1])
    
    content = re.sub(r"\{%content2%\}",html2,content)

    a = """
        <tr><td>%s</td><td>%s</td><td>%s</td><td><input type="button" value="添加" /></td></tr>
    """
    html3 = ""
    for line_info in goods:

        html3 += a%(line_info[0],line_info[1],line_info[2])
    
    content = re.sub(r"\{%content3%\}",html3,content)

    return content


@set_url("/shopping.html")
def shopping():

    with open("./templates/shopping.html") as f:

        content= f.read()

    conn = connect(host="localhost",port=3306,user="root",password="root",database="jing_dong",charset="utf8")
    cursor = conn.cursor()
    sql = "select g.id,c.name,g.name,d.name,g.price from goods as g inner join goods_cates as c on g.cate_id=c.id inner join goods_brands as d on g.brand_id=d.id order by g.id"
    cursor.execute(sql)
    shop = cursor.fetchall()
    cursor.close()
    conn.close()

    tr_temp = """
        <tr>
        <td>%s</td>
        <td>%s</td>
        <td>%s</td>
        <td>%s</td>
        <td>%s</td>
        <td>
        <input type="submit" value="修改" id="change">
        </td>
        <td>
        <input type="submit" value="删除" id="delete">
        </td>
        </tr>
    """

    html = ""

    for line_info in shop:

        html += tr_temp %(line_info[0],line_info[1],line_info[2],line_info[3],line_info[4])    

    content = re.sub(r"\{%content%\}",html,content)

    return content

@set_url("/zcjm.html")
def zcjm():

    with open("./templates/zcjm.html") as f:

        return f.read()

@set_url("/dljm.html")
def dljm():

    with open("./templates/dljm.html") as f:

        return f.read()

def application(environ,set_header):

    # 此时的save_header()就是调用了服务器里面的set_header(status_number, describe)方法,把'200 OK'作为第一个参数传递过去,
    # 把[('Content-Type','text/html;charset=utf-8')]作为第二个参数传递过去,传递过去之后通过for  in 遍历拿到请求头信息,

    set_header('200 OK',[('Content-Type','text/html;charset=utf-8')])

    file_name = environ['PATH_NAME']    # 服务器中以"PATH_NAME"为key,把请求内容当做value存放到字典中,此时,通过字典的key("PATH_NAME)来获取value(file_name)

    try:
        key = URL_RESPONSE[file_name]   # 把请求当做key在字典中寻找对应的key
        return key()
    except Exception as rel:

        return "发生了异常具体如下:%s"%str(rel)





3、服务器配置文件 web_server.conf

{
"static_path":"./static",
"dynamic_path":"./dynamic"
}

4、shell脚本 run.sh

python3 web_server.py 7896 mini_frame:application

整体结构图
在这里插入图片描述

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值