标签归档:flask

Python Flask,如何设置内容类型

问题:Python Flask,如何设置内容类型

我正在使用Flask,并且从get请求返回一个XML文件。如何将内容类型设置为xml?

例如

@app.route('/ajax_ddl')
def ajax_ddl():
    xml = 'foo'
    header("Content-type: text/xml")
    return xml

I am using Flask and I return an XML file from a get request. How do I set the content type to xml ?

e.g.

@app.route('/ajax_ddl')
def ajax_ddl():
    xml = 'foo'
    header("Content-type: text/xml")
    return xml

回答 0

尝试这样:

from flask import Response
@app.route('/ajax_ddl')
def ajax_ddl():
    xml = 'foo'
    return Response(xml, mimetype='text/xml')

实际的Content-Type基于mimetype参数和字符集(默认为UTF-8)。

响应(和请求)对象记录在这里:http : //werkzeug.pocoo.org/docs/wrappers/

Try like this:

from flask import Response
@app.route('/ajax_ddl')
def ajax_ddl():
    xml = 'foo'
    return Response(xml, mimetype='text/xml')

The actual Content-Type is based on the mimetype parameter and the charset (defaults to UTF-8).

Response (and request) objects are documented here: http://werkzeug.pocoo.org/docs/wrappers/


回答 1

就这么简单

x = "some data you want to return"
return x, 200, {'Content-Type': 'text/css; charset=utf-8'}

希望能帮助到你

更新:使用此方法,因为它可以与python 2.x和python 3.x一起使用

其次,它还消除了多头问题。

from flask import Response
r = Response(response="TEST OK", status=200, mimetype="application/xml")
r.headers["Content-Type"] = "text/xml; charset=utf-8"
return r

As simple as this

x = "some data you want to return"
return x, 200, {'Content-Type': 'text/css; charset=utf-8'}

Hope it helps

Update: Use this method because it will work with both python 2.x and python 3.x

and secondly it also eliminates multiple header problem.

from flask import Response
r = Response(response="TEST OK", status=200, mimetype="application/xml")
r.headers["Content-Type"] = "text/xml; charset=utf-8"
return r

回答 2

我喜欢并赞成@Simon Sapin的答案。但是,我最终采取了稍有不同的策略,并创建了自己的装饰器:

from flask import Response
from functools import wraps

def returns_xml(f):
    @wraps(f)
    def decorated_function(*args, **kwargs):
        r = f(*args, **kwargs)
        return Response(r, content_type='text/xml; charset=utf-8')
    return decorated_function

并以此方式使用它:

@app.route('/ajax_ddl')
@returns_xml
def ajax_ddl():
    xml = 'foo'
    return xml

我认为这稍微舒适一些。

I like and upvoted @Simon Sapin’s answer. I ended up taking a slightly different tack, however, and created my own decorator:

from flask import Response
from functools import wraps

def returns_xml(f):
    @wraps(f)
    def decorated_function(*args, **kwargs):
        r = f(*args, **kwargs)
        return Response(r, content_type='text/xml; charset=utf-8')
    return decorated_function

and use it thus:

@app.route('/ajax_ddl')
@returns_xml
def ajax_ddl():
    xml = 'foo'
    return xml

I think this is slightly more comfortable.


回答 3

使用make_response方法获取数据响应。然后设置mimetype属性。最后返回此响应:

@app.route('/ajax_ddl')
def ajax_ddl():
    xml = 'foo'
    resp = app.make_response(xml)
    resp.mimetype = "text/xml"
    return resp

如果Response直接使用,您将失去通过设置来定制响应的机会app.response_class。该make_response方法使用app.responses_class制作响应对象。在此您可以创建自己的类,添加以使您的应用程序全局使用它:

class MyResponse(app.response_class):
    def __init__(self, *args, **kwargs):
        super(MyResponse, self).__init__(*args, **kwargs)
        self.set_cookie("last-visit", time.ctime())

app.response_class = MyResponse  

Use the make_response method to get a response with your data. Then set the mimetype attribute. Finally return this response:

@app.route('/ajax_ddl')
def ajax_ddl():
    xml = 'foo'
    resp = app.make_response(xml)
    resp.mimetype = "text/xml"
    return resp

If you use Response directly, you lose the chance to customize the responses by setting app.response_class. The make_response method uses the app.responses_class to make the response object. In this you can create your own class, add make your application uses it globally:

class MyResponse(app.response_class):
    def __init__(self, *args, **kwargs):
        super(MyResponse, self).__init__(*args, **kwargs)
        self.set_cookie("last-visit", time.ctime())

app.response_class = MyResponse  

回答 4

from flask import Flask, render_template, make_response
app = Flask(__name__)

@app.route('/user/xml')
def user_xml():
    resp = make_response(render_template('xml/user.html', username='Ryan'))
    resp.headers['Content-type'] = 'text/xml; charset=utf-8'
    return resp
from flask import Flask, render_template, make_response
app = Flask(__name__)

@app.route('/user/xml')
def user_xml():
    resp = make_response(render_template('xml/user.html', username='Ryan'))
    resp.headers['Content-type'] = 'text/xml; charset=utf-8'
    return resp

回答 5

通常,您不必Response自己创建对象,因为make_response()它将为您处理。

from flask import Flask, make_response                                      
app = Flask(__name__)                                                       

@app.route('/')                                                             
def index():                                                                
    bar = '<body>foo</body>'                                                
    response = make_response(bar)                                           
    response.headers['Content-Type'] = 'text/xml; charset=utf-8'            
    return response

还有一件事,似乎没有人提到after_this_request,我想说点什么:

after_this_request

在此请求后执行功能。这对于修改响应对象很有用。该函数传递给响应对象,并且必须返回相同或新的对象。

因此我们可以使用来实现after_this_request,代码应如下所示:

from flask import Flask, after_this_request
app = Flask(__name__)

@app.route('/')
def index():
    @after_this_request
    def add_header(response):
        response.headers['Content-Type'] = 'text/xml; charset=utf-8'
        return response
    return '<body>foobar</body>'

Usually you don’t have to create the Response object yourself because make_response() will take care of that for you.

from flask import Flask, make_response                                      
app = Flask(__name__)                                                       

@app.route('/')                                                             
def index():                                                                
    bar = '<body>foo</body>'                                                
    response = make_response(bar)                                           
    response.headers['Content-Type'] = 'text/xml; charset=utf-8'            
    return response

One more thing, it seems that no one mentioned the after_this_request, I want to say something:

after_this_request

Executes a function after this request. This is useful to modify response objects. The function is passed the response object and has to return the same or a new one.

so we can do it with after_this_request, the code should look like this:

from flask import Flask, after_this_request
app = Flask(__name__)

@app.route('/')
def index():
    @after_this_request
    def add_header(response):
        response.headers['Content-Type'] = 'text/xml; charset=utf-8'
        return response
    return '<body>foobar</body>'

回答 6

您可以尝试以下方法(python3.6.2):

情况一:

@app.route('/hello')
def hello():

    headers={ 'content-type':'text/plain' ,'location':'http://www.stackoverflow'}
    response = make_response('<h1>hello world</h1>',301)
    response.headers = headers
    return response

案例二:

@app.route('/hello')
def hello():

    headers={ 'content-type':'text/plain' ,'location':'http://www.stackoverflow.com'}
    return '<h1>hello world</h1>',301,headers

我正在使用Flask。如果您想返回json,您可以编写以下代码:

import json # 
@app.route('/search/<keyword>')
def search(keyword):

    result = Book.search_by_keyword(keyword)
    return json.dumps(result),200,{'content-type':'application/json'}


from flask import jsonify
@app.route('/search/<keyword>')
def search(keyword):

    result = Book.search_by_keyword(keyword)
    return jsonify(result)

You can try the following method(python3.6.2):

case one:

@app.route('/hello')
def hello():

    headers={ 'content-type':'text/plain' ,'location':'http://www.stackoverflow'}
    response = make_response('<h1>hello world</h1>',301)
    response.headers = headers
    return response

case two:

@app.route('/hello')
def hello():

    headers={ 'content-type':'text/plain' ,'location':'http://www.stackoverflow.com'}
    return '<h1>hello world</h1>',301,headers

I am using Flask .And if you want to return json,you can write this:

import json # 
@app.route('/search/<keyword>')
def search(keyword):

    result = Book.search_by_keyword(keyword)
    return json.dumps(result),200,{'content-type':'application/json'}


from flask import jsonify
@app.route('/search/<keyword>')
def search(keyword):

    result = Book.search_by_keyword(keyword)
    return jsonify(result)

什么时候应使用Flask.g?

问题:什么时候应使用Flask.g?

g将请求上下文移动到应用程序上下文瓶0.10,这让我感到困惑的预期用途g

我的理解(对于Flask 0.9)是:

  • g 驻留在请求上下文中,即在请求开始时重新创建,直到结束时可用
  • g旨在用作“请求黑板”,在这里我可以放置与请求持续时间相关的内容(即,在请求的开始处设置一个标志,并在结束时(可能从before_request/ after_request对开始)进行处理)
  • 除了保持请求级别状态外,g还可以并且应该用于资源管理,即保持数据库连接等。

在Flask 0.10中,以下哪句话不再适用?有人可以指点我讨论这种变化原因的资源吗?在Flask 0.10中,我应该将什么用作“请求黑板”?我应该创建自己的应用程序/扩展特定于线程的本地代理并将其推送到上下文堆栈before_request吗?如果我的应用程序生存时间很长(不像请求),因此资源从未被释放,那么在应用程序上下文中资源管理的意义何在?

I saw that g will move from the request context to the app context in Flask 0.10, which made me confused about the intended use of g.

My understanding (for Flask 0.9) is that:

  • g lives in the request context, i.e., created afresh when the requests starts, and available until it ends
  • g is intended to be used as a “request blackboard”, where I can put stuff relevant for the duration of the request (i.e., set a flag at the beginning of the request and handle it at the end, possibly from a before_request/after_request pair)
  • in addition to holding request-level-state, g can and should be used for resource management, i.e., holding database connections, etc.

Which of these sentences are no longer true in Flask 0.10? Can someone point me to a resource discussing the reasons for the change? What should I use as a “request blackboard” in Flask 0.10 – should I create my own app/extension specific thread-local proxy and push it to the context stack before_request? What’s the point of resource management at the application context, if my application lives for a long while (not like a request) and thus the resources are never freed?


回答 0

高级瓶模式,如由马库斯联系,解释了一些变化到g0.10:

  • g 现在位于应用程序上下文中。
  • 每个请求都会推送一个新的应用程序上下文,从而清除旧的应用程序上下文,因此g仍可以用于按请求设置标志,而无需更改代码。
  • 调用 弹出应用程序上下文teardown_request。(Armin的演示文稿解释了这是因为创建数据库连接之类的事情是为请求设置环境的任务,不应在before_request和中处理after_request

Advanced Flask Patterns, as linked by Markus, explains some of the changes to g in 0.10:

  • g now lives in the application context.
  • Every request pushes a new application context, wiping the old one, so g can still be used to set flags per-request without change to code.
  • The application context is popped after teardown_request is called. (Armin’s presentation explains this is because things like creating DB connections are tasks which setup the environment for the request, and should not be handled inside before_request and after_request)

回答 1

作为该线程中信息的附录:我也flask.g对它的行为感到困惑,但是一些快速测试帮助我弄清了它。这是我尝试过的方法:

from flask import Flask, g
app = Flask(__name__)

with app.app_context():
    print('in app context, before first request context')
    print('setting g.foo to abc')
    g.foo = 'abc'
    print('g.foo should be abc, is: {0}'.format(g.foo))

    with app.test_request_context():
        print('in first request context')
        print('g.foo should be abc, is: {0}'.format(g.foo))
        print('setting g.foo to xyz')
        g.foo = 'xyz'
        print('g.foo should be xyz, is: {0}'.format(g.foo))

    print('in app context, after first request context')
    print('g.foo should be abc, is: {0}'.format(g.foo))

    with app.test_request_context():
        print('in second request context')
        print('g.foo should be abc, is: {0}'.format(g.foo))
        print('setting g.foo to pqr')
        g.foo = 'pqr'
        print('g.foo should be pqr, is: {0}'.format(g.foo))

    print('in app context, after second request context')
    print('g.foo should be abc, is: {0}'.format(g.foo))

这是它提供的输出:

in app context, before first request context
setting g.foo to abc
g.foo should be abc, is: abc  

in first request context
g.foo should be abc, is: abc
setting g.foo to xyz
g.foo should be xyz, is: xyz  

in app context, after first request context
g.foo should be abc, is: xyz  

in second request context
g.foo should be abc, is: xyz
setting g.foo to pqr
g.foo should be pqr, is: pqr  

in app context, after second request context
g.foo should be abc, is: pqr

正如Y4Kman所说的那样,“每个请求都推送一个新的应用程序上下文”。而随着烧瓶文档说,应用程序上下文“将不被请求之间共享”。现在,尚未明确说明的内容(尽管我猜想这是这些语句的隐含内容),而我的测试清楚地表明,您永远不应明确创建嵌套在一个应用程序上下文中的多个请求上下文,因为flask.g(和co)没有它具有任何神奇的功能,使其可以在上下文的两个不同“级别”中起作用,并且在应用程序和请求级别独立存在不同的状态。

现实情况是,“应用程序上下文”可能app.app_context() 一个颇具误导性的名称,因为每个请求上下文,与“请求上下文”完全相同。将其视为“请求上下文精简版”,仅在需要一些通常需要请求上下文的变量但不需要访问任何请求对象的情况下才需要(例如,在数据库中运行批处理DB操作时)外壳脚本)。如果您尝试将应用程序上下文扩展为包含多个请求上下文,那么您将遇到麻烦。因此,您应该在Flask的上下文中编写如下代码,而不是上面的测试:

from flask import Flask, g
app = Flask(__name__)

with app.app_context():
    print('in app context, before first request context')
    print('setting g.foo to abc')
    g.foo = 'abc'
    print('g.foo should be abc, is: {0}'.format(g.foo))

with app.test_request_context():
    print('in first request context')
    print('g.foo should be None, is: {0}'.format(g.get('foo')))
    print('setting g.foo to xyz')
    g.foo = 'xyz'
    print('g.foo should be xyz, is: {0}'.format(g.foo))

with app.test_request_context():
    print('in second request context')
    print('g.foo should be None, is: {0}'.format(g.get('foo')))
    print('setting g.foo to pqr')
    g.foo = 'pqr'
    print('g.foo should be pqr, is: {0}'.format(g.foo))

这将产生预期的结果:

in app context, before first request context
setting g.foo to abc
g.foo should be abc, is: abc  

in first request context
g.foo should be None, is: None
setting g.foo to xyz
g.foo should be xyz, is: xyz  

in second request context
g.foo should be None, is: None
setting g.foo to pqr
g.foo should be pqr, is: pqr

As an addendum to the information in this thread: I’ve been a bit confused by the behavior of flask.g too, but some quick testing has helped me to clarify it. Here’s what I tried out:

from flask import Flask, g
app = Flask(__name__)

with app.app_context():
    print('in app context, before first request context')
    print('setting g.foo to abc')
    g.foo = 'abc'
    print('g.foo should be abc, is: {0}'.format(g.foo))

    with app.test_request_context():
        print('in first request context')
        print('g.foo should be abc, is: {0}'.format(g.foo))
        print('setting g.foo to xyz')
        g.foo = 'xyz'
        print('g.foo should be xyz, is: {0}'.format(g.foo))

    print('in app context, after first request context')
    print('g.foo should be abc, is: {0}'.format(g.foo))

    with app.test_request_context():
        print('in second request context')
        print('g.foo should be abc, is: {0}'.format(g.foo))
        print('setting g.foo to pqr')
        g.foo = 'pqr'
        print('g.foo should be pqr, is: {0}'.format(g.foo))

    print('in app context, after second request context')
    print('g.foo should be abc, is: {0}'.format(g.foo))

And here’s the output that it gives:

in app context, before first request context
setting g.foo to abc
g.foo should be abc, is: abc  

in first request context
g.foo should be abc, is: abc
setting g.foo to xyz
g.foo should be xyz, is: xyz  

in app context, after first request context
g.foo should be abc, is: xyz  

in second request context
g.foo should be abc, is: xyz
setting g.foo to pqr
g.foo should be pqr, is: pqr  

in app context, after second request context
g.foo should be abc, is: pqr

As theY4Kman said above, “Every request pushes a new application context”. And as the Flask docs say, the application context “will not be shared between requests”. Now, what hasn’t been explicitly stated (although I guess it’s implied from these statements), and what my testing clearly shows, is that you should never explicitly create multiple request contexts nested inside one application context, because flask.g (and co) doesn’t have any magic whereby it functions in the two different “levels” of context, with different states existing independently at the application and request levels.

The reality is that “application context” is potentially quite a misleading name, because app.app_context() is a per-request context, exactly the same as the “request context”. Think of it as a “request context lite”, only required in the case where you need some of the variables that normally require a request context, but you don’t need access to any request object (e.g. when running batch DB operations in a shell script). If you try and extend the application context to encompass more than one request context, you’re asking for trouble. So, rather than my test above, you should instead write code like this with Flask’s contexts:

from flask import Flask, g
app = Flask(__name__)

with app.app_context():
    print('in app context, before first request context')
    print('setting g.foo to abc')
    g.foo = 'abc'
    print('g.foo should be abc, is: {0}'.format(g.foo))

with app.test_request_context():
    print('in first request context')
    print('g.foo should be None, is: {0}'.format(g.get('foo')))
    print('setting g.foo to xyz')
    g.foo = 'xyz'
    print('g.foo should be xyz, is: {0}'.format(g.foo))

with app.test_request_context():
    print('in second request context')
    print('g.foo should be None, is: {0}'.format(g.get('foo')))
    print('setting g.foo to pqr')
    g.foo = 'pqr'
    print('g.foo should be pqr, is: {0}'.format(g.foo))

Which will give the expected results:

in app context, before first request context
setting g.foo to abc
g.foo should be abc, is: abc  

in first request context
g.foo should be None, is: None
setting g.foo to xyz
g.foo should be xyz, is: xyz  

in second request context
g.foo should be None, is: None
setting g.foo to pqr
g.foo should be pqr, is: pqr

ImportError:没有名为bs4的模块(BeautifulSoup)

问题:ImportError:没有名为bs4的模块(BeautifulSoup)

我正在使用Python并使用Flask。当我在计算机上运行我的主Python文件时,它可以正常运行,但是当我激活venv并在终端中运行Flask Python文件时,它表示我的主Python文件具有“没有名为bs4的模块”。任何意见或建议,不胜感激。

I’m working in Python and using Flask. When I run my main Python file on my computer, it works perfectly, but when I activate venv and run the Flask Python file in the terminal, it says that my main Python file has “No Module Named bs4.” Any comments or advice is greatly appreciated.


回答 0

激活virtualenv,然后安装BeautifulSoup4:

$ pip install BeautifulSoup4

当您安装bs4使用easy_install,您在系统范围内进行了安装。因此,您的系统python可以导入它,但您的virtualenv python不能导入。如果您不需要bs4在系统python路径中安装,请卸载它并将其保留在virtualenv中。

有关virtualenvs的更多信息,请阅读此内容

Activate the virtualenv, and then install BeautifulSoup4:

$ pip install BeautifulSoup4

When you installed bs4 with easy_install, you installed it system-wide. So your system python can import it, but not your virtualenv python. If you do not need bs4 to be installed in your system python path, uninstall it and keep it in your virtualenv.

For more information about virtualenvs, read this


回答 1

对于python2.x

sudo pip install BeautifulSoup4

对于python3

sudo apt-get install python3-bs4

For python2.x:

sudo pip install BeautifulSoup4

For python3:

sudo apt-get install python3-bs4

回答 2

只需标记Balthazar的答案即可。跑步

pip install BeautifulSoup4

没有为我工作。改为使用

pip install beautifulsoup4

Just tagging onto Balthazar’s answer. Running

pip install BeautifulSoup4

did not work for me. Instead use

pip install beautifulsoup4

回答 3

pip3 install BeautifulSoup4

试试这个。这个对我有用。原因在这里得到了很好的解释..

pip3 install BeautifulSoup4

Try this. It works for me. The reason is well explained here..


回答 4

如果您将Anaconda用于软件包管理,则应执行以下操作:

conda install -c anaconda beautifulsoup4

If you are using Anaconda for package management, following should do:

conda install -c anaconda beautifulsoup4


回答 5

如果您使用Pycharm,请转到preferences - project interpreter - install bs4

如果尝试安装BeautifulSoup,它将仍然显示没有名为的模块bs4

If you use Pycharm, go to preferences - project interpreter - install bs4.

If you try to install BeautifulSoup, it will still show that no module named bs4.


回答 6

我建议您使用以下命令来卸载bs4库:

pip卸载bs4

然后使用以下命令进行安装:

须藤apt-get install python3-bs4

当我使用以下命令安装bs4库时,在Linux Ubuntu中遇到了相同的问题:

点安装bs4

I will advise you to uninstall the bs4 library by using this command:

pip uninstall bs4

and then install it using this command:

sudo apt-get install python3-bs4

I was facing the same problem in my Linux Ubuntu when I used the following command for installing bs4 library:

pip install bs4


回答 7

试试这个:

sudo python3 -m pip install bs4

Try this:

sudo python3 -m pip install bs4

回答 8

pip install --user BeautifulSoup4

pip install --user BeautifulSoup4


回答 9

pip3.7 install bs4

试试这个。它适用于python 3.7

pip3.7 install bs4

Try this. It works with python 3.7


回答 10

我做了@ rayid-ali所说的,除了我在Windows 10机器上,所以我省略了sudo。也就是说,我做了以下工作:

python3 -m pip install bs4

它就像一个pycharm。无论如何都像魅力一样工作。

I did what @rayid-ali said, except I’m on a Windows 10 machine so I left out the sudo. That is, I did the following:

python3 -m pip install bs4

and it worked like a pycharm. Worked like a charm anyway.


回答 11

最简单的是使用easy_install。

easy_install bs4 

如果pip失败,它将起作用。

The easiest is using easy_install.

easy_install bs4 

It will work if pip fails.


回答 12

很多针对Python 2编写的教程/参考资料都告诉您使用pip install somename。如果您使用的是Python 3,则要将其更改为pip3 install somename。

A lot of tutorials/references were written for Python 2 and tell you to use pip install somename. If you’re using Python 3 you want to change that to pip3 install somename.


回答 13

您可能想尝试使用安装bs4

pip install --ignore-installed BeautifulSoup4

如果上述方法不适合您。

You might want to try install bs4 with

pip install --ignore-installed BeautifulSoup4

if the methods above didn’t work for you.


回答 14

尝试重新安装模块,或者尝试使用以下命令与漂亮的汤一起安装

pip install --ignore-installed BeautifulSoup4

Try reinstalling the module OR Try installing with beautiful soup with the below command

pip install --ignore-installed BeautifulSoup4

回答 15

原始查询的附录:modules.py

help('modules')

$python modules.py

它列出了已经安装的模块bs4。

_codecs_kr          blinker             json                six
_codecs_tw          brotli              kaitaistruct        smtpd
_collections        bs4                 keyword             smtplib
_collections_abc    builtins            ldap3               sndhdr
_compat_pickle      bz2                 lib2to3             socket

正确的解决方案是:

pip install --upgrade bs4

应该解决问题。

不仅如此,其他模块也会显示相同的错误。因此,对于那些错误的模块,您必须以与上述相同的方式发出pip命令。

Addendum to the original query: modules.py

help('modules')

$python modules.py

It lists that module bs4 already been installed.

_codecs_kr          blinker             json                six
_codecs_tw          brotli              kaitaistruct        smtpd
_collections        bs4                 keyword             smtplib
_collections_abc    builtins            ldap3               sndhdr
_compat_pickle      bz2                 lib2to3             socket

Proper solution is:

pip install --upgrade bs4

Should solve the problem.

Not only that, it will show same error for other modules as well. So you got to issue the pip command same way as above for those errored module(s).


一个Flask进程接收多少个并发请求?

问题:一个Flask进程接收多少个并发请求?

我正在用Flask构建一个应用程序,但是我对WSGI并不太了解,它是基于HTTP的Werkzeug。当我开始使用gunicorn和4个工作进程处理Flask应用程序时,这是否意味着我可以处理4个并发请求?

我的意思是并发请求,而不是每秒的请求或其他任何请求。

I’m building an app with Flask, but I don’t know much about WSGI and it’s HTTP base, Werkzeug. When I start serving a Flask application with gunicorn and 4 worker processes, does this mean that I can handle 4 concurrent requests?

I do mean concurrent requests, and not requests per second or anything else.


回答 0

在运行开发服务器时(这是通过运行获得的)app.run(),您将获得一个同步过程,这意味着一次最多处理一个请求。

通过将Gunicorn保留在其默认配置中并简单地增加Gunicorn的数量--workers,您所获得的实际上是一些流程(由Gunicorn管理),每个流程的行为都类似于app.run()开发服务器。4个工作人员== 4个并发请求。这是因为Gunicorn sync默认使用其包含的工作程序类型。

重要的是要注意,Gunicorn还包含异步工作程序,即eventletgevent(以及tornado,但似乎最好在Tornado框架中使用)。通过使用--worker-class标志指定这些异步工作程序之一,您将获得Gunicorn管理多个异步进程的信息,每个进程管理自己的并发性。这些进程不使用线程,而是协程。基本上,在每个进程中,一次只能发生1件事(1个线程),但是当对象等待外部进程完成(例如数据库查询或等待网络I / O)时,它们可以被“暂停”。

这意味着,如果您使用的是Gunicorn的异步工作程序之一,则每个工作程序一次最多可以处理多个请求。多少工人才是最好的,取决于您的应用程序的性质,其环境,运行的硬件等。有关更多详细信息,请参见Gunicorn的设计页面,并在其介绍页面上介绍gevent的工作方式

When running the development server – which is what you get by running app.run(), you get a single synchronous process, which means at most 1 request is being processed at a time.

By sticking Gunicorn in front of it in its default configuration and simply increasing the number of --workers, what you get is essentially a number of processes (managed by Gunicorn) that each behave like the app.run() development server. 4 workers == 4 concurrent requests. This is because Gunicorn uses its included sync worker type by default.

It is important to note that Gunicorn also includes asynchronous workers, namely eventlet and gevent (and also tornado, but that’s best used with the Tornado framework, it seems). By specifying one of these async workers with the --worker-class flag, what you get is Gunicorn managing a number of async processes, each of which managing its own concurrency. These processes don’t use threads, but instead coroutines. Basically, within each process, still only 1 thing can be happening at a time (1 thread), but objects can be ‘paused’ when they are waiting on external processes to finish (think database queries or waiting on network I/O).

This means, if you’re using one of Gunicorn’s async workers, each worker can handle many more than a single request at a time. Just how many workers is best depends on the nature of your app, its environment, the hardware it runs on, etc. More details can be found on Gunicorn’s design page and notes on how gevent works on its intro page.


回答 1

当前,存在比已提供的解决方案简单得多的解决方案。运行应用程序时,只需将threaded=True参数传递给app.run()调用,例如:

app.run(host="your.host", port=4321, threaded=True)

根据在werkzeug文档中可以看到的另一种选择是使用processes参数,该参数接收的数字> 1表示要处理的最大并发进程数:

  • 线程化–进程应在单独的线程中处理每个请求吗?
  • 进程–如果大于1,则将处理新进程中的每个请求,直到最大并发进程数。

就像是:

app.run(host="your.host", port=4321, processes=3) #up to 3 processes

关于更多信息run()方法在这里,和博客文章,导致我找到解决方案和API引用。


注意:在Flask文档中,关于run()方法的方法表明不鼓励在生产环境中使用它,因为(quote):“虽然Flask轻巧易用,但其内置服务器不适合生产,因为它的扩展性不好”。

但是,他们确实指向其“ 部署选项”页面,以了解在投入生产时执行此操作的推荐方法。

Currently there is a far simpler solution than the ones already provided. When running your application you just have to pass along the threaded=True parameter to the app.run() call, like:

app.run(host="your.host", port=4321, threaded=True)

Another option as per what we can see in the werkzeug docs, is to use the processes parameter, which receives a number > 1 indicating the maximum number of concurrent processes to handle:

  • threaded – should the process handle each request in a separate thread?
  • processes – if greater than 1 then handle each request in a new process up to this maximum number of concurrent processes.

Something like:

app.run(host="your.host", port=4321, processes=3) #up to 3 processes

More info on the run() method here, and the blog post that led me to find the solution and api references.


Note: on the Flask docs on the run() methods it’s indicated that using it in a Production Environment is discouraged because (quote): “While lightweight and easy to use, Flask’s built-in server is not suitable for production as it doesn’t scale well.”

However, they do point to their Deployment Options page for the recommended ways to do this when going for production.


回答 2

Flask将同时为每个线程处理一个请求。如果您有2个进程,每个进程有4个线程,则是8个并发请求。

Flask不会产生或管理线程或进程。这就是WSGI网关(例如gunicorn)的责任。

Flask will process one request per thread at the same time. If you have 2 processes with 4 threads each, that’s 8 concurrent requests.

Flask doesn’t spawn or manage threads or processes. That’s the responsability of the WSGI gateway (eg. gunicorn).


回答 3

不,您绝对可以处理更多。

重要的是要记住,假设您正在运行一台单核计算机,那么CPU实际上一次只能运行一条指令*。

也就是说,CPU只能执行非常有限的一组指令,并且每个时钟周期不能执行多个指令(许多指令甚至需要1个周期以上)。

因此,我们在计算机科学中谈论的大多数并发是软件并发。换句话说,有一些软件实现层从我们这里提取底层CPU,并使我们认为我们正在同时运行代码。

这些“事物”可以是进程,它们是代码单元,它们在每个进程都认为其在自己的世界中使用自己的非共享内存在运行时可以并发运行。

另一个示例是线程,线程是进程内部的代码单元,也允许并发。

您的4个辅助进程能够处理4个以上请求的原因是,它们将触发线程以处理越来越多的请求。

实际的请求限制取决于所选择的HTTP服务器,I / O,操作系统,硬件,网络连接等。

祝好运!

*说明是CPU可以运行的最基本的命令。示例-加两个数字,从一条指令跳转到另一条指令

No- you can definitely handle more than that.

Its important to remember that deep deep down, assuming you are running a single core machine, the CPU really only runs one instruction* at a time.

Namely, the CPU can only execute a very limited set of instructions, and it can’t execute more than one instruction per clock tick (many instructions even take more than 1 tick).

Therefore, most concurrency we talk about in computer science is software concurrency. In other words, there are layers of software implementation that abstract the bottom level CPU from us and make us think we are running code concurrently.

These “things” can be processes, which are units of code that get run concurrently in the sense that each process thinks its running in its own world with its own, non-shared memory.

Another example is threads, which are units of code inside processes that allow concurrency as well.

The reason your 4 worker processes will be able to handle more than 4 requests is that they will fire off threads to handle more and more requests.

The actual request limit depends on HTTP server chosen, I/O, OS, hardware, network connection etc.

Good luck!

*instructions are the very basic commands the CPU can run. examples – add two numbers, jump from one instruction to another


我如何知道是否可以禁用SQLALCHEMY_TRACK_MODIFICATIONS?

问题:我如何知道是否可以禁用SQLALCHEMY_TRACK_MODIFICATIONS?

每次我运行使用Flask-SQLAlchemy的应用程序时,都会收到以下警告,提示该SQLALCHEMY_TRACK_MODIFICATIONS选项将被禁用。

/home/david/.virtualenvs/flask-sqlalchemy/lib/python3.5/site-packages/flask_sqlalchemy/__init__.py:800: UserWarning: SQLALCHEMY_TRACK_MODIFICATIONS adds significant overhead and will be disabled by default in the future.  Set it to True to suppress this warning.
  warnings.warn('SQLALCHEMY_TRACK_MODIFICATIONS adds significant overhead and will be disabled by default in the future.  Set it to True to suppress this warning.')

我试图找出此选项的作用,但是Flask-SQLAlchemy文档尚不清楚该跟踪的用途。

SQLALCHEMY_TRACK_MODIFICATIONS

如果设置为True(默认值),Flask-SQLAlchemy将跟踪对象的修改并发出信号。这需要额外的内存,如果不需要,可以将其禁用。

如何确定我的项目是否需要,SQLALCHEMY_TRACK_MODIFICATIONS = True或者是否可以安全地禁用此功能并在服务器上节省内存?

Every time I run my app that uses Flask-SQLAlchemy I get the following warning that the SQLALCHEMY_TRACK_MODIFICATIONS option will be disabled.

/home/david/.virtualenvs/flask-sqlalchemy/lib/python3.5/site-packages/flask_sqlalchemy/__init__.py:800: UserWarning: SQLALCHEMY_TRACK_MODIFICATIONS adds significant overhead and will be disabled by default in the future.  Set it to True to suppress this warning.
  warnings.warn('SQLALCHEMY_TRACK_MODIFICATIONS adds significant overhead and will be disabled by default in the future.  Set it to True to suppress this warning.')

I tried to find out what this option does, but the Flask-SQLAlchemy documentation isn’t clear about what uses this tracking.

SQLALCHEMY_TRACK_MODIFICATIONS

If set to True (the default) Flask-SQLAlchemy will track modifications of objects and emit signals. This requires extra memory and can be disabled if not needed.

How do I find out if my project requires SQLALCHEMY_TRACK_MODIFICATIONS = True or if I can safely disable this feature and save memory on my server?


回答 0

您的应用程序很可能没有使用Flask-SQLAlchemy事件系统,因此可以安全地关闭它。您需要审核代码以进行验证-您正在寻找与models_committedbefore_models_committed挂钩的任何内容。如果确实发现您正在使用Flask-SQLAlchemy事件系统,则可能应该更新代码以使用SQLAlchemy的内置事件系统。

要关闭Flask-SQLAlchemy事件系统(并禁用警告),只需添加:

SQLALCHEMY_TRACK_MODIFICATIONS = False

更改为您的应用程序配置,直到更改默认设置为止(很有可能在Flask-SQLAlchemy v3中)。


背景-警告告诉您的是以下内容:

Flask-SQLAlchemy有自己的事件通知系统,该系统在SQLAlchemy之上分层。为此,它跟踪对SQLAlchemy会话的修改。这需要额外的资源,因此该选项SQLALCHEMY_TRACK_MODIFICATIONS允许您禁用修改跟踪系统。当前,该选项默认为True,但将来该默认值将更改为False,从而禁用事件系统。

据我了解,更改的理由有三点:

  1. 使用Flask-SQLAlchemy的事件系统的人并不多,但是大多数人没有意识到他们可以通过禁用它来节省系统资源。因此,更明智的默认设置是禁用它,想要它的人可以打开它。

  2. Flask-SQLAlchemy中的事件系统存在相当多的错误(请参阅下面提到的请求请求中与之相关的问题),需要为很少有人使用的功能进行额外的维护。

  3. 在v0.7中,SQLAlchemy本身添加了一个强大的事件系统,其中包括创建自定义事件的功能。理想情况下,Flask-SQLAlchemy事件系统除了创建一些自定义的SQLAlchemy事件挂钩和侦听器外,无所不用其事,然后让SQLAlchemy自己管理事件触发器。

您可以在有关拉动请求的讨论中看到更多信息,该请求开始触发此警告

Most likely your application doesn’t use the Flask-SQLAlchemy event system, so you’re probably safe to turn off. You’ll need to audit the code to verify–you’re looking for anything that hooks into models_committed or before_models_committed. If you do find that you’re using the Flask-SQLAlchemy event system, you probably should update the code to use SQLAlchemy’s built-in event system instead.

To turn off the Flask-SQLAlchemy event system (and disable the warning), just add:

SQLALCHEMY_TRACK_MODIFICATIONS = False

to your app config until the default is changed (most likely in Flask-SQLAlchemy v3).


Background–here’s what the warning is telling you:

Flask-SQLAlchemy has its own event notification system that gets layered on top of SQLAlchemy. To do this, it tracks modifications to the SQLAlchemy session. This takes extra resources, so the option SQLALCHEMY_TRACK_MODIFICATIONS allows you to disable the modification tracking system. Currently the option defaults to True, but in the future, that default will change to False, thereby disabling the event system.

As far as I understand, the rationale for the change is three-fold:

  1. Not many people use Flask-SQLAlchemy’s event system, but most people don’t realize they can save system resources by disabling it. So a saner default is to disable it and those who want it can turn it on.

  2. The event system in Flask-SQLAlchemy has been rather buggy (see issues linked to in the pull request mentioned below), requiring additional maintenance for a feature that few people use.

  3. In v0.7, SQLAlchemy itself added a powerful event system including the ability to create custom events. Ideally, the Flask-SQLAlchemy event system should do nothing more than create a few custom SQLAlchemy event hooks and listeners, and then let SQLAlchemy itself manage the event trigger.

You can see more in the discussion around the pull request that started triggering this warning.


回答 1

Jeff Widman的详细解释非常完美。

由于在完成此操作之前我曾进行过一些“复制粘贴”的操作,因此我想使下一个穿鞋的操作变得更容易。

在您的代码中,紧接在

app = Flask(__name__)

如果要启用轨道修改,只需添加:

app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = True

否则,如果您使用此功能,则可能需要将值更改为False,以免浪费系统资源。由于您仍在显式设置配置,因此这仍然会使警告保持沉默。

这是具有False值的相同代码段:

app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False

感谢Jeff Widman提出的建议和详细信息。

Jeff Widman’s detailed explanation is simply perfect.

Since I had some copy’n’paste fights before getting this right I’d like to make it easier for the next one that will be in my shoes.

In your code, immediately after:

app = Flask(__name__)

If you want to enable track modifications simply add:

app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = True

Otherwise, if you are not using this feature, you may want to change the value to False in order not to waste system resources. This will still silence the warning since you’re anyway explicitly setting the config.

Here’s the same snippet with False value:

app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False

Thanks to Jeff Widman for this added suggestion and details.


回答 2

上面的答案看起来不错。但是,我想在Flask-SQLAlchemy文档中指出这一行,因为SQLALCHEMY_TRACK_MODIFICATIONS = False在我的应用程序配置中设置后,我仍然收到这些警告。

在此页面上:http : //flask-sqlalchemy.pocoo.org/2.3/config/

Flask-SQLAlchemy存在以下配置值。Flask-SQLAlchemy从您的主要Flask配置中加载这些值,可以通过多种方式填充。请注意,其中一些不能在创建引擎后进行修改,因此请确保尽早进行配置,并且不要在运行时进行修改。

换句话说,app.config 创建Flask-SQLAlchemy数据库之前,请确保设置您的数据库。

例如,如果您将应用程序配置为set SQLALCHEMY_TRACK_MODIFICATIONS = False

from flask import Flask
app = Flask(__name__)
app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False

db = SQLAlchemy(app)

The above answers look good. However, I wanted to point out this line in the Flask-SQLAlchemy documentation because I was still getting these warnings after setting SQLALCHEMY_TRACK_MODIFICATIONS = False in my application config.

On this page: http://flask-sqlalchemy.pocoo.org/2.3/config/

The following configuration values exist for Flask-SQLAlchemy. Flask-SQLAlchemy loads these values from your main Flask config which can be populated in various ways. Note that some of those cannot be modified after the engine was created so make sure to configure as early as possible and to not modify them at runtime.

In other words, make sure to set up your app.config before creating your Flask-SQLAlchemy database.

For example, if you are configuring your application to set SQLALCHEMY_TRACK_MODIFICATIONS = False:

from flask import Flask
app = Flask(__name__)
app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False

db = SQLAlchemy(app)

如何调试Flask应用

问题:如何调试Flask应用

您打算如何调试Flask中的错误?打印到控制台?向页面闪现消息?还是有一个更强大的选项可用来找出出现问题时发生的情况?

How are you meant to debug errors in Flask? Print to the console? Flash messages to the page? Or is there a more powerful option available to figure out what’s happening when something goes wrong?


回答 0

出现错误时,以开发模式运行该应用程序将在浏览器中显示交互式回溯和控制台。要在开发模式下运行,请设置FLASK_ENV=development环境变量,然后使用flask run命令(请记住也指向FLASK_APP您的应用程序)。

对于Linux,Mac,Windows的Linux子系统,Windows的Git Bash等:

export FLASK_APP=myapp
export FLASK_ENV=development
flask run

对于Windows CMD,使用set而不是导出:

set FLASK_ENV=development

对于PowerShell,请使用$env

$env:FLASK_ENV = "development"

在Flask 1.0之前,它是由FLASK_DEBUG=1环境变量控制的。

如果您使用的是app.run()方法而不是flask run命令,请传递debug=True以启用调试模式。

不管开发模式如何,都将回溯打印到运行服务器的终端。

如果您使用的是PyCharm,VS Code等,则可以利用其调试器逐步使用带有断点的代码。运行配置可以指向调用app.run(debug=True, use_reloader=False)venv/bin/flask脚本,也可以将其指向脚本并像在命令行中一样使用它。您可以禁用重新加载器,但是重新加载将终止调试上下文,并且您将不得不再次捕获断点。

您还可以通过set_trace在要开始调试的视图中调用来使用pdb,pudb或其他终端调试器。


确保不要使用太宽的积木。将所有代码都包含在“包罗万象”中try... except...将使您想要调试的错误静音。一般来说,这是不必要的,因为Flask已经可以通过显示调试器或500错误并将回溯打印到控制台来处理异常。

Running the app in development mode will show an interactive traceback and console in the browser when there is an error. To run in development mode, set the FLASK_ENV=development environment variable then use the flask run command (remember to point FLASK_APP to your app as well).

For Linux, Mac, Linux Subsystem for Windows, Git Bash on Windows, etc.:

export FLASK_APP=myapp
export FLASK_ENV=development
flask run

For Windows CMD, use set instead of export:

set FLASK_ENV=development

For PowerShell, use $env:

$env:FLASK_ENV = "development"

Prior to Flask 1.0, this was controlled by the FLASK_DEBUG=1 environment variable instead.

If you’re using the app.run() method instead of the flask run command, pass debug=True to enable debug mode.

Tracebacks are also printed to the terminal running the server, regardless of development mode.

If you’re using PyCharm, VS Code, etc., you can take advantage of its debugger to step through the code with breakpoints. The run configuration can point to a script calling app.run(debug=True, use_reloader=False), or point it at the venv/bin/flask script and use it as you would from the command line. You can leave the reloader disabled, but a reload will kill the debugging context and you will have to catch a breakpoint again.

You can also use pdb, pudb, or another terminal debugger by calling set_trace in the view where you want to start debugging.


Be sure not to use too-broad except blocks. Surrounding all your code with a catch-all try... except... will silence the error you want to debug. It’s unnecessary in general, since Flask will already handle exceptions by showing the debugger or a 500 error and printing the traceback to the console.


回答 1

您可以按如下所述app.run(debug=True)用于Werkzeug调试器 编辑,我应该知道。

You can use app.run(debug=True) for the Werkzeug Debugger edit as mentioned below, and I should have known.


回答 2

1.1.x文档中,您可以通过将环境变量导出到Shell提示符来启用调试模式:

export FLASK_APP=/daemon/api/views.py  # path to app
export FLASK_DEBUG=1
python -m flask run --host=0.0.0.0

From the 1.1.x documentation, you can enable debug mode by exporting an environment variable to your shell prompt:

export FLASK_APP=/daemon/api/views.py  # path to app
export FLASK_DEBUG=1
python -m flask run --host=0.0.0.0

回答 3

人们还可以使用Flask Debug Toolbar扩展程序来获取嵌入在渲染页面中的更多详细信息。

from flask import Flask
from flask_debugtoolbar import DebugToolbarExtension
import logging

app = Flask(__name__)
app.debug = True
app.secret_key = 'development key'

toolbar = DebugToolbarExtension(app)

@app.route('/')
def index():
    logging.warning("See this message in Flask Debug Toolbar!")
    return "<html><body></body></html>"

启动应用程序,如下所示:

FLASK_APP=main.py FLASK_DEBUG=1 flask run

One can also use the Flask Debug Toolbar extension to get more detailed information embedded in rendered pages.

from flask import Flask
from flask_debugtoolbar import DebugToolbarExtension
import logging

app = Flask(__name__)
app.debug = True
app.secret_key = 'development key'

toolbar = DebugToolbarExtension(app)

@app.route('/')
def index():
    logging.warning("See this message in Flask Debug Toolbar!")
    return "<html><body></body></html>"

Start the application as follows:

FLASK_APP=main.py FLASK_DEBUG=1 flask run

回答 4

如果您使用的是Visual Studio Code,请替换

app.run(debug=True)

app.run()

当打开内部调试器禁用VS Code调试器时,它会出现。

If you’re using Visual Studio Code, replace

app.run(debug=True)

with

app.run()

It appears when turning on the internal debugger disables the VS Code debugger.


回答 5

如果要调试flask应用程序,则只需转到flask应用程序所在的文件夹。不要忘了激活您的虚拟环境,并将控制台行中的行更改“ mainfilename”粘贴到flask主文件。

export FLASK_APP="mainfilename.py"
export FLASK_DEBUG=1
python -m flask run --host=0.0.0.0

启用flask应用程序的调试器后,几乎所有错误都会打印在控制台或浏览器窗口上。如果您想弄清楚发生了什么,可以使用简单的打印语句,也可以将console.log()用于javascript代码。

If you want to debug your flask app then just go to the folder where flask app is. Don’t forget to activate your virtual environment and paste the lines in the console change “mainfilename” to flask main file.

export FLASK_APP="mainfilename.py"
export FLASK_DEBUG=1
python -m flask run --host=0.0.0.0

After you enable your debugger for flask app almost every error will be printed on the console or on the browser window. If you want to figure out what’s happening, you can use simple print statements or you can also use console.log() for javascript code.


回答 6

python-dotenv在虚拟环境中安装。

在项目根目录中创建一个.flaskenv。用项目根目录,是指包含您的app.py文件的文件夹

在此文件中写入以下内容:

FLASK_APP=myapp 
FLASK_ENV=development

现在发出以下命令:

flask run

Install python-dotenv in your virtual environment.

Create a .flaskenv in your project root. By project root, I mean the folder which has your app.py file

Inside this file write the following:

FLASK_APP=myapp 
FLASK_ENV=development

Now issue the following command:

flask run

回答 7

要在Flask中激活调试模式,您只需FLASK_DEBUG=1CMDWindows 上键入set 并FLASK_DEBUG=1在Linux terminal上导出,然后重新启动您的应用程序就可以了!

To activate debug mode in flask you simply type set FLASK_DEBUG=1 on your CMD for windows and export FLASK_DEBUG=1 on Linux termial then restart your app and you are good to go!!


回答 8

快速提示-如果您使用的是PyCharm,请转到Edit Configurations=> Configurations并启用FLASK_DEBUG复选框,然后重新启动Run

Quick tip – if you use a PyCharm, go to Edit Configurations => Configurations and enable FLASK_DEBUG checkbox, restart the Run.


回答 9

在开发环境中使用记录器和打印语句,在生产环境中可以进行岗哨。

Use loggers and print statements in the Development Environment, you can go for sentry in case of production environments.


回答 10

对于Windows用户:

打开Powershell并cd进入您的项目目录。

在Powershell中使用这些突击队,其他所有东西在Powershell中将无法使用。

$env:FLASK_APP = "app"  
$env:FLASK_ENV = "development"

For Windows users:

Open Powershell and cd into your project directory.

Use these commandos in Powershell, all the other stuff won’t work in Powershell.

$env:FLASK_APP = "app"  
$env:FLASK_ENV = "development"

回答 11

如果您在本地运行它并希望能够逐步执行代码:

python -m pdb script.py

If you are running it locally and want to be able to step through the code:

python -m pdb script.py


无论内容类型标头如何,都可以在Python Flask中获取原始POST正文

问题:无论内容类型标头如何,都可以在Python Flask中获取原始POST正文

以前,我问过如何获取Flask请求中的数据,因为它request.data是空的。答案解释request.data为原始帖子正文,但如果分析表单数据将为空。我如何无条件获得原始职位?

@app.route('/', methods=['POST'])
def parse_request():
    data = request.data  # empty in some cases
    # always need raw data here, not parsed form data

Previously, I asked How to get data received in Flask request because request.data was empty. The answer explained that request.data is the raw post body, but will be empty if form data is parsed. How can I get the raw post body unconditionally?

@app.route('/', methods=['POST'])
def parse_request():
    data = request.data  # empty in some cases
    # always need raw data here, not parsed form data

回答 0

使用request.get_data()获得的原始数据,而不管内容类型。该数据被缓存,您可以随后访问request.datarequest.jsonrequest.form随意。

如果您request.data首先访问,它将首先调用get_data一个参数以解析表单数据。如果请求具有形式的内容类型(multipart/form-dataapplication/x-www-form-urlencoded,或application/x-url-encoded),则原始数据将被消耗。request.data并且request.json在这种情况下将显示为空。

Use request.get_data() to get the raw data, regardless of content type. The data is cached and you can subsequently access request.data, request.json, request.form at will.

If you access request.data first, it will call get_data with an argument to parse form data first. If the request has a form content type (multipart/form-data, application/x-www-form-urlencoded, or application/x-url-encoded) then the raw data will be consumed. request.data and request.json will appear empty in this case.


回答 1

request.stream是WSGI服务器传递给应用程序的原始数据流。读取时不进行任何解析,尽管通常会这样做request.get_data()

data = request.stream.read()

如果该流先前被request.data或其他属性读取,则将为空。

request.stream is the stream of raw data passed to the application by the WSGI server. No parsing is done when reading it, although you usually want request.get_data() instead.

data = request.stream.read()

The stream will be empty if it was previously read by request.data or another attribute.


回答 2

我创建了一个WSGI中间件,用于存储environ['wsgi.input']流中的原始内容。我将值保存在WSGI环境中,因此可以从request.environ['body_copy']我的应用程序中访问它。

在Werkzeug或Flask中这不是必需的,因为request.get_data()无论内容类型如何,都将获取原始数据,但是可以更好地处理HTTP和WSGI行为。

这会将整个主体读入内存,如果发布了一个大文件,这将是一个问题。如果Content-Length缺少标题,它将不会读取任何内容,因此它将无法处理流式请求。

from io import BytesIO

class WSGICopyBody(object):
    def __init__(self, application):
        self.application = application

    def __call__(self, environ, start_response):
        length = int(environ.get('CONTENT_LENGTH') or 0)
        body = environ['wsgi.input'].read(length)
        environ['body_copy'] = body
        # replace the stream since it was exhausted by read()
        environ['wsgi.input'] = BytesIO(body)
        return self.application(environ, start_response)

app.wsgi_app = WSGICopyBody(app.wsgi_app)
request.environ['body_copy']

I created a WSGI middleware that stores the raw body from the environ['wsgi.input'] stream. I saved the value in the WSGI environ so I could access it from request.environ['body_copy'] within my app.

This isn’t necessary in Werkzeug or Flask, as request.get_data() will get the raw data regardless of content type, but with better handling of HTTP and WSGI behavior.

This reads the entire body into memory, which will be an issue if for example a large file is posted. This won’t read anything if the Content-Length header is missing, so it won’t handle streaming requests.

from io import BytesIO

class WSGICopyBody(object):
    def __init__(self, application):
        self.application = application

    def __call__(self, environ, start_response):
        length = int(environ.get('CONTENT_LENGTH') or 0)
        body = environ['wsgi.input'].read(length)
        environ['body_copy'] = body
        # replace the stream since it was exhausted by read()
        environ['wsgi.input'] = BytesIO(body)
        return self.application(environ, start_response)

app.wsgi_app = WSGICopyBody(app.wsgi_app)
request.environ['body_copy']

回答 3

request.data如果request.headers["Content-Type"]被识别为表单数据,则将为空,并将其解析为request.form。要获取原始数据而不管内容类型如何,请使用request.get_data()

request.data调用request.get_data(parse_form_data=True),这导致表单数据的行为不同。

request.data will be empty if request.headers["Content-Type"] is recognized as form data, which will be parsed into request.form. To get the raw data regardless of content type, use request.get_data().

request.data calls request.get_data(parse_form_data=True), which results in the different behavior for form data.


解密Flask app.secret_key

问题:解密Flask app.secret_key

如果 app.secret_key未设置,则Flask将不允许您设置或访问会话字典。

这是烧瓶使用者指南必须针对此主题的所有内容。

我对Web开发非常陌生,我不知道任何/为什么安全性工作原理。我想了解Flask在幕后所做的事情。

  • 为什么Flask强迫我们设置此secret_key属性?
  • Flask如何使用该secret_key物业?

If app.secret_key isn’t set, Flask will not allow you to set or access the session dictionary.

This is all that the flask user guide has to say on the subject.

I am very new to web development and I have no idea how/why any security stuff works. I would like to understand what Flask is doing under the hood.

  • Why does Flask force us to set this secret_key property?
  • How does Flask use the secret_key property?

回答 0

任何需要加密的内容(为了安全防范攻击者的篡改)都需要设置密钥。对于刚刚瓶本身,即“什么”是Session对象,但其他的扩展可以使用相同的秘密的。

secret_key仅仅是为SECRET_KEY配置密钥设置的值,或者您可以直接设置它。

快速入门中的会话”部分对应设置哪种服务器端机密提供了很好的建议。

加密取决于机密;如果您没有设置要使用的加密服务器端密码,那么每个人都可以破坏您的加密;就像您计算机的密码一样。秘密加上要签名的数据用于创建签名字符串,使用加密哈希算法很难重新创建值;仅当您具有完全相同的机密原始数据时,您才能重新创建此值,让Flask检测是否未经许可对任何内容进行了更改。由于Flask永远不会将秘密包含在发送给客户端的数据中,因此客户端无法篡改会话数据,并希望产生新的有效签名。

Flask使用该itsdangerous来完成所有艰苦的工作;会话使用带有自定义JSON序列化程序的itsdangerous.URLSafeTimedSerializer

Anything that requires encryption (for safe-keeping against tampering by attackers) requires the secret key to be set. For just Flask itself, that ‘anything’ is the Session object, but other extensions can make use of the same secret.

secret_key is merely the value set for the SECRET_KEY configuration key, or you can set it directly.

The Sessions section in the Quickstart has good, sane advice on what kind of server-side secret you should set.

Encryption relies on secrets; if you didn’t set a server-side secret for the encryption to use, everyone would be able to break your encryption; it’s like the password to your computer. The secret plus the data-to-sign are used to create a signature string, a hard-to-recreate value using a cryptographic hashing algorithm; only if you have the exact same secret and the original data can you recreate this value, letting Flask detect if anything has been altered without permission. Since the secret is never included with data Flask sends to the client, a client cannot tamper with session data and hope to produce a new, valid signature.

Flask uses the itsdangerous library to do all the hard work; sessions use the itsdangerous.URLSafeTimedSerializer class with a customized JSON serializer.


回答 1

以下答案主要与Signed Cookies有关,后者会话概念的实现(在Web应用程序中使用)。Flask同时提供普通(未签名)Cookie(通过request.cookiesresponse.set_cookie())和签名Cookie(via flask.session)。答案有两个部分,第一部分描述了如何生成签名Cookie,第二部分以解决方案不同方面的QA形式呈现。这些示例使用的语法是Python3,但是这些概念也适用于以前的版本。

什么是SECRET_KEY(或如何创建签名Cookie)?

签署cookie是防止cookie篡改的预防措施。在对Cookie进行签名的过程中,SECRET_KEY使用方式类似于在对哈希进行哈希处理之前使用“盐”来混淆密码的方式。这是对该概念的(疯狂的)简化描述。示例中的代码是说明性的。许多步骤已被省略,并非实际上所有功能都存在。这里的目的是提供对一般概念的理解,实际的实现将更多地涉及到。另外,请记住,Flask在后台为您完成了大部分操作。因此,除了(通过会话API)为cookie设置值并提供SECRET_KEY,不仅不建议您自己重新实现,而且也不需要这样做:

一个穷人的饼干签名

在发送响应到浏览器之前:

(1)首先SECRET_KEY建立。它仅应由应用程序知道,并且应在应用程序的生命周期中保持相对恒定,包括通过重新启动应用程序。

# choose a salt, a secret string of bytes
>>> SECRET_KEY = 'my super secret key'.encode('utf8')

(2)创建一个cookie

>>> cookie = make_cookie(
...     name='_profile', 
...     content='uid=382|membership=regular',
...     ...
...     expires='July 1 2030...'
... )

>>> print(cookie)
name: _profile
content: uid=382|membership=regular...
    ...
    ...
expires: July 1 2030, 1:20:40 AM UTC

(3)创建签名,将其附加(或前置)SECRET_KEY到cookie字节字符串,然后根据该组合生成哈希。

# encode and salt the cookie, then hash the result
>>> cookie_bytes = str(cookie).encode('utf8')
>>> signature = sha1(cookie_bytes+SECRET_KEY).hexdigest()
>>> print(signature)
7ae0e9e033b5fa53aa....

(4)现在将签名粘贴content在原始cookie字段的一端。

# include signature as part of the cookie
>>> cookie.content = cookie.content + '|' + signature
>>> print(cookie)
name: _profile
content: uid=382|membership=regular|7ae0e9...  <--- signature
domain: .example.com
path: /
send for: Encrypted connections only
expires: July 1 2030, 1:20:40 AM UTC

这就是发送给客户端的内容。

# add cookie to response
>>> response.set_cookie(cookie)
# send to browser --> 

从浏览器收到Cookie后:

(5)当浏览器将此Cookie返回服务器时,请从Cookie的content字段中删除签名以获取原始Cookie。

# Upon receiving the cookie from browser
>>> cookie = request.get_cookie()
# pop the signature out of the cookie
>>> (cookie.content, popped_signature) = cookie.content.rsplit('|', 1)

(6)使用与SECRET_KEY步骤3相同的方法,将原始cookie与应用程序一起使用以重新计算签名。

# recalculate signature using SECRET_KEY and original cookie
>>> cookie_bytes = str(cookie).encode('utf8')
>>> calculated_signature = sha1(cookie_bytes+SECRET_KEY).hexdigest()

(7)将计算结果与先前从刚收到的cookie中弹出的签名进行比较。如果它们匹配,我们就知道cookie没有被弄乱。但是,即使只是在cookie中添加了一个空格,签名也不会匹配。

# if both signatures match, your cookie has not been modified
>>> good_cookie = popped_signature==calculated_signature

(8)如果它们不匹配,那么您可以采取任何行动来响应,记录事件,丢弃cookie,发出新的cookie,重定向到登录页面等。

>>> if not good_cookie:
...     security_log(cookie)

基于哈希的消息验证码(HMAC)

上面生成的签名类型需要一个密钥来确保某些内容的完整性,在密码学中称为消息验证码MAC

我在前面指出,上面的示例是对该概念的过度简化,并且实现自己的签名不是一个好主意。这是因为用于在Flask中对Cookie进行签名的算法称为HMAC,并且比上述简单的逐步操作要复杂得多。总体思路是相同的,但是由于超出了本讨论范围的原因,因此一系列计算更为复杂。如果您仍然对制作DIY感兴趣(通常是这样),Python提供了一些模块来帮助您入门:)这是一个起点:

import hmac
import hashlib

def create_signature(secret_key, msg, digestmod=None):
    if digestmod is None:
        digestmod = hashlib.sha1
    mac = hmac.new(secret_key, msg=msg, digestmod=digestmod)
    return mac.digest()

hmachashlib的文档。


SECRET_KEY:) 的“神秘化”

在这种情况下,什么是“签名”?

这是确保除未经授权的个人或实体以外的任何人未修改某些内容的方法。

签名的最简单形式之一是“ 校验和 ”,它简单地验证两个数据是否相同。例如,从源代码安装软件时,重要的是首先确认您的源代码副本与作者的副本相同。一种常用的方法是通过加密哈希函数运行源,并将输出与项目主页上发布的校验和进行比较。

举例来说,假设您要从网络镜像以压缩文件的形式下载项目的源代码。在该项目的网页上发布的SHA1校验和为“ eb84e8da7ca23e9f83 …”。

# so you get the code from the mirror
download https://mirror.example-codedump.com/source_code.tar.gz
# you calculate the hash as instructed
sha1(source_code.tar.gz)
> eb84e8da7c....

这两个哈希是相同的,您知道您拥有相同的副本。

什么是Cookie?

关于cookie的广泛讨论将超出此问题的范围。我在这里提供了一个概述,因为对基础知识的最低了解对于更好地了解如何以及为什么SECRET_KEY有用很有用。我强烈建议您跟进一些有关HTTP Cookie的个人阅读。

Web应用程序中的一种常见做法是将客户端(Web浏览器)用作轻量级缓存。Cookies是这种做法的一种实现。Cookie通常是服务器通过其标头添加到HTTP响应的一些数据。它由浏览器保存,然后在发出请求时也通过HTTP标头将其发送回服务器。Cookie中包含的数据可用于模拟所谓的有状态性,即服务器正在与客户端保持持续连接的错觉。仅在这种情况下,您无需为保持连接“活动”而进行连接,而仅在处理了客户端的请求后才拥有应用程序状态的快照。这些快照在客户端和服务器之间来回传送。收到请求后,服务器首先读取cookie的内容,以重新建立与客户端的对话上下文。然后,它在该上下文中处理请求,然后将响应返回给客户端,然后更新cookie。因此,可以保持正在进行的会话的幻觉。

Cookie是什么样的?

典型的cookie如下所示:

name: _profile
content: uid=382|status=genie
domain: .example.com
path: /
send for: Encrypted connections only
expires: July 1 2030, 1:20:40 AM UTC

从任何现代浏览器中都可以细读Cookie。例如,在Firefox上,转到“首选项”>“隐私”>“历史记录”>删除单个cookie

content字段与应用程序最相关。其他字段大多带有元指令,以指定各种影响范围。

为什么要使用Cookie?

简短的答案是性能。使用cookie可以最大程度地减少在各种数据存储(内存缓存,文件,数据库等)中查找内容的需求,从而可以在服务器应用程序方面加快查找速度。请记住,cookie越大,网络上的有效负载就越重,因此您在服务器上的数据库查找中保存的内容可能会通过网络丢失。请仔细考虑您的Cookie中要包含的内容。

为什么需要对cookie进行签名?

Cookies用于保存各种信息,其中某些信息可能非常敏感。从本质上讲,它们也不安全,并且要求采取许多辅助预防措施,无论对于客户端还是服务器双方,都应以任何方式视为安全。签名cookie专门解决了在欺骗服务器应用程序时可能对其进行修补的问题。还有其他措施可以缓解其他类型的漏洞,我建议您阅读有关Cookie的更多信息。

Cookie如何被篡改?

Cookies以文本形式驻留在客户端上,可以轻松进行编辑。您的服务器应用程序收到的Cookie可能由于多种原因而被修改,其中某些原因可能并非无辜。想象一个Web应用程序在cookie上保留有关其用户的许可信息,并根据该信息授予特权。如果该cookie不可靠,则任何人都可以修改其cookie,以将其状态从“ role = visitor”提升到“ role = admin”,并且该应用程序再合适不过了。

为什么SECRET_KEY需要签署Cookie?

验证cookie与之前描述的验证源代码有点不同。对于源代码,原始作者是参考指纹(校验和)的受托人和所有者,参考指纹将保持公开状态。您不信任的是源代码,但是您信任公共签名。因此,要验证您的源副本,您只希望您计算出的哈希值与公共哈希值匹配。

对于Cookie,应用程序不跟踪签名,而是跟踪SECRET_KEY。的SECRET_KEY是参考指纹。Cookies随身携带其声称合法的签名。此处的合法性意味着签名是由Cookie的所有者(即应用程序)发布的,在这种情况下,这是声称您不信任并且需要检查签名的有效性。为此,您需要在签名中包括一个只有您自己知道的元素,即SECRET_KEY。有人可以更改Cookie,但是由于他们没有秘密成分来正确计算有效签名,因此无法欺骗它。如前所述,这种类型的指纹识别在校验和的顶部还提供了一个秘密密钥,

会话呢?

传统实现中的会话是cookie,该cookie在content字段中仅携带一个ID session_id。会话的目的与签名的cookie完全相同,即防止cookie篡改。古典会议有不同的方法。收到会话cookie后,服务器将使用ID在其自己的本地存储中查找会话数据,该本地存储可以是数据库,文件,有时还可以是内存中的缓存。会话cookie通常设置为在关闭浏览器时过期。由于存在本地存储查找步骤,因此会话的这种实现通常会导致性能下降。签名cookie正在成为首选,这就是Flask会话的实现方式。换句话说,Flask会话对Cookie 进行了签名,要在Flask中使用签名的Cookie,只需使用其Session API。

为什么不同时加密cookie?

有时,在对Cookie的内容进行签名之前,可以对其进行加密。如果认为它们太敏感而无法从浏览器中看到它们,则可以执行此操作(加密将隐藏内容)。但是,仅对cookie进行签名就解决了另一种需求,即希望在浏览器上保持一定程度的可见性和可用性,同时又避免被cookie所干扰。

如果我改变了会SECRET_KEY怎样?

通过更改,SECRET_KEY您将使所有使用前一个密钥签名的cookie 失效。当应用程序接收到一个请求,该请求包含一个使用前一个签名的cookie时,SECRET_KEY它将尝试使用新的签名计算签名SECRET_KEY,并且两个签名都不匹配,该cookie及其所有数据将被拒绝,就像浏览器是第一次连接到服务器。用户将被注销,他们的旧Cookie和内部存储的所有内容都将被遗忘。请注意,这与处理过期Cookie的方式不同。如果过期的cookie的签名签出,则可以延长其租约。无效的签名仅表示一个普通的无效cookie。

因此,除非您想使所有已签名的Cookie失效,否则请尝试SECRET_KEY长时间保持相同。

有什么好处SECRET_KEY

密钥应该很难猜到。有关Sessions的文档为随机密钥生成提供了一个很好的方法:

>>> import os
>>> os.urandom(24)
'\xfd{H\xe5<\x95\xf9\xe3\x96.5\xd1\x01O<!\xd5\xa2\xa0\x9fR"\xa1\xa8'

您复制密钥并将其粘贴为配置文件中的值SECRET_KEY

除了使用随机生成的密钥之外,您还可以使用各种复杂的单词,数字和符号,这些单词,数字和符号可能排列在您只知道的句子中,并且以字节形式编码。

不要设置SECRET_KEY与生成每个这就是所谓的时间不同的键的功能直接。例如,不要这样做:

# this is not good
SECRET_KEY = random_key_generator()

每次重新启动应用程序时,都会获得一个新密钥,从而使之前的密钥失效。

而是打开一个交互式python shell并调用该函数以生成密钥,然后将其复制并粘贴到配置中。

The answer below pertains primarily to Signed Cookies, an implementation of the concept of sessions (as used in web applications). Flask offers both, normal (unsigned) cookies (via request.cookies and response.set_cookie()) and signed cookies (via flask.session). The answer has two parts, the first describes how a Signed Cookie is generated, and the second is presented in the form of a QA that addresses different aspects of the scheme. The syntax used for the examples is Python3, but the concepts apply also to previous versions.

What is SECRET_KEY (or how to create a Signed Cookie)?

Signing cookies is a preventive measure against cookie tampering. During the process of signing a cookie, the SECRET_KEY is used in a way similar to how a “salt” would be used to muddle a password before hashing it. Here’s a (wildly) simplified description of the concept. The code in the examples is meant to be illustrative. Many of the steps have been omitted and not all of the functions actually exist. The goal here is to provide an understanding of the general idea, actual implementations will be a bit more involved. Also, keep in mind that Flask does most of this for you in the background. So, besides setting values to your cookie (via the session API) and providing a SECRET_KEY, it’s not only ill-advised to reimplement this yourself, but there’s no need to do so:

A poor man’s cookie signature

Before sending a Response to the browser:

( 1 ) First a SECRET_KEY is established. It should only be known to the application and should be kept relatively constant during the application’s life cycle, including through application restarts.

# choose a salt, a secret string of bytes
>>> SECRET_KEY = 'my super secret key'.encode('utf8')

( 2 ) create a cookie

>>> cookie = make_cookie(
...     name='_profile', 
...     content='uid=382|membership=regular',
...     ...
...     expires='July 1 2030...'
... )

>>> print(cookie)
name: _profile
content: uid=382|membership=regular...
    ...
    ...
expires: July 1 2030, 1:20:40 AM UTC

( 3 ) to create a signature, append (or prepend) the SECRET_KEY to the cookie byte string, then generate a hash from that combination.

# encode and salt the cookie, then hash the result
>>> cookie_bytes = str(cookie).encode('utf8')
>>> signature = sha1(cookie_bytes+SECRET_KEY).hexdigest()
>>> print(signature)
7ae0e9e033b5fa53aa....

( 4 ) Now affix the signature at one end of the content field of the original cookie.

# include signature as part of the cookie
>>> cookie.content = cookie.content + '|' + signature
>>> print(cookie)
name: _profile
content: uid=382|membership=regular|7ae0e9...  <--- signature
domain: .example.com
path: /
send for: Encrypted connections only
expires: July 1 2030, 1:20:40 AM UTC

and that’s what’s sent to the client.

# add cookie to response
>>> response.set_cookie(cookie)
# send to browser --> 

Upon receiving the cookie from the browser:

( 5 ) When the browser returns this cookie back to the server, strip the signature from the cookie’s content field to get back the original cookie.

# Upon receiving the cookie from browser
>>> cookie = request.get_cookie()
# pop the signature out of the cookie
>>> (cookie.content, popped_signature) = cookie.content.rsplit('|', 1)

( 6 ) Use the original cookie with the application’s SECRET_KEY to recalculate the signature using the same method as in step 3.

# recalculate signature using SECRET_KEY and original cookie
>>> cookie_bytes = str(cookie).encode('utf8')
>>> calculated_signature = sha1(cookie_bytes+SECRET_KEY).hexdigest()

( 7 ) Compare the calculated result with the signature previously popped out of the just received cookie. If they match, we know that the cookie has not been messed with. But if even just a space has been added to the cookie, the signatures won’t match.

# if both signatures match, your cookie has not been modified
>>> good_cookie = popped_signature==calculated_signature

( 8 ) If they don’t match then you may respond with any number of actions, log the event, discard the cookie, issue a fresh one, redirect to a login page, etc.

>>> if not good_cookie:
...     security_log(cookie)

Hash-based Message Authentication Code (HMAC)

The type of signature generated above that requires a secret key to ensure the integrity of some contents is called in cryptography a Message Authentication Code or MAC.

I specified earlier that the example above is an oversimplification of that concept and that it wasn’t a good idea to implement your own signing. That’s because the algorithm used to sign cookies in Flask is called HMAC and is a bit more involved than the above simple step-by-step. The general idea is the same, but due to reasons beyond the scope of this discussion, the series of computations are a tad bit more complex. If you’re still interested in crafting a DIY, as it’s usually the case, Python has some modules to help you get started :) here’s a starting block:

import hmac
import hashlib

def create_signature(secret_key, msg, digestmod=None):
    if digestmod is None:
        digestmod = hashlib.sha1
    mac = hmac.new(secret_key, msg=msg, digestmod=digestmod)
    return mac.digest()

The documentaton for hmac and hashlib.


The “Demystification” of SECRET_KEY :)

What’s a “signature” in this context?

It’s a method to ensure that some content has not been modified by anyone other than a person or an entity authorized to do so.

One of the simplest forms of signature is the “checksum“, which simply verifies that two pieces of data are the same. For example, when installing software from source it’s important to first confirm that your copy of the source code is identical to the author’s. A common approach to do this is to run the source through a cryptographic hash function and compare the output with the checksum published on the project’s home page.

Let’s say for instance that you’re about to download a project’s source in a gzipped file from a web mirror. The SHA1 checksum published on the project’s web page is ‘eb84e8da7ca23e9f83….’

# so you get the code from the mirror
download https://mirror.example-codedump.com/source_code.tar.gz
# you calculate the hash as instructed
sha1(source_code.tar.gz)
> eb84e8da7c....

Both hashes are the same, you know that you have an identical copy.

What’s a cookie?

An extensive discussion on cookies would go beyond the scope of this question. I provide an overview here since a minimal understanding can be useful to have a better understanding of how and why SECRET_KEY is useful. I highly encourage you to follow up with some personal readings on HTTP Cookies.

A common practice in web applications is to use the client (web browser) as a lightweight cache. Cookies are one implementation of this practice. A cookie is typically some data added by the server to an HTTP response by way of its headers. It’s kept by the browser which subsequently sends it back to the server when issuing requests, also by way of HTTP headers. The data contained in a cookie can be used to emulate what’s called statefulness, the illusion that the server is maintaining an ongoing connection with the client. Only, in this case, instead of a wire to keep the connection “alive”, you simply have snapshots of the state of the application after it has handled a client’s request. These snapshots are carried back and forth between client and server. Upon receiving a request, the server first reads the content of the cookie to reestablish the context of its conversation with the client. It then handles the request within that context and before returning the response to the client, updates the cookie. The illusion of an ongoing session is thus maintained.

What does a cookie look like?

A typical cookie would look like this:

name: _profile
content: uid=382|status=genie
domain: .example.com
path: /
send for: Encrypted connections only
expires: July 1 2030, 1:20:40 AM UTC

Cookies are trivial to peruse from any modern browser. On Firefox for example go to Preferences > Privacy > History > remove individual cookies.

The content field is the most relevant to the application. Other fields carry mostly meta instructions to specify various scopes of influence.

Why use cookies at all?

The short answer is performance. Using cookies, minimizes the need to look things up in various data stores (memory caches, files, databases, etc), thus speeding things up on the server application’s side. Keep in mind that the bigger the cookie the heavier the payload over the network, so what you save in database lookup on the server you might lose over the network. Consider carefully what to include in your cookies.

Why would cookies need to be signed?

Cookies are used to keep all sorts of information, some of which can be very sensitive. They’re also by nature not safe and require that a number of auxiliary precautions be taken to be considered secure in any way for both parties, client and server. Signing cookies specifically addresses the problem that they can be tinkered with in attempts to fool server applications. There are other measures to mitigate other types of vulnerabilities, I encourage you to read up more on cookies.

How can a cookie be tampered with?

Cookies reside on the client in text form and can be edited with no effort. A cookie received by your server application could have been modified for a number of reasons, some of which may not be innocent. Imagine a web application that keeps permission information about its users on cookies and grants privileges based on that information. If the cookie is not tinker-proof, anyone could modify theirs to elevate their status from “role=visitor” to “role=admin” and the application would be none the wiser.

Why is a SECRET_KEY necessary to sign cookies?

Verifying cookies is a tad bit different than verifying source code the way it’s described earlier. In the case of the source code, the original author is the trustee and owner of the reference fingerprint (the checksum), which will be kept public. What you don’t trust is the source code, but you trust the public signature. So to verify your copy of the source you simply want your calculated hash to match the public hash.

In the case of a cookie however the application doesn’t keep track of the signature, it keeps track of its SECRET_KEY. The SECRET_KEY is the reference fingerprint. Cookies travel with a signature that they claim to be legit. Legitimacy here means that the signature was issued by the owner of the cookie, that is the application, and in this case, it’s that claim that you don’t trust and you need to check the signature for validity. To do that you need to include an element in the signature that is only known to you, that’s the SECRET_KEY. Someone may change a cookie, but since they don’t have the secret ingredient to properly calculate a valid signature they cannot spoof it. As stated a bit earlier this type of fingerprinting, where on top of the checksum one also provides a secret key, is called a Message Authentication Code.

What about Sessions?

Sessions in their classical implementation are cookies that carry only an ID in the content field, the session_id. The purpose of sessions is exactly the same as signed cookies, i.e. to prevent cookie tampering. Classical sessions have a different approach though. Upon receiving a session cookie the server uses the ID to look up the session data in its own local storage, which could be a database, a file, or sometimes a cache in memory. The session cookie is typically set to expire when the browser is closed. Because of the local storage lookup step, this implementation of sessions typically incurs a performance hit. Signed cookies are becoming a preferred alternative and that’s how Flask’s sessions are implemented. In other words, Flask sessions are signed cookies, and to use signed cookies in Flask just use its Session API.

Why not also encrypt the cookies?

Sometimes the contents of cookies can be encrypted before also being signed. This is done if they’re deemed too sensitive to be visible from the browser (encryption hides the contents). Simply signing cookies however, addresses a different need, one where there’s a desire to maintain a degree of visibility and usability to cookies on the browser, while preventing that they’d be meddled with.

What happens if I change the SECRET_KEY?

By changing the SECRET_KEY you’re invalidating all cookies signed with the previous key. When the application receives a request with a cookie that was signed with a previous SECRET_KEY, it will try to calculate the signature with the new SECRET_KEY, and both signatures won’t match, this cookie and all its data will be rejected, it will be as if the browser is connecting to the server for the first time. Users will be logged out and their old cookie will be forgotten, along with anything stored inside. Note that this is different from the way an expired cookie is handled. An expired cookie may have its lease extended if its signature checks out. An invalid signature just implies a plain invalid cookie.

So unless you want to invalidate all signed cookies, try to keep the SECRET_KEY the same for extended periods.

What’s a good SECRET_KEY?

A secret key should be hard to guess. The documentation on Sessions has a good recipe for random key generation:

>>> import os
>>> os.urandom(24)
'\xfd{H\xe5<\x95\xf9\xe3\x96.5\xd1\x01O<!\xd5\xa2\xa0\x9fR"\xa1\xa8'

You copy the key and paste it in your configuration file as the value of SECRET_KEY.

Short of using a key that was randomly generated, you could use a complex assortment of words, numbers, and symbols, perhaps arranged in a sentence known only to you, encoded in byte form.

Do not set the SECRET_KEY directly with a function that generates a different key each time it’s called. For example, don’t do this:

# this is not good
SECRET_KEY = random_key_generator()

Each time your application is restarted it will be given a new key, thus invalidating the previous.

Instead, open an interactive python shell and call the function to generate the key, then copy and paste it to the config.


如何在Flask-SQLAlchemy中按ID删除记录

问题:如何在Flask-SQLAlchemy中按ID删除记录

users在MySql数据库中有表。这个表有idname而且age领域。

如何删除某些记录id

现在,我使用以下代码:

user = User.query.get(id)
db.session.delete(user)
db.session.commit()

但是我不想在删除操作之前进行任何查询。有什么办法吗?我知道,我可以使用db.engine.execute("delete from users where id=..."),但是我想使用delete()方法。

I have users table in my MySql database. This table has id, name and age fields.

How can I delete some record by id?

Now I use the following code:

user = User.query.get(id)
db.session.delete(user)
db.session.commit()

But I don’t want to make any query before delete operation. Is there any way to do this? I know, I can use db.engine.execute("delete from users where id=..."), but I would like to use delete() method.


回答 0

你可以这样做,

User.query.filter_by(id=123).delete()

要么

User.query.filter(User.id == 123).delete()

确保作出commitdelete()生效。

You can do this,

User.query.filter_by(id=123).delete()

or

User.query.filter(User.id == 123).delete()

Make sure to commit for delete() to take effect.


回答 1

只想分享另一个选择:

# mark two objects to be deleted
session.delete(obj1)
session.delete(obj2)

# commit (or flush)
session.commit()

http://docs.sqlalchemy.org/en/latest/orm/session_basics.html#deleting

在此示例中,以下代码可以正常工作:

obj = User.query.filter_by(id=123).one()
session.delete(obj)
session.commit()

Just want to share another option:

# mark two objects to be deleted
session.delete(obj1)
session.delete(obj2)

# commit (or flush)
session.commit()

http://docs.sqlalchemy.org/en/latest/orm/session_basics.html#deleting

In this example, the following codes shall works fine:

obj = User.query.filter_by(id=123).one()
session.delete(obj)
session.commit()

回答 2

另一个可能的解决方案,特别是如果要批量删除

deleted_objects = User.__table__.delete().where(User.id.in_([1, 2, 3]))
session.execute(deleted_objects)
session.commit()

Another possible solution specially if you want batch delete

deleted_objects = User.__table__.delete().where(User.id.in_([1, 2, 3]))
session.execute(deleted_objects)
session.commit()

Flask中的“端点”是什么?

问题:Flask中的“端点”是什么?

瓶文档显示

add_url_rule(*args, **kwargs)
      Connects a URL rule. Works exactly like the route() decorator.
      If a view_func is provided it will be registered with the endpoint.

     endpoint  the endpoint for the registered URL rule. Flask itself assumes the name of the view function as endpoint

“端点”到底是什么意思?

The Flask documentation shows:

add_url_rule(*args, **kwargs)
      Connects a URL rule. Works exactly like the route() decorator.
      If a view_func is provided it will be registered with the endpoint.

     endpoint – the endpoint for the registered URL rule. Flask itself assumes the name of the view function as endpoint

What exactly is meant by an “endpoint”?


回答 0

烧瓶路由如何工作

Flask(和基础的Werkzeug库)的整个想法是将URL路径映射到您将要运行的某些逻辑(通常是“视图功能”)。基本视图的定义如下:

@app.route('/greeting/<name>')
def give_greeting(name):
    return 'Hello, {0}!'.format(name)

请注意,您引用的函数(add_url_rule)达到了相同的目标,而无需使用装饰符表示法。因此,以下是相同的:

# No "route" decorator here. We will add routing using a different method below.
def give_greeting(name):
    return 'Hello, {0}!'.format(name)

app.add_url_rule('/greeting/<name>', 'give_greeting', give_greeting)

假设您的网站位于“ www.example.org”并使用上述视图。用户在浏览器中输入以下URL:

http://www.example.org/greeting/Mark

Flask的工作是获取此URL,弄清楚用户想要做什么,然后将其传递给许多python函数之一进行处理。它采取的路径

/greeting/Mark

…并将其与路线列表匹配。在我们的案例中,我们定义了该路径以转到give_greeting函数。

但是,尽管这是创建视图的典型方式,但实际上它会从您那里抽象一些额外的信息。在幕后,Flask没有直接从URL跳转到应处理此请求的视图函数。它不只是说…

URL (http://www.example.org/greeting/Mark) should be handled by View Function (the function "give_greeting")

实际上,还有另一步,它将URL映射到端点:

URL (http://www.example.org/greeting/Mark) should be handled by Endpoint "give_greeting".
Requests to Endpoint "give_greeting" should be handled by View Function "give_greeting"

基本上,“端点”是用于确定代码的逻辑单元应处理请求的标识符。通常,端点只是视图函数的名称。但是,您实际上可以更改端点,如以下示例所示。

@app.route('/greeting/<name>', endpoint='say_hello')
def give_greeting(name):
    return 'Hello, {0}!'.format(name)

现在,当Flask路由请求时,逻辑如下所示:

URL (http://www.example.org/greeting/Mark) should be handled by Endpoint "say_hello".
Endpoint "say_hello" should be handled by View Function "give_greeting"

您如何使用端点

该端点通常用于“反向查找”。例如,在Flask应用程序的一个视图中,您想引用另一个视图(例如,当您从站点的一个区域链接到另一个区域时)。您可以使用而不是对URL进行硬编码url_for()。假设以下

@app.route('/')
def index():
    print url_for('give_greeting', name='Mark') # This will print '/greeting/Mark'

@app.route('/greeting/<name>')
def give_greeting(name):
    return 'Hello, {0}!'.format(name)

这是有利的,因为现在我们可以更改应用程序的URL,而无需更改引用该资源的行。

为什么不总是使用视图函数的名称?

可能会出现以下问题:“为什么我们需要这个额外的层?” 为什么将路径映射到端点,然后将端点映射到视图函数?为什么不跳过这一中间步骤呢?

原因是因为它以这种方式更强大。例如,烧瓶蓝图允许您将应用程序分成多个部分。我可能将所有管理员端资源都放在一个名为“ admin”的蓝图中,而所有用户级资源都放在一个名为“ user”的端点中。

蓝图允许您将它们分成命名空间。例如…

main.py:

from flask import Flask, Blueprint
from admin import admin
from user import user

app = Flask(__name__)
app.register_blueprint(admin, url_prefix='admin')
app.register_blueprint(user, url_prefix='user')

admin.py:

admin = Blueprint('admin', __name__)

@admin.route('/greeting')
def greeting():
    return 'Hello, administrative user!'

user.py:

user = Blueprint('user', __name__)
@user.route('/greeting')
def greeting():
    return 'Hello, lowly normal user!'

请注意,在两个蓝图中,“ / greeting”路由是一个称为“ greeting”的函数。如果我想参考管理员的“ greeting”功能,我不能只说“ greeting”,因为还有一个用户的“ greeting”功能。端点可以通过指定蓝图的名称作为端点的一部分来实现某种命名空间。因此,我可以执行以下操作…

print url_for('admin.greeting') # Prints '/admin/greeting'
print url_for('user.greeting') # Prints '/user/greeting'

How Flask Routing Works

The entire idea of Flask (and the underlying Werkzeug library) is to map URL paths to some logic that you will run (typically, the “view function”). Your basic view is defined like this:

@app.route('/greeting/<name>')
def give_greeting(name):
    return 'Hello, {0}!'.format(name)

Note that the function you referred to (add_url_rule) achieves the same goal, just without using the decorator notation. Therefore, the following is the same:

# No "route" decorator here. We will add routing using a different method below.
def give_greeting(name):
    return 'Hello, {0}!'.format(name)

app.add_url_rule('/greeting/<name>', 'give_greeting', give_greeting)

Let’s say your website is located at ‘www.example.org’ and uses the above view. The user enters the following URL into their browser:

http://www.example.org/greeting/Mark

The job of Flask is to take this URL, figure out what the user wants to do, and pass it on to one of your many python functions for handling. It takes the path:

/greeting/Mark

…and matches it to the list of routes. In our case, we defined this path to go to the give_greeting function.

However, while this is the typical way that you might go about creating a view, it actually abstracts some extra info from you. Behind the scenes, Flask did not make the leap directly from URL to the view function that should handle this request. It does not simply say…

URL (http://www.example.org/greeting/Mark) should be handled by View Function (the function "give_greeting")

Actually, it there is another step, where it maps the URL to an endpoint:

URL (http://www.example.org/greeting/Mark) should be handled by Endpoint "give_greeting".
Requests to Endpoint "give_greeting" should be handled by View Function "give_greeting"

Basically, the “endpoint” is an identifier that is used in determining what logical unit of your code should handle the request. Normally, an endpoint is just the name of a view function. However, you can actually change the endpoint, as is done in the following example.

@app.route('/greeting/<name>', endpoint='say_hello')
def give_greeting(name):
    return 'Hello, {0}!'.format(name)

Now, when Flask routes the request, the logic looks like this:

URL (http://www.example.org/greeting/Mark) should be handled by Endpoint "say_hello".
Endpoint "say_hello" should be handled by View Function "give_greeting"

How You Use the Endpoint

The endpoint is commonly used for the “reverse lookup”. For example, in one view of your Flask application, you want to reference another view (perhaps when you are linking from one area of the site to another). Rather than hard-code the URL, you can use url_for(). Assume the following

@app.route('/')
def index():
    print url_for('give_greeting', name='Mark') # This will print '/greeting/Mark'

@app.route('/greeting/<name>')
def give_greeting(name):
    return 'Hello, {0}!'.format(name)

This is advantageous, as now we can change the URLs of our application without needing to change the line where we reference that resource.

Why not just always use the name of the view function?

One question that might come up is the following: “Why do we need this extra layer?” Why map a path to an endpoint, then an endpoint to a view function? Why not just skip that middle step?

The reason is because it is more powerful this way. For example, Flask Blueprints allow you to split your application into various parts. I might have all of my admin-side resources in a blueprint called “admin”, and all of my user-level resources in an endpoint called “user”.

Blueprints allow you to separate these into namespaces. For example…

main.py:

from flask import Flask, Blueprint
from admin import admin
from user import user

app = Flask(__name__)
app.register_blueprint(admin, url_prefix='admin')
app.register_blueprint(user, url_prefix='user')

admin.py:

admin = Blueprint('admin', __name__)

@admin.route('/greeting')
def greeting():
    return 'Hello, administrative user!'

user.py:

user = Blueprint('user', __name__)
@user.route('/greeting')
def greeting():
    return 'Hello, lowly normal user!'

Note that in both blueprints, the ‘/greeting’ route is a function called “greeting”. If I wanted to refer to the admin “greeting” function, I couldn’t just say “greeting” because there is also a user “greeting” function. Endpoints allow for a sort of namespacing by having you specify the name of the blueprint as part of the endpoint. So, I could do the following…

print url_for('admin.greeting') # Prints '/admin/greeting'
print url_for('user.greeting') # Prints '/user/greeting'

回答 1

端点是用于反向查询url规则url_for的名称,默认为视图函数的名称。

小例子:

from flask import Flask, url_for

app = Flask(__name__)

# We can use url_for('foo_view') for reverse-lookups in templates or view functions
@app.route('/foo')
def foo_view():
    pass

# We now specify the custom endpoint named 'bufar'. url_for('bar_view') will fail!
@app.route('/bar', endpoint='bufar')
def bar_view():
    pass

with app.test_request_context('/'):
    print url_for('foo_view')
    print url_for('bufar')
    # url_for('bar_view') will raise werkzeug.routing.BuildError
    print url_for('bar_view')

Endpoint is the name used to reverse-lookup the url rules with url_for and it defaults to the name of the view function.

Small example:

from flask import Flask, url_for

app = Flask(__name__)

# We can use url_for('foo_view') for reverse-lookups in templates or view functions
@app.route('/foo')
def foo_view():
    pass

# We now specify the custom endpoint named 'bufar'. url_for('bar_view') will fail!
@app.route('/bar', endpoint='bufar')
def bar_view():
    pass

with app.test_request_context('/'):
    print url_for('foo_view')
    print url_for('bufar')
    # url_for('bar_view') will raise werkzeug.routing.BuildError
    print url_for('bar_view')