Python Falcon - get POST data
Asked Answered
T

8

16

I try to use falcon package in my project. Problem is I didn't find a way to get body data from the HTTP post request.

I used code from example, but req.stream.read() doesn't return JSON as expected.

The code is:

raw_json = req.stream.read()
result.json(raw_json, encoding='utf-8')
resp.body = json.dumps(result_json, encoding='utf-8')

How to get the POST data?

Thanks for any help

Tamtam answered 8/2, 2018 at 6:42 Comment(3)
It took me a long time to find this answer: #34619119Gluconeogenesis
The field you're looking for is req.media. See my answer below.Tosh
req.stream.read() can only be used once. Think of it as reading from a file stream, where you can't do f.read() again unless you f.seek(0). It's the same, except there is no .seek() for the request stream.Dappled
L
9

in falcon 2, if you work with json type, use req.media

for example:

import falcon
from json import dumps

class Resource(object):
    def on_post(self, req, resp, **kwargs):
        result = req.media
        # do your job
        resp.body = dumps(result)


api = falcon.API()

api.add_route('/test', Resource())
Lowrance answered 15/7, 2019 at 6:35 Comment(0)
C
8

Little digging into the problem led to the following linked issue on github. It states that falcon framework at least in its version 0.3 and working with Python 2 didn't parse data 'POSTed' as string if they are aptly escaped. We could use more information on what data you are trying to send over POST request and in what format is that being sent, as in if its being send as simple text, or with Header Information Content-Type:application/json, or if its coming through an HTML form.

While the exact issue is not clear from the question I could still suggest trying to use bounded_stream instead of stream as in:

raw_json = req.bounded_stream.read()
result.json(raw_json, encoding='utf-8')
resp.body = json.dumps(result_json, encoding='utf-8')

for the official documentation suggests use of bounded_stream where uncertain conditions such as Content-Length undefined or 0, or if header information is missing altogether.

bounded_stream is described as the following in the official falcon documentation.

File-like wrapper around stream to normalize certain differences between the native input objects employed by different WSGI servers. In particular, bounded_stream is aware of the expected Content-Length of the body, and will never block on out-of-bounds reads, assuming the client does not stall while transmitting the data to the server.

Falcon receives the HTTP requests data as buffer object as passed by WSGI wrapper which receives the data from client, and its possible it doesn't run proper parsing on top of the data to convert to a more usable data structure for performance reasons.

Courtier answered 8/2, 2018 at 9:19 Comment(0)
L
6

Big thanks to Ryan (and Prateek Jain) for the answer.

The solution is simply to put app.req_options.auto_parse_form_urlencoded=True. For example:

import falcon

class ThingsResource(object):
    def on_post(self, req, resp):
        value = req.get_param("value", required=True)
        #do something with value

app = falcon.API()
app.req_options.auto_parse_form_urlencoded=True

things = ThingsResource()

app.add_route('/things', things)
Luteal answered 21/2, 2019 at 8:42 Comment(0)
T
3

The field you're looking for is somewhat confusingly named, but it's req.media:

Returns a deserialized form of the request stream. When called, it will attempt to deserialize the request stream using the Content-Type header as well as the media-type handlers configured via falcon.RequestOptions.

If the request is JSON, req.media already contains a python dict.

Tosh answered 4/3, 2019 at 10:8 Comment(0)
A
2

I have added changes in request.py in falcon framework to parse application/x-www-form-urlencoded and multipart/from-data. I have raised pull request - https://github.com/falconry/falcon/pull/1236 but it is not yet merged in master. Check this - https://github.com/branelmoro/falcon

I have added new code to parse POST, PUT and DELETE application/x-www-form-urlencoded and multipart/form-data. Text fields will be available in req.form_data dictionary and upload file buffer stream will be available in req.files dictionary.

I hope this will help to access POST and GET parameters separately and we will be able to upload files as well. Good thing about the change is that it will not load entire uploaded file in memory.

Below is sample code to show how to use POST, PUT and DELETE application/x-www-form-urlencoded and multipart/form-data:

import falcon

class Resource(object):

    def on_post(self, req, resp):

        # req.form_data will return dictionary of text field names and their values
        print(req.form_data)

        # req.form_data will return dictionary of file field names and
        # their buffer class FileStream objects as values
        print(req.files)

        # support we are uploading a image.jpg in `pancard` file field then
        # req.files["pancard"] will be FileStream buffer object

        # We can use set_max_upload_size method to set maximum allowed
        # file size let say 1Mb = 1*1024*1024 bytes for this file

        req.files["pancard"].set_max_upload_size(1*1024*1024)

        # We can use uploadto method to upload file on required path (Note: absolute filepath is required)
        # This method returns boolean - `True` on successful upload
        # and if upload is unsuccessful then it returns `False` and sets error on failure.
        path = "/tmp/" + req.files["pancard"].name

        response = req.files["pancard"].uploadto("/tmp/" + path)

        print(response)

        # Once file is uploaded sucessfully, we can check it's size
        print(req.files["pancard"].size)

        # If file is not uploaded sucessfully, we can check it's error
        print(req.files["pancard"].error)

        resp.body = "Done file upload"

        resp.status = falcon.HTTP_200

# falcon.API instances are callable WSGI apps
app = falcon.API()

things = Resource()

# things will handle post requests to the '/post_path' URL path
app.add_route('/post_path', things)

Do let me know if you have any doubts.

Anaemic answered 28/3, 2018 at 18:53 Comment(2)
Falcon is killing me. Why is it so hard to receive Post data. I'm getting AttributeError: 'Request' object has no attribute 'form_data'.Gluconeogenesis
#34619119Gluconeogenesis
R
0

So far... for me bounded_stream.read() and stream.read() both get the posted data as type str. I have only found one way around the issue so far:

def on_post(self, req, resp):
    posted_data = json.loads(req.stream.read())
    print(str(type(posted_data)))
    print(posted_data)

Loading the string into a json dict once the posted data is received is my only solution that I can come up with

Rafaelle answered 21/8, 2018 at 14:55 Comment(0)
D
0

Here's something I used while designing an API.

import falcon
import json


class VerifierResource():
    def on_post(self, req, resp):
        credentials = json.loads(req.stream.read())

        if credentials['username'] == USER \
          and credentials['passwd'] == PASSWORD:
            resp.body = json.dumps({"status": "verified"})
        else:
            resp.body = json.dumps({"status": "invalid"})

api = falcon.API()
api.add_route('/verify', VerifierResource())

This returns a serialized JSON with corresponding response body.

Dappled answered 24/10, 2019 at 18:8 Comment(0)
N
0

there is a sample way to get media from body. I use to get the body in the post method:

def on_post(req,resp)
    arguments = {}
            # get body media on post method
            body = req.get_media()
            if 'something' in body:
                arguments['something'] = body['something']

send body content type Media-Type and print resp or use in code, but if want to send JSON body your code should cover give JSON parameters.

Do let me know if you have any doubts.

Needlepoint answered 15/12, 2021 at 5:44 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.