Gefr with multiple backends support

I have been working on gefr this week to add apache MINA backend for it. The goal of gefr is to provide adapters between Python’s WSGI and Java network infrastructures. In cPython world, most WSGI servers are implemented with C code(fapws, meinheld, etc.), which is not applicable on Jython platform. However, the Java world has its own reliable network foundations. So my gefr turns them into WSGI servers. The ultimate goal of gefr is to make it possible to port any of your WSGI web applications to Jython seamlessly.

There has been many attempts to use Jython in Java web development. Most of them is focusing on applying Jython in Servlet framework. This is helpful, but it doesn’t take the whole advantage of Python web programming. The most valuable thing in Python web development is its various web frameworks and agile tools. But inside servlet framework, we can hardly apply them, the only things we have is Jython’s beautiful syntax. That should not be enough.

OK. You are eager to meet gefr? Let’s get hands dirty with some setup. I just assume that you have some essential software installed in the right place. Is it true for you?

Create Jython virtual environment:

$ virtualenv -p /usr/local/bin/jython gefr-test
$ cd gefr-test
$ source bin/activate

Install jip. jip is a great tool to manage Java dependencies for your Jython application. jip>=0.3 is required in this guide, and pip will handle this for you.
$ pip install jip

Install gefr. Since gefr is a pure Python package, pip is still helpful.
$ pip install gefr

Now create a jip configuration file called .jip in your virtual home. We should add sonatype oss repository.

[repos:oss]
uri=http://oss.sonatype.org/content/repositories/snapshots/
type=remote

[repos:central]
uri=http://repo1.maven.org/maven2/
type=remote

[repos:local]
uri=/home/sun/.m2/repository/
type=local

New in jip 0.3, we can use a single command to resolve dependencies of gefr.
$ jip install-dependencies info.sunng.gefr:gefr:0.2-SNAPSHOT

OK. Then everything is ready. Create your first Jython WSGI application. Actually, there is no difference.
I just copy the example of gefr here, you can find it in the code repository.

#! /usr/bin/python

__author__="Sun Ning <classicning@gmail.com>"
__date__ ="$Jan 5, 2011 10:14:27 PM$"

import sys
from gefr.core import Gefr

def simple(environ, start_response):
    """a static app"""
    headers = []
    headers.append(('Content-Type', "text/html"))
    start_response("200 OK", headers)

    return ["<h1>it works.</h1>"]

if __name__ == "__main__":
    from optparse import OptionParser
    parser = OptionParser()
    parser.add_option('-b', '--backend', dest='backend', help='server backend')
    (options, args) = parser.parse_args()

    if options.backend == 'soldat':
        from gefr.backends.soldat import SoldatBackend as backend
    elif options.backend == 'mina':
        from gefr.backends.mina import MinaBackend as backend
    else:
        print 'backend %s not supported.' % options.backend
        sys.exit(1)

    g = Gefr(simple, backend)
    g.start()

With the command line options, you can control which backend to be used. Now start it with:
$ jython-all example.py -b soldat
or
$ jython-all example.py -b mina

Soldat is another network framework written by myself. And MINA, you must know. Open your browser, redirect it to http://localhost:8080 , now you see it works.

Sure, that’s not enough for your needs. Let’s have an advanced example. Among all the web frameworks in Python, I like bottle most. So we just build a simple application with GET and POST in bottle.

$ pip install bottle
$ pip install mako
$ mkdir bottleapp
$ cd bottleapp

The bottle application looks like:
echo.py

# -*- coding:utf-8 -*-

from bottle import Bottle, mako_view, request
from gefr.core import Gefr
from gefr.backends.mina import MinaBackend

bapp = Bottle()

@bapp.get('/')
@mako_view('edit')
def display_form():
    return dict(ip=request.environ.get('REMOTE_ADDR'))

@bapp.post('/')
@mako_view('show')
def display_content():
    content = request.POST.get('content')
    return dict(content=content)

Gefr(bapp, MinaBackend).start()

The simple mako templates are:

edit.tpl

<html>
<head>
    <title>Bottle on MINA</title>
</head>
<body>
    <h1>Bottle on MINA</h1>
    <p>User from ${ip}, leave your words please:</p>
    <form action="/" method="post">
        <textarea cols="60" rows="20" name="content"></textarea>
        <p><input type="submit" /></p>
    </form>
</body>
</html>

show.tpl

<html>
    <head>
        <title>Bottle on MINA</title>
    </head>
    <body>
        <h1>Bottle on MINA</h1>
        <pre>${content | h}</pre>

    </body>
</html>

Start the bottle app:
$ jython-all echo.py

This is rather simple but it covers the most basic features of a web server.

I should say thank you for reaching here. Gefr is still an experimental project in early development. So there must be bugs and potential performance improvements in it. Feel free to contact me if you are also interested in it. The project is hosted on bitbucket:
https://bitbucket.org/sunng/gefr

gefr API updates

早上收到jythonet作者的一封邮件,受到启发,我打算扩展一下gefr这个WSGI adapter。原本gefr是写给soldat用来测试的,结果发现现在出现了买椟还珠的效果,既然大家更加关注这个,我决定多花一些时间在上面。不过话说回来gefr确实是Jython Web程序的新思路,过去大家都在想怎样把Jython放进Servlet中,gefr的思路是把Java的服务器实现放到WSGI后面。未来会有gefr-netty, gefr-mina, gefr-servlet等等出现,如果真的存在一个可靠的backend,也许这种思路也不失为一个办法,毕竟python的web框架更吸引人,而java的基础设施相对可靠,各取所长。

根据这个目的,现在gefr 0.2的API更新为:

即在创建Gefr的时候需要告知backend类型,目前还只有soldat一种后端。未来也可能通过自动检测让这个参数成为可选。

gefr 0.2已经在开发中,您可以通过这里关注:
https://bitbucket.org/sunng/gefr/overview

Setting up soldat and gefr

本文介绍soldat服务器和gefr WSGI适配器的环境搭建,以及jip的基本使用。

安装python工具

virtualenv和pip是python开发的关键工具
sudo apt-get install python-virtualenv
sudo apt-get install python-pip

jython需要您手动下载安装。推荐安装到/usr/local/下,并建立软连接到/usr/local/bin/中,下文将假设您是这么做的。

创建虚拟jython环境

virtualenv -p /usr/local/bin/jython gefr-test
cd gefr-test
source bin/activate

安装jip

pip install jip

配置jip

在$HOME下创建文件.jip,内容为:

[repos:oss]
uri=http://oss.sonatype.org/content/repositories/snapshots/
type=remote

[repos:central]
uri=http://repo1.maven.org/maven2/
type=remote

[repos:local]
uri=/home/sun/.m2/repository/
type=local

安装soldat

配置完jip后,可以使用jip来安装soldat
jip install info.sunng.soldat:soldat:1.0-SNAPSHOT

文件将被下载到 javalib 目录中,您可以检查安装的正确性:
$ ls javalib/
log4j-1.2.16.jar slf4j-log4j12-1.6.1.jar
slf4j-api-1.6.1.jar soldat-1.0-SNAPSHOT.jar

安装gefr

pip install gefr==0.1dev2

创建一个简单的Python WSGI程序

创建test.py

from gefr import Gefr

def wsgiapp(environ, start_response):
    status = '200 OK'
    res_body = "<html><head><title>Welcome</title></head><body><h1>It works!</h1></body></html>"
    res_headers = [('Content-Type', 'text/html'),
            ('Content-Length', str(len(res_body)))]
    start_response(status, res_headers)
    return [res_body]

Gefr(wsgiapp, host='0.0.0.0', port=8000).start()

启动服务

使用jip附带的jython-all
jython-all test.py

打开浏览器,访问 http://localhost:8000/

用ab测试服务性能

$ ab -n 10000 -c 100 http://localhost:8000/

This is ApacheBench, Version 2.3 <$Revision: 655654 $>
Copyright 1996 Adam Twiss, Zeus Technology Ltd, http://www.zeustech.net/
Licensed to The Apache Software Foundation, http://www.apache.org/

Benchmarking localhost (be patient)
Completed 1000 requests
Completed 2000 requests
Completed 3000 requests
Completed 4000 requests
Completed 5000 requests
Completed 6000 requests
Completed 7000 requests
Completed 8000 requests
Completed 9000 requests
Completed 10000 requests
Finished 10000 requests

Server Software:        gefr/0.1dev
Server Hostname:        localhost
Server Port:            8000

Document Path:          /
Document Length:        79 bytes

Concurrency Level:      100
Time taken for tests:   2.539 seconds
Complete requests:      10000
Failed requests:        0
Write errors:           0
Total transferred:      1640000 bytes
HTML transferred:       790000 bytes
Requests per second:    3938.11 [#/sec] (mean)
Time per request:       25.393 [ms] (mean)
Time per request:       0.254 [ms] (mean, across all concurrent requests)
Transfer rate:          630.71 [Kbytes/sec] received

Connection Times (ms)
              min  mean[+/-sd] median   max
Connect:        0    0   0.6      0       8
Processing:     9   25  22.4     21     239
Waiting:        9   25  22.4     21     239
Total:         13   25  22.3     21     239

Percentage of the requests served within a certain time (ms)
  50%     21
  66%     22
  75%     22
  80%     23
  90%     26
  95%     40
  98%     65
  99%    233
 100%    239 (longest request)

Good Luck!

Python Web 开发框架 Bottle

Bottle是一个非常精致的WSGI框架,它提供了Python Web开发中需要的基本支持:URL路由,Request/Response对象封装,模板支持,与WSGI服务器集成支持。整个框架的全部代码约有2000行,它的核心部分没有其他任何依赖,只要有Python环境就可以运行。

Bottle适用于小型的Web开发,在应用程序规模比较小的情况下可以实现快速开发。但是由于自身功能所限,对于大型的Web程序,Bottle的功能略显不足,程序员需要手动管理模块、数据库、配置等等,与Pylons等框架相比Bottle的优势就难以体现出来了。

快速入门

通过一个简单的、典型的例子描述Bottle的使用:

from bottle import Bottle, run, mako_view, request

myapp = Bottle()

@myapp.get('/hello/:name/:count#\\d+#')
@mako_view('hello')
def hello(name, count):
    ip = request.environ.get('REMOTE_ADDR')
    return dict(n=name, c=int(count), ip=ip)

run(app=myapp)

我们创建一个Bottle对象,通过decorator配置一条路由记录。Bottle的url映射支持具名参数,“/hello/:name/:count#\\d+#” 格式的参数,可以匹配/hello/(.+?)/(\d+?)的URL。在方法体中,通过environ字典获得客户端IP,这个操作和其他WSGI框架是一致的。接着通过一个字典类型将Model数据传递给View。View模板通过decorator定义,采用mako模板引擎实现,模板名为hello,他表示在当前目录下一个名叫hello.tpl的文件。

剖析

Bottle主要可以分成4个模块:

  • Router和WSGI Application
  • Request和Response, Web helpers
  • Template Adapters
  • WSGI Server Adapters

Router和WSGI Application

一个Bottle对象是一个标准的WSGI App,这使他可以与很多WSGI Server集成。每个app下管理一个Router用于针对URL映射到handler方法。根据Python decorator的特性,路由规则是在程序启动时自动执行的。

Bottle的route方法用于注册handler到router中。除此以外,还有很多function currying帮助您简单地路由GET / POST 等特定HTTP方法。

Bottle最近以来支持了hook,可以注册一些方法在每个请求之前、或之后执行。

当接受一个HTTP请求时,WSGI Server会调用WSGI App的wsgi方法。它的流程是:

  1. 根据标准的WSGI接口规范,从WSGI environ中生成一个Request对象,初始化一个response对象。
  2. 在寻找方法之前,实际上Bottle会自动执行您注册的before_hook。(Bottle._add_hook_wrapper)
  3. 根据request.path的URL在router中寻找对应的handler方法(Bottle.match_url, Router.match)。这一步中除了找到合适的handler方法,还要负责提取url中的具名参数,将结果以tuple的形式返回。
  4. 以返回的方法和参数,执行handler,获得返回值。
  5. 执行所有的after_hooks。
  6. 根据返回的不同类型,写入Response的头部。在这一步,Bottle还会应用一些filter,在Bottle中,filter是用于处理handler返回类型的。例如,一个典型的filter即内置的dict2json,他将handler返回的dict类型自动换成json。(Bottle._cast)
  7. 根据返回的HTTP状态码,对handler返回对象进行处理。调用WSGI Server的start_response方法将返回对象写给客户端。

Request和Response, Web helpers

Bottle对HTTP的请求和响应封装了Request和Response对象。它采用threadlocal的方式,由Bottle app管理生命周期。您可以在声明handler方法时不必像Java Servlet那样将他们以参数传入,这样增加了方法设计的灵活性,也使得单元测试变得相对轻松。

Request对象是对WSGI environ属性的封装,可以从中取得的属性取决于WSGI Server对PEP333的实现。

关于Request API,可以参考文档

类似的,可以参考Response API了解如何对HTTP响应进行操作

和很多Web框架一样,你不必手动去设置HTTP重定向,helper方法会简化这些操作:abort, redirect, static_file。当然,你也可以自己创建一些helper。

Template Adapters

template方法用于渲染视图,您可以使用不同的模板实现:mako, jinja2, cheetah, simpletal以及Bottle自己的简单实现。

view作为一个decorator可以简化模板的选择。与route类似,作者也提供了一些function currying来支持mako_view这样简便的写法。

Server Adapters

Bottle的Server Adapters简直可以说是WSGI Server的博览会,从这里您可以了解目前比较流行的WSGI实现:

  • flup
  • wsgiref
  • cherrypy
  • paste
  • fapws3
  • tornado
  • Google Appengine
  • twisted
  • diesel
  • meinheld
  • gunicorn
  • eventlet
  • gevent
  • rocket

当然,这些不是全部,如果要使用不在其中的WSGI Server,您只需实现一个ServerAdapter的run方法即可,需要做的就是将Bottle app传给server并启动它。

内置的run方法用于启动服务,您还可以指定一个reloader参数使Bottle在后台检查源文件的修改情况,实现热加载。
除了run方式的启动,由于Bottle app本身就是一个符合标准的WSGI app,所以也可以通过一些服务器特有的方式启动服务,例如gunicorn:
gunicorn -w 2 -D -b 127.0.0.1:18080 module:app

实战

Middlewares

Middleware是WSGI的重要概念http://www.python.org/dev/peps/pep-0333/#middleware-components-that-play-both-sides 借助一些成熟的middleware可以添加一些Bottle目前不具备的功能:没错,Session。

Session Middleware最著名的选择叫做Beaker,要其用Beaker,只需要一个装饰器模式的App声明即可,您可以参考Beaker的文档.

Pylons以使用Middleware著称,而除了Routing这样核心的Middleware,包括Beaker和Authkit都可以应用在Bottle程序上。

App Mounting

Bottle的App提供一个很有用的mount方法帮助你将Web应用模块化。您可以将一个Bottle App挂载到一个父App上的某个路径,以父App启动后,父App可以为子App在一个路径下提供路由。

不过你会遇到这样的问题:

child = Bottle()

@child.get("/")
def hello():
    return "hello world"

parent = Bottle()
parent.mount(child, "/child")

很自然的,您希望打开浏览器访问http://localhost:8000/child可能看到hello world,然后却得到404.这个问题不难察觉,于是访问http://localhost:8000/child/,工作正常。

我曾给作者提过这个问题http://github.com/defnull/bottle/issues#issue/88,(补一句,Bottle的作者少有的Nice,对你提出的问题他通常都会认真解答),作者提了一个重定向的方式,不过还是没有决定把它直接加进mount方法里。

那么,我们需要一个新的mount来支持http://localhost:8000/child

def mount(parent, child, path):
    parent.mount(child, path)
    @parent.get(path)
    def redir():
        redirect(path+"/")

Google AppEngine

因为有WSGI标准,你可以用Bottle开发Google AppEngine程序。您只需要在handler文件中加入这样的代码即可:

from google.appengine.ext.webapp.util import run_wsgi_app
def main():
    run_wsgi_app(my_bottle_app)

if __name__ == '__main__':
    main()

依赖?Bottle本身是self-contained,不过你可能还需要一个强大一些的模板引擎来完成你的应用,比如Mako。这样,你需要把依赖和自己的程序一起上传到GAE。对于Mako来说,除了它自己,还依赖marksave,别忘了它。

以上就是我对Bottle的理解和使用心得,希望您在合适的场景下使用Bottle,只有这样,才能感受到这个框架所带来的乐趣。

bottle & fapws3

Bottle是一个Python web框架,兼容wsgi标准,lightweight,self-contained。

提到web框架,自然要和相类似的python框架相比。

Django是大型框架,包含ORM、Controller、Templating全套,这也是Django的缺点,使用Django意味着必须使用关系型数据库进行存储(尽管有一些Model层的其他实现,但绝大多数都是Hack的方式实现),必须使用Django并不非常出色的Template系统。Pylons针对Django的这些问题,采用了松散的方式,数据层可选择由SQLAlchemy实现,模板系统可以选择mako / jinja等。Pylons用paster来管理项目、创建代码模板。借鉴了rails的哲学,目录结构也相类似。可是pylons仍然显得重量级,把注意力放到web.py。只要定义一个router,定义相应的handler就可以处理web请求,handler对象的GET POST等方法分别对应相应的HTTP请求。看起来不错了,不过与bottle相比,webpy仍然显得繁琐、功能有限,而且它本身的db模块就更加鸡肋了。

看一个实例便知:
定义一个简单的HTTP页面:

from bottle import Bottle, run, mako_view, request
from bottle import FapwsServer

myapp = Bottle()

@myapp.route('/nihao/:name/:count#\\d+#')
@mako_view('nihao')
def nihao(name, count):
    return dict(n=name, c=int(count), ip=request.environ.get('REMOTE_ADDR'))

run(app=myapp, server=FapwsServer)

对应的nihao.tpl模板,用mako引擎实现:

<html>
<head>
    <meta http-equiv="content-type" content="text/html; charset=utf-8">

    <title>Nihao</title>

</head>
<body>
    <div id="ip">
        <h1 id="heading">Request From ${ip}</h1>
    </div>
    <%def name="greeting(n)">
        <div id="name">
            Nihao ${n}
        </div>
    </%def>
    % for i in range(c):
        ${greeting(n)}
    % endfor
</body>
</html>

仅仅是一些简单的内容,接着只需要:
python bottle-test.py
即可运行服务器。

bottle通过decorator定义route规则,还支持url提取参数。通过decorator指定模板、模板引擎。可以说近乎简化到了极致。

bottle遵循单一职责原则,不提供数据层的实现,由用户自己指定,bottle没有任何限制。模板引擎,bottle支持mako / jinja / cheetah,本身还内建一个默认SimpleTemplate引擎。bottle还支持多种wsgi服务器,包括flup / wsgiref / cherrypy / paste / twisted / tornado / fapws3 等等。

最后提一个wsgi的实现fapws3,号称是目前最快的wsgi服务器。fapws3用libev实现,在不同的操作系统上采用不同的多路IO模型以达到高性能。

bottle的作者做过一个关于不同实现的性能比较:
http://bottle.paws.de/page/2009-12-19_Comparing_HelloWorld_Performance

The post is brought to you by lekhonee v0.7