美文网首页编程
werkzeug实现WSGI Application

werkzeug实现WSGI Application

作者: Stone0823 | 来源:发表于2020-05-31 22:45 被阅读0次

    WSGI 为 Web Server Gateway Inferface 的缩写,是 Python Web 框架(或应用程序)与 Web 服务器 (Web Server) 之间通讯的规范,本质上是定义了一种 Web server 与 Web application 解耦的规范。比如 Flask 就是运行在 WSGI 协议之上的 web 框架。

    来看一幅图:

    左边,Client 和 Server 之间,Client 发送请求,Server 返回响应,遵守 HTTP 协议; 右边:Python 语言编写的 Web Application 和 Web Server 之间通讯,建议遵守 WSGI 规范。该规范被定义在 PEP 333

    WSGI 规定:每个使用 Python 语言编写的 Web Application 必须是一个可调用对象(实现了call 函数的方法或者类),接受两个参数 :

    • environ :WSGI 的环境信息
    • start_response:回调函数,在发送 response body 之前被调用,也是一个可调用对象。

    如果使用 werkzeug 来实现 Web Application 和 Web Server,只需要下面的代码:

    from werkzeug.serving import run_simple
    
    def application(environ, start_response):
        headers = [('Content-Type', 'text/plain')]
        start_response('200 OK', headers)
        return [b'Hello World']
    
    if __name__ == "__main__":
        run_simple('localhost', 5000, application)
    

    start_response 函数必须接受两个参数: status(HTTP状态)和 response_headers(响应消息的头)。

    Request 和 Response

    werkzeug 的 Request 对象对 environ 对象进行了封装 (The Request class wraps the environ for easier access to request variables),Response 对象则封装了 WSGI Application。经过 Request 和 Response 的封装,编写 web application 变得更加简单。比如,下面的代码实现了与刚才程序代码相同的功能。

    from werkzeug.serving import run_simple
    from werkzeug.wrappers import Request, Response
    
    def application(environ, start_response):
        req = Request(environ)   
        body = 'Hello World'
    
        resp = Response(body, mimetype='text/plain')
        return resp(environ, start_response)
    
    if __name__ == "__main__":
        run_simple('localhost', 5000, application)
    

    理解了 WSGI 规范和 werkzeug 封装的 Request 和 Response,接下来我们要实现 web application 的几个主要功能:路由、模板渲染 (render template)、请求和响应循环。通过代码的逐步演变,有助于理解 Flask 的思路和源码。

    基本框架

    下面的代码基于 werkzeug ,实现了 Web Application 和 Web Server 的功能。无论 url 的 path 是什么,都返回 Hello World!

    from werkzeug.serving import run_simple
    from werkzeug.wrappers import Request, Response
    
    class WebApp(object):
        def __init__(self):
            pass
    
        def dispatch_request(self, request):
            return Response('Hello World')
    
        def wsgi_app(self, environ, start_response):
            request = Request(environ)
            response = self.dispatch_request(request)
    
            return response(environ, start_response)
    
        def __call__(self, environ, start_response):
            return self.wsgi_app(environ, start_response)
    
    def create_app(host='localhost', port=5000):
        app = WebApp()
        return app
    
    if __name__ == "__main__":
        app = create_app()
        run_simple('localhost', 5000, app)
    

    实现路由

    上面的代码中,无论客户端请求的 url path 是什么,都返回固定的字符串。前面我们在深入理解Flask路由(2)- werkzeug 路由系统 这篇博文中,介绍了 werkzeug 的路由系统,我们基于上面的代码,实现可以处理下面两个路径的路由:

    • root path
    • /users/userid

    如果客户端请求其它的 url,将得到 Not Found 错误。

    from werkzeug.serving import run_simple
    from werkzeug.wrappers import Request, Response
    from werkzeug.routing import Map, Rule
    from werkzeug.exceptions import HTTPException
    
    class WebApp(object):
        def __init__(self):
            self.url_map = Map([
                Rule('/', endpoint='index'),
                Rule('/users/<userid>', endpoint='userinfo')
            ])
    
        def dispatch_request(self, request):
            adapter = self.url_map.bind_to_environ(request.environ)
            try:
                endpoint, args = adapter.match()
                # 根据endpoint,找到视图函数 on_endpointname,并且执行
                return getattr(self, 'on_'+endpoint)(request, **args)
            except HTTPException as ex:
                return ex
    
        def wsgi_app(self, environ, start_response):
            req = Request(environ)
            resp = self.dispatch_request(req)
            return resp(environ, start_response)
    
        def __call__(self, environ, start_response):
            return self.wsgi_app(environ, start_response)
    
        def on_index(self, request):        
            return Response('index page')
        
        def on_userinfo(self, request, userid):
            return Response('Hello, {}'.format(userid))
    
    def create_app(host='localhost', port=5000):
        app = WebApp()
        return app
    
    if __name__ == "__main__":
        app = create_app()
        run_simple('localhost', 5000, app)
    
    

    代码的主要变化在 __init__() 方法和 dispatch_request() 方法中:


    实现视图和模板渲染

    对客户端的请求,不能只是返回简单的字符串。接下来,我们对程序的功能加上视图函数,返回真正的页面,并且借助 jinjia2 的模板功能,允许向页面传递参数。

    首先编写两个 html 页面,放在工程文件 templates 文件夹下面:

    templates/index.html:

    <!DOCTYPE html>
    <html lang="en">
    <link rel=stylesheet href=/static/style.css type=text/css>
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1.0">
        <title>Document</title>
    </head>
    <body>
        <h1>This is index page</h1>
    </body>
    </html>
    

    templates/user.html

    <!DOCTYPE html>
    <html lang="en">
    <link rel=stylesheet href=/static/style.css type=text/css>
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1.0">
        <title>Document</title>
    </head>
    <body>
        <h1>Hello, {{ userid }} </h1>
    </body>
    </html>
    

    然后在 WebApp 类中实现 render_template() 方法:

    def __init__(self):
            template_path = os.path.join(os.path.dirname(__file__), 'templates')
            self.jinja_env = Environment(loader=FileSystemLoader(template_path),
                                         autoescape=True)
            # 其它代码略
            
    def render_template(self, template_name, **context):
        t = self.jinja_env.get_template(template_name)
        return Response(t.render(context), mimetype='text/html')
    

    这样,视图函数 on_index()on_userinfo() 就可以返回 html 文件了。完整代码如下:

    from werkzeug.serving import run_simple
    from werkzeug.wrappers import Request, Response
    from werkzeug.routing import Map, Rule
    from werkzeug.wsgi import SharedDataMiddleware
    from werkzeug.exceptions import HTTPException, NotFound
    from jinja2 import Environment, FileSystemLoader
    import os
    
    class WebApp(object):
        def __init__(self):
            template_path = os.path.join(os.path.dirname(__file__), 'templates')
            self.jinja_env = Environment(loader=FileSystemLoader(template_path),
                                         autoescape=True)
            self.url_map = Map([
                Rule('/', endpoint='index'),
                Rule('/users/<userid>', endpoint='userinfo')
            ])
    
            self.view_functions = {
                'index': self.on_index,
                'userinfo': self.on_userinfo
            }
    
        def dispatch_request(self, request):
            adapter = self.url_map.bind_to_environ(request.environ)
            try:
                endpoint, args = adapter.match()
                return self.view_functions[endpoint](endpoint, **args)
            except HTTPException as e:
                return e
    
        def render_template(self, template_name, **context):
            t = self.jinja_env.get_template(template_name)
            return Response(t.render(context), mimetype='text/html')
    
        def wsgi_app(self, environ, start_response):
            req = Request(environ)
            resp = self.dispatch_request(req)
            return resp(environ, start_response)
    
        def __call__(self, environ, start_response):
            return self.wsgi_app(environ, start_response)
    
        def on_index(self, request):
            return self.render_template('index.html')
    
        def on_userinfo(self, request, userid):
            return self.render_template('user.html', userid=userid)
    
    def create_app(host='localhost', port=5000, with_static=True):
        app = WebApp()
        if with_static:
            app.wsgi_app = SharedDataMiddleware(app.wsgi_app, {
                '/static':  os.path.join(os.path.dirname(__file__), 'static')
            })
        return app
    
    if __name__ == "__main__":
        app = create_app()
        run_simple('localhost', 5000, app)
    
    

    代码

    完整代码:github: werkzeug-web-app-evolve

    参考

    相关文章

      网友评论

        本文标题:werkzeug实现WSGI Application

        本文链接:https://www.haomeiwen.com/subject/nvrdzhtx.html