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。

讲完,收工 :)


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

查看所有标签

猜你喜欢:

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

平台革命:改变世界的商业模式

平台革命:改变世界的商业模式

[美]杰奥夫雷G.帕克(Geoffrey G. Parker)、马歇尔W.范·埃尔斯泰恩(Marshall W. Van Alstyne)、桑基特·保罗·邱达利(Sangeet Paul Choudary) / 志鹏 / 机械工业出版社 / 2017-10 / 65.00

《平台革命》一书从网络效应、平台的体系结构、颠覆市场、平台上线、盈利模式、平台开放的标准、平台治理、平台的衡量指标、平台战略、平台监管的10个视角,清晰地为读者提供了平台模式最权威的指导。 硅谷著名投资人马克·安德森曾经说过:“软件正在吞食整个世界。”而《平台革命》进一步指出:“平台正在吞食整个世界”。以平台为导向的经济变革为社会和商业机构创造了巨大的价值,包括创造财富、增长、满足人类的需求......一起来看看 《平台革命:改变世界的商业模式》 这本书的介绍吧!

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

多种字符组合密码

MD5 加密
MD5 加密

MD5 加密工具

XML、JSON 在线转换
XML、JSON 在线转换

在线XML、JSON转换工具