Web开发系列(四):Flask, Tornado和WSGI

栏目: Python · 发布时间: 5年前

内容简介:如上篇 所讲,作为一个web服务器, 我们需要建立socket连接,并且监听在指定端口,当请求来到时我们要解析请求的内容,做出判断,给出响应, 然后关闭连接,进行下一个服务。可是谁也不愿意需要做web开发的时候都从头开始,然后每次都处理这么多事情,那简直是太麻烦了。于是便有了框架, 今天我们讲两个框架,Flask,Tornado。Flask似乎很受欢迎,emm。。。实际上我目前所在公司和上一家都是用Flask,不过我个人并不喜欢Flask的设计,最讨厌两点:

如上篇 所讲,作为一个web服务器, 我们需要建立socket连接,并且监听在指定端口,当请求来到时我们要解析请求的内容,做出判断,给出响应, 然后关闭连接,进行下一个服务。

可是谁也不愿意需要做web开发的时候都从头开始,然后每次都处理这么多事情,那简直是太麻烦了。于是便有了框架, 今天我们讲两个框架,Flask,Tornado。

Flask

Flask似乎很受欢迎,emm。。。实际上我目前所在公司和上一家都是用Flask,不过我个人并不喜欢Flask的设计,最讨厌两点:

  • proxy
  • 借助proxy提供伪全局变量

那么,怎么写一个简单的Flask应用呢?在开始之前我们先来讲讲一个web框架大概需要哪些东西。

首先我们需要一个路由器,不是发射Wi-Fi的那个路由器,而是将URL里,不同的URL指向不同的函数或者其他能处理并且作出响应的 东西,我们叫做路由器,或者叫路由。此外我们需要一个东西,包含一些默认的配置,一般情况下都会叫做 "app",一般都会把router 放在app里。这样我们就可以做出一个简单的web框架。

但是框架之所以叫做框架,是因为它规定了一系列流程,定义好了一系列接口,应用 程序员 只需要按照给定的接口写出符合接口的代码, 便可以做出web服务来。比如吧,我们决定我们的web框架有这样一系列动作:

def before_request(app, request):
    pass

def handle_request(app, request):
    pass

def after_request(app, request):
    pass

我们的应用将会从上至下依次调用函数,那么我们只要实现具体的函数,便可以完成指定的功能。

我们来看一个简单的Flask示例,来自官网:

from flask import Flask
app = Flask(__name__)


@app.route("/")
def hello():
    return "Hello World!"


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

保存并运行,就可以了。Flask的核心之一在于 @app.route 这个装饰器,他有一个概念叫做Blueprint,是什么呢?就是把一伙URL 集结在一起,比如,凡是 /api/say/v1 开头的URL都放在 say_bp_v1 下,那么便可以这样使用:

@say_bp_v1.route("/hello")
def foo():
    pass


@say_bp_v1.route("/world")
def bar():
    pass

其作用吧。。。其实是可以少些很多重复的代码,差不多就这样。我们来看看 @app.route 的源码:

def route(self, rule, **options):
    def decorator(f):
        endpoint = options.pop('endpoint', None)
        self.add_url_rule(rule, endpoint, f, **options)
        return f
    return decorator

所以我们应该追下去看 add_url_rule ,这里我们暂不继续 展开。

我们再看一下Flask中最最核心的东西,proxy:

https://github.com/pallets/flask/blob/master/flask/globals.py#L14

继续追到Werkzeug的代码中看 LocalProxyLocalStack :

class LocalStack(object):
    def __init__(self):
        self._local = Local()

    def __release_local__(self):
        self._local.__release_local__()

    def _get__ident_func__(self):
        return self._local.__ident_func__

    def _set__ident_func__(self, value):
        object.__setattr__(self._local, '__ident_func__', value)
    __ident_func__ = property(_get__ident_func__, _set__ident_func__)
    del _get__ident_func__, _set__ident_func__

    def __call__(self):
        def _lookup():
            rv = self.top
            if rv is None:
                raise RuntimeError('object unbound')
            return rv
        return LocalProxy(_lookup)

    def push(self, obj):
        """Pushes a new item to the stack"""
        rv = getattr(self._local, 'stack', None)
        if rv is None:
            self._local.stack = rv = []
        rv.append(obj)
        return rv

    def pop(self):
        """Removes the topmost item from the stack, will return the
        old value or `None` if the stack was already empty.
        """
        stack = getattr(self._local, 'stack', None)
        if stack is None:
            return None
        elif len(stack) == 1:
            release_local(self._local)
            return stack[-1]
        else:
            return stack.pop()

    @property
    def top(self):
        """The topmost item on the stack.  If the stack is empty,
        `None` is returned.
        """
        try:
            return self._local.stack[-1]
        except (AttributeError, IndexError):
            return None

@implements_bool
class LocalProxy(object):
    __slots__ = ('__local', '__dict__', '__name__', '__wrapped__')

    def __init__(self, local, name=None):
        object.__setattr__(self, '_LocalProxy__local', local)
        object.__setattr__(self, '__name__', name)
        if callable(local) and not hasattr(local, '__release_local__'):
            # "local" is a callable that is not an instance of Local or
            # LocalManager: mark it as a wrapped function.
            object.__setattr__(self, '__wrapped__', local)

    def _get_current_object(self):
        """Return the current object.  This is useful if you want the real
        object behind the proxy at a time for performance reasons or because
        you want to pass the object into a different context.
        """
        if not hasattr(self.__local, '__release_local__'):
            return self.__local()
        try:
            return getattr(self.__local, self.__name__)
        except AttributeError:
            raise RuntimeError('no object bound to %s' % self.__name__)

    @property
    def __dict__(self):
        try:
            return self._get_current_object().__dict__
        except RuntimeError:
            raise AttributeError('__dict__')

    # 略略略

可以看出(当然,肯定不是这样随随便便看两眼,其实Flask的源码还是可以研究研究的),Flask的本质就是,如果你执行以下代码:

from flask import request

@app.route("/")
def foo():
    print(request.args.get("hello"))

request本来是一个导入的object,但实际上从中获取属性或者值时,会从栈顶的ctx里,再取出来,所以他是个代理,哎,不多说了, 等你看过Flask源码之后你就知道这个设计有多么不科学了(虽然很多人都似乎比较喜欢Flask。。。)。

对Flask有兴趣的可以看看我的这篇博客: https://jiajunhuang.com/articles/2016_09_15-flask_source_code.rst.html

Tornado

Tornado我还是比较喜欢,可惜除了web框架之外,数据库或者其他几乎都是阻塞的。Tornado与Flask的函数形式的写法不一样,Tornado 属于class形式的写法,我认为这个设计比较科学,举个例子:

import tornado.ioloop
import tornado.web


class MainHandler(tornado.web.RequestHandler):
    def get(self):
        self.write("Hello, world")

    def post(self):
        self.write("post :)")


def make_app():
    return tornado.web.Application([
        (r"/", MainHandler),
    ])


if __name__ == "__main__":
    app = make_app()
    app.listen(8888)
    tornado.ioloop.IOLoop.current().start()

于是乎,对同一个URL的get,post,put等请求,我们都可以只要实现对应方法便可以,有人要说了,Flask也有class based view,emm, 是有,你去看看源码看看你会想用吗?

Tornado比我们最上面所说的框架所需要的东西还多了什么呢?还多了一个IOLoop,I/O多路复用,此外借助yield,用同步的方式写异步代码, 当然,前提是带上病毒式传播的decorator----只要想写非阻塞代码,那么这个decorator便一加到底。

关于yield是如何把本来回调式的代码连接起来编程同步式的代码,可以看这篇博客: https://jiajunhuang.com/articles/2016_11_29-python_yield.md.html

此外我写了一个类似的Tornado的代码,基于Cython和UVLoop: https://github.com/jiajunhuang/storm 有兴趣的话可以看看。

既然Tornado这么好用,性能又高,为什么好像还没有Flask受欢迎呢?因为Web开发虽然看起来就是分析一下请求,给一下响应,但是远 不是这么简单,还需要和数据库打交道,Tornado自身可以写出非阻塞的代码,但是连数据库,想用ORM的时候却不行,所以也不是特别方便。

因此很多人选择使用Flask或者是Django,然后Gunicorn挡在前面,加上Gevent加持,于是又可以愉快的用写同步的方式写异步。说起Gunicorn, 我们就得说说WSGI了。

WSGI

WSGI全名Web Server Gateway Interface,Python界web框架百家争鸣,怎么统一一下呢?于是便有了WSGI这种,定义接口,而非定义实现的方式。 具体需要看看这里: https://www.python.org/dev/peps/pep-3333/#specification-details 实现了对应的接口,便可以接入针对WSGI的 应用例如Gunicorn。

讲完,收工 :)


以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,也希望大家多多支持 码农网

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

Google's PageRank and Beyond

Google's PageRank and Beyond

Amy N. Langville、Carl D. Meyer / Princeton University Press / 2006-7-23 / USD 57.50

Why doesn't your home page appear on the first page of search results, even when you query your own name? How do other web pages always appear at the top? What creates these powerful rankings? And how......一起来看看 《Google's PageRank and Beyond》 这本书的介绍吧!

JS 压缩/解压工具
JS 压缩/解压工具

在线压缩/解压 JS 代码

随机密码生成器
随机密码生成器

多种字符组合密码