• wsgi初探(转)


     
    wsgi初探 
    前言 

    本文不涉及WSGI的具体协议的介绍,也不会有协议完整的实现,甚至描述中还会掺杂着本人自己对于WSGI的见解。所有的WSGI官方定义请看http://www.python.org/dev/peps/pep-3333/。 


    WSGI是什么? 

    WSGI的官方定义是,the Python Web Server Gateway Interface。从名字就可以看出来,这东西是一个Gateway,也就是网关。网关的作用就是在协议之间进行转换。 



    也就是说,WSGI就像是一座桥梁,一边连着web服务器,另一边连着用户的应用。但是呢,这个桥的功能很弱,有时候还需要别的桥来帮忙才能进行处理。 



    下面对本文出现的一些名词做定义。 wsgi app ,又称 应用 ,就是一个WSGI application。 wsgi container ,又称 容器 ,虽然这个部分常常被称为handler,不过我个人认为handler容易和app混淆,所以我称之为容器。wsgi_middleware ,又称 中间件 。一种特殊类型的程序,专门负责在容器和应用之间干坏事的。 



    一图胜千言,直接来一个我自己理解的WSGI架构图吧。 

     

    可以看出,服务器,容器和应用之间存在着十分纠结的关系。下面就要把这些纠结的关系理清楚。 


    WSGI应用 


    WSGI应用其实就是一个callable的对象。举一个最简单的例子,假设存在如下的一个应用: 


    Python代码  收藏代码
    1. def application(environ, start_response):  
    2.   status = '200 OK'  
    3.   output = 'World!'  
    4.   response_headers = [('Content-type''text/plain'),  
    5.                       ('Content-Length', str(12)]  
    6.   write = start_response(status, response_headers)  
    7.   write('Hello ')  
    8.   return [output]  



    这个WSGI应用简单的可以用简陋来形容,但是他的确是一个功能完整的WSGI应用。只不过给人留下了太多的疑点,environ是什么?start_response是什么?为什么可以同时用write和return来返回内容? 



    对于这些疑问,不妨自己猜测一下他的作用。联想到CGI,那么environ可能就是一系列的环境变量,用来表示HTTP请求的信息,比如说method之类的。start_response,可能是接受HTTP response头信息,然后返回一个write函数,这个write函数可以把HTTP response的body返回给客户端。return自然是将HTTP response的body信息返回。不过这里的write和函数返回有什么区别?会不会是其实外围默认调用write对应用返回值进行处理?而且为什么应用的返回值是一个列表呢?说明肯定存在一个对应用执行结果的迭代输出过程。难道说他隐含的支持iterator或者generator吗? 



    等等,应用执行结果?一个应用既然是一个函数,说明肯定有一个对象去执行它,并且可以猜到,这个对象把environ和start_response传给应用,将应用的返回结果输出给客户端。那么这个对象是什么呢?自然就是WSGI容器了。 


    WSGI容器 


    先说说WSGI容器的来源,其实这是我自己编造出来的一个概念。来源就是JavaServlet容器。我个人理解两者有相似的地方,就顺手拿过来用了。 



    WSGI容器的作用,就是构建一个让WSGI应用成功执行的环境。成功执行,意味着需要传入正确的参数,以及正确处理返回的结果,还得把结果返回给客户端。 



    所以,WSGI容器的工作流程大致就是,用webserver规定的通信方式,能从webserver获得正确的request信息,封装好,传给WSGI应用执行,正确的返回response。 



    一般来说,WSGI容器必须依附于现有的webserver的技术才能实现,比如说CGI,FastCGI,或者是embed的模式。 



    下面利用CGI的方式编写一个最简单的WSGI容器。关于WSGI容器的协议官方文档并没有具体的说如何实现,只是介绍了一些需要约束的东西。具体内容看PEP3333中的协议。 


    Python代码  收藏代码
    1. #!/usr/bin/python  
    2. #encoding:utf8  
    3.   
    4. import cgi  
    5. import cgitb  
    6. import sys  
    7. import os  
    8.   
    9. #Make the environ argument  
    10. environ = {}  
    11. environ['REQUEST_METHOD'] = os.environ['REQUEST_METHOD']  
    12. environ['SCRIPT_NAME'] = os.environ['SCRIPT_NAME']  
    13. environ['PATH_INFO'] = os.environ['PATH_INFO']  
    14. environ['QUERY_STRING'] = os.environ['QUERY_STRING']  
    15. environ['CONTENT_TYPE'] = os.environ['CONTENT_TYPE']  
    16. environ['CONTENT_LENGTH'] = os.environ['CONTENT_LENGTH']  
    17. environ['SERVER_NAME'] = os.environ['SERVER_NAME']  
    18. environ['SERVER_PORT'] = os.environ['SERVER_PORT']  
    19. environ['SERVER_PROTOCOL'] = os.environ['SERVER_PROTOCOL']  
    20. environ['wsgi.version'] = (10)  
    21. environ['wsgi.url_scheme'] = 'http'  
    22. environ['wsgi.input']        = sys.stdin  
    23. environ['wsgi.errors']       = sys.stderr  
    24. environ['wsgi.multithread']  = False  
    25. environ['wsgi.multiprocess'] = True  
    26. environ['wsgi.run_once']     = True  
    27.   
    28.   
    29. #make the start_response argument  
    30. #注意,WSGI协议规定,如果没有body内容,是不能返回http response头信息的。  
    31. sent_header = False  
    32. res_status = None  
    33. res_headers = None  
    34.   
    35. def write(body):  
    36.     global sent_header  
    37.     if sent_header:  
    38.         sys.stdout.write(body)  
    39.     else:  
    40.         print res_status  
    41.         for k, v in res_headers:  
    42.             print k + ': ' + v  
    43.         print   
    44.         sys.stdout.write(body)  
    45.         sent_header = True  
    46.   
    47. def start_response(status, response_headers):  
    48.     global res_status  
    49.     global res_headers  
    50.     res_status = status  
    51.     res_headers = response_headers  
    52.     return write  
    53.   
    54. #here is the application  
    55.   def application(environ, start_response):  
    56.     status = '200 OK'  
    57.     output = 'World!'  
    58.     response_headers = [('Content-type''text/plain'),  
    59.                         ('Content-Length', str(12)]  
    60.     write = start_response(status, response_headers)  
    61.     write('Hello ')  
    62.     return [output]  
    63.   
    64. #here run the application  
    65. result = application(environ, start_response)  
    66. for value in result:   
    67.     write(value)  



    看吧。其实实现一个WSGI容器也不难。 



    不过我从WSGI容器的设计中可以看出WSGI的应用设计上面存在着一个重大的问题就是:为什么要提供两种方式返回数据?明明只有一个write函数,却既可以在application里面调用,又可以在容器中传输应用的返回值来调用。如果说让我来设计的话,直接把start_response给去掉了。就用application(environ)这个接口。传一个方法,然后返回值就是status, response_headers和一个字符串的列表。实际传输的方法全部隐藏了。用户只需要从environ中读取数据处理就行了。。 



    可喜的是,搜了一下貌似web3的标准里面应用的设计和我的想法类似。希望web3协议能早日普及。 


    Middleware中间件 


    中间件是一类特殊的程序,可以在容器和应用之间干一些坏事。。其实熟悉python的decorator的人就会发现,这和decoraotr没什么区别。 



    下面来实现一个route的简单middleware。 

    Python代码  收藏代码
    1. class Router(object):  
    2.     def __init__(self):  
    3.         self.path_info = {}  
    4.     def route(self, environ, start_response):  
    5.         application = self.path_info[environ['PATH_INFO']]  
    6.         return application(environ, start_response)  
    7.     def __call__(self, path):  
    8.         def wrapper(application):  
    9.             self.path_info[path] = application  
    10.         return wrapper  



    这就是一个很简单的路由功能的middleware。将上面那段wsgi容器的代码里面的应用修改成如下: 


    Python代码  收藏代码
    1. router = Router()  
    2.   
    3. #here is the application  
    4. @router('/hello')  
    5. def hello(environ, start_response):  
    6.     status = '200 OK'  
    7.     output = 'Hello'  
    8.     response_headers = [('Content-type''text/plain'),  
    9.                         ('Content-Length', str(len(output)))]  
    10.     write = start_response(status, response_headers)  
    11.     return [output]  
    12.  
    13. @router('/world')  
    14. def world(environ, start_response):  
    15.     status = '200 OK'  
    16.     output = 'World!'  
    17.     response_headers = [('Content-type''text/plain'),  
    18.                         ('Content-Length', str(len(output)))]  
    19.     write = start_response(status, response_headers)  
    20.     return [output]  
    21. #here run the application  
    22. result = router.route(environ, start_response)  
    23. for value in result:   
    24.     write(value)  



    这样,容器就会自动的根据访问的地址找到对应的app执行了。 


    延伸 


    写着写着,怎么越来越像一个框架了?看来Python开发框架真是简单。。 



    其实从另外一个角度去考虑。如果把application当作是一个运算单元。利用middleware调控IO和运算资源,那么利用WSGI组成一个分布式的系统。 



    好吧,全文完。


    作者:呆头龙
    出处:http://www.cnblogs.com/waniu/
    本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。
    该文章也同时发布在我的独立博客中-呆头龙
    欢迎园友讨论下自己的见解,及推荐更好资料。
    本文如对读者有帮助,还请多帮 下此文。
    谢谢!!! 

  • 相关阅读:
    查看linux文件目录的大小和文件夹包含的文件数
    linux下oracle 基本操作
    中宇 设备证书 导入配置
    as5 samba 图形配置
    QQ用户免费申领新型电子密码卡
    GRUB引导下进Linux单用户模式的三种方式
    Iptables 规则 一些简单实例和详细介绍
    Linux上JDK的 安装和卸载 详细 (转)
    Linux/Unix命令之Ethtool (设置千兆网卡速度及模式)
    oracle 下 创建数据库和用户界面操作 和sql 语句 下的创建方法
  • 原文地址:https://www.cnblogs.com/waniu/p/3045346.html
Copyright © 2020-2023  润新知