欢迎各位兄弟 发布技术文章

这里的技术是共享的

You are here

关于wsgi的理解(web服务器网关接口)

shiping1 的头像

用Python写一个简单的Web框架

一、概述

在Python中,WSGI(Web Server Gateway Interface)定义了Web服务器与Web应用(或Web框架)之间的标准接口。在WSGI的规范下,各种各样的Web服务器和Web框架都可以很好的交互。

由于WSGI的存在,用Python写一个简单的Web框架也变得非常容易。然而,同很多其他的强大软件一样,要实现一个功能丰富、健壮高效的Web框架并非易事;如果您打算这么做,可能使用一个现成的Web框架(如 DjangoTornadoweb.py 等)会是更合适的选择。

本文尝试写一个类似web.py的Web框架。好吧,我承认我夸大其辞了:首先,web.py并不简单;其次,本文只重点实现了 URL调度(URL dispatch)部分。

二、从demo_app开始

首先,作为一个初步体验,我们可以借助 wsgiref.simple_server 来搭建一个简单无比(trivial)的Web应用:

#!/usr/bin/env python
# -*- coding: utf-8 -*-

from wsgiref.simple_server import make_server, demo_app

httpd = make_server('', 8086, demo_app)
sa = httpd.socket.getsockname()
print 'http://{0}:{1}/'.format(*sa)

# Respond to requests until process is killed
httpd.serve_forever()

运行脚本:

$ python code.py
http://0.0.0.0:8086/

打开浏览器,输入http://0.0.0.0:8086/后可以看到:一行"Hello world!" 和 众多环境变量值。

三、WSGI中的application

WSGI中规定:application是一个 可调用对象(callable object),它接受 environ  start_response 两个参数,并返回一个 字符串迭代对象

其中,可调用对象 包括 函数方法 或者 具有__call__方法的 实例environ 是一个字典对象,包括CGI风格的环境变量(CGI-style environment variables)和 WSGI必需的变量(WSGI-required variables);start_response 是一个可调用对象,它接受两个 常规参数(status,response_headers)和 一个 默认参数(exc_info);字符串迭代对象 可以是 字符串列表生成器函数 或者 具有__iter__方法的可迭代实例。更多细节参考 Specification Details

The Application/Framework Side 中给出了一个典型的application实现:

#!/usr/bin/env python
# -*- coding: utf-8 -*-

"""application.py"""

def simple_app(environ, start_response):
    """Simplest possible application object"""
    status = '200 OK'
    response_headers = [('Content-type', 'text/plain')]
    start_response(status, response_headers)
    return ['Hello world!\n']

现在用simple_app来替换demo_app:

#!/usr/bin/env python
# -*- coding: utf-8 -*-

"""code.py"""

from wsgiref.simple_server import make_server
from application import simple_app as app

if __name__ == '__main__':
    httpd = make_server('', 8086, app)
    sa = httpd.socket.getsockname()
    print 'http://{0}:{1}/'.format(*sa)

    # Respond to requests until process is killed
    httpd.serve_forever()

运行脚本code.py后,访问http://0.0.0.0:8086/就可以看到那行熟悉的句子:Hello world!

四、区分URL

倒腾了一阵子后,您会发现不管如何改变URL中的path部分,得到的响应都是一样的。因为simple_app只识别host+port部分。

为了对URL中的path部分进行区分处理,需要修改application.py的实现。

首先,改用  来实现application:

#!/usr/bin/env python
# -*- coding: utf-8 -*-

"""application.py"""

class my_app:
    def __init__(self, environ, start_response):
        self.environ = environ
        self.start = start_response

    def __iter__(self):
        status = '200 OK'
        response_headers = [('Content-type', 'text/plain')]
        self.start(status, response_headers)
        yield "Hello world!\n"

然后,增加对URL中path部分的区分处理:

#!/usr/bin/env python
# -*- coding: utf-8 -*-

"""application.py"""

class my_app:
    def __init__(self, environ, start_response):
        self.environ = environ
        self.start = start_response

    def __iter__(self):
        path = self.environ['PATH_INFO']
        if path == "/":
            return self.GET_index()
        elif path == "/hello":
            return self.GET_hello()
        else:
            return self.notfound()

    def GET_index(self):
        status = '200 OK'
        response_headers = [('Content-type', 'text/plain')]
        self.start(status, response_headers)
        yield "Welcome!\n"

    def GET_hello(self):
        status = '200 OK'
        response_headers = [('Content-type', 'text/plain')]
        self.start(status, response_headers)
        yield "Hello world!\n"

    def notfound(self):
        status = '404 Not Found'
        response_headers = [('Content-type', 'text/plain')]
        self.start(status, response_headers)
        yield "Not Found\n"

修改code.py中的from application import simple_app as app,用my_app来替换simple_app后即可体验效果。

五、重构

上面的代码虽然奏效,但是在编码风格和灵活性方面有很多问题,下面逐步对其进行重构。

1、正则匹配URL

消除URL硬编码,增加URL调度的灵活性:

#!/usr/bin/env python
# -*- coding: utf-8 -*-

"""application.py"""

import re ##########修改点

class my_app:

    urls = (
        ("/", "index"),
        ("/hello/(.*)", "hello"),
    ) ##########修改点

    def __init__(self, environ, start_response):
        self.environ = environ
        self.start = start_response

    def __iter__(self): ##########修改点
        path = self.environ['PATH_INFO']
        method = self.environ['REQUEST_METHOD']

        for pattern, name in self.urls:
            m = re.match('^' + pattern + '$', path)
            if m:
                # pass the matched groups as arguments to the function
                args = m.groups()
                funcname = method.upper() + '_' + name
                if hasattr(self, funcname):
                    func = getattr(self, funcname)
                    return func(*args)

        return self.notfound()

    def GET_index(self):
        status = '200 OK'
        response_headers = [('Content-type', 'text/plain')]
        self.start(status, response_headers)
        yield "Welcome!\n"

    def GET_hello(self, name): ##########修改点
        status = '200 OK'
        response_headers = [('Content-type', 'text/plain')]
        self.start(status, response_headers)
        yield "Hello %s!\n" % name

    def notfound(self):
        status = '404 Not Found'
        response_headers = [('Content-type', 'text/plain')]
        self.start(status, response_headers)
        yield "Not Found\n"

2、DRY

消除GET_*方法中的重复代码,并且允许它们返回字符串:

#!/usr/bin/env python
# -*- coding: utf-8 -*-

"""application.py"""

import re

class my_app:

    urls = (
        ("/", "index"),
        ("/hello/(.*)", "hello"),
    )

    def __init__(self, environ, start_response): ##########修改点
        self.environ = environ
        self.start = start_response
        self.status = '200 OK'
        self._headers = []

    def __iter__(self): ##########修改点
        result = self.delegate()
        self.start(self.status, self._headers)

        # 将返回值result(字符串 或者 字符串列表)转换为迭代对象
        if isinstance(result, basestring):
            return iter([result])
        else:
            return iter(result)

    def delegate(self): ##########修改点
        path = self.environ['PATH_INFO']
        method = self.environ['REQUEST_METHOD']

        for pattern, name in self.urls:
            m = re.match('^' + pattern + '$', path)
            if m:
                # pass the matched groups as arguments to the function
                args = m.groups()
                funcname = method.upper() + '_' + name
                if hasattr(self, funcname):
                    func = getattr(self, funcname)
                    return func(*args)

        return self.notfound()

    def header(self, name, value): ##########修改点
        self._headers.append((name, value))

    def GET_index(self): ##########修改点
        self.header('Content-type', 'text/plain')
        return "Welcome!\n"

    def GET_hello(self, name): ##########修改点
        self.header('Content-type', 'text/plain')
        return "Hello %s!\n" % name

    def notfound(self): ##########修改点
        self.status = '404 Not Found'
        self.header('Content-type', 'text/plain')
        return "Not Found\n"

3、抽象出框架

为了将类my_app抽象成一个独立的框架,需要作出以下修改:

  • 剥离出其中的具体处理细节:urls配置 和 GET_*方法(改成在多个类中实现相应的GET方法)
  • 把方法header实现为类方法(classmethod),以方便外部作为功能函数调用
  • 改用 具有__call__方法的 实例 来实现application

修改后的application.py(最终版本):

#!/usr/bin/env python
# -*- coding: utf-8 -*-

"""application.py"""

import re

class my_app:
    """my simple web framework"""

    headers = []

    def __init__(self, urls=(), fvars={}):
        self._urls = urls
        self._fvars = fvars

    def __call__(self, environ, start_response):
        self._status = '200 OK' # 默认状态OK
        del self.headers[:] # 清空上一次的headers

        result = self._delegate(environ)
        start_response(self._status, self.headers)

        # 将返回值result(字符串 或者 字符串列表)转换为迭代对象
        if isinstance(result, basestring):
            return iter([result])
        else:
            return iter(result)

    def _delegate(self, environ):
        path = environ['PATH_INFO']
        method = environ['REQUEST_METHOD']

        for pattern, name in self._urls:
            m = re.match('^' + pattern + '$', path)
            if m:
                # pass the matched groups as arguments to the function
                args = m.groups()
                funcname = method.upper() # 方法名大写(如GET、POST)
                klass = self._fvars.get(name) # 根据字符串名称查找类对象
                if hasattr(klass, funcname):
                    func = getattr(klass, funcname)
                    return func(klass(), *args)

        return self._notfound()

    def _notfound(self):
        self._status = '404 Not Found'
        self.header('Content-type', 'text/plain')
        return "Not Found\n"

    @classmethod
    def header(cls, name, value):
        cls.headers.append((name, value))

对应修改后的code.py(最终版本):

#!/usr/bin/env python
# -*- coding: utf-8 -*-

"""code.py"""

from application import my_app

urls = (
    ("/", "index"),
    ("/hello/(.*)", "hello"),
)

wsgiapp = my_app(urls, globals())

class index:
    def GET(self):
        my_app.header('Content-type', 'text/plain')
        return "Welcome!\n"

class hello:
    def GET(self, name):
        my_app.header('Content-type', 'text/plain')
        return "Hello %s!\n" % name

if __name__ == '__main__':
    from wsgiref.simple_server import make_server
    httpd = make_server('', 8086, wsgiapp)

    sa = httpd.socket.getsockname()
    print 'http://{0}:{1}/'.format(*sa)

    # Respond to requests until process is killed
    httpd.serve_forever()

当然,您还可以在code.py中配置更多的URL映射,并实现相应的类来对请求作出响应。

六、参考

本文主要参考了 How to write a web framework in Python(作者 anandology 是web.py代码的两位维护者之一,另一位则是大名鼎鼎却英年早逝的 Aaron Swartz),在此基础上作了一些调整和修改,并掺杂了自己的一些想法。

如果您还觉得意犹未尽,Why so many Python web frameworks? 也是一篇很好的文章,也许它会让您对Python中Web框架的敬畏之心荡然无存:-)

来自  http://www.cnblogs.com/russellluo/p/3338616.html 


WSGI的理解(转载)

分类: Python 7365人阅读 评论(4) 收藏 举报
wsgi是一个搞web开发的pythoner必须了解的内容,之前也零散的看过一些文章,但总感觉好多概念很模糊。这几天抽空又把相关内容好好整理了一下,把笔记贴出来,一些只言片语也许对某些正在研究这个的人有所帮助。
       wsgi 是一个 web 组件的接口规范.,wsgi将 web 组件分为三类: web服务器,web中间件,web应用程序,下图来自ibm developerworks ,很好的说明了三者之间的关系

从上图可以看出来,wsgi基本处理模式为 : WSGI Server -> (WSGI Middleware)* -> WSGI Application 。 

下面分别来看这三个组件
 
WSGI Server/gateway
 
        wsgi server可以理解为一个符合wsgi规范的web server,接收request请求,封装一系列环境变量,按照wsgi规范调用注册的wsgi app,最后将response返回给客户端。文字很难解释清楚wsgi server到底是什么东西,以及做些什么事情,最直观的方式还是看wsgi server的实现代码。以python自带的wsgiref为例,wsgiref是按照wsgi规范实现的一个简单wsgi server。它的代码也不复杂,下图是我读wsgiref代码后整理的。
通过这个图可以看出来wsgi server 基本工作流程
  1. 服务器创建socket,监听端口,等待客户端连接。
  2. 当有请求来时,服务器解析客户端信息放到环境变量environ中,并调用绑定的handler来处理请求。
  3. handler解析这个http请求,将请求信息例如method,path等放到environ中。
  4. wsgi handler再将一些服务器端信息也放到environ中,最后服务器信息,客户端信息,本次请求信息全部都保存到了环境变量environ中。
  5. wsgi handler 调用注册的wsgi app,并将environ和回调函数传给wsgi app
  6. wsgi app 将reponse header/status/body 回传给wsgi handler
  7. 最终handler还是通过socket将response信息塞回给客户端。
WSGI Application
         wsgi application就是一个普通的callable对象,当有请求到来时,wsgi server会调用这个wsgi app。这个对象接收两个参数,通常为environ,start_response。environ就像前面介绍的,可以理解为环境变量,跟一次请求相关的所有信息都保存在了这个环境变量中,包括服务器信息,客户端信息,请求信息。start_response是一个callback函数,wsgi application通过调用start_response,将response headers/status 返回给wsgi server。此外这个wsgi app会return 一个iterator对象 ,这个iterator就是response body。这么空讲感觉很虚,对着下面这个简单的例子看就明白很多了。
下面这个例子是一个最简单的wsgi app,引自http://www.python.org/dev/peps/pep-3333/
 
def simple_app(environ, start_response):
    status = '200 OK'
    response_headers = [('Content-type', 'text/plain')]
    start_response(status, response_headers)
    return [u"This is hello wsgi app".encode('utf8')]

我们再用wsgiref 作为wsgi server ,然后调用这个wsgi app,就能直观看到一次request,response的效果,简单修改代码如下:

from wsgiref.simple_server import make_server

def simple_app(environ, start_response):
    status = '200 OK'
    response_headers = [('Content-type', 'text/plain')]
    start_response(status, response_headers)
    return [u"This is hello wsgi app".encode('utf8')]

httpd = make_server('', 8000, simple_app)
print "Serving on port 8000..."
httpd.serve_forever()

访问http://127.0.0.1:8000 就能看到效果了。

此外,上面讲到了wsgi app只要是一个callable对象就可以了,因此不一定要是函数,一个实现了__call__方法的实例也可以,示例代码如下:

from wsgiref.simple_server import make_server

class AppClass:

    def __call__(self,environ, start_response):
        status = '200 OK'
        response_headers = [('Content-type', 'text/plain')]
        start_response(status, response_headers)
        return ["hello world!"]

app = AppClass()
httpd = make_server('', 8000, app)
print "Serving on port 8000..."
httpd.serve_forever()

WSGI MiddleWare

上面的application看起来没什么意思,感觉没有太大用,但加上一层层的middleware包装之后就不一样了。一堆文字解释可能还没有一个demo更容易说明白,我写了一个简单Dispatcher Middleware,用来实现URL 路由:
from wsgiref.simple_server import make_server

URL_PATTERNS= (
    ('hi/','say_hi'),
    ('hello/','say_hello'),
    )

class Dispatcher(object):

    def _match(self,path):
        path = path.split('/')[1]
        for url,app in URL_PATTERNS:
            if path in url:
                return app

    def __call__(self,environ, start_response):
        path = environ.get('PATH_INFO','/')
        app = self._match(path)
        if app :
            app = globals()[app]
            return app(environ, start_response)
        else:
            start_response("404 NOT FOUND",[('Content-type', 'text/plain')])
            return ["Page dose not exists!"]

def say_hi(environ, start_response):
    start_response("200 OK",[('Content-type', 'text/html')])
    return ["kenshin say hi to you!"]

def say_hello(environ, start_response):
    start_response("200 OK",[('Content-type', 'text/html')])
    return ["kenshin say hello to you!"]

app = Dispatcher()

httpd = make_server('', 8000, app)
print "Serving on port 8000..."
httpd.serve_forever()

上面的例子可以看出来,middleware 包装之后,一个简单wsgi app就有了URL dispatch功能。然后我还可以在这个app外面再加上其它的middleware来包装它,例如加一个权限认证的middleware:

class Auth(object):
    def __init__(self,app):
        self.app = app

    def __call__(self,environ, start_response):
        #TODO
        return self.app(environ, start_response)

app = Dispatcher()
auth_app = Auth(app)

httpd = make_server('', 8000, auth_app)
print "Serving on port 8000..."
httpd.serve_forever()

经过这些middleware的包装,已经有点框架的感觉了。其实基于wsgi的框架,例如paste,pylons就是这样通过一层层middleware组合起来的。只是一个成熟的框架,这样的middleware会有很多,例如:

def configure(app):
   return ErrorHandlerMiddleware(
           SessionMiddleware(
            IdentificationMiddleware(
             AuthenticationMiddleware(
              UrlParserMiddleware(app))))))

只要这些Middleware符合wsgi规范,甚至还可以在各个框架之间组合重用。例如pylons的认证Middleware可以直接被TurboGears拿去使用。

好了,各个部分都写完了,以后有时间再看看pylons的代码,相信又会对wsgi有很多新的理解。


来自  http://blog.csdn.net/sraing/article/details/8455242



WSGI基础知识

翻译自Basics of WSGIhttp://agiliq.com/blog/2013/07/basics-wsgi/

在这篇文章里,我们将会写一个web app,这个app它会服务一些url。我们将不会使用任何Python框架来写它。我们只是去说明一下这些机制的背后原理。

在开始写这个web app之前,我们先来澄清几个下文将要用到的术语。

  • Web服务器(Web Server): 当我们说Web 服务器时,我们指的是软件,而不是那些存储你代码的硬件机器。这个服务器会接收从客户端(Web浏览器)发送过来的请求并返回一个响应(response)。Web服务器本身并不创建响应,它只负责返回响应。所以,Web服务器就需要同Web应用程序交流,因为Web应用程序它能产生响应。

  • Web应用程序(Web Application):Web服务器从它这里拿到响应。Web Application的职责就是根据url来创建响应并将响应传回给Web服务器。然后Web服务器就只是返回这个响应到客户端而已。

  • WSGI: WSGI是一个接口,它只是一份规范或者说是一系列的规则。WSIG不是一个软件。

WSGI会引起人们的注意是因为Web服务器需要跟Web应用程序通信。WSGI规定了Web应用程序和Web服务器双方必须实现的规则,以便让它们之间能够互相交互。这么说吧,一个符合WSGI规范的服务器就可以跟一个符合WSGI规范的Web应用程序通信。

在WSGI架构里,WSGI应用程序必须是一个可调用者(callable),并且这个可调用者(callable)必须被传递给Web服务器,这样不管什么时候,只要Web服务器接收到客户端的一个请求,它就能随时调用Web应用程序。

想要了解更多关于为什么WSGI会得以出现的原因,请参照 WSGI的维基百科

现在开始写我们的Web应用程序。你暂时只需要全部拷贝下面的代码即可,我们稍后会对每一行代码做详细解释以便清楚地知道它们都做了什么。

#web_application.py
from wsgiref.simple_server import make_server

def application(environ, start_response):
    path = environ.get('PATH_INFO')
    if path == '/':
        response_body = "Index"
    else:
        response_body = "Hello"
    status = "200 OK"
    response_headers = [("Content-Length", str(len(response_body)))]
    start_response(status, response_headers)
    return [response_body]

httpd = make_server(
    '127.0.0.1', 8051, application)

httpd.serve_forever()

这个脚本可以通过运行命令python web_application.py来执行,接着你可以在浏览器中访问链接http://127.0.0.1:8051/http://127.0.0.1:8051/abcd。你将看到第一个页面会返回Index,而第二个页面返回Hello。

现在,我们来逐句分析下代码。

  • Python提供了一个方法叫做make_server。我们可以用它来创建一个遵循WSGI规范的基于Python的Web服务器。
  • 我们创建了一个callable叫做application。你可以认为这个callable就是上文所说的Web应用程序。
  • make_server()方法创建了一个遵循WSGI规范的web服务器。这里的httpd就是Web服务器。
  • make_server()的前两个参数指定了主机(host)以及Web服务器监听请求的端口(port)。
  • make_server()的第三个参数是Web应用程序,Web服务器将利用这个参数来获取响应。
  • 代码的最后一行通过serve_forever来启动Web服务器。

不管什么时候,只要有请求进来,运行在8051端口的Web服务器就会去调用Web应用程序,在我们这个例子里,Web应用程序就是callable application.

下面是关于Web应用程序代码的细节。

  • 为了能和WSGI的Web服务器交互,Web应用程序同样也必须遵循WSGI规范。
  • 服务器调用它时需要两个参数。所以,它必须接收两个参数,这是Web应用程序遵循WSGI规范的第一个条件。
  • 传给它的第一个参数是包含了许多关于请求(request)信息的一个变量。在我们这个例子里,我们用它来读取请求的路径。
  • 传给它的第二个参数是一个callable。应用程序必须利用这个callable来通知服务器关于响应的状态以及设定各种报头(headers)。这是Web应用程序遵循WSGI规范的第二个条件。
  • 我们的应用程序满足了遵循WSGI规范所要求的两个条件。
  • 最后,应用程序返回一个响应给WSGI服务器。
  • 然后,服务器最终会转发这个响应给客户端。

编辑:

我之前在application的代码里忽略了一件事,defnull童鞋在reddit上指出了我的错误。

application的最后一行不能返回response_body,正确的情况是它应该返回[response_body]。原因是:

  • WSGI服务器期望从应用程序返回一个iterable然后它会将这个iterable的每一个元素以一种无缓冲的方式发送给客户端。
  • 如果我们坚持返回response_body,那么response_body是一个string同时也是一个iterable,这样的话我们的代码确实还能正常工作不会报错。但是同时也使得response_body中的内容将会被一个字符一个字符地传递。
  • 然而,如果我们返回的是[response_body], 在这里,这个iterable是一个list, 这个list只有唯一一个元素即response_body字符串。这样,在我们这个例子里,整个response_body就只需要被传递一次。
  • 这就是为什么我们应该返回[response_body]而不是response_body。上面的代码我已经改过来了。

你应该读读这篇WSGI来了解更多关于WSGI。

译者注

  • 更新时间:2014-11-28
  • 本人翻译的初衷是为了自身学习和记录,翻译不好的地方,还望读者见谅.
来自  https://github.com/mainframer/Python-Doc-zh-CN/blob/master/WSGI%E5%9F%BA%E7%A1%80%E7%9F%A5%E8%AF%86.md



[原]【python web学习】python web窥探

2015-1-15阅读183 评论0

<span style="font-family: Arial, Helvetica, sans-serif; background-color: rgb(255, 255, 255);">                                                                                                                                                                                                                                                                                        </span>

本文为参考网上一些博客翻译以及想法,自己写的一篇总结博文,可能有重复的地方,纯粹总结只用。

阅读之前可参考

1、 How to write a web framework in Python(作者 anandology 是web.py代码的两位维护者之一,另一位则是大名鼎鼎却英年早逝的 Aaron Swartz

2、Why so many Python web frameworks? 也是一篇很好的文章,也许它会让您对Python中Web框架的敬畏之心荡然无存:-)

如果你打算用python进行网络开发的话,自己写的框架可以说是一种不受支持的想法,可能使用一个现成的Web框架(如 DjangoTornadoweb.py 、Pylons等)会是更合适的选择,毕竟都是大师级的作品。

 

一、一次最简单的web之旅

 

#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""myweb.py"""
from wsgiref.simple_server import make_server, demo_app

httpd = make_server('', 8086, demo_app)
sa = httpd.socket.getsockname()
print 'http://{0}:{1}/'.format(*sa)

# Respond to requests until process is killed
httpd.serve_forever()

在命令运行之后

打开浏览器:http://0.0.0.0:8086/

一行"Hello world!" 和 众多环境变量值。

定位到simple_server.py文件,我们看到make_server函数和WSGIServer类

 

def make_server(
    host, port, app, server_class=WSGIServer, handler_class=WSGIRequestHandler
):
    """Create a new WSGI server listening on `host` and `port` for `app`"""
    server = server_class((host, port), handler_class)
    server.set_app(app)
    return server


class WSGIServer(HTTPServer):

    """BaseHTTPServer that implements the Python WSGI protocol"""

    application = None

    def server_bind(self):
        """Override server_bind to store the server name."""
        HTTPServer.server_bind(self)
        self.setup_environ()

    def setup_environ(self):
        # Set up base environment
        env = self.base_environ = {}
        env['SERVER_NAME'] = self.server_name
        env['GATEWAY_INTERFACE'] = 'CGI/1.1'
        env['SERVER_PORT'] = str(self.server_port)
        env['REMOTE_HOST']=''
        env['CONTENT_LENGTH']=''
        env['SCRIPT_NAME'] = ''

    def get_app(self):
        return self.application

    def set_app(self,application):
        self.application = application

可以看到,我们运行python文件后启动的是WSGIServer类对象(继承于HTTPServer,子类有run函数,后文会详细讲一下),而demo_app是一个拥有特定格式:接受两个参数,一个列表return对象的函数,抑或是类、类对象(见下文)。

很多时候,要简单写一个web框架,主要需要改动传入的app以及server。

 

二、app的修改

 

    其中,可调用对象 包括 函数、方法、类 或者 具有__call__方法的 实例;environ 是一个字典对象,包括CGI风格的环境变量(CGI-style environment variables)和 WSGI必需的变量(WSGI-required variables);start_response 是一个可调用对象,它接受两个常规参数(status,response_headers)和 一个 默认参数(exc_info);字符串迭代对象 可以是 字符串列表、生成器函数 或者 具有__iter__方法的可迭代实例。更多细节参考 Specification Details

The Application/Framework Side 中给出了一个典型的application实现:

 

<span style="font-size:18px;">#!/usr/bin/env python
# -*- coding: utf-8 -*-

"""application.py"""

def simple_app(environ, start_response):
    """Simplest possible application object"""
    status = '200 OK'
    response_headers = [('Content-type', 'text/plain')]
    start_response(status, response_headers)
    return ['Hello world!\n']</span>
替换原来自带的demo_app,重新运行之

 

#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""code.py"""
from wsgiref.simple_server import make_server
from application import my_app as app

if __name__ == '__main__':
    httpd = make_server('', 8086, app)
    sa = httpd.socket.getsockname()
    print 'http://{0}:{1}/'.format(*sa)

    # Respond to requests until process is killed
    httpd.serve_forever()

这时就输出hello world!而没有环境变量。因为demo_app.py是这样的:

 

def demo_app(environ,start_response):
    from StringIO import StringIO
    stdout = StringIO()
    print >>stdout, "Hello world!"
    print >>stdout
    h = environ.items(); h.sort()
    for k,v in h:
        print >>stdout, k,'=', repr(v)
    start_response("200 OK", [('Content-Type','text/plain')])
    return [stdout.getvalue()]

三、URL调度修改

之前的访问server都是基于host+port的形式,那要怎样实现url的分发呢。这需要对app进行修改才行。说到这里,就先将app从一个函数改为一个类吧,再做url区分处理。

 

#!/usr/bin/env python
# -*- coding: utf-8 -*-

"""application.py"""

class my_app:
    def __init__(self, environ, start_response):
        self.environ = environ
        self.start = start_response

    def __iter__(self):
        status = '200 OK'
        response_headers = [('Content-type', 'text/plain')]
        self.start(status, response_headers)
        yield "Hello world!\n"

复习一下python类的语法,说说为什么可以这样写。开始的app可以这样用

list = simple_app(a,b)

现在也可以这样用

list = my_app(a,b)

注:其中参数来自__init__(),返回值来自__iter__()的return值(yield返回的就是一个可迭代对象),也许你会问,如果是传类对象的话呢?且看下下文

再在return的函数即__iter__()中修改根据不同的path进行不同返回。

 

#!/usr/bin/env python
# -*- coding: utf-8 -*-

"""application.py"""

class my_app:
    def __init__(self, environ, start_response):
        self.environ = environ
        self.start = start_response

    def __iter__(self):
        path = self.environ['PATH_INFO']  #environ的作用看到了吧
        if path == "/":
            return self.GET_index()
        elif path == "/hello":
            return self.GET_hello()
        else:
            return self.notfound()

    def GET_index(self):
        status = '200 OK'
        response_headers = [('Content-type', 'text/plain')]
        self.start(status, response_headers)
        yield "Welcome!\n"

    def GET_hello(self):
        status = '200 OK'
        response_headers = [('Content-type', 'text/plain')]
        self.start(status, response_headers)
        yield "Hello world!\n"

    def notfound(self):
        status = '404 Not Found'
        response_headers = [('Content-type', 'text/plain')]
        self.start(status, response_headers)
        yield "Not Found\n"

这时用浏览器就可以访问/,/hello,其他访问为Not Found。

 

四、重构

1、正则匹配URL

消除URL硬编码,增加URL调度的灵活性:

 

#!/usr/bin/env python
# -*- coding: utf-8 -*-

"""application.py"""

import re ##########修改点

class my_app:

    urls = (
        ("/", "index"),
        ("/hello/(.*)", "hello"),
    ) ##########修改点,Django工程中url.py即视感

    def __init__(self, environ, start_response):
        self.environ = environ
        self.start = start_response

    def __iter__(self): ##########修改点
        path = self.environ['PATH_INFO']
        method = self.environ['REQUEST_METHOD']

        for pattern, name in self.urls:
            m = re.match('^' + pattern + '$', path) #注意这里,url匹配函数名
            if m:
                # pass the matched groups as arguments to the function
                args = m.groups()
                funcname = method.upper() + '_' + name
                if hasattr(self, funcname):
                    func = getattr(self, funcname)
                    return func(*args)

        return self.notfound()

    def GET_index(self):
        status = '200 OK'
        response_headers = [('Content-type', 'text/plain')]
        self.start(status, response_headers) #遵循调用start_response后再return iterObject
        yield "Welcome!\n"

    def GET_hello(self, name): ##########修改点
        status = '200 OK'
        response_headers = [('Content-type', 'text/plain')]
        self.start(status, response_headers) 
        yield "Hello %s!\n" % name

    def notfound(self):
        status = '404 Not Found'
        response_headers = [('Content-type', 'text/plain')]
        self.start(status, response_headers)
        yield "Not Found\n"

2、消除GET_*方法中的重复代码,并且允许它们返回字符串:

#!/usr/bin/env python
# -*- coding: utf-8 -*-

"""application.py"""

import re

class my_app:

    urls = (
        ("/", "index"),
        ("/hello/(.*)", "hello"),
    )

    def __init__(self, environ, start_response): ##########修改点
        self.environ = environ
        self.start = start_response
        self.status = '200 OK'
        self._headers = []

    def __iter__(self): ##########修改点
        result = self.delegate() #利用这个函数先进行返回结果的收集
        self.start(self.status, self._headers) #start_response

        # 将返回值result(字符串 或者 字符串列表)转换为迭代对象
        if isinstance(result, basestring):
            return iter([result])
        else:
            return iter(result)

    def delegate(self): ##########修改点
        path = self.environ['PATH_INFO']
        method = self.environ['REQUEST_METHOD']

        for pattern, name in self.urls:
            m = re.match('^' + pattern + '$', path)
            if m:
                # pass the matched groups as arguments to the function
                args = m.groups()
                funcname = method.upper() + '_' + name
                if hasattr(self, funcname):
                    func = getattr(self, funcname)
                    return func(*args)

        return self.notfound()

    def header(self, name, value): ##########修改点
        self._headers.append((name, value))

    def GET_index(self): ##########修改点
        self.header('Content-type', 'text/plain')
        return "Welcome!\n"

    def GET_hello(self, name): ##########修改点
        self.header('Content-type', 'text/plain')
        return "Hello %s!\n" % name

    def notfound(self): ##########修改点
        self.status = '404 Not Found'
        self.header('Content-type', 'text/plain')
        return "Not Found\n"

3、抽象出框架

为了将类my_app抽象成一个独立的框架,需要作出以下修改:

1、剥离出其中的具体处理细节:urls配置 和 GET_*方法(改成在多个类中实现相应的GET方法)

2、把方法header实现为类方法(classmethod),以方便外部作为功能函数调用

3、改用 具有__call__方法的 实例 来实现application(上文提到)

修改后的application.py(最终版本):

 

#!/usr/bin/env python
# -*- coding: utf-8 -*-

"""application.py"""

import re

class my_app:
    """my simple web framework"""

    headers = []

    def __init__(self, urls=(), fvars={}):
        self._urls = urls
        self._fvars = fvars

    def __call__(self, environ, start_response):
        self._status = '200 OK' # 默认状态OK
        del self.headers[:] # 清空上一次的headers

        result = self._delegate(environ)
        start_response(self._status, self.headers)

        # 将返回值result(字符串 或者 字符串列表)转换为迭代对象
        if isinstance(result, basestring):
            return iter([result])
        else:
            return iter(result)

    def _delegate(self, environ):
        path = environ['PATH_INFO']
        method = environ['REQUEST_METHOD']

        for pattern, name in self._urls:
            m = re.match('^' + pattern + '$', path)
            if m:
                # pass the matched groups as arguments to the function
                args = m.groups()
                funcname = method.upper() # 方法名大写(如GET、POST)
                klass = self._fvars.get(name) # 根据字符串名称查找类对象
                if hasattr(klass, funcname):
                    func = getattr(klass, funcname)
                    return func(klass(), *args)

        return self._notfound()

    def _notfound(self):
        self._status = '404 Not Found'
        self.header('Content-type', 'text/plain')
        return "Not Found\n"

    @classmethod
    def header(cls, name, value):
        cls.headers.append((name, value))

到这里,基本上就算是小功告成了,但只是了解了怎么用那些子类,大篇幅还是讲怎么设计。窥探一下wsgiref.simple_server。

五、wsgiref原理介绍

1、概述

a.什么是WSGI, WSGI application, WSGI server, WSGI middleware.

WSGI是关于Python脚本与Web服务器交互的协议,wsgi将 web 组件分为三类: web服务器,web中间件,web应用程序。

b.WSGI Server有哪些
比如 Django、CherryPy 都自带 WSGI server,主要是测试用途, 发布时则使用生产环境的 WSGI server,例如Apache,nginx等,而有些 WSGI 下的框架比如 pylons、bfg 等, 自己不实现 WSGI server。wsgiref就是python自带的WSGI server。上面提到的app需要传入的两个参数application(environ, start_response),其实就是一个接口两个参数的集合体。一篇博文这样说明:

 

wsgi server 基本工作流程:
  • 1、服务器创建socket,监听端口,等待客户端连接。
  • 2、当有请求来时,服务器解析客户端信息放到环境变量environ中,并调用绑定的handler来处理请求。
  • 3、handler解析这个http请求,将请求信息例如method,path等放到environ中。
  • 4、wsgi handler再将一些服务器端信息也放到environ中,最后服务器信息,客户端信息,本次请求信息全部都保存到了环境变量environ中。
  • 5、wsgi handler 调用注册的wsgi app,并将environ和回调函数传给wsgi app
  • 6、wsgi app 将reponse header/status/body 回传给wsgi handler
  • 7、最终handler还是通过socket将response信息塞回给客户端。
 
2、组成(python2.7.8)
  • simple_server
    这一模块实现了一个简单的 HTTP 服务器,并给出了一个简单的 demo,运行:

    python simple_server.py
    

会启动这个demo,运行一次请求,并把这次请求中涉及到的环境变量在浏览器中显示出来。

  • handlers
    simple_server模块将HTTP服务器分成了 Server 部分和Handler部分,前者负责接收请求,后者负责具体的处理, 其中Handler部分主要在handlers中实现。
  • headers
    这一模块主要是为HTTP协议中header部分建立数据结构。
  • util
    这一模块包含了一些工具函数,主要用于对环境变量,URL的处理。
  • validate
    这一模块提供了一个验证工具,可以用于验证你的实现是否符合WSGI标准。

simple_server 模块主要有两部分内容,上面一到四的内容可以总结。

  • 应用程序
    函数demo_app是应用程序部分
  • 服务器程序
    服务器程序主要分成Server 和 Handler两部分,另外还有一个函数 make_server 用来生成一个服务器实例
各种继承关系:
# M:
#         +------------+
#         | BaseServer |
#         +------------+
#               |
#               V
#         +------------+
#         | TCPServer  |
#         +------------+
#               |
#               V
#         +------------+
#         | HTTPServer |
#         +------------+
#               |
#               V
#         +------------+
#         | WSGIServer |
#         +------------+

# M:
#         +--------------------+
#         | BaseRequestHandler |
#         +--------------------+
#                   |
#                   V
#         +-----------------------+
#         | StreamRequestHandler  |
#         +-----------------------+
#                   |
#                   V
#         +------------------------+
#         | BaseHTTPRequestHandler |
#         +------------------------+
#                   |
#                   V
#         +--------------------+
#         | WSGIRequestHandler |
#         +--------------------+

# M:
#        +-------------+
#        | BaseHandler |  
#        +-------------+
#               |
#               V
#       +----------------+
#       | SimpleHandler  |
#       +----------------+
#               |
#               V
#       +---------------+
#       | ServerHandler |
#       +---------------+
# 

在调用make_server的时候,都发生了什么
 
再这里,就不细讲handler的处理过程了,很多时候网络handler的研究需要看源码才能真正消化。

以下完全引用on_1y的一篇博文,该博文讲得很细,不过需要研究源码才能真正消化。可以先看

 

headers

这个模块是对HTTP 响应部分的头部设立的数据结构,实现了一个类似Python 中 dict的数据结构。可以看出,它实现了一些函数来支持一些运算符,例如 __len____setitem____getitem____delitem____str__, 另外,还实现了 dict 操作中的getkeysvalues函数。

util

这个模块主要就是一些有用的函数,用于处理URL, 环境变量。

validate

这个模块主要是检查你对WSGI的实现,是否满足标准,包含三个部分:

  • validator
  • Wrapper
  • Check

validator 调用后面两个部分来完成验证工作,可以看出Check部分对WSGI中规定的各个部分进行了检查。



来自  http://m.blog.csdn.net/blog/gugugujiawei/42742343

普通分类: