From bb93a41490474eb51d3bc5161795a2c4bf055834 Mon Sep 17 00:00:00 2001 From: "eric.goulart" Date: Sun, 8 Dec 2024 12:10:16 -0300 Subject: [PATCH 1/4] feat(recipes): add text/plain media handler example --- examples/recipes/plain_text_main.py | 21 +++++++++++++++++++++ tests/test_recipes.py | 25 +++++++++++++++++++++++++ 2 files changed, 46 insertions(+) create mode 100644 examples/recipes/plain_text_main.py diff --git a/examples/recipes/plain_text_main.py b/examples/recipes/plain_text_main.py new file mode 100644 index 000000000..4b03e2b73 --- /dev/null +++ b/examples/recipes/plain_text_main.py @@ -0,0 +1,21 @@ +import cgi +import functools + +import falcon + + +class TextHandler(falcon.media.BaseHandler): + DEFAULT_CHARSET = 'utf-8' + + @classmethod + @functools.lru_cache + def _get_charset(cls, content_type): + _, params = cgi.parse_header(content_type) + return params.get('charset') or cls.DEFAULT_CHARSET + + def deserialize(self, stream, content_type, content_length): + data = stream.read() + return data.decode(self._get_charset(content_type)) + + def serialize(self, media, content_type): + return media.encode(self._get_charset(content_type)) diff --git a/tests/test_recipes.py b/tests/test_recipes.py index 9b2160087..d8ad3851e 100644 --- a/tests/test_recipes.py +++ b/tests/test_recipes.py @@ -141,3 +141,28 @@ def test_raw_path(self, asgi, app_kind, util): ) assert result2.status_code == 200 assert result2.json == {'cached': True} + + +class TestTextPlainHandler: + class MediaEcho: + def on_post(self, req, resp): + resp.content_type = req.content_type + resp.media = req.get_media() + + def test_text_plain_basic(self, util): + recipe = util.load_module('examples/recipes/plain_text_main.py') + + app = falcon.App() + app.req_options.media_handlers['text/plain'] = recipe.TextHandler() + app.resp_options.media_handlers['text/plain'] = recipe.TextHandler() + + app.add_route('/media', self.MediaEcho()) + + client = falcon.testing.TestClient(app) + payload = 'Hello, Falcon!' + headers = {'Content-Type': 'text/plain'} + response = client.simulate_post('/media', body=payload, headers=headers) + + assert response.status_code == 200 + assert response.content_type == 'text/plain' + assert response.text == payload From 3a598ae93602a8b5256d93f2f7ed03dba2a1f770 Mon Sep 17 00:00:00 2001 From: "eric.goulart" Date: Mon, 9 Dec 2024 14:20:58 -0300 Subject: [PATCH 2/4] adding the rst file --- docs/user/recipes/index.rst | 1 + docs/user/recipes/plain-text-handler.rst | 36 ++++++++++++++++++++++++ 2 files changed, 37 insertions(+) create mode 100644 docs/user/recipes/plain-text-handler.rst diff --git a/docs/user/recipes/index.rst b/docs/user/recipes/index.rst index 0c90a4111..cad306b07 100644 --- a/docs/user/recipes/index.rst +++ b/docs/user/recipes/index.rst @@ -7,6 +7,7 @@ Recipes header-name-case multipart-mixed output-csv + plain-text-handler pretty-json raw-url-path request-id diff --git a/docs/user/recipes/plain-text-handler.rst b/docs/user/recipes/plain-text-handler.rst new file mode 100644 index 000000000..319ddc0e9 --- /dev/null +++ b/docs/user/recipes/plain-text-handler.rst @@ -0,0 +1,36 @@ +.. _plain-text-recipe: + +Handling Plain Text +=================== + +.. _custom-text-handler: + +Custom Text Media Handler +========================= + +This example demonstrates how to create a custom handler in Falcon to +process `text/plain` media type. The handler implements serialization +and deserialization of textual content, respecting the charset specified +in the `Content-Type` header or defaulting to `utf-8` when no charset is provided. + +.. literalinclude:: ../../../examples/recipes/plain_text_main.py + :language: python + +To use this handler, register it in the Falcon application's media +options for both requests and responses: + +.. code:: python + + import falcon + from your_module import TextHandler + + app = falcon.App() + app.req_options.media_handlers['text/plain'] = TextHandler() + app.resp_options.media_handlers['text/plain'] = TextHandler() + +With this setup, the application can handle textual data directly +as `text/plain`, ensuring support for various character encodings as needed. + +.. warning:: + Be sure to validate and limit the size of incoming data when + working with textual content to prevent server overload or denial-of-service attacks. From 08ae4c4c13f05c7f095a16284c2d84aa3785ab1d Mon Sep 17 00:00:00 2001 From: diegomirandap Date: Mon, 9 Dec 2024 14:24:07 -0300 Subject: [PATCH 3/4] fix plain/text handler --- examples/recipes/plain_text_main.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/examples/recipes/plain_text_main.py b/examples/recipes/plain_text_main.py index 4b03e2b73..aad21f997 100644 --- a/examples/recipes/plain_text_main.py +++ b/examples/recipes/plain_text_main.py @@ -1,4 +1,3 @@ -import cgi import functools import falcon @@ -10,7 +9,7 @@ class TextHandler(falcon.media.BaseHandler): @classmethod @functools.lru_cache def _get_charset(cls, content_type): - _, params = cgi.parse_header(content_type) + _, params = falcon.parse_header(content_type) return params.get('charset') or cls.DEFAULT_CHARSET def deserialize(self, stream, content_type, content_length): From ca349edeb6f0b3dd014d7f91ab3448615dbc80d7 Mon Sep 17 00:00:00 2001 From: "eric.goulart" Date: Wed, 11 Dec 2024 15:35:30 -0300 Subject: [PATCH 4/4] reformatted --- tests/test_recipes.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/tests/test_recipes.py b/tests/test_recipes.py index be0a2bc35..adebd8001 100644 --- a/tests/test_recipes.py +++ b/tests/test_recipes.py @@ -132,10 +132,11 @@ def test_raw_path(self, asgi, app_kind, util): result2 = falcon.testing.simulate_get(recipe.app, url2) assert result2.status_code == 200 assert result2.json == {'cached': True} - + scope2 = falcon.testing.create_scope(url2) assert scope2['raw_path'] == url2.encode() + class TestTextPlainHandler: class MediaEcho: def on_post(self, req, resp): @@ -158,4 +159,4 @@ def test_text_plain_basic(self, util): assert response.status_code == 200 assert response.content_type == 'text/plain' - assert response.text == payload \ No newline at end of file + assert response.text == payload