zoukankan      html  css  js  c++  java
  • [Python WEB开发] 使用WSGI开发类Flask框架 (二)

    WSGI     Web服务器网关接口

    WSGI主要规定了Web服务器如何与Web应用程序进行通信,以及如何将Web应用程序链接在一起来处理一个请求。

     

    wsgiref  Python中的WSGI参考模块

    一、WSGI 应用程序端:

    1、 根据WSGI定义,应用程序应该是可调用对象

    2、该可调用对象必须有两个固定参数:environ、start_response

    一个是含有服务器环境变量的字典,另一个是可调用对象,该对象使用HTTP状态码和会返回给客户端的HTTP头来初始化响应

    environ 变量包含一些熟悉的环境变量,如HTTP_HOST,HTTP_USER_AGENT,REMOTE_ADDR,REQUEST_METHOD,SERVER_PORT,部分如下:

    Hello world!
    
    GATEWAY_INTERFACE = 'CGI/1.1'
    HTTP_ACCEPT = 'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8'
    HTTP_ACCEPT_ENCODING = 'gzip, deflate, br'
    HTTP_ACCEPT_LANGUAGE = 'zh-CN,zh;q=0.9,en;q=0.8'
    HTTP_CONNECTION = 'keep-alive'
    HTTP_HOST = '127.0.0.1:9999'
    HTTP_USER_AGENT = 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_13_1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/63.0.3239.84 Safari/537.36'
    QUERY_STRING = ''
    REMOTE_ADDR = '127.0.0.1'
    REQUEST_METHOD = 'GET'
    SERVER_PORT = '9999'
    SERVER_PROTOCOL = 'HTTP/1.1'
    SERVER_SOFTWARE = 'WSGIServer/0.2'

    3、这个可调用对象必须返回一个可迭代对象用于组成响应

    res_str = b'github.com
    '
    
    # 函数实现
    def application(environ, start_response):
    	return [res_str]
    
    # 类实现
    class Application:
    	def __init__(self, environ, start_response):
    		pass
    	def __iter__(self):
    		yield res_str
    
    # 类实现
    class Application:
    	def __call__(self, environ, start_response):
    		retur [res_str]
    

      

    wsgiref参考库中有以下几个子模块:

    * util -- 一些有用的功能和包装

    * headers -- 管理响应头

    * handlers -- 为server/gateway实现如何处理的基类

    * simple_server -- 实现一个简单的WSGI HTTP服务器

    * validate -- 位于应用程序和server之间检测错误的校验包装

    二、WSGI HTTP Server端的使用

    1. 启动一个简单的WSGI HTTP Server:

    # 简单web 1
    from wsgiref.simple_server import make_server
    
    
    def demo_app(environ, start_response): #copy自simple_server模块
        from io import StringIO
        stdout = StringIO()
        print("Hello world!", file=stdout)
        print(file=stdout)
        h = sorted(environ.items())
        for k, v in h:
            print(k, '=', repr(v), file=stdout)
        start_response("200 OK", [('Content-Type', 'text/plain; charset=utf-8')])
        return [stdout.getvalue().encode("utf-8")]
    
    
    ip = '127.0.0.1'
    port = 9999
    server = make_server(ip, port, demo_app)
    server.serve_forever()
    
    server.server_close()
    
    
    
    
    
    wsgiref.simple_server.make_server(host, port, app, server_class=WSGIServer, handler_class=WSGIRequestHandler)
    通过这个函数可以启动一个用于简单访问的WSGI参考服务器,必须传入host, port, app三个参数。

    在运行这段程序之后,就已经实现了一个监听在9999端口的webServer,下面是服务端运行状态和浏览器中访问结果:

    访问 http://127.0.0.1:9999/
    
    #server端运行状态:
    127.0.0.1 - - [26/Dec/2017 15:01:13] "GET / HTTP/1.1" 200 2128
    127.0.0.1 - - [26/Dec/2017 15:01:13] "GET /favicon.ico HTTP/1.1" 200 2096
    
    
    #浏览器访问结果:
    Hello world!
    
    Apple_PubSub_Socket_Render = '/private/tmp/com.apple.launchd.Gx10g4snot/Render'
    CLICOLOR = '1'
    CONTENT_LENGTH = ''
    CONTENT_TYPE = 'text/plain'
    GATEWAY_INTERFACE = 'CGI/1.1'
    GREP_OPTIONS = '--color=auto'
    HOME = '/Users/ihoney'
    HTTP_ACCEPT = 'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8'
    HTTP_ACCEPT_ENCODING = 'gzip, deflate, br'
    HTTP_ACCEPT_LANGUAGE = 'zh-CN,zh;q=0.9,en;q=0.8'
    HTTP_CONNECTION = 'keep-alive'
    HTTP_HOST = '127.0.0.1:9999'
    HTTP_UPGRADE_INSECURE_REQUESTS = '1'
    HTTP_USER_AGENT = 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_13_1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/63.0.3239.84 Safari/537.36'
    LC_CTYPE = 'zh_CN.UTF-8'
    ......
    

      

    2. 自定义响应的网页内容:

    # 简单web 2
    from wsgiref.simple_server import make_server
    
    def application(environ:dict,start_response):
        # print(type(environ),environ)
        html = "<h1>北京欢迎你</h1>"
        # start_response("200 OK", [('Content-Type','text/plain; charset=utf-8')]) #文本格式
        start_response("200 OK", [('Content-Type', 'text/html; charset=utf-8')]) #html格式
    
        return [html.encode()]
    
    
    ip = '127.0.0.1'
    port =9999
    server = make_server(ip,port,application)
    server.serve_forever()
    
    server.server_close()
    
    
    #运行结果:
    127.0.0.1 - - [26/Dec/2017 15:38:55] "GET / HTTP/1.1" 200 24
    127.0.0.1 - - [26/Dec/2017 15:38:55] "GET /favicon.ico HTTP/1.1" 200 24
    

      浏览器访问结果:

      simple_server 只是参考,不可用于生产环境。

    三、QUERY_STRING 查询字符串的解析

    1. 使用cgi模块:

    # 简单web 3 使用cgi模块解析query_string
    import cgi
    from wsgiref.simple_server import make_server
    
    def application(environ:dict,start_response):
        qstr = environ.get("QUERY_STRING")
        print(qstr)
        # ?id=5&name=ihoney&age=18,19
        print(cgi.parse_qs(qstr)) #字典,value为列表类型
        print(cgi.parse_qsl(qstr)) #二元组列表
    
        html = "<h1>北京欢迎你</h1>"
        start_response("200 OK", [('Content-Type', 'text/html; charset=utf-8')])
        return [html.encode()]
    
    
    ip = '127.0.0.1'
    port =9999
    server = make_server(ip,port,application)
    server.serve_forever()
    
    server.server_close()
    
    #浏览器访问http://127.0.0.1:9999/?id=5&name=ihoney&age=18,19
    #运行结果:
    127.0.0.1 - - [26/Dec/2017 15:51:17] "GET /?id=5&name=ihoney&age=18,19 HTTP/1.1" 200 24
    id=5&name=ihoney&age=18,19
    {'age': ['18,19'], 'name': ['ihoney'], 'id': ['5']}
    [('id', '5'), ('name', 'ihoney'), ('age', '18,19')]
    

      在写的时候IDE工具就会提示CGI模块已经过期了,建议使用urllib库。

    2. 使用urllib库

    # 简单web 4 使用urllib模块解析query_string
    from urllib import parse
    from wsgiref.simple_server import make_server
    
    def application(environ:dict,start_response):
        qstr = environ.get("QUERY_STRING")
        print(qstr)
        # ?id=5&name=ihoney&age=18,19
        print(parse.parse_qs(qstr)) #字典,value为列表类型
        print(parse.parse_qsl(qstr)) #二元组列表
    
        html = "<h1>北京欢迎你</h1>"
        start_response("200 OK", [('Content-Type', 'text/html; charset=utf-8')])
        return [html.encode()]
    
    
    ip = '127.0.0.1'
    port =9999
    server = make_server(ip,port,application)
    server.serve_forever()
    
    server.server_close()
    
    #浏览器访问:http://127.0.0.1:9999/?id=5&name=ihoney&age=18,19
    #运行结果:
    id=5&name=ihoney&age=18,19
    {'id': ['5'], 'age': ['18,19'], 'name': ['ihoney']}
    [('id', '5'), ('name', 'ihoney'), ('age', '18,19')]
    127.0.0.1 - - [26/Dec/2017 15:58:40] "GET /?id=5&name=ihoney&age=18,19 HTTP/1.1" 200 24
    

      

    3. 使用第三方库webob

    pip3 install webob

    第三方库webob可以把环境数据的解析封装成对象,使用时直接调用。

    webob.request module:

    req.method: 请求方法
    req.GET: 返回一个类字典对象,GET请求方式提交的查询字符串的二元组格式。
    req.POST: 也返回一个类字典对象,POST请求正文的查询字符串。一般是表单提交
    req.params: 一个类字典对象,包括GET和POST的所有查询字符串。
    req.body: POST提交的请求正文的内容
    req.cookies: 字典格式的所有cookie
    req.headers: 包含所有请求头的字典,不区分大小写

    web.response module:

    response.status: 响应码加描述信息,如"200 OK"
    response.status_code: 响应码,只有 "200"
    response.headerlist: 所有响应头的列表,如"[('Content-Type', 'text/html')]"
    response.app_iter: 一个可迭代对象(如列表和生成器),用于产生响应的内容
    response.content_type: 响应内容的类型,如"text/html","text/plain"
    response.charset: 字符集编码类型
    response.set_cookie(name=None, value='', max_age=None, path='/', domain=None, secure=False, httponly=False, comment=None, overwrite=False): 为客户端设置一个cookie,max_age控制cookie的有效时长,以秒为单位
    response.delete_cookie(key, path='/', domain=None): 从客户端删除一个cookie
    response.cache_expires(seconds=0): 设置这个响应的缓存时间,单位为秒,如果seconds为0表示这个响应不缓存
    response(environ, start_response): 返回对象是一个WSGI应用程序的响应

    3.1 webob.Request

    #简单web 5,使用第三方库webob解析
    from wsgiref.simple_server import make_server
    from webob import Request, Response
    
    
    def application(environ: dict, start_response):
        request = Request(environ)
        print(request.method)
        print(request.path)
        print(request.GET)
        print(request.POST)
        print(request.params)
        print(request.query_string)
    
        html = "<h1>北京欢迎你</h1>"
        start_response("200 OK", [('Content-Type', 'text/html; charset=utf-8')])
        return [html.encode()]
    
    
    ip = '127.0.0.1'
    port = 9999
    server = make_server(ip, port, application)
    server.serve_forever()
    
    server.server_close()
    
    #浏览器访问:http://127.0.0.1:9999/index.html?id=5&name=tom,jerry&age=17&age=18,19
    #运行结果:
    GET
    /index.html
    GET([('id', '5'), ('name', 'tom,jerry'), ('age', '17'), ('age', '18,19')])
    <NoVars: Not a form request>
    NestedMultiDict([('id', '5'), ('name', 'tom,jerry'), ('age', '17'), ('age', '18,19')])
    id=5&name=tom,jerry&age=17&age=18,19
    127.0.0.1 - - [26/Dec/2017 16:51:41] "GET /index.html?id=5&name=tom,jerry&age=17&age=18,19 HTTP/1.1" 200 24
    

      

    3.2 webob.Resphone

    #
    from wsgiref.simple_server import make_server
    from webob import Request, Response
    
    
    def application(environ: dict, start_response):
        res = Response("<h1>北京欢迎你</h1>")
        return res(environ,start_response)   #__call__
    
    
    ip = '127.0.0.1'
    port = 9999
    server = make_server(ip, port, application)
    server.serve_forever()
    
    server.server_close()
    
    #浏览器访问:http://127.0.0.1:9999/index.html?id=5&name=tom,jerry&age=17&age=18,19
    #运行结果:
    127.0.0.1 - - [26/Dec/2017 18:08:03] "GET /index.html?id=5&name=tom,jerry&age=17&age=18,19 HTTP/1.1" 200 24
    

      

    3.3 MultiDict

    MultiDict允许一个key存好几个值。

    Request.GET、Request.POST 都是MultiDict字典

    # multidict
    from webob.multidict import MultiDict
    
    md = MultiDict()
    md[1] = 'b'
    md.add(1,'a')
    
    print(md.get(1)) #只返回一个值
    print(md.getall(1))
    # print(md.getone(1)) #要求key的value只能有一个,否则抛KeyError异常
    print(md.get('c')) #不存在返回默认值None
    #运行结果:
    a
    ['b', 'a']
    None
    

     

    3.4 webob.dec.wsgify 装饰器

    官方文档:https://docs.pylonsproject.org/projects/webob/en/stable/api/dec.html

    功能:将一个函数变成一个WSGI应用程序

    使用举例:

    from webob.dec import wsgify
    
    @wsgify
        def myfunc(req):
            return webob.Response('hey there')
    

      wsgi装饰器装饰的函数应该具有一个参数,这个参数是webob.Request类型,是对字典environ的对象化后的实例。返回值必须是一个webob.Respnose类型,所以在函数中应该创建一个webob.Response类型的实例。

    # wsgify
    from wsgiref.simple_server import make_server
    from webob import Request, Response,dec
    
    
    def application(environ: dict, start_response):
        res = Response("<h1>北京欢迎你</h1>")
        #200 OK
    	#[('Content-Type', 'text/html; charset=UTF-8'), ('Content-Length', '0')]
        return res(environ,start_response)   #__call__
    
    @dec.wsgify
    def app(request:Request) -> Response:
    	return Response("<h1>Welcome to BeiJing</h1>")
    
    
    ip = '127.0.0.1'
    port = 9999
    # server = make_server(ip, port, application)
    server = make_server(ip, port, app)
    server.serve_forever()
    
    server.server_close()
    
    #浏览器访问:http://127.0.0.1:9999/index.html?id=5&name=tom,jerry&age=17&age=18,19
    #运行结果:
    127.0.0.1 - - [26/Dec/2017 20:25:14] "GET /index.html?id=5&name=tom,jerry&age=17&age=18,19 HTTP/1.1" 200 27
    127.0.0.1 - - [26/Dec/2017 20:25:14] "GET /favicon.ico HTTP/1.1" 200 27
    

      

     改进:

    from wsgiref.simple_server import make_server
    from webob import Request, Response,dec
    
    
    @dec.wsgify
    def app(request:Request) -> Response:
    	return Response("<h1>Welcome to BeiJing.</h1>")
    
    if __name__ == "__main__":
    	ip = '127.0.0.1'
    	port = 9999
    	server = make_server(ip, port, app)
    	try:
    		server.serve_forever()
    	except KeyBoardInterrupt:
    		pass
    	finally:
    		server.server_close()
    

      

    3.5  webob.Response SourceCode

        def __call__(self, environ, start_response):
            """
            WSGI application interface
            """
            if self.conditional_response:
                return self.conditional_response_app(environ, start_response)
    
            headerlist = self._abs_headerlist(environ)
    
            start_response(self.status, headerlist)
            if environ['REQUEST_METHOD'] == 'HEAD':
                # Special case here...
                return EmptyResponse(self._app_iter)
            return self._app_iter
    

      

    Chrome插件Postman POST 提交:

    总结:

    本文简单介绍了WSGI、WSGI HTTP Server、查询字符串的处理、第三方库webob的一些用法。

      

  • 相关阅读:
    WCF中NetTCp配置
    生产者消费者模式
    MVC 引擎优化
    Wcf
    MongoDB运用
    Sock基础
    WebService
    线程
    委托
    特性
  • 原文地址:https://www.cnblogs.com/i-honey/p/8110848.html
Copyright © 2011-2022 走看看