当前位置: 首页 > 工具软件 > quixote > 使用案例 >

gunicorn.werkzeug和quixote的结合使用-框架篇

全弘深
2023-12-01

开发python web站点时,本地用gunicorn,werkzeug和quixote来搭建环境。


其实这是两种方式: 

①gunicorn+quixote

②werkzeug+quixote


Gunicorn是一个python wsgi的 web-server服务器

werkeug则是一个wsgi的lib工具。


quixote:通俗来说,主要功能就是转发请求,模板渲染神马功能。 主要死转发请求!!


Ok,那理解起来就很简单了: gunicorn和werkzeug负责启动服务,监听请求,并把请求转给quixote。 quixote负责寻找合适的fun或model来处理这个请求.

(当然后续还有模板渲染,暂时先不考虑,我们先把前边请求到来的流程搞定.)


------------------------------------------------------------------------------------------

gunicorn和werkzeug都是wsgi规范的。那我们所有操作都围绕该规范来就是了.这个类比一下php,php中一般都是所有请求都转到一个index.php中处理。

同样的;wsgi也是,其是所有请求到来时,都转到一个函数中去处理。这个函数是所有请求的统一入口!


ok,为了实现以上几点:

①你要定义一个入口函数。

②你要把所有请求都转到该入口函数来处理。


如果全部用面向对象方式来做:

①入口函数我们封装到一个类中,那为了可以用类名直接调用这个函数,我们需要将之封装到__call__()函数中.

class RealApp(object):
    def __init__(self, rns):
        pass

    def cyrildou(environ, start_response):
        data = "Hello, World!\n"
        start_response("200 OK", [
        ("Content-Type", "text/plain"),
        ("Content-Length", str(len(data)))
        ])
        return iter([data])

    def __call__(self, env, start_response):
        cyrildou(env, start_response)

如上:其实我们实际处理请求都放在cyrildou()这个函数中,但是为了可以直接用类名给调用到,所以需要将之放到__call__函数中。


②如何将所有请求都转到这个函数中呢?gunicorn和werkzeug都有自己的封装。 但是不管如何封装,都得符合wsgi规范不是?

其实规范也简单,对于上述①中的入口函数,其__call__()函数必须接收env和start_response这两个参数。


说白一点:第一个参数env是用户发送请求过来的所有请求信息和环境信息。 而start_response则是一个回调函数,其可以理解成:是控制请求传入的web-server的一个函数,用于获取返回的header。 所以我们在这个处理函数__call__中需要通过这个start_response()函数来返回请求的http header···


那么:按照协议,所有的wsgi的web-server或者lib都得符合这种样式来提供给后边的app用。


总结一下:

①我们要建自己的app,要求其接收2个参数,一个是传入info,一个是设置response header的回调函数(这个函数也是开启返回的函数,说白了:在处理请求后只有先调用该函数才算是开始返回信息!)

②任何web-server或者lib都是调用上边①中的app来处理所有请求的.

---------------------------------------------------------------------------------------------

ok,那下一步看一下gunicorn和werkzeug是如何转发请求的.

这里就不分析源码了,只说明如何调用。


①对于werkzeug:

    非常简单,只需要调用run_simple()函数即可


demo_wsgi.py

application = RealApp()
try:
    from werkzeug import run_simple
except ImportError:                              
    raise Exception("please install werkzeug")

run_simple(devsite, port, application, use_reloader=True, use_debugger=True,
                use_evalex=True, processes=2, extra_files=extra_files)


最关键的几个参数:参数1,2指定了监听的domian/ip 和port。 而参数3则指定了处理请求的入口app(这里就是上边我们创建的app类对象)

所以:通过参数3就将请求和处理函数给关联到了一起。

       如上之后,我们就可以执行命令 `python demo_wsgi.py`来执行启动监听了.命令行中就可以看到各种info啦·····


②对于gunicorn

   我们也用面向对象的方式来做。这里直接贴代码了

class DemoApplication(Application):

    #初始化时调用
    def init(self, parser, opts, args): 
        self.app_uri = args[0]
        sys.path.insert(0, os.getcwd())
            
    #运行时调用
    def load(self):
        try:
            #important!
            return util.import_app(self.app_uri)
        except Exception, e:
            exc = sys.exc_info()
            return make_tb_echo_app(exc)                                                                                                      




def main():
    sys.argv += ['-c', os.path.join(os.path.dirname(__file__), 'gunicorn_config.py'),'demo_wsgi',]
    DemoApplication("%prog [OPTIONS] APP_MODULE").run()

if __name__ == '__main__':
    main()

本质上:我们就是从Application继承一个自己的Application类,而后调用run()函数运行之即可。

当然啦:你可能会有很多疑问,先别着急,一步步来。

这个自己的Application类中最重要的2个函数是init()和load()函数:其中init()是创建这个类对象时调用的,而load则是执行run()函数时调用的。

所以执行run()函数来启动之,其对我们而言:就是调用了load()函数,所以我们要做的就是在load()函数中来导入app即可! 这样Application会将请求都转到这个app中去执行!!


说白话一点:我们要做的仅仅是重新实现Application类的load()函数,让其返回实际处理请求的application即可!(这里的application就是前边RealApp)


那还有问题:

①如何找到处理请求的application的呢?

②配置如何加载的?监听哪个端口?哪个域名?。。。


对于①:

我们导入application是用的gunicorn中的系统函数:util.import_app(model)

这个函数顾名思义就是:导入一个app,而参数就是一个模块的名字. 其在实现上就是用的__import__()方法。所以实际上这里写的应该就是最终python脚本的模块名字。而后gunicorn就会来到这个python脚本中去查找application的变量,并将之作为处理请求的app来加载使用!


所以这里注意了:使用该种方式时:最终处请求的app的对象名字必须是:application!!!! 否则gunicorn会找不到!!!


对于②

这个是通过加参数 -c filename来实现的!

这个配置文件filename也应该是一个python脚本,其中含有一些指定的变量和对应的你指定的值.举个例子如下:

daemon = False
debug = True
loglevel = 'error'
workers = 2
keepalive = 10                                                                                                                                
timeout = 600
bind = '0.0.0.0:%s' % port
proc_name = 'demo %s:%s' % (devsite, port)


============================================================================

那上边通过这两种不同的组合,我们都已经在一个函数中接收到用户的请求啦,下一步是处理请求。在上边例子中我们给出了一个简单的处理:不管任何请求到来,都返回200, hello world!


但是实际上不可能这样的:肯定要先解析用户传过来的请求,依据此来决定如何处理

那就要首先拿到传过来的信息。

我们知道:处理函数的样式如下:

def dispatch(environ, start_response):
其中environ是请求和环境信息的集合。从中我们如何拿到我们需要需要的信息呢?

最简单的:用户请求如下: www.baidu.com/a/b/c?curosr=123   是一个post请求,body中信息为 file=amc 

那我们如何拿到GET[],POST],HEADER[]等信息呢?php简单,就有这些系统全局数组。但是这里木有貌似··


另外一个问题:我们如何转发请求呢? /a/b/c /a/b/s 应该是不同的path,对应不同请求···这里用的就是quixote来转发到不同函数来处理啦!!O(∩_∩)O哈哈~

类比php,application就类比于index.php入口脚本,而后边的实际处理函数就是php的各个实际action!!


那当我们进入application()这个请求处理入口函数之后,下一步就该让quixote接手了。应该让它来把不同的请求转到不同的函数中去处理!

那quixote需要做哪些工作呢?

①其会解析wsgi穿过的environ信息,将之封装为Request对象,

②将①创建的Request对象交给后边的实际处理函数处理并返回信息给用户即可!!


那quixote是如何实现上述2点的呢?直接贴一段代码吧:

def __call__(self, env, start_response):
        quixote.publish._publisher = self.publisher
        input = env['wsgi.input']
        request = self.publisher.create_request(input, env)
        output = self.publisher.process_request(request, env)
        response = request.response
       
        status = "%03d %s" % (response.status_code, response.reason_phrase)
        headers = response.generate_headers()
        start_response(status, headers)
        return str(output)
嗯 说白了:

我们首先拿到用户的输入 input=env['wsgi.input']

而后create_request来封装生成一个request对象,而后process_request处理之获取返回的body. 

最后就调用回调函数start_response()来启动返回的过程并返回http header,最后返回http body


从上可见:quixote功能实现最重要的是使用了quixote中Publisher这个类对象,其负责处理所有请求的转发等~~

那我们在使用时:只需要创建一个Publisher对象,并按照指定格式调用即可! 而创建Publisher只需要传入唯一的一个参数:指定root namespace!!!

说白了就是指定一下controller的目录!



=============================================================


如上之后:我们框架就搭建完毕啦,下一步就是只负责处理各种请求即可!!!




 类似资料: