FAQ

设计理念

为什么Falcon不带电池?

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

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

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

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

如果我的响应者提出错误,会发生什么情况?

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

备注

从3.0版开始,框架将不再将未捕获的异常传播到应用服务器。相反,默认情况下, Exception 处理程序将返回HTTP 500响应并将异常详细信息记录到 wsgi.errors

虽然Falcon提供了基本的错误处理程序,但它针对资源响应器不会为有效请求引发任何错误的最常见情况进行了优化。考虑到这一点,编写基于Falcon的高质量API要求:

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

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

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

如何为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应用程序将是线程安全的。

Falcon支持Asyncio吗?

从3.0版开始, ASGI Falcon的味道现在自豪地支持 asyncio 好了!使用 falcon.asgi.App 类创建异步应用程序,并通过 ASGI application server 比如Uvicorn。

或者,受IO限制的WSGI应用程序可以使用经过战斗测试的 gevent 库通过Gunicorn或uWSGI。 meinheld 社区还成功地使用它来支持高吞吐量、低延迟的WSGI服务。

小技巧

请注意,如果您使用Gunicorn,您可以结合使用gevent和PyPy来实现令人印象深刻的性能水平。(不幸的是,uWSGI还不支持同时使用gevent和pyPy。)

Falcon支持WebSocket吗?

Falcon的异步风格支持 ASGI WebSocket协议。另请参阅: WebSocket(仅限ASGI)

WSGI应用程序可能会尝试利用 uWSGI's native WebSocket supportgevent-websocket's GeventWebSocketWorker 为了Gunicorn。

作为一种选择,将WebSocket支持设计为单独的服务可能是有意义的,因为与常规的RESTful API相比,WebSocket支持的性能特征和交互模式非常不同。除了(显然!)Falcon的本地ASGI支持,独立的WebSocket服务也可以通过Aymeric Augustin的Handy实现 websockets 类库。

路由

我如何用Falcon实现CORS?

为了使网站或SPA能够访问不同域名下托管的API,该API必须实现 Cross-Origin Resource Sharing (CORS) 。对于公共API,在Falcon中实现CORS非常简单,只需将 cors_enable 标志(设置为 True )实例化时 your application

可以通过以下方式进一步定制CORS CORSMiddleware (有关在Falcon中管理CORS的详细信息,另请参阅 CORS )。

有关更复杂的用例,请查看社区中的Falcon附加组件,例如 falcon-cors ,或尝试其中一个泛型 WSGI CORS libraries available on PyPI 。如果您使用API网关,您还可以查看它在该级别提供了哪些CORS功能。

为什么我的授权请求被阻止,尽管 cors_enable

当您从浏览器(或验证CORS策略的另一个HTTP客户端)发出跨域请求,并且该请求使用Authorization标头进行身份验证时,浏览器会添加 authorization 在印前检查中访问控制请求标头 (OPTIONS )请求,但是,在此阶段省略了实际的授权凭证。

如果您的请求身份验证/授权是在 middleware 组件,该组件通过引发 HTTPUnauthorized (或者以另一种方式呈现4xx响应),一个常见的陷阱是,即使是 OPTIONS 请求(根据上面的解释没有授权)以这种方式产生错误。由于印前检查失败,浏览器选择不继续处理主请求。

如果您自己实现了授权中间件,则只需让 OPTIONS 通过:

class MyAuthMiddleware:
    def process_request(self, req, resp):
        # NOTE: Do not authenticate OPTIONS requests.
        if req.method == 'OPTIONS':
            return

        # -- snip --

        # My authorization logic...

或者,如果中间件来自第三方库,则将其子类化可能更实用:

class CORSAwareMiddleware(SomeAuthMiddleware):
    def process_request(self, req, resp):
        # NOTE: Do not authenticate OPTIONS requests.
        if req.method != 'OPTIONS':
            super().process_request(req, resp)

在这种情况下,有问题的中间件改为挂钩到 process_resource() ,你可以使用类似的治疗方法。

如果您尝试了上述方法,但您仍然怀疑问题出在Falcon的 CORS middleware ,它可能是一个虫子! Let us know 这样我们就能帮忙了。

如何在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')

为什么我的URL带有百分比编码的正斜杠 (%2F )路由不正确?

这是WSGI规范的一个不幸之处,它没有提供访问“原始”请求URL的标准方法。根据PEP 3333, the recommended way to reconstruct a request's URL path 正在使用 PATH_INFO CGI变量,它已经被百分比解码,有效地生成了最初的百分比编码的正斜杠 (%2F )与逐字传递的其他类型没有区别(并且打算分隔URI字段)。

尽管没有标准化,但一些WSGI服务器将原始URL作为非标准扩展提供;例如,Gunicorn将其公开为 RAW_URI ,uWSGI称之为 REQUEST_URI 等。您可以实现WSGI(或ASGI,参见下面的讨论)中间件组件,以使用原始URL的路径组件覆盖请求路径,请参阅以下食谱中的更多内容: 解码原始URL路径

与WSGI不同,ASGI规范确实定义了标准连接HTTP作用域变量名 (raw_path )用于未修改的HTTP路径。但是,它不是强制性的,一些应用程序服务器可能无法提供它。然而,我们正在探索添加一个可选功能的可能性,以便在该框架的ASGI风格中使用这条原始路径进行路由。

可扩展性

如何在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中给出。

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

您可以从钩子中注入额外的响应者Kwarg,方法是将它们添加到 参数 迪克特把球打进了钩子。您还可以在 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.App.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对象中缺少我的查询参数?

如果查询参数没有值,并且 keep_blank_qs_values 请求选项设置为 False (截至Falcon 2.0+,默认值为 True ),Falcon将忽略该参数。例如,传递 'foo''foo=' 将导致该参数被忽略。

如果您想要识别这些参数,请使用 keep_blank_qs_values 请求选项应设置为 True (或者简单地在Falcon 2.0+中保持其默认值)。请求选项是为每个实例全局设置的 falcon.App 通过 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.App()
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() 上述简易方法。

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

Falcon4发布了对 field converters 可以匹配多个数据段。这个 path field converter 当放置在URL模板的末尾时,能够使用多个路径段。

在以前的版本中,您可以通过实现Falcon中间件组件在路径被路由之前重写路径来解决此问题。如果您控制了客户端,则可以在相关字段内对正斜杠进行百分比编码,但是请注意,为了访问原始编码的URI,也不可避免地要进行预处理。另见: 为什么我的URL带有百分比编码的正斜杠 (%2F )路由不正确?

如何使代码适应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.textresp.data 非常相似,它们都允许您设置响应的正文。不同的是, text 获取一个字符串,然后 data 取字节。

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

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

resp.stream 允许您设置生成字节的生成器,或设置具有 read() 方法返回字节。对于类似文件的对象,框架将调用 read() 直到溪流耗尽。

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

另请参阅 输出CSV文件 食谱,以了解如何使用 resp.stream 用一台发电机。

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

的默认JSON处理程序 resp.media 仅支持下列表格中列出的对象和类型 json.JSONEncoder

要在JSON中处理其他类型,可以预先序列化它们,或者创建一个定制的JSON媒体处理程序,该处理程序设置 default Param for json.dumps() 。在反序列化传入请求正文时,您可能还希望实现 object_hookjson.loads() 。但是,请注意,设置 defaultobject_hook 参数可能会对(反)序列化的性能产生负面影响。

如果您使用替代的JSON库,还可以查看它是否支持其他数据类型。例如,广受欢迎的 orjson 选择自动序列化 dataclassesenumsdatetime 物件等

此外,不同的互联网媒体类型,如YAML, msgpack 等可能支持比JSON更多的数据类型,无论是作为各自(反)序列化格式的一部分,还是通过自定义类型扩展。

参见

看见 定制JSON编码器 获取有关如何使用定制JSON编码器的示例。

备注

在测试使用定制JSON编码器的应用程序时,请记住 TestClient 是与应用程序分离的,它模拟请求,就好像它们是由第三方客户端(只是SANS网络)执行的一样。因此,传递 json 参数设置为 simulate_* 方法将有效地使用stdlib的 json.dumps() 。如果要序列化用于测试的自定义对象,则需要自己将它们转储到字符串中,并使用 body 参数来代替(随附 application/json 内容类型报头)。

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

Falcon将尝试为您执行此操作,基于 resp.textresp.dataresp.media (以响应中设置的值为准,按该顺序选中)。

对于动态生成的内容,您可以选择不设置 content_length 在这种情况下,Falcon将去掉Content-Length报头,并且希望您的WSGI服务器会做正确的事情™(假设您已经告诉服务器启用Keep-Alive,它可能会选择使用分块编码)。

备注

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

与WSGI类似, ASGI HTTP connection scope 规范规定,没有内容长度的响应“可以在服务器认为合适的情况下被分块”。

当我引发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
with self._engine.connect() as connection:
    result = connection.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-sqla 库可用于以这种方式自动签出和关闭SQLAlChemy连接(尽管它还支持显式上下文管理器模式)。

如何使用ASGI管理我的数据库连接?

此示例与上面的示例类似,但它使用ASGI生命周期挂钩来设置连接池,并在应用程序结束时将其处置。该示例使用 psycopg 来连接到PostgreSQL数据库,但类似的模式可能适用于其他异步数据库库。

import psycopg_pool

url = 'postgresql://scott:tiger@127.0.0.1:5432/test'

class AsyncPoolMiddleware:
    def __init__(self):
        self._pool = None

    async def process_startup(self, scope, event):
        self._pool = psycopg_pool.AsyncConnectionPool(url)
        await self._pool.wait()  # created the pooled connections

    async def process_shutdown(self, scope, event):
        if self._pool:
            await self._pool.close()

    async def process_request(self, req, resp):
        req.context.pool = self._pool

        try:
            req.context.conn = await self._pool.getconn()
        except Exception:
            req.context.conn = None
            raise

    async def process_response(self, req, resp, resource, req_succeeded):
        if req.context.conn:
            await self._pool.putconn(req.context.conn)

然后,示例资源可以使用该连接或池:

class Numbers:
    async def on_get(self, req, resp):
        # This endpoint uses the connection created for the request by the Middleware
        async with req.context.conn.cursor() as cur:
            await cur.execute('SELECT value FROM numbers')
            rows = await cur.fetchall()

        resp.media = [row[0] for row in rows]

    async def on_get_with_pool(self, req, resp):
        # This endpoint uses the pool to acquire a connection
        async with req.context.pool.connection() as conn:
            cur = await conn.execute('SELECT value FROM numbers')
            rows = await cur.fetchall()
            await cur.close()

        resp.media = [row[0] for row in rows]

然后可以将该应用程序用作

from falcon.asgi import App

app = App(middleware=[AsyncPoolMiddleware()])
num = Numbers()
app.add_route('/conn', num)
app.add_route('/pool', num, suffix='with_pool')

如何测试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'}}}} .