1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305
|
.. _media:
Media
=====
Falcon allows for easy and customizable internet media type handling. By
default Falcon only enables handlers for JSON and HTML (URL-encoded and
multipart) forms. However, additional handlers can be configured through the
:any:`falcon.RequestOptions` and :any:`falcon.ResponseOptions` objects
specified on your :any:`falcon.App`.
.. note::
WebSocket media is handled differently from regular HTTP requests. For
information regarding WebSocket media handlers, please
see: :ref:`ws_media_handlers` in the WebSocket section.
Usage
-----
Zero configuration is needed if you're creating a JSON API. Simply use
:meth:`~falcon.Request.get_media()` and :attr:`~falcon.Response.media` (WSGI)
, or :meth:`~falcon.asgi.Request.get_media()` and
:attr:`~falcon.asgi.Response.media` (ASGI) to let Falcon
do the heavy lifting for you.
.. tab-set::
.. tab-item:: WSGI
.. code:: python
import falcon
class EchoResource:
def on_post(self, req, resp):
# Deserialize the request body based on the Content-Type
# header in the request, or the default media type
# when the Content-Type header is generic ('*/*') or
# missing.
obj = req.get_media()
message = obj.get('message')
# The framework will look for a media handler that matches
# the response's Content-Type header, or fall back to the
# default media type (typically JSON) when the app does
# not explicitly set the Content-Type header.
resp.media = {'message': message}
resp.status = falcon.HTTP_200
.. tab-item:: ASGI
.. code:: python
import falcon
class EchoResource:
async def on_post(self, req, resp):
# Deserialize the request body. Note that the ASGI version
# of this method must be awaited.
obj = await req.get_media()
message = obj.get('message')
# The framework will look for a media handler that matches
# the response's Content-Type header, or fall back to the
# default media type (typically JSON) when the app does
# not explicitly set the Content-Type header.
resp.media = {'message': message}
resp.status = falcon.HTTP_200
.. warning::
Once :meth:`falcon.Request.get_media()` or
:meth:`falcon.asgi.Request.get_media()` is called on a request, it will
consume the request's body stream. To avoid unnecessary overhead, Falcon
will only process request media the first time it is referenced. Subsequent
interactions will use a cached object.
Validating Media
----------------
Falcon currently only provides a JSON Schema media validator; however,
JSON Schema is very versatile and can be used to validate any deserialized
media type that JSON also supports (i.e. dicts, lists, etc).
.. autofunction:: falcon.media.validators.jsonschema.validate
If JSON Schema does not meet your needs, a custom validator may be
implemented in a similar manner to the one above.
.. _content-type-negotiaton:
Content-Type Negotiation
------------------------
Falcon currently only supports partial negotiation out of the box. By default,
when the ``get_media()`` method or the ``media`` attribute is used, the
framework attempts to (de)serialize based on the ``Content-Type`` header value.
The missing link that Falcon doesn't provide is the connection between the
``Accept`` header provided by a user and the ``Content-Type`` header set on the
response.
If you do need full negotiation, it is very easy to bridge the gap using
middleware. Here is an example of how this can be done:
.. tab-set::
.. tab-item:: WSGI
.. code:: python
from falcon import Request, Response
class NegotiationMiddleware:
def process_request(self, req: Request, resp: Response) -> None:
resp.content_type = req.accept
.. tab-item:: ASGI
.. code:: python
from falcon.asgi import Request, Response
class NegotiationMiddleware:
async def process_request(self, req: Request, resp: Response) -> None:
resp.content_type = req.accept
Exception Handling
------------------
Version 3 of Falcon updated how the handling of exceptions raised by handlers behaves:
* Falcon lets the media handler try to deserialized an empty body. For the media types
that don't allow empty bodies as a valid value, such as ``JSON``, an instance of
:class:`falcon.MediaNotFoundError` should be raised. By default, this error
will be rendered as a ``400 Bad Request`` response to the client.
This exception may be suppressed by passing a value to the ``default_when_empty``
argument when calling :meth:`Request.get_media`. In this case, this value will
be returned by the call.
* If a handler encounters an error while parsing a non-empty body, an instance of
:class:`falcon.MediaMalformedError` should be raised. The original exception, if any,
is stored in the ``__cause__`` attribute of the raised instance. By default, this
error will be rendered as a ``400 Bad Request`` response to the client.
If any exception was raised by the handler while parsing the body, all subsequent invocations
of :meth:`Request.get_media` or :attr:`Request.media` will result in a re-raise of the same
exception, unless the exception was a :class:`falcon.MediaNotFoundError` and a default value
is passed to the ``default_when_empty`` attribute of the current invocation.
External handlers should update their logic to align to the internal Falcon handlers.
.. _custom_media_handlers:
Replacing the Default Handlers
------------------------------
By default, the framework installs :class:`falcon.media.JSONHandler`,
:class:`falcon.media.URLEncodedFormHandler`, and
:class:`falcon.media.MultipartFormHandler` for the ``application/json``,
``application/x-www-form-urlencoded``, and ``multipart/form-data`` media types,
respectively.
When creating your App object you can either add or completely replace all of
the handlers. For example, let's say you want to write an API that sends and
receives `MessagePack <https://msgpack.org/>`_. We can easily do this by telling
our Falcon API that we want a default media type of ``application/msgpack``, and
then creating a new :class:`~falcon.media.Handlers` object to map that media
type to an appropriate handler.
The following example demonstrates how to replace the default handlers. Because
Falcon provides a :class:`~.falcon.media.MessagePackHandler` that is not enabled
by default, we use it in our examples below. However, you can always substitute
a :ref:`custom media handler <custom-media-handler-type>` as needed.
.. code:: python
import falcon
from falcon import media
handlers = media.Handlers({
falcon.MEDIA_MSGPACK: media.MessagePackHandler(),
})
app = falcon.App(media_type=falcon.MEDIA_MSGPACK)
app.req_options.media_handlers = handlers
app.resp_options.media_handlers = handlers
Alternatively, you can simply update the existing
:class:`~falcon.media.Handlers` object to retain the default handlers:
.. code-block:: python
import falcon
from falcon import media
extra_handlers = {
falcon.MEDIA_MSGPACK: media.MessagePackHandler(),
}
app = falcon.App()
app.req_options.media_handlers.update(extra_handlers)
app.resp_options.media_handlers.update(extra_handlers)
The ``falcon`` module provides a number of constants for common media types.
See also: :ref:`media_type_constants`.
.. _note_json_handler:
.. note::
The configured :class:`falcon.Response` JSON handler is also used to serialize
:class:`falcon.HTTPError` and the ``json`` attribute of :class:`falcon.asgi.SSEvent`.
The JSON handler configured in :class:`falcon.Request` is used by
:meth:`falcon.Request.get_param_as_json` to deserialize query params.
Therefore, when implementing a custom handler for the JSON media type, it is required
that the sync interface methods, meaning
:meth:`falcon.media.BaseHandler.serialize` and :meth:`falcon.media.BaseHandler.deserialize`,
are implemented even in ``ASGI`` applications. The default JSON handler,
:class:`falcon.media.JSONHandler`, already implements the methods required to
work with both types of applications.
Supported Handler Types
-----------------------
.. autoclass:: falcon.media.JSONHandler
:no-members:
.. autoclass:: falcon.media.MessagePackHandler
:no-members:
.. autoclass:: falcon.media.MultipartFormHandler
:no-members:
.. autoclass:: falcon.media.URLEncodedFormHandler
:no-members:
.. _custom-media-handler-type:
Custom Handler Type
-------------------
If Falcon doesn't have an Internet media type handler that supports your
use case, you can easily implement your own using the abstract base class
provided by Falcon and documented below.
In general ``WSGI`` applications only use the sync methods, while
``ASGI`` applications only use the async one.
The JSON handled is an exception to this, since it's used also by
other parts of the framework, not only in the media handling.
See the :ref:`note above<note_json_handler>` for more details.
.. autoclass:: falcon.media.BaseHandler
:members:
:member-order: bysource
.. tip::
In order to use your custom media handler in a :ref:`Falcon app <app>`,
you'll have to add an instance of your class to the app's media handlers
(specified in :attr:`RequestOptions <falcon.RequestOptions.media_handlers>`
and :attr:`ResponseOptions<falcon.ResponseOptions.media_handlers>`,
respectively).
See also: :ref:`custom_media_handlers`.
Handlers Mapping
----------------
.. autoclass:: falcon.media.Handlers
:members:
.. _media_type_constants:
Media Type Constants
--------------------
The ``falcon`` module provides a number of constants for
common media type strings, including the following:
.. code:: python
falcon.MEDIA_JSON
falcon.MEDIA_MSGPACK
falcon.MEDIA_MULTIPART
falcon.MEDIA_URLENCODED
falcon.MEDIA_YAML
falcon.MEDIA_XML
falcon.MEDIA_HTML
falcon.MEDIA_JS
falcon.MEDIA_TEXT
falcon.MEDIA_JPEG
falcon.MEDIA_PNG
falcon.MEDIA_GIF
|