flask使用心得

Flask开发

flask不像django那样重量,它非常轻巧,可由程序员自己深度掌控。很适合用来做一些原型快速开发。
一个hello world的例子:

from flask import Flask
import logging

app = Flask(__name__)

@app.route('/')
def hello_world():
    app.logger.info('hello called')
    return 'hello, lee'
    
# 这里指定路径参数类型为int,否则flask默认认为是string
@app.route('/repair_plans/page/<int:page_size>/<int:cur_page>')
def query_data(page_size, cur_page):
    app.logger.info('size:%d, cur_page:%d' % (page_size, cur_page))
    return jsonify(
        pageVO = {'totalRows':1},
        result = [{'bom':'1', 'repairMethod':'维修', 'battleField':'门店', 'isInherited':True, 'repairTech': '打磨', 'repairTechId':101}]
    )

if __name__ == '__main__':
    handler = logging.FileHandler('flask.log')
    handler.setFormatter(logging.Formatter('[%(process)d|%(thread)d][%(asctime)s] [%(levelname)s] %(message)s'))
    app.logger.setLevel(logging.INFO)
    app.logger.addHandler(handler)
    # 这里使用debug模式,可以自动加载修改,非常方便调试
    app.run(debug=True, host='0.0.0.0')   

首先,flask使用注解来绑定url和其处理函数,比django更直观,近似于java web框架的做法了。
其次,flask对日志的使用,就跟普通程序里使用logging一样,不用像django那样受限于框架。

获得incoming request和session

直接访问全局变量request和session即可。

构建response

rv = {'status': script.status, 'msg': msg}
Response(json.dumps(rv), mimetype='application/json')

request

flask里的request看起来是一个全局变量,但其实是threadLocal的,可以放心使用,session变量也是类似的情况。参看该文

另外,flask的request使用了werkzeug库来包装,它提供了一些成员:

request.form 存放的是界面form表单里的数据;
request.json 存放的是请求体里的数据,相当于spring的@RequestBody注解
request.args 存放的是url路径中的参数,类似于spring的@RequestParam注解。

下载文件

有两种方法:

  1. 将文件放到通过url可访问到的路径
  2. 使用flask.send_from_directory,可从url不能访问到的路径传输文件

注意:send_from_directory会做路径检查,确保没有…这样的可能引发安全问题的路径。

flask底层的文件传输有几种策略:

  1. WSGI server’s file_wrapper support
  2. X-Sendfile头

静态资源服务

flask的send_static_file实际调用的就是send_from_directory(顺带说一下,网上说send_static_file不安全,其实既然send_from_directory是安全的,那send_static_file就也是安全的!)。因此,flask对静态资源的支持跟文件下载是同一原理。

这里有个问题,是让nginx这样的代理服务器代为管理静态资源(nginx在这方面是非常高效的),还是由flask来处理静态资源?

SO上有一段说明:

The preferred method is to use nginx or another web server to serve static files; they'll be able to do it more efficiently than Flask.

我理解,这里面有2点值得注意:

  1. 静态资源的缓存问题,为了效率起见,我们不可能每次都去重新传输一个文件;
  2. 如果有多个flask实例,这些实例间可以共享同一份静态资源,此时可以在多个flask实例前再套一个nginx,由nginx在负载均衡的同时,提供静态资源服务。

flask caching

参考此文

Blueprint自动添加url前缀

使用Blueprint,可以很方便的为rest请求和静态资源增加url前缀,类似于spring的server.servlet.context-path

先定义蓝图(view.py文件):

main_view = Blueprint('main-view', __name__, static_folder='webapp/')

然后在该蓝图下写路由:

@main_view.route('/')
def home_page():
    gen_session_if_needed()
    return render_template('index.html',
                           example_content='',
                           example_filename='',
                           prerequisite_content='',
                           prerequisite_filename='')

最后注册到flask里面:

from view import main_view

app.register_blueprint(main_view)

注意:蓝图默认不会注册静态资源的路由,需要显示用static_folder来指定。

Blueprint的原理

def route(self, rule, **options):
        """Like :meth:`Flask.route` but for a blueprint.  The endpoint for the
        :func:`url_for` function is prefixed with the name of the blueprint.
        """
        ## f是url的映射函数
        def decorator(f):
            endpoint = options.pop("endpoint", f.__name__)
            ## 缓存映射函数
            self.add_url_rule(rule, endpoint, f, **options)
            return f

        return decorator

def add_url_rule(self, rule, endpoint=None, view_func=None, **options):
        ## 这里记录的是BlueprintSetupState.add_url_rule函数
        self.record(lambda s: s.add_url_rule(rule, endpoint, view_func, **options))    
        
def record(self, func):
        """Registers a function that is called when the blueprint is
        registered on the application.  This function is called with the
        state as argument as returned by the :meth:`make_setup_state`
        method.
        """
        if self._got_registered_once and self.warn_on_modifications:
            from warnings import warn

            warn(
                Warning(
                    "The blueprint was already registered once "
                    "but is getting modified now.  These changes "
                    "will not show up."
                )
            )
        self.deferred_functions.append(func)        
        
def register(self, app, options, first_registration=False):
        """Called by :meth:`Flask.register_blueprint` to register all views
        and callbacks registered on the blueprint with the application. Creates
        a :class:`.BlueprintSetupState` and calls each :meth:`record` callback
        with it.

        :param app: The application this blueprint is being registered with.
        :param options: Keyword arguments forwarded from
            :meth:`~Flask.register_blueprint`.
        :param first_registration: Whether this is the first time this
            blueprint has been registered on the application.
        """
        self._got_registered_once = True
        ## 创建一个BlueprintSetupState
        state = self.make_setup_state(app, options, first_registration)

        ## 处理静态资源 
        if self.has_static_folder:
            state.add_url_rule(
                self.static_url_path + "/<path:filename>",
                view_func=self.send_static_file,
                endpoint="static",
            )
        
        ## 调用前面缓存的url映射函数
        for deferred in self.deferred_functions:
            deferred(state)        
...
...

## BlueprintSetupState.add_url_rule函数,真正添加flask的url路由的地方
def add_url_rule(self, rule, endpoint=None, view_func=None, **options):
        """A helper method to register a rule (and optionally a view function)
        to the application.  The endpoint is automatically prefixed with the
        blueprint's name.
        """
        ##自动为rule添加url_prefix
        if self.url_prefix is not None:
            if rule:
                rule = "/".join((self.url_prefix.rstrip("/"), rule.lstrip("/")))
            else:
                rule = self.url_prefix
        options.setdefault("subdomain", self.subdomain)
        if endpoint is None:
            endpoint = _endpoint_from_view_func(view_func)
        defaults = self.url_defaults
        if "defaults" in options:
            defaults = dict(defaults, **options.pop("defaults"))
        # 然后加到flask app的url路由中,由于该函数肯定是在register blueprint之后调,self.app必然已设置    
        self.app.add_url_rule(
            rule,
            "%s.%s" % (self.blueprint.name, endpoint),
            view_func,
            defaults=defaults,
            **options
        )

总结:蓝图会事先缓存url映射函数,当注册到flask时(通过app.register_blueprint)才真正去添加url路由。

常见问题

结尾有无反斜杠的区别

我们的根url结尾最好加一个斜杠,否则会找不到链接,故障现象是zuul里压根就没有请求的url。因为我们的代码一般是这么写的:

@main_view.route('/')
def home_page():
    gen_session_if_needed()
    return render_template('index.html',
                           example_content='',
                           example_filename='',
                           prerequisite_content='',
                           prerequisite_filename='',
                           url_prefix=SCIMATE_URL_PREFIX)

这就表示根url最后必须要有一个斜杠。

Flask部署

前端都使用nginx做反向代理及负载均衡。

结合gevent

Flask+gevent+gevent.pywsgi:使用gevent自带的WSGIServer

Flask+gevent+uwsgi: uwsgi里有使用gevent的选项

Flask+gevent+gunicorn:gunicorn里有使用gevent的选项

不使用gevent

Flask+uwsgi

Flask+gunicorn

参考文档

flask启动

如果在代码里写了app.run,就可以直接像普通python程序那样运行。
若没写app.run,启动命令:

flask run -h 0.0.0.0 -p 8080
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值