一个UWSGI的例子

时间:2025-02-20 21:34:20

摘要:uwsgi执行顺序:启动master进程,执行python脚本的公共代码(import同一层)。然后生成worker进程,uwsgi.post_fork_hook=init_functions,初始化生成work子进程。然后请求来临时,每个子进程执行application代码

配置文件ini:

[uwsgi]

socket=192.168.62.20:8001

http=192.168.62.20:8003

master=true

pythonpath=../

module=breapi

processes=12

listen=256

disable-logging=true

daemonize=uwsgi.log

pidfile=uwsgi.pid

vacuum=true

harakiri=5

buffer-size=36384

stats=127.0.0.1:1717

原文http://pythonpaste.org/do-it-yourself-framework.html,http://blog.xsudo.com/archives/530.html

一个用来diy的框架

什么是WSGI

wsgi是一个在web服务器和应用程序app之间统一的接口,这样来规范app和web server,说的简单一点就是规范的接受web请求(request),但是不仅如此

1 你在类似CGI环境里分发传递,意味着数据可以在登录用户安全传递

2 在类似CGI环境里传递更多的上下文信息,脚本名称和路径

3 你可以扩展自身的wsgi环境,并允许回调,扩展信息并插入python对象,任何你想添加的却不能在http头里添加的

wsgi不仅仅是在web和应用之间,可以用在交互的每个层面 ,这使得应用app变成lib库,可以很好的扩展和重用

写一个wsgi应用

我们来简单的写一个wsgi的应用

简短的一个摘要

1 一个简单的wsgi应用就是一个可调用的对象比如一个function,有两个参数environ 和 start_response

2 环境environment是一个字典,python的标准字典有一些传统的http参数比如REQUEST_METHOD和HTTP_POST

3 environment可以包含一些特别的键wsgi.input 像post请求里的输入流

4 start_response是一个返回客户端结果的方法,需要提供http的状态和headers

start_response 是一个接受两个必需的固定参数和一个可选参数的 callable 对象. 为便于说明, 我们把这三个参数分别命名为: status, response_headers, 和 exc_info, 当然你也可以给他们起其他名字. 应用程序必需使用固定参数调用 start_response (比如:

start_response(status,response_headers)) , 参数 status 是一个形如 “999 Message here” 的表示状态的字符串。而 response_headers 参数是一个描述 http 响应头的列表, 其中每一个元素都是像 (header_name,header_value) 这样的元组。可选的 exc_info 参数会在后面的 start_response() callable 和 错误处理 两节中进行描述,该参数只有在应用程序产生了错误并希望在浏览器上显示错误信息的时候才用得上。

5 最后应用返回一个含有迭代器的response(通常是一个字符串列表)

例子

1

2

3
def app(environ, start_response):

    start_response('200
OK', [('content-type', 'text/html')])

    return ['Hello
world!']

然后更简单的一个server来运行这个app

1

2

3
if __name__ == '__main__':

    from paste import httpserver

    httpserver.serve(app, host='127.0.0.1', port='8080')

这样就可以在本机用8080端口来请求他了

一个带有互动的例子

1

2

3

4

5

6

7

8

9

10
from paste.request import parse_formvars

def app(environ, start_response):

    fields = parse_formvars(environ)

    if environ['REQUEST_METHOD'] == 'POST':

        start_response('200
OK', [('content-type', 'text/html')])

        return ['Hello,
', fields['name'], '!']

    else:

        start_response('200
OK', [('content-type', 'text/html')])

        return ['<form
method="POST">Name: <input type="text" '

                'name="name"><input type="submit"></form>']

这些都很简单,我们需要更多的页面,测试还有运行环境

对象发布 Object publishing

一个典型的对象发布 转换 ‘/’ 为 ‘..’

/articles/view?id=5 转换为 root.articles.view(id=5)

我们必须启动相应的对象

1

2

3

4

5

6
class ObjectPublisher(object):

def __init__(self, root):

        self.root = root

def __call__(self, environ, start_response):

        ...

app = ObjectPublisher(my_root_object)

我们重写__call__方法来实现一个callable ObjectPublisher ,就像一个wsgi的方法,现在我们要做的事情就是传递environ进去,调用它,然后返回结果response

路径

WSGI会把请求的路径放到两个变量SCRIPT_NAME和PATH_INFO里

SCRIPT_NAME就是我们将要到达的地方脚本名称,PATH_INFO就是请求的路径,这是框架寻找对象的方式

我们来实现__call__方法

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31
def __call__(self, environ, start_response):

    fields = parse_formvars(environ)

    obj = self.find_object(self.root, environ)

    response_body = obj(**fields.mixed())

    start_response('200
OK', [('content-type', 'text/html')])

    return [response_body]

def find_object(self, obj, environ):

    path_info = environ.get('PATH_INFO', '')

    if not path_info or path_info == '/':

        # We've arrived!

        return obj

    # PATH_INFO always starts with a /, so we'll get rid of it:

    path_info = path_info.lstrip('/')

    # Then split the path into the "next" chunk, and everything

    # after it ("rest"):

    parts = path_info.split('/', 1)

    next = parts[0]

    if len(parts) == 1:

        rest = ''

    else:

        rest = '/' +
parts[1]

    # Hide private methods/attributes:

    assert not next.startswith('_')

    # Now we get the attribute; getattr(a, 'b') is equivalent

    # to a.b...

    next_obj = getattr(obj, next)

    # Now fix up SCRIPT_NAME and PATH_INFO...

    environ['SCRIPT_NAME'] += '/' +
next

    environ['PATH_INFO'] = rest

    # and now parse the remaining part of the URL...

    return self.find_object(next_obj, environ)

现在用ObjectPublisher来包装应用,并放到一个包中去

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16
from objectpub import ObjectPublisher

class Root(object):

# The "index" method:

    def __call__(self):

        return '''

        <form action="welcome">

        Name: <input type="text" name="name">

        <input type="submit">

        </form>

        '''

def welcome(self, name):

        return 'Hello
%s!' % name

app = ObjectPublisher(Root())

if __name__ == '__main__':

    from paste import httpserver

    httpserver.serve(app, host='127.0.0.1', port='8080')

需要注意的是,本来WSGI是要传入一个app的方法的,于是这里用了对象,并实现了__call__方法,结果是一样的

我们会发现缺少很多东西,比较特殊的我们没有地方去设置输出的headers,还有request里的信息是很少的

1

2

3

4

5

6

7

8

9

10

11
# This is just a dictionary-like object that has case-

# insensitive keys:

from paste.response import HeaderDict

class Request(object):

    def __init__(self, environ):

        self.environ = environ

        self.fields = parse_formvars(environ)

class Response(object):

    def __init__(self):

        self.headers = HeaderDict(

            {'content-type': 'text/html'})

我们不想改变方法,但是不能让request对象和response对象在全局的变量里,因为我们想要线程安全

修改__call__方法为

1

2

3

4

5

6

7

8

9

10

11
import threading

webinfo = threading.local()

class ObjectPublisher(object):

    ...

def __call__(self, environ, start_response):

        webinfo.request = Request(environ)

        webinfo.response = Response()

        obj = self.find_object(self.root, environ)

        response_body = obj(**dict(webinfo.request.fields))

        start_response('200
OK', webinfo.response.headers.items())

        return [response_body]

现在在我们的方法里可以这么做

1

2

3
class Root:

    def rss(self):

        webinfo.response.headers['content-type'] = 'text/xml'