FAQ

设计理念

为什么Falcon不带电池?

Falcon是为需要高度定制或性能调整的应用程序而设计的。框架的极简设计使开发人员能够为手头的任务选择最佳策略和第三方包。

python生态系统提供了许多优秀的包,您可以从响应程序、钩子和中间件组件中使用这些包。作为一个起点,社区维护了 Falcon add-ons and complementary packages .

为什么Falcon不为每个请求创建一个新的资源实例?

Falcon通常尝试最小化它实例化的对象的数量。这样做有两个原因:第一,为了避免创建对象的开销,第二,通过减少高度并发工作负载下所需的对象总数来减少内存使用。因此,在添加路线时,Falcon需要 实例 资源类,而不是类类型。同一个实例将用于服务该路由上传入的所有请求。

为什么在资源中引发错误会使我的应用程序崩溃?

一般来说,Falcon假设资源响应者(如 on_get()on_post() 在大多数情况下,会做正确的事情。换句话说,Falcon并不努力保护响应程序代码不受其影响。

这种方法减少了falcon必须执行的检查次数,从而提高了框架的效率。有鉴于此,基于Falcon编写高质量的API需要:

  1. 资源响应程序将响应变量设置为正常值。

  2. 您的代码经过了良好的测试,代码覆盖率很高。

  3. 在每个响应程序中以及在自定义错误处理程序的帮助下,正确地预测、检测和处理错误。

小技巧

Falcon将重新引发不从中继承的错误 HTTPError 除非您已经为该类型注册了自定义错误处理程序(另请参见: falcon.App

如何为Falcon API生成API文档?

当涉及到API文档时,一些开发人员倾向于使用API实现作为用户契约或真相来源(采用实现优先的方法),而其他开发人员则倾向于使用API规范本身作为契约,根据该规范实现和测试API(t以设计为先的方法)。

在灵活性方面存在错误的风险,Falcon不提供现成的API规范支持。然而,在这方面有几个社区项目可用。我们的 Add on Catalog 列出这些项目中的一些,但您也可能希望搜索 PyPI 对于附加包。

如果您对上面提到的“设计优先”方法感兴趣,您可能还需要查看API设计和网关服务,如Tyk、Apariy、Amazon API网关或Google云端点。

性能

Falcon是否使用HTTP/2?

Falcon是一个WSGi框架,因此不直接为HTTP请求提供服务。但是,您可以通过在应用程序前面部署任何符合HTTP/2的Web服务器或负载平衡器来在HTTP/2和HTTP/1.1之间进行转换,从而获得HTTP/2的大部分好处。最后,我们期望python web服务器(如uwsgi)能够本机支持HTTP/2,从而消除了对转换层的需求。

Falcon线安全吗?

Falcon框架本身是线程安全的。例如,新 RequestResponse 为每个传入的HTTP请求创建对象。但是,连接到路由的每个资源类的单个实例在所有请求之间共享。中间件对象和其他类型的钩子(如自定义错误处理程序)也同样是共享的。因此,只要您以线程安全的方式实现这些类和可调用文件,并确保应用程序使用的任何第三方库也是线程安全的,那么整个WSGi应用程序将是线程安全的。

也就是说,IO绑定的falcon API通常是通过多个进程和绿色线程(由 gevent 库或类似的库)不能同时运行,因此可能存在一些边缘情况,其中falcon不是我们所不知道的线程安全的。如果您遇到任何问题,请通知我们。

Falcon支持Asyncio吗?

由于WSGi的限制,Falcon无法支持 asyncio 在这个时候。但是,我们正在探索WSGi的替代方案(例如 ASGI )这将允许我们在将来本地支持异步。

同时,我们建议使用测试过的战斗。 gevent 类库通过Gunicorn或Uwsgi扩展IO绑定服务。 meinheld 社区也成功地使用它来为高吞吐量、低延迟的服务供电。请注意,如果您使用Gunicorn,您可以将gevent和pypy结合起来,以获得令人印象深刻的性能水平。(不幸的是,Uwsgi还不支持将gevent和pypy一起使用。)

Falcon支持WebSocket吗?

由于WSGi的限制,Falcon无法支持上述WebSocket协议。

同时,你可以尝试利用 uWSGI's native WebSocket support 或者通过Aymeric Augustin的Handy实现独立的服务 websockets 类库。

路由

我如何用Falcon实现CORS?

为了让网站或SPA访问以不同域名托管的API,该API必须实现 Cross-Origin Resource Sharing (CORS) .对于公共API,在Falcon中实现CORS可以与实现类似于以下内容的中间件组件一样简单:

class CORSComponent:
    def process_response(self, req, resp, resource, req_succeeded):
        resp.set_header('Access-Control-Allow-Origin', '*')

        if (req_succeeded
            and req.method == 'OPTIONS'
            and req.get_header('Access-Control-Request-Method')
        ):
            # NOTE(kgriffs): This is a CORS preflight request. Patch the
            #   response accordingly.

            allow = resp.get_header('Allow')
            resp.delete_header('Allow')

            allow_headers = req.get_header(
                'Access-Control-Request-Headers',
                default='*'
            )

            resp.set_headers((
                ('Access-Control-Allow-Methods', allow),
                ('Access-Control-Allow-Headers', allow_headers),
                ('Access-Control-Max-Age', '86400'),  # 24 hours
            ))

当使用上述方法时,选项请求还必须在用于身份验证、内容协商等的任何其他中间件或钩子中进行特殊情况处理。例如,您通常会跳过预处理请求的身份验证,因为这是完全不必要的;请注意,此类请求不包括作者在任何情况下,都是化头。

对于更复杂的用例,请查看社区中的falcon附加组件,例如 falcon-cors 或者尝试一个通用的 WSGI CORS libraries available on PyPI .如果您使用一个API网关,您还可以研究它在那个级别上提供了什么CORS功能。

如何在Falcon中实现重定向?

Falcon提供了许多异常类,可以引发这些类来将客户机重定向到不同的位置(另请参见 Redirection

但是,请注意,如果可能的话,直接使用Web服务器处理永久重定向比在应用程序上为此类请求增加额外的负载更有效。

如何在我的原始应用程序和我迁移到Falcon的部分之间分割请求?

在最需要的地方,通常是将应用程序的一部分分割出来,然后在Falcon中重新实现,以提高性能。

如果您有权访问负载均衡器或反向代理配置,我们建议您设置基于路径或子域的规则,以便在原始实现和迁移到Falcon的部分之间拆分请求(例如,通过添加 location 对nginx配置的指令)。

如果上面的方法不是您部署的一个选项,那么您可以实现一个简单的wsgi包装器,它执行相同的操作:

def application(environ, start_response):
    try:
        # NOTE(kgriffs): Prefer the host header; the web server
        # isn't supposed to mess with it, so it should be what
        # the client actually sent.
        host = environ['HTTP_HOST']
    except KeyError:
        # NOTE(kgriffs): According to PEP-3333, this header
        # will always be present.
        host = environ['SERVER_NAME']

    if host.startswith('api.'):
        return falcon_app(environ, start_response)
    elif:
        return webapp2_app(environ, start_response)

也见 PEP 3333 有关通过以下方式提供的变量的完整列表: environ .

如何为同一资源实现过账和获取项目?

假设您有以下路线:

# Resource Collection
GET /resources{?marker, limit}
POST /resources

# Resource Item
GET /resources/{id}
PATCH /resources/{id}
DELETE /resources/{id}

您可以通过使用两个Python类来实现这种API,一个类表示单个资源,另一个类表示所述资源的集合。通常将两个类放在同一个模块中(另请参见 this section of the tutorial

或者,可以使用后缀响应程序将两个路由映射到同一个资源类:

class MyResource:
    def on_get(self, req, resp, id):
        pass

    def on_patch(self, req, resp, id):
        pass

    def on_delete(self, req, resp, id):
        pass

    def on_get_collection(self, req, resp):
        pass

    def on_post_collection(self, req, resp):
        pass


# -- snip --


resource = MyResource()
app.add_route('/resources/{id}', resource)
app.add_route('/resources', resource, suffix='collection')

可扩展性

如何在Falcon中使用WSGi中间件?

实例 falcon.App 是一流的WSGi应用程序,因此您可以使用PEP-3333中概述的标准模式。在主“app”文件中,您只需使用中间件应用程序包装API实例。例如:

import my_restful_service
import some_middleware

app = some_middleware.DoSomethingFancy(my_restful_service.app)

也见 WSGI middleware example 在PEP-3333中给出。

如何将数据从一个钩子传递给响应程序,以及在钩子之间传递?

您可以将额外的响应Kwargs添加到 帕拉姆 口述进入钩子。还可以在 req.context 对象,作为传递上下文信息的方式:

def authorize(req, resp, resource, params):
    # TODO: Check authentication/authorization

    # -- snip --

    req.context.role = 'root'
    req.context.scopes = ('storage', 'things')
    req.context.uid = 0

# -- snip --

@falcon.before(authorize)
def on_post(self, req, resp):
    pass

如何在Falcon中编写404和500页的自定义处理程序?

当找不到传入请求的路由时,Falcon使用一个默认响应程序,该响应程序只引发一个 HTTPRouteNotFound ,然后框架将呈现为404响应。你可以用 falcon.App.add_error_handler() 要重写此异常类型(或其父类型)的默认处理程序, HTTPNotFound ). 或者,您可以配置您的web服务器来为您转换响应(例如,使用nginx的 error_page 指令)。

默认情况下,非系统退出的异常不继承自 HTTPErrorHTTPStatus 由Falcon处理,但有一个纯HTTP 500错误。要提供自己的500逻辑,可以为Python的base添加一个自定义错误处理程序 Exception 类型。这不会影响的默认处理程序 HTTPErrorHTTPStatus .

错误处理 以及 falcon.API.add_error_handler() 更多详细信息。

请求处理

如何验证请求?

钩子和中间件组件可以一起用于对请求进行身份验证和授权。例如,中间件组件可以用来解析传入的凭证并将结果放在 req.context .考虑到用户的角色和请求的资源,下游组件或挂钩随后可以使用此信息来授权请求。

为什么req.stream.read()对于某些请求挂起?

这种行为是一个不幸的产物,因为wsgi规范(PEP-3333)没有完全定义请求的身体力学。这在参考文件中讨论 stream ,并以以下形式提供了解决方案: bounded_stream .

Falcon如何处理请求路径中的尾随斜杠?

如果你的应用程序设置 strip_url_path_trailing_slashTrue Falcon将规范传入的URI路径,以简化后期处理并提高应用程序逻辑的可预测性。这在实现RESTAPI模式时很有用,该模式不会将尾随斜杠字符解释为引用隐式子资源的名称,而网站通常使用该名称来引用索引页。

例如,启用此选项后,为添加路由 '/foo/bar' 隐式添加的路由 '/foo/bar/' .换句话说,任何路径的请求都将发送到相同的资源。

警告

如果 strip_url_path_trailing_slash 如果启用,则添加带有尾随斜杠的路由将有效地使其无法从正常路由中访问(理论上讲,仍然可以通过重写中间件中的请求路径来匹配)。

在这种情况下,应该添加不带尾随斜杠的路由(显然除了根路径之外 '/' ),如 '/foo/bar' 在上面的例子中。

注解

从2.0版开始,默认的 strip_url_path_trailing_slash 请求选项已从更改 TrueFalse .

为什么req对象中缺少我的查询参数?

如果查询参数没有值,falcon将默认忽略该参数。例如,通过 'foo''foo=' 将导致忽略参数。

如果要识别这些参数,必须设置 keep_blank_qs_values 请求选项 True .为的每个实例全局设置请求选项 falcon.API 通过 req_options 属性。例如:

app.req_options.keep_blank_qs_values = True

为什么将参数中的“+”字符转换为空格?

这个 + 通常使用字符代替 %20 由于表单参数编码的历史合并,在查询字符串参数中表示空格 (application/x-www-form-urlencoded )和URI百分比编码。因此,Falcon,转化者 + 对字符串解码时的空格。

为了解决这个问题,RFC3986规定 + 作为保留字符,并建议在需要这些字符的文字值时对其进行百分比编码。 (%2B 如果是 +

如何访问已发布的表单参数?

默认情况下,Falcon不使用请求体。但是,一个 media handler 对于 application/x-www-form-urlencoded 默认情况下安装了content-type,因此发布的表单可用作 Request.media 零配置:

import falcon


class MyResource:
    def on_post(self, req, resp):
        # TODO: Handle the submitted URL-encoded form
        form = req.media

        # NOTE: Falcon chooses the right media handler automatically, but
        #   if we wanted to differentiate from, for instance, JSON, we
        #   could check whether req.content_type == falcon.MEDIA_URLENCODED
        #   or use mimeparse to implement more sophisticated logic.

注解

在Falcon的早期版本中,发布的URL编码表单可以被自动消费并合并到 params 通过设置 auto_parse_form_urlencoded 选择权 True . Falcon 3.x系列仍然支持此行为。然而,它已经被弃用,取而代之的是 URLEncodedFormHandler ,以及将URL编码的表单数据合并到 params 可能在将来的版本中删除。

也可以直接从 stream 通过分析 falcon.uri.parse_query_string()urllib.parse.parse_qs() .

如何访问已发布的文件?

如果文件是 POST 作为 multipart form ,默认的 MultipartFormHandler 可用于高效解析提交的 multipart/form-data request media 通过迭代多部分 body parts

for part in req.media:
    # TODO: Do something with the body part
    pass

如何将发布的文件(从多部分表单)直接保存到aws3?

如前一个答案所强调的 files posted as multipart formfalcon.media.MultipartFormHandler 可用于迭代上载的多部分主体部分。

这个 stream of a body part is a file-like object implementing the read() method, making it compatible with boto3's upload_fileobj

import boto3

# -- snip --

s3 = boto3.client('s3')

for part in req.media:
    if part.name == 'myfile':
        s3.upload_fileobj(part.stream, 'mybucket', 'mykey')

注解

Falcon不支持任何特定的云服务提供商,以及AWS S3和 boto3 这里引用的只是一个流行的例子。同样的模式可以应用于任何支持直接从类似文件的对象流式传输的存储API。

如何解析嵌套的多部分表单?

Falcon不提供解析嵌套多部分表单的官方支持(即,使用嵌套的 multipart/mixed 部分)此时。根据用法被视为已弃用 living HTML5 standardRFC 7578, Section 4.3 .

小技巧

如果你的应用程序绝对必须处理这样的遗留表单,解析器实际上可能能够完成这项任务。更多信息请参见本配方: 解析嵌套的多部分表单 .

如何从查询字符串中检索JSON值?

为了从查询字符串中检索JSON编码的值,Falcon提供 get_param_as_json() 方法,示例如下:

import falcon


class LocationResource:

    def on_get(self, req, resp):
        places = {
            'Chandigarh, India': {
                'lat': 30.692781,
                'long': 76.740875
            },

            'Ontario, Canada': {
                'lat': 43.539814,
                'long': -80.246094
            }
        }

        coordinates = req.get_param_as_json('place')

        place = None
        for (key, value) in places.items():
            if coordinates == value:
                place = key
                break

        resp.media = {
            'place': place
        }


app = falcon.API()
app.add_route('/locations', LocationResource())

在上面的例子中, LocationResource 要求查询字符串包含名为的JSON编码值 'place' . 使用 get_param_as_json() 方法。给定一个请求URL,例如:

/locations?place={"lat":43.539814,"long":-80.246094}

这个 coordinates 变量将设置为 dict 果不其然。

默认情况下, auto_parse_qs_csv 选项设置为 False . 上面的例子假设这个默认值。

另一方面,什么时候 auto_parse_qs_csv 设置为 True ,Falcon将查询字符串中的逗号视为分隔逗号分隔列表的文字字符。例如,给定查询字符串 ?c=1,2,3 ,Falcon会把这个加到你的 request.params 字典为 {{'c': ['1', '2', '3']}} .例如,如果试图在查询字符串的值中使用JSON ?c={{"a":1,"b":2}} ,值将被添加到 request.params 以一种意想不到的方式: {{'c': ['{{"a":1', '"b":2}}']}} .

逗号是一个保留字符,可以根据 RFC 3986 - 2.2. Reserved Characters ,因此一个可能的解决方案是对JSON查询字符串中出现的任何逗号进行百分比编码。

另一个选择是离开 auto_parse_qs_csv 禁用,只使用JSON数组语法代替CSV。

什么时候? auto_parse_qs_csv 未启用,则查询字符串的值 ?c={{"a":1,"b":2}} 将添加到 req.params 字典为 {{'c': '{{"a":1,"b":2}}'}} . 无论客户机是否选择对请求中的逗号进行百分比编码,都可以使用JSON。在本例中,您可以通过 get_param() 或使用 get_param_as_json() 上述简易方法。

如何处理路由模板字段中的正斜杠?

在Falcon 1.3中,我们为 field converters .我们已经讨论了基于这个特性的构建,以支持使用多个路径段ala flask。这项工作目前计划在2.0进行。

同时,解决方法是对正斜杠进行百分比编码。如果您不能控制客户机并且不能强制执行,那么您可以实现一个falcon中间件组件来在路由之前重写路径。

如何使代码适应Falcon2.0中的默认上下文类型更改?

在Falcon2.0中,默认的请求/响应上下文类型已从dict更改为bare类。现在,您可以简单地设置对象的属性,而不是设置字典项:

# Before Falcon 2.0
req.context['cache_backend'] = MyUltraFastCache.connect()

# Falcon 2.0
req.context.cache_backend = MyUltraFastCache.connect()

新的默认上下文类型以上下文属性链接到dict项的方式模拟类似dict的映射接口,即设置对象属性也设置相应的dict项,反之亦然。因此,现有代码在很大程度上可以在不修改falcon 2.0的情况下工作。不过,建议如上文所述迁移到新接口,因为在将来的版本中,类似dict的映射接口可能会从上下文类型中删除。

警告

如果需要在迁移下混合和匹配这两种方法,请注意设置属性,例如 项目价值观 显然会隐藏相应的映射接口函数。

如果现有项目正在大量使用字典上下文,则可以通过使用自定义请求/响应类型将类型显式重写回dict:

class RequestWithDictContext(falcon.Request):
    context_type = dict

class ResponseWithDictContext(falcon.Response):
    context_type = dict

# -- snip --

app = falcon.App(request_type=RequestWithDictContext,
                 response_type=ResponseWithDictContext)

响应处理

我什么时候使用媒体、数据和流?

这三个参数相互排斥,在定义响应时只应设置一个。

resp.media 当要使用falcon序列化机制时使用。只需将数据分配给属性,falcon就会处理其余的。

class MyResource:
    def on_get(self, req, resp):
        resp.media = { 'hello': 'World' }

resp.bodyresp.data 非常相似,它们都允许你设置主体的响应。区别在于, body 接受一个字符串并 data 接收字节。

class MyResource:
    def on_get(self, req, resp):
        resp.body = json.dumps({ 'hello': 'World' })

    def on_post(self, req, resp):
        resp.data = b'{ "hello": "World" }'

resp.stream 允许您设置类似文件的返回字节的对象。我们会打电话的 read() 直到对象被消耗。

class MyResource:
    def on_get(self, req, resp):
        resp.stream = open('myfile.json', mode='rb')

如何在datetime类型中使用resp.media?

的默认JSON处理程序 resp.media 仅支持下表中列出的对象和类型 json.JSONEncoder .要处理其他类型,可以预先对其进行序列化,或者创建一个自定义的JSON媒体处理程序来设置 default 参数 json.dumps() .当反序列化传入请求体时,您可能还希望实现 object_hook 对于 json.loads() .但是,请注意,设置 defaultobject_hook 参数会对(反)序列化的性能产生负面影响。

Falcon是设置内容长度,还是需要显式设置?

Falcon将根据 resp.bodyresp.dataresp.stream_len (以响应中设置的为准,按该顺序检查。)

对于动态生成的内容,可以选择不设置 stream_len 在这种情况下,Falcon将不再使用Content-Length头文件,希望您的WSGi服务器能够做正确的事情?(假设您告诉它启用Keep-Alive)。

注解

PEP-3333禁止应用程序自行设置逐点报头,例如传输编码。

当我引发httperror实例时,为什么返回空的响应主体?

Falcon试图将 HTTPError 实例使用其 to_json()to_xml() 方法,根据请求中的Accept头。如果JSON和XML都不可接受,则不会生成响应主体。如果需要,您可以通过 set_error_serializer() .

我正在设置一个响应主体,但它不会被返回。发生什么事?

当根据HTTP规范,不应返回任何主体时,falcon跳过处理响应主体。如果客户机发送一个head请求,框架将始终返回一个空的主体。当响应状态为以下任一状态时,Falcon也将返回空体:

falcon.HTTP_100
falcon.HTTP_204
falcon.HTTP_416
falcon.HTTP_304

如果你有另一个没有归还尸体的案例,那很可能是个虫子! Let us know 所以我们可以帮忙。

如何使用Falcon提供可下载的文件?

on_get() Responder方法对于资源,可以通过设置Content Disposition头来通知用户代理下载文件。Falcon包括 downloadable_as 属性以简化此操作:

resp.downloadable_as = 'report.pdf'

也见 输出CSV文件 一个更复杂的动态生成可下载内容示例的配方。

为什么Falcon要把我的标题名改成小写?

Falcon在将头名称存储到内部之前总是将其小写 Response 结构,以便使响应头处理简单而高效,因为头名称查找可以使用简单的 dict . 由于HTTP头不区分大小写,所以这种优化通常不应影响API使用者。

在不太可能的情况下,您绝对必须处理不符合要求的HTTP客户端,希望使用特定的头名称大写,请参阅下面的方法如何使用通用WSGI中间件重写头名称: 响应头名称大写 .

注意,这个问题只适用于Falcon的WSGI风格。这个 ASGI HTTP scope specification 要求HTTP标头名称为小写。

此外,HTTP2标准还要求头字段名必须转换为小写(请参见 RFC 7540, Section 8.1.2

Falcon能提供静态文件吗?

Falcon通过简单地将打开的文件分配给 resp.stream as demonstrated in the tutorial .您还可以通过 falcon.App.add_static_route() .但是,如果可能的话,最好是直接从网络服务器(如nginx)或cdn提供静态文件。

其他

如何管理数据库连接?

假设数据库库管理自己的连接池,您所需要做的就是初始化客户机并将其实例传递到资源类中。例如,使用sqlacalchemy core:

engine = create_engine('sqlite:///:memory:')
resource = SomeResource(engine)

然后,在 SomeResource

# Read from the DB
result = self._engine.execute(some_table.select())
for row in result:
    # TODO: Do something with each row

result.close()

# -- snip --

# Write to the DB within a transaction
with self._engine.begin() as connection:
    r1 = connection.execute(some_table.select())

    # -- snip --

    connection.execute(
        some_table.insert(),
        col1=7,
        col2='this is some data'
    )

使用数据访问层时,只需将引擎传递到数据访问对象中即可。另请参见 this sample Falcon project 这说明使用ORM和Falcon。

您还可以创建一个中间件组件来自动检查每个请求的数据库连接,但这会使跟踪错误或根据单个请求的需要进行调优变得更加困难。

如果您需要透明地处理错误后的重新连接,或者对于客户机库不支持的其他用例,只需将客户机库封装在处理所有棘手位的管理类中,然后将其传递。

如何测试Falcon应用程序?我能用试纸吗?

Falcon的测试框架支持 unittestpytest .事实上,docs中的教程提供了对 testing Falcon apps with pytest .

(另见: Testing

如何在模拟请求时设置cookies?

最简单的方法就是通过 cookies 参数输入 simulate_request . 下面是一个例子:

import falcon
import falcon.testing
import pytest

class TastyCookies:

    def on_get(self, req, resp):
        resp.media = {'cookies': req.cookies}


@pytest.fixture
def client():
    app = falcon.App()
    app.add_route('/cookies', TastyCookies())

    return falcon.testing.TestClient(app)


def test_cookies(client):
    resp = client.simulate_get('/cookies', cookies={'cookie': 'cookie value'})

    assert resp.json == {'cookies': {'cookie': 'cookie value'}}

或者,可以直接设置Cookie头,如此版本的 test_cookies()

def test_cookies(client):
    resp = client.simulate_get('/cookies', headers={'Cookie': 'xxx=yyy'})

    assert resp.json == {'cookies': {'xxx': 'yyy'}}

要包含多个值,只需使用 "; " 将每个名称-值对分开。例如,如果你通过 {{'Cookie': 'xxx=yyy; hello=world'}} 你会得到 {{'cookies': {{'xxx': 'yyy', 'hello': 'world'}}}} .