0%

之前我们已经:

  • 搭建好了libgdx开发环境
  • 知道如何使用创建工具创建的工程
  • 成功运行程序显示了一张图片

但是这种方式其实比较底层,可以看看一下核心工程中的ApplicationListener的实现类.

这里我会用一些更像flash和cocos2d的方式来显示一张图片.

打开核心工程的主文件,删除不需要的代码,剩下的内容据基本如下了:

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
32
33
34
35
public class MyGdxGame implements ApplicationListener {

//初始化会调用的函数
@Override
public void create() {
}

//销毁时调用的函数
@Override
public void dispose() {

}

//逐帧调用的函数
@Override
public void render() {
Gdx.gl.glClearColor(1, 1, 1, 1);
Gdx.gl.glClear(GL10.GL_COLOR_BUFFER_BIT);
}

//屏幕重置大小调用的函数
@Override
public void resize(int width, int height) {
}

//暂停时调用的函数
@Override
public void pause() {
}

//恢复到前台调用的函数
@Override
public void resume() {
}
}

我们现在需要关注的两个函数是create()render()

  • 在create的时候我们创建需要显示的对象,然后把它添加到舞台
  • 在render的时候我们清空屏幕,然后同时stage去重绘

现在我们介绍libgdx中几个类:

  • Actor:演员,可以理解为一个显示对象,可以被添加到舞台上,但是需要自己实现draw方法
  • Group:Actor的子类,实现了draw方法,可以被当作Actor的容器,会自动绘制容器中的Actor们
  • Stage:Group的子类,可以理解为一个顶级的显示对象
  • Image:Actor子类,libgdx为我们实现的显示图片的类

如果需要显示Actor,我们必须创建一个Stage用于绘制这些Actor,否则这些Actor无法显示出来
于是我们需要新建类变量,在create中创建Stage,在render中控制stage刷新和重绘.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
private Stage stage;
//初始化会调用的函数
@Override
public void create() {
stage = new Stage(Gdx.graphics.getWidth(), Gdx.graphics.getHeight(), false);
}
//逐帧调用的函数
@Override
public void render() {
Gdx.gl.glClearColor(1, 1, 1, 1);
Gdx.gl.glClear(GL10.GL_COLOR_BUFFER_BIT);

stage.act(Gdx.graphics.getDeltaTime());
stage.draw();
}

至于Stage构造函数的参数到底是什么意思,可以看一下api:
http://libgdx.badlogicgames.com/nightlies/docs/api/.
这是每天更新的版本,在下载的zip文件中也有api.
那个是和你所使用的版本对应的,Stage的构造函数的参数这里就不具体介绍了.
我会用Image这个类来说明怎么使用libgdx的api文档.

好,现在我们有了一个舞台用于显示我们的图片,
那我们怎么把一张图片添加到舞台上呢?

我们打开api网页,
在”Package”一栏中找到com.badlogic.gdx.scenes.scene2d.ui 这个包,点击.

我们能在”Classes”栏中看到这个包里有哪些类.

根据经验,图片的类叫做”Image“,点进去在右侧看看这个类的构造函数和为我们提供的方法.

Image的的构造函数大体够味两类,
一个以NinePath作为参数的,
一个以Texture或者TextureRegin作为参数的.
点击链接分辨看看这两个是做什么的.

在NinePath中没什么简介,
但是看到NinePath中有几个构造函数也是用Texture作为参数的.
回去看Texture的说明.

Texture:说明中说,这是一个标准的OpenGL ES的texture……
好吧,对于没用过OpenGL的人来说和没说一样.

google texture,意思是材质贴图,然后深入一下能了解到:
这其实相当于一个图片文件在程序内从中的映像,
也可以理解为图片在程序内存中的储存形式.

ok,那怎么得到这样一个Texture对象呢 ,继续看Textrue的构造函数.

有一个是以FileHandle作为参数的, 貌似是通过一个文件创建,
额,libgdx应该提供了文件读取功能吧?

去看看wiki,确实有, http://code.google.com/p/libgdx/wiki/FileHandling.
Gdx.file.xxxx("路径"),就返回一个FileHandle对象.

所以事情变得简单了,创建一个Image对象的方式就是:

1
2
3
4
5
6
7
8
//获得一个文件对象
FileHandle imgfile = Gdx.files.internal("data/libgdx.png");
//创建一个Texture对象
Texture t = new Texture(imgfile);
//创建一个图片Actor
img = new Image(t, Scaling.none);
//把它添加到舞台上
stage.addActor(img);

把如上代码加入creat函数中,我们就成功的让一张图片显示在舞台上了.

需要注意的是,internal对应的是android工程中asset目录,
在文件处理的wiki中有说明,可以阅读一下.

我这里加载的这种图片是自动生成的工程里自带的,
libgdx对于加载的图片有要求,长宽必须是2的幂.
这张图片正好符合这个要求,如果换自己的图片,必须注意长宽,
否则会报错.

现在可能认为这个长宽的限制很大,
但是之后说到资源加载的时候我们会介绍一个工具,
用于把很多图片打包到一张大图里,
切割使用 所以现在不用担心.

那么总结一下:

  • 我们这里使用的都是libgdx的高层类
  • 底层的Sprite和SpriteBatch之类的暂时不管
  • 如果你使用过flash或者接触过cocos2d可能更容易理解

我们使用一个舞台(Stage)作为显示对象的根节点.
然后在Stage上我们可以添加和移除各种Group,
Actor作为我们的显示对像并对他们进行控制来实现游戏逻辑展示.

这里重点告诉大家,学习使用libgdx的api文档,
之后会和大家详细介绍,资源加载

首先,请确保安装好了android基于eclipse的开发环境:

  • eclipse
  • Android SDK
  • ADT这些工具

libgdx的代码是存放在Google code的,
地址是:libgdx,
如果耐心的看英文的话这里有大部分你需要的东西.

前不久有位大哥为我们写了一个libgdx的部署工具,
着实方便了大家新建libgdx项目.
使用方法原文在这里:ProjectSetupNew.

大概说明一下这篇wiki的内容

这个部署工具被打包成了可执行的jar文件:gdx-setup-ui.jar

下载以后在命令行运行:

1
java  -jar  gdx-setup-ui.jar

远行争取的话会弹出ui的libgdx工程创建工具了,如果不成功,检查一下path之类的是不是配置正确.

其中libgdx开发包的下载地址是libgdx download.

我这里用的是0.9.4版本

在创建完成后,可以看见在创建的目录下有右侧显示的几个文件夹,
现在打开eclipse, import->existing project inti workspace,导入这几个文件夹.

我没有创建html工程,导入后又三个工程:

  • 核心的工程 ("my-gdx-game"),
    包含 所有的程序代码,作为源代码被链接到其他的工程,今后的编程大部分在这里.

    由于libgdx想实现在pc和android上相同的测试效果,
    所以desktop和android工程可以理解为一个壳.
    而核心的功能代码在这里,外壳只是一个启动器.
    启动以后交给这个核心工程处理.

  • desktop工程 ("my-gdx-game-desktop")

    桌面工程,在pc上直接运行的工程,测试工作大部分可以在这里完成

  • 和android工程 ("my-gdx-game-android")

这时候运行desktop和android工程,如果没问题,证明创建成功了.

之后介绍一个这几个工程的文件布局,以便可以手动生成自己需要的工程文件:
原文如下:wiki/ProjectSetup.

首先,核心工程是一个java工程,在libs中需要包含 gdx.jar文件

其次,desktop工程,也是一个java工程,需要包含

  • gdx-natives.jar,
  • gdx-backend-lwjgl.jar
  • gdx-backend-lwjgl-natives.jar

另外就是要添加核心工程这个工程依赖了.

之后是android工程,这个要新建的就不能是java工程了,而是一个标准的android工程,
需要添加gdx-backend-android.jar依赖.

另外值得说明的是,libgdx是一个整合了android ndk的引擎.
我们虽然不用安装ndk开发环境,
但是需要把开发包中编译好的链接文件添加到android工程中的libs目录中,
也就是zip文件中的armeabiarmeabi-v7a目录.

这里简略的说明了一下wiki上搭建libgdx开发环境的文章,更详细的内容可以参考wiki.

asunit是actionscript的一个单元测试框架,
对于同步执行的函数不用多说,
网上能查到很多资料.

但是actionscript里存在大量的异步函数,
load,dispatchEventListener.

如果涉及到这些异步的函数应该怎样测试呢?

例如

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public class Tmp
{
public var _bitmap:Bitmap;
private var _func:Function;
public function Tmp()
{

}

public function loadRes(func:Function):void {
var loader:Loader = new Loader();
loader.loaderInfo.addEventListener(Event.COMPLETE, loaded);
_func = func;
loader.load(new URLRequest("test.jpg"));
}

private function loaded(e:Event):void
{
_bitmap = (e.target as LoaderInfo).loader.content;
}
}

我们怎么测试loadRes有没有正确执行呢?

其实asunit已经为我们提供了异步测试函数addAsync

我们只需要创建一个TestCase

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
public class AssetManagerTest extends TestCase
{
private var _tmp:Tmp;

public function AssetManagerTest(methodName:String=null)
{
super(methodName);
}

override protected function setUp():void {
super.setUp();
_tmp= new Tmp();
}

override protected function tearDown():void {
super.tearDown();
}

public function testLoadRes():void {
_tmp.loadRes(addAsync(checkLoadRes));
}

////////////////////////////////////////////
private function checkLoadRes():void {
assertNotNull(_tmp._bitmap);
}
}

需要注意的是,我们的回调函数一定要用asunit的addAsync包装一下.
这样asunint才会等待一定的时间执行检测函数,否则起不到测试效果.

addAsync具体用法可以看一下源码,还有一些参数可用

在请求中验证用户输入是否合法是个比较麻烦的事情,
但其实web.form已经为我们提供了这方便的函数可以直接调用.

我们仅仅需要定义出一个form和每个字段的验证规则,
其他的事情web.form就自动替我们完成了.

下面是个十分简单的例子

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
32
33
import web
from web import form
urls = (
'/', 'hello',
)
app = web.application(urls, globals())
#定义字段验证规则
vpass = form.regexp(r".{3,20}$", 'must be between 3 and 20 characters')
#定义form表格内容
t_form = form.Form(
form.Textbox('u',vpass,description='username'),
form.Textbox('p',vpass,description='password')
)
class hello:
def GET(self):
"""GET方法返回一个Form表格"""
return """<html>
<body>
<form action="/" method="post">
<p>user name: <input type="text" name="u" /></p>
<p>password : <input type="password" name="p" /></p>
<input type="submit" value="Submit" />
</form>
</body>
</html>
"""
def POST(self):
#生成一个form实例
f = t_form()
#返回form验证是否通过
return f.validates()
if __name__ == "__main__":
app.run()

更多form用法可以参考文档

先上代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
import web
urls = (
'/', 'hello',
)
app = web.application(urls, globals())
def hold(func):
print 'run hlod'
def f(self):
print 'in hold func'
return 'hold'
return f
class hello:
@hold
def GET(self):
print 'in hello'
return 'hello'

if __name__ == "__main__":
app.run()

现在的问题是,hold会在什么时候调用?

过去我一直以为是每次调用GET函数的时候会调用一遍hold函数,
但事实并不是这样的.

事实是,当定义GET函数加载的时候,就会调用一遍hold函数,
而且仅仅调用一遍hold.

这时候hold返回的函数就会替代原始的GET函数定义,
所以运行这点python代码,输出结果如下:

1
2
3
4
5
6
7
http://0.0.0.0:8080/
run hlod
in hold func
10.0.2.2:51602 - - [23/Sep/2011 08:04:11] "HTTP/1.1 GET /" - 200 OK
10.0.2.2:51602 - - [23/Sep/2011 08:04:11] "HTTP/1.1 GET /favicon.ico" - 404 Not Found
in hold func
10.0.2.2:51602 - - [23/Sep/2011 08:04:12] "HTTP/1.1 GET /" - 200 OK

##首先看一下uwsgi对于一次普通wsgi函数的调用过程

uwsgi可以通过一下命令启动

uwsgi -s :9090 -w index

其中

  • -s表示监听的端口
  • -w表示python启动模块

例如index.py的模块名就是index

更多uwsgi的使用可以查看官方文档http://projects.unbit.it/uwsgi/wiki/Doc

uwsgi要求在启动模块中有一个application变量
这个变量其实就是一个遵循wsgi标准的处理函数,接受两个参数,例如

1
2
3
4
5
6
7
8
9
10
11
12
13
#File : index.py
def simple_app(environ, start_response):
"""遵循wsgi标准的函数,
environ是wsgi传递的http请求参数
start_response是一个函数,调是需要传递两个参数
status 状态码 "200 OK" 或者 "404 NOT FOUND"等
head http的head头信息
"""
status = '200 OK'
response_headers = [('Content-type', 'text/plain')]
start_response(status, response_headers)
return ['Hello world!n']
application = simple_app

这时候运行

1
uwsgi -s :9090 -w index

就会运行在9090端口监听的wsgi程序,
配合nginx进行测试,修改nginx配置文件:

1
2
3
4
5
6
7
8
server{
listen 80;
server_name localhost;
location / {
uwsgi_pass 127.0.0.1:9090;
include uwsgi_params;
}
}

重启nginx,访问本机就会出现index.py提供的hello word页面了

##接下来查看web.py的调用过程

修改index.py文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#!/usr/bin/env python
# -*- coding: utf-8 -*-

import web

urls = (
'/.*', 'index',
)

app = web.application(urls, globals())

class index(object):
def GET(self):
return 'hello web.py'
#除了web.py的基础代码,只是把application改成了app.wsgifunc()
application = app.wsgifunc()

去看看wsgifunc()的定义

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
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
#web.py的application.py文件的一部分
def wsgifunc(self, *middleware):
"""Returns a WSGI-compatible function for this application."""
def peep(iterator):
#.....
return itertools.chain([firstchunk], iterator)

def is_generator(x): return x and hasattr(x, 'next')

def wsgi(env, start_resp):
#web.py自己的wsgi处理函数
#实际上每次处理request就是从这里开始调用的
# clear threadlocal to avoid inteference of previous requests
self._cleanup()

self.load(env)
try:
# allow uppercase methods only
if web.ctx.method.upper() != web.ctx.method:
raise web.nomethod()

result = self.handle_with_processors()
if is_generator(result):
result = peep(result)
else:
result = [result]
except web.HTTPError, e:
result = [e.data]

result = web.safestr(iter(result))

status, headers = web.ctx.status, web.ctx.headers
#调用wsgi的状态和head设置函数
start_resp(status, headers)

def cleanup():
self._cleanup()
yield '' # force this function to be a generator
#返回wsgi要求的可迭代对象
return itertools.chain(result, cleanup())
#裹上我们自己设置的n层中间件,并加入web.py的wsgi处理函数
#包裹的过程只执行一次
for m in middleware:
wsgi = m(wsgi)
#uwsgi保存的就是这个包裹了中间件的wsgi函数
return wsgi

这里对wsgifunc的执行过程做了注释,每次request的web.py的内部处理过程实际上
就是wsgi这个函数,而wsgifunc的作用是:生成挂载于uwsgi上的wsgi函数.

官方cookbook有介绍在web.py中使用jinja2模板的方法,
http://webpy.org/cookbook/template_jinja.zh-cn但是有个缺憾,
只能渲染同级目录下的文件,下面是web.py的jinja2使用的实现.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
class render_jinja:
"""Rendering interface to Jinja2 Templates

Example:

render= render_jinja('templates')
render.hello(name='jinja2')
"""
def __init__(self, *a, **kwargs):
extensions = kwargs.pop('extensions', [])
globals = kwargs.pop('globals', {})

from jinja2 import Environment,FileSystemLoader
self._lookup = Environment(loader=FileSystemLoader(*a, **kwargs), extensions=extensions)
self._lookup.globals.update(globals)

def __getattr__(self, name):
# Assuming all templates end with .html
#这里造成了只能使用同级目录的文件,而且时能是html后缀的文件
path = name + '.html'
t = self._lookup.get_template(path)
return t.render

web.py只是提供了jinja2模板使用的简单封装,至于实际使用中还是要自己封装.

下面演示了自己编写的最简单的jinja2的使用方法

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
#!/usr/bin/env python
# -*- coding: utf-8 -*-

import web
from jinja2 import Environment, FileSystemLoader

urls = (
'/index', 'index',
'/admin', 'admin',
)

app = web.application(urls, globals())
#初始化jinja2的运行环境,使用FileSystemLoader
env = Environment(loader = FileSystemLoader('templates'))

class index(object):
def GET(self):
#取得同级目录下的文件
return env.get_template('index.html').render()

class admin(object):
def GET(self):
#取得下层目录的文件
return env.get_template('admin/admin.html').render()

if __name__ == '__main__':
app.run()

更详细的使用方法可以去jinja2的官网查http://jinja.pocoo.org/docs/

在自己的home目录下编辑.vimrc文件,如果没有新建一个.

下面的配置能让自己过得舒服一点

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
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
" 不要使用vi的键盘模式,而是vim自己的
set nocompatible

" 设定解码
if has("multi_byte")
" When 'fileencodings' starts with 'ucs-bom', don't do this manually
"set bomb
set fileencodings=ucs-bom,utf-8,chinese,taiwan,japan,korea,latin1
" CJK environment detection and corresponding setting
if v:lang =~ "^zh_CN"
" Simplified Chinese, on Unix euc-cn, on MS-Windows cp936
set encoding=utf-8
set termencoding=utf-8
if &fileencoding == ''
set fileencoding=utf-8
endif
elseif v:lang =~ "^zh_TW"
" Traditional Chinese, on Unix euc-tw, on MS-Windows cp950
set encoding=euc-tw
set termencoding=euc-tw
if &fileencoding == ''
set fileencoding=euc-tw
endif
elseif v:lang =~ "^ja_JP"
" Japanese, on Unix euc-jp, on MS-Windows cp932
set encoding=euc-jp
set termencoding=euc-jp
if &fileencoding == ''
set fileencoding=euc-jp
endif
elseif v:lang =~ "^ko"
" Korean on Unix euc-kr, on MS-Windows cp949
set encoding=euc-kr
set termencoding=euc-kr
if &fileencoding == ''
set fileencoding=ecu-kr
endif
endif
" Detect UTF-8 locale, and override CJK setting if needed
if v:lang =~ "utf8$" || v:lang =~ "UTF-8$"
set encoding=utf-8
endif
else
echoerr 'Sorry, this version of (g)Vim was not compiled with "multi_byte"'
endif

" 自动格式化设置
filetype indent on
set autoindent
set smartindent

" 显示未完成命令
set showcmd
" 侦测文件类型
filetype on

" 载入文件类型插件
filetype plugin on

" 为特定文件类型载入相关缩进文件
filetype indent on

" 语法高亮
syntax on

" 显示行号
set number

" tab宽度
set tabstop=4
set cindent shiftwidth=4
set autoindent shiftwidth=4

" 保存文件格式
set fileformats=unix,dos

" 文件被其他程序修改时自动载入
set autoread

" 命令行补全
set wildmenu

" 打开文件时,总是跳到退出之前的光标处
autocmd BufReadPost *
if line("'"") > 0 && line("'"") <= line("$") |
exe "normal! g`"" |
endif

filetype plugin on "允许使用ftplugin目录下的文件类型特定脚本
filetype indent on "允许使用indent目录下的文件类型缩进

" PYTHON 相关的设置 "
"Python 文件的一般设置,比如不要 tab 等
"设置自动缩进为4,插入模式里: 插入 <Tab> 时使用合适数量的空格。
"要插入实际的制表,可用 CTRL-V<Tab>
autocmd FileType python setlocal expandtab | setlocal shiftwidth=4 |
setlocal softtabstop=4 | setlocal textwidth=76 |
setlocal tabstop=4

"搜索逐字符高亮
set hlsearch
set incsearch

"设置代码样式
colorscheme desert

"设置tags查找位置
set tags=tags;
set autochdir

官方地址http://www.vim.org/scripts/script.php?script_id=850,
下载zip包,在home目录下查找.vim文件夹,如果没有创建这个目录

官网有安装说明”install details”,
完成后.vim的文件结构如下:

1
2
3
4
5
6
.vim
└── after
└── ftplugin
├── pydiction
│   └── complete-dict
└── python_pydiction.vim

然后配置.vimrc,添加语句
let g:pydiction_location = '~/.vim/after/ftplugin/pydiction/complete-dict'

后面的值是complete-dict文件的路径,
用vim编辑一个py文件,import os.<TAB>,这时候应该出现提示,证明成功了.

ctrl+n ctrl+p选择列表里的提示项

其实7.2版本的vim自身已经提供了比较强悍的补全功能, vim的OMNI补全(也叫”全能补全”)

os.<CTRL+x , CTRL+o>,如果开启了vim的python模块,现在应该有一个分割窗口显示函数的参数,以及__doc__信息

如果需要动态输入刷新提示内容,在配置文件中加入
set completeopt=longest,menu

ctags提示

首先确定安装了ctags软件,运行tags生成脚本

1
ctags -R *.py`

这时会生成tags文件

安装taglist插件,网址http://www.vim.org/scripts/script.php?script_id=273

安装完成后,编辑py文件,执行vim命令:Tlist,
会出现taglist窗口,如果需要tags文件中的关键词补全,CTRL+n,如果需要跟踪关键词文件CTRL+],跳回来CTRL+t

代码模板,主页地址 http://www.vim.org/scripts/script.php?script_id=2540

安装后,这个插件默认的快捷键是<TAB>

但是pydiction_location默认的快捷键也是<TAB>,这里修改 pydiction_location的快捷键

找到.vim/after/ftplugin/python_pydiction.vim文件,
修改

1
2
" Make the Tab key do python code completion:
inoremap <silent> <buffer> <TAB>

1
2
" Make the Tab key do python code completion:
inoremap <silent> <buffer> <C-P>

这样就把pydiction_location的快捷键修改为CTRL+p了,
然后编辑py文件,输入 cl<TAB>,就会出现class的定义模板了,
这些模板定义在.vim/syntax文件夹下,可自行修改.

py语法检查插件 http://www.vim.org/scripts/script.php?script_id=2441

安装以后会用红色,提示py代码的错误

web.py本身提供profile中间件 web.profiler

在wsgi和自带的httpserver中同样适用

只需要在app.run或者app.wsgifun中传入传入中间件参数web.profiler就可以了

在每个请求返回的网页内容之后会打印分析数值但是有时我们需要数值在后台显示,而不是显示在页面中,这时候我们可以自己改写一下profile中中间件

1
2
3
4
5
6
7
8
class Profile_Middleware(object):
def __init__(self, app):
self.app = app
def __call__(self, mapping, fvars):
cp = cProfile.Profile()
r = cp.runcall(self.app, mapping, fvars)
cp.print_stats()
return r

把Profile_Middleware当中间件传给web.py,这时候就会正常返回显示页面,
分析信息打印在命令行下了,如果需要打印日志,可以自行更改__call__函数

web.py有添加中间件和钩子的功能

钩子在web.py的cookbook中有介绍,
中间件可以理解为app的钩子,
其作用范围可以大致通过web.ctx是否初始化看出来.

中间件是一个callable的对象,初始化参数是app,
调用参数就是app的调用参数

下面的代码说明了两种创建中间件的方式(def定义和class定义),
以及中间件和钩子的执行顺序:

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
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
#!/usr/bin/env python
# -*- coding: utf-8 -*-

import web

def midd(app):
"""函数规则定义的中间件"""
def inapp(e, o):
print 'func befor handle'
r = app(e, o)
print 'func after handle'
return r
return inapp

class Midd(object):
"""class规则定义的中间件,
其实这就是一个函数,
传入参数时调用__init__,
然后返回self"""
def __init__(self, app):
self.app = app
def __call__(self, o, e):
print 'class befor handle'
print web.ctx
r = self.app(o, e)
print 'class after handle'
return r

def my_processor(handler):
print 'hook before handling'
print web.ctx
result = handler()
print 'hook after handling'
return result
urls = ("/.*", "hello")
app = web.application(urls, globals())
app.add_processor(my_processor)

class hello:
def GET(self):
print 'start handle'
return 'Hello, world!n'

if '__main__' == __name__:
app.run(Midd) #使用class中间件
#app.run(midd) #使用函数中间件

执行和输出信息

1
2
3
4
5
6
7
8
9
10
$ python index.py
http://0.0.0.0:8080/
class befor handle
<ThreadedDict {}>
hook before handling
<ThreadedDict {'status': '200 OK', ......}>
start handle
hook after handling
127.0.0.1:35260 - - [26/Aug/2011 11:01:59] "HTTP/1.1 GET /" - 200 OK
class after handle