• [Python之路] 使用装饰器给Web框架添加路由功能(静态、动态、伪静态URL)


    一、观察以下代码

    以下来自 Python实现简易HTTP服务器与MINI WEB框架(利用WSGI实现服务器与框架解耦) 中的mini_frame最后版本的代码:

    import time
    
    
    def index():
        with open("templates/index.html", 'rb') as f:
            content = f.read()
        return content.decode("utf-8")
    
    
    def login():
        return "----login page----
     %s" % time.ctime()
    
    
    def register():
        return "----register page----
     %s" % time.ctime()
    
    
    def application(env, start_response):
        file_name = env['PATH_INFO']
        if file_name == '/login.py':
            start_response('200 OK', [('Content-Tpye', 'text/html')])
            return login()
        elif file_name == '/register.py':
            start_response('200 OK', [('Content-Tpye', 'text/html')])
            return register()
        elif file_name == '/index.py':
            start_response('200 OK', [('Content-Tpye', 'text/html')])
            return index()
        else:
            start_response('404 NOT FOUND', [])
            return "Not found page..."

    我们可以看到,在前面的代码实现中,application函数中通过if...else判断来对用户的请求做判断,然后决定调用什么函数来进行处理。这是不合理的,如果支持100个请求,那么这样就需要些100个分支。

    二、通过字典来实现请求与处理函数之间的映射

    添加一个全局字典,用于存放 请求字符串:函数引用,例如 "/index.py" : index。

    import time
    
    # 定义一个全局的字典,用来存放请求字符串与处理函数引用之间的映射关系
    URL_MAP_DICT = dict()

    我们假设我们已经手工加入了映射数据到这个字典中,则我们可以将application函数修改为:

    def application(env, start_response):
        # 从服务器获取到用户的请求字符串
        file_name = env['PATH_INFO']
    
        # 手工为字典加入映射数据
        URL_MAP_DICT['/index.py'] = index
        URL_MAP_DICT['/register.py'] = register
        URL_MAP_DICT['/login.py'] = login
    
        try:
            # 如果在字典中存在着映射,则执行响应的函数并返回结果
            return URL_MAP_DICT[file_name]()
        except Exception as ret:
            # 如果字典中不存在映射,则返回404
            start_response("404 NOT FOUND", [])
            return "404 异常"

    由于mini_frame是我们实现的框架, 框架一旦做好以为,我们不会轻易来修改框架的代码,而application函数是mini_frame的核心。也就是说我们不能老是通过修改application函数来添加功能。

    三、使用装饰器来自动添加映射关系

    为每一个实际的请求处理函数添加装饰器,在装饰器中实现将映射关系添加到字典中:

    整体代码:

    import time
    
    # 定义一个全局的字典,用来存放请求字符串与处理函数引用之间的映射关系
    URL_MAP_DICT = dict()
    
    
    # 实现装饰器
    def router(url):
        def set_func(func):
            URL_MAP_DICT[url] = func
    
            def call_func(*args, **kwargs):
                return func(*args, **kwargs)
    
            return call_func
    
        return set_func
    
    
    @router('/index.py')
    def index():
        with open("templates/index.html", 'rb') as f:
            content = f.read()
        return content.decode("utf-8")
    
    
    @router('/login.py')
    def login():
        return "----login page----
     %s" % time.ctime()
    
    
    @router('/register.py')
    def register():
        return "----register page----
     %s" % time.ctime()
    
    
    def application(env, start_response):
        # 从服务器获取到用户的请求字符串
        file_name = env['PATH_INFO']
    
        try:
            # 如果在字典中存在着映射,则执行响应的函数并返回结果
            return URL_MAP_DICT[file_name]()
        except Exception as ret:
            # 如果字典中不存在映射,则返回404
            start_response("404 NOT FOUND", [])
            return "404 异常"

    为什么可以使用装饰器来实现?

    因为装饰器的执行时间是在被装饰函数定义好之后立即执行,也就是在模块被导入的时候。所以服务器调用application函数的时候,所有的处理函数已经被注册到了全局字典中。

    四、静态、动态、伪静态URL

    静态URL:例如 www.leeoo.com/110.html,其中110.html是一个静态文件。

    动态URL:例如 www.leeoo.com/search.asp?id=5,这种带有"?"的URL,一般称为动态URL,每个动态URL是一个逻辑地址,并不是对应真实服务器目录文件的。

    伪静态URL:使用 www.leeoo.com/74.html这种和静态URL非常相似的URL。但是他并不是对应服务器静态文件的。例如74.html,我们可以认为74是页数,返回第74页的内容(数据来自数据库)。

    在搜索引擎中,除了给钱使其排名靠前。使用静态和伪静态URL也能使SEO(搜索引擎优化)更加优化,能够提高网页在搜索引擎中的排列顺序。

    要实现伪静态URL,我们需要将web_server.py进行修改:

    完整服务器代码:

    import socket
    import re
    import multiprocessing
    
    from dynamic import mini_frame
    
    
    class WSGIServer(object):
        def __init__(self):
            self.headers = list()
            self.status = ""
            # 创建socket实例
            self.tcp_server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
            # 设置资源重用
            self.tcp_server_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
            # 绑定IP端口
            self.tcp_server_socket.bind(("", 7890))
            # 开始监听
            self.tcp_server_socket.listen(128)
    
        # 请求处理函数
        def handle_request(self, new_socket):
            # 接收请求
            recv_msg = ""
            recv_msg = new_socket.recv(1024).decode("utf-8")
            if recv_msg == "":
                print("recv null")
                new_socket.close()
                return
    
            # 从请求中解析出URI
            recv_lines = recv_msg.splitlines()
            # 使用正则表达式提取出URI
            ret = re.match(r"[^/]+(/[^ ]*)", recv_lines[0])
            if ret:
                # 获取URI字符串
                file_name = ret.group(1)
                # 如果URI是/,则默认返回index.html的内容
                if file_name == "/":
                    file_name = "templates/index.html"
    
            # 如果请求字符串不是以".html"结尾,则都认为是静态资源,直接在磁盘中读取并返回
            if not file_name.endswith(".html"):
                try:
                    # 根据请求的URI,读取相应的文件
                    fp = open("." + file_name, "rb")
                except:
                    # 找不到文件,响应404
                    response_msg = "HTTP/1.1 404 NOT FOUND
    "
                    response_msg += "
    "
                    response_msg += "<h1>----file not found----</h1>"
                    new_socket.send(response_msg.encode("utf-8"))
                else:
                    html_content = fp.read()
                    fp.close()
                    # 响应正确 200 OK
                    response_msg = "HTTP/1.1 200 OK
    "
                    response_msg += "
    "
    
                    # 返回响应头
                    new_socket.send(response_msg.encode("utf-8"))
                    # 返回响应体
                    new_socket.send(html_content)
            else:
                env = dict()
                # 将请求的内容添加到字典中,并传递给application
                env['PATH_INFO'] = file_name
                body = mini_frame.application(env, self.set_response_header)
    
                # 将框架返回的status组合进response header中
                header = "HTTP/1.1 %s
    " % self.status
                # 将框架返回的响应头键值对加入到header中
                for temp in self.headers:
                    header += "%s:%s
    " % (temp[0], temp[1])
                # 最后加上一个分割行
                header += "
    "
    
                # 将响应体数据加在header后面
                response = header + body
                # 返回给客户端
                new_socket.send(response.encode("utf-8"))
    
            # 关闭该次socket连接
            new_socket.close()
    
        # 该成员函数提供给web框架的applicaiton函数调用,并将status,headers设置到服务器中
        def set_response_header(self, status, headers):
            self.status = status
            # 如果要在响应头中添加服务器信息,一定是在服务器代码中加,而不是web框架代码中加
            self.headers = [("server", "mini_server v1.0")]
            self.headers += headers
    
        # 开始无限循环,接受请求
        def run_forever(self):
            while True:
                new_socket, client_addr = self.tcp_server_socket.accept()
                # 启动一个子进程来处理客户端的请求
                sub_p = multiprocessing.Process(target=self.handle_request, args=(new_socket,))
                sub_p.start()
                # 这里要关闭父进程中的new_socket,因为创建子进程会复制一份new_socket给子进程
                new_socket.close()
    
            # 关闭整个SOCKET
            tcp_server_socket.close()
    
    
    def main():
        wsgi_server = WSGIServer()
        wsgi_server.run_forever()
    
    
    if __name__ == "__main__":
        main()

    在之前的代码中,我们判断请求是否以".py结尾",如果不是".py",则认为是静态资源,例如png、css、js、html等,由服务器直接读取并返回给客户端。

    而在这里,我们将file_name.endswith(".py")修改为file_name.endswith(".html"),也就是说出了html,其他的我们都认为是静态资源。当请求以"html"结尾时,我们认为是动态资源,交给mini_frame处理。

    在mini_frame的代码中,我们将所有的装饰器参数修改为".html"结尾即可:

    import time
    
    # 定义一个全局的字典,用来存放请求字符串与处理函数引用之间的映射关系
    URL_MAP_DICT = dict()
    
    
    # 实现装饰器
    def router(url):
        def set_func(func):
            URL_MAP_DICT[url] = func
    
            def call_func(*args, **kwargs):
                return func(*args, **kwargs)
    
            return call_func
    
        return set_func
    
    
    @router('/index.html')
    def index():
        with open("templates/index.html", 'rb') as f:
            content = f.read()
        return content.decode("utf-8")
    
    
    @router('/login.html')
    def login():
        return "----login page----
     %s" % time.ctime()
    
    
    @router('/register.html')
    def register():
        return "----register page----
     %s" % time.ctime()
    
    
    def application(env, start_response):
        # 从服务器获取到用户的请求字符串
        file_name = env['PATH_INFO']
    
        try:
            # 如果在字典中存在着映射,则执行响应的函数并返回结果
            return URL_MAP_DICT[file_name]()
        except Exception as ret:
            # 如果字典中不存在映射,则返回404
            start_response("404 NOT FOUND", [])
            return "404 异常"
  • 相关阅读:
    兴趣与心态比较重要【转】
    网站发布到iis上,附加进程调试,打不到断点
    MVC时间格式化
    javascript数组扁平化处理
    Object.prototype.toString.call()
    获取浏览器大小
    HTTP状态码
    未能加载文件或程序集“Microsoft.ReportViewer.WebForms, Version=10.0.0.0
    javascript基础知识-1
    Html5上传图片的预览
  • 原文地址:https://www.cnblogs.com/leokale-zz/p/11984375.html
Copyright © 2020-2023  润新知