又一款 Web 微框架 japronto

上周被推荐了一款 Python 的 Web 微框架:japronto,然后就看了一下,作者吹嘘得很厉害,下面是作者的性能对比图:

japronto_perfomance_compare.png

看上去可是非常厉害的,速度比 nodejs 和 golang 快了两个数量级!!!但是,当我细看一下 README 的时候,我发现了这一段:

This is an early preview with alpha quality implementation. APIs are provisional meaning that they will change between versions and more testing is needed. Don't use it for anything serious for now and definitely don't use it in production. Please try it though and report back feddback. If you are shopping for your next project's framework I would recommend Sanic.

不知道你看完有什么感觉,反正我看完就觉得作者过于吹嘘了。我尝试了作者提供的多个 example,使用过程中发现其实这个框架的功能非常简单,真的可以算的上是微框架了,比 Flask 的微高到不知道哪里去了。说是简单,其实就是封装很少,尽可能得保持原始代码,例如 python3.5 的 asyncio 的使用,基本可以说是编程语言级别的精简,这样的代码性能能不高?

性能高的同时,必然就带来维护的艰难性,并且从作者的文档来看,并没有大型应用的比较好实践,因此,我可以先莽撞得断定一下,这款框架拿来小打小闹一下还是可以的,但是,真的千万别在企业级应用中使用,不然,后期肯定会后悔死。

好吧,喷了这么多,还是要拿出点证据来的,下面就以我的尝试介绍一下这款框架的主要功能,可能有不周到的地方,欢迎各位同学留言讨论,所有 DEMO 代码都在 Github 上:https://github.com/yetship/blog_codes/tree/master/japronto_demo

Demos

凡是 Web 框架必然少不了这几样:路由,请求和响应(同见:Flask Tornado 简单对比),所以,就先拿这几样说说:

  1. 路由

    japronto 的路由没有太大特色,反而有点累赘,因为你添加路由还得获得 router 对象,然后再 add_route,一个非常简单的 japronto 应用如下 route01.py

    1
    2
    3
    4
    5
    6
    7
    8
    from japronto import Application
    
     def hello(req):
         return req.Response("hello world")
    
     app = Application()
     app.router.add_route('/', hello)
     app.run(debug=True)
    

    可以看到是比较累赘得,不过,在 URL 参数方面也还是可以的,支持路径参数,对于请求方法的处理还是比较普通常见的 route02.py

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    from japronto import Application
    
     app = Application()
     r = app.router
    
     def methods(req):
         return req.Response(text=req.method)
     r.add_route('/methods', methods, methods=['POST', 'DELETE'])
    
     def params(req):
         return req.Response(text=str(req.match_dict))
     r.add_route('/params/{p1}/{p2}', params)
    
     app.run(debug=True)
    

    这基本上就是 router 的所有功能了,非常的简单。

  2. 请求和响应

    router 的部分我们可以发现,请求都是通过函数参数来传递的,例如:def hello(req);一个值得吐槽的点就是响应类居然是 request 的参数!然后响应的处理我觉得也是比较粗糙的,因为响应都需要构建一个 Response 对象,我觉得比较舒服的处理方式可以是这样的:

    1
    2
    def hello(req):
         return "hello world"
    

    在请求和响应的处理上,我个人觉得 Flask 的处理是非常优美的,使用 ThreadLocal 的方式处理,但是带来的确实性能上的缺失;但是,这也正说明了 japronto 的性能是通过这种麻烦刷出来的,前面已经吐槽过了!

  3. 异步

    这个特色可以说是 japronto 的大卖点,其实根本上就是 python3.5 的 asyncio 库,并没有太大的创新,用法也基本上是保持了 asyncio 的用法,例如下面这个 demoasyncio.py

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    import asyncio
     from japronto import Application
    
     async def asynchronous(req):
         for i in range(1, 4):
             await asyncio.sleep(1)
             print(i, 'seconds elapsed')
         return req.Response(text='3 senconds elapsed')
    
     app = Application()
     app.router.add_route('/async', asynchronous)
     app.run(debug=True)
    

    基本是原生的 asyncio 套路,作者没有为便捷性做一些封装之类的,还是那个槽点,虽然性能上去了,但是,复杂度也上去了。

  4. 扩展

    作为一个微框架,扩展性是必不可少的,不然就不叫微框架了,应该叫函数库。作为微框架japronto 为我们提供了两个扩展点:request扩展 和 异常处理

    1. request 扩展

      所谓的 request 扩展其实就是给 request 对象添加属性和方法,例如下面这个例子就分别给 request 添加了一个 属性 和一个 方法

       1
       2
       3
       4
       5
       6
       7
       8
       9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      from japronto import Application
      
       def extended_hello(request):
           if request.host_startswith('api.'):
               text = 'Hello ' + request.reversed_agent
           else:
               text = 'Hello stranger'
      
           return request.Response(text=text)
      
       def reversed_agent(request):
           return request.headers['User-Agent'][::-1]
      
       def host_startswith(request, prefix):
           return request.headers['Host'].startswith(prefix)
      
       app = Application()
       app.extend_request(reversed_agent, property=True)
       app.extend_request(host_startswith)
      
       r = app.router
       r.add_route('/', extended_hello)
       app.run()
      

      这个扩展其实就相当于 Flask 中的 pre_request,同样的,japronto 也提供了类似于 Flask 中的 post_request 的方式,不过 japronto 称之为 callback,例子为:

      1
      2
      3
      4
      5
      6
      7
      8
      def with_callback(request):
           def cb(r):
               print('Done!')
      
           request.add_done_callback(cb)
      
           print('return')
           return request.Response(text='cb')
      

      这就是关于 request 的扩展了,还是相当简单,相比与 Flask 还有多个扩展点没有提供,不知道加上这两个扩展之后,并发性能能有多少?

    2. 异常处理

      写程序肯定少不了异常处理,而在 Web 程序中,因为业务逻辑都是在 View 中完成的,所以有很多异常需要框架处理,但是框架是通用的,所以对于一些自建的或者不通用的异常肯定没有预设处理,这个时候 japronto 倒是给我们提供了一个入口,让我们自定义异常的处理函数,看示例 except.py:

       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
      from japronto import Application, RouteNotFoundException
      
       class KittyError(Exception):
           def __init__(self):
               self.greet = 'meow'
       class DoggieError(Exception):
           def __init__(self):
               self.greet = 'woof'
      
       def cat(request):
           raise KittyError()
       def dog(request):
           raise DoggieError()
       def unhandled(request):
           1 / 0
      
       app = Application()
       r = app.router
       r.add_route('/cat', cat)
       r.add_route('/dog', dog)
       r.add_route('/unhandled', unhandled)
      
       def handle_cat(request, exception):
           return request.Response(text='Just a kitty, ' + exception.greet)
       def handle_dog(request, exception):
           return request.Response(text='Just a doggie, ' + exception.greet)
       def handle_not_found(request, exception):
           return request.Response(code=404, text="Are you lost, pal?")
      
       app.add_error_handler(KittyError, handle_cat)
       app.add_error_handler(DoggieError, handle_dog)
       app.add_error_handler(RouteNotFoundException, handle_not_found)
      
       app.run()
      

至此,关于 japronto 的关键特性就算是介绍完了,相信你通过这些介绍也可以看到 japronto 的出发点就是为了性能,从而使整个框架比较原始,开发需要做的工作比较多。但是,确实,这样会让性能有很多的提高,很明显,这又是软件领域的经典问题——取舍。是要性能,还是要维护性和开发速度,这些都会因为我们开发场景不同而有不同的考虑,见仁见智。

Reference

  1. japronto documents
  2. A million requests per second with Python

· EOF ·

最近爬虫很是凶猛,标注下文章的原文地址: https://liuliqiang.info/post/another-python-micro-framework-japronto/