Return a requests.Response object from Flask

78,944

Solution 1

Ok, found it:

If a tuple is returned the items in the tuple can provide extra information. Such tuples have to be in the form (response, status, headers). The status value will override the status code and headers can be a list or dictionary of additional header values.

(Flask docs.)

So

return (resp.text, resp.status_code, resp.headers.items())

seems to do the trick.

Solution 2

Using text or content property of the Response object will not work if the server returns encoded data (such as content-encoding: gzip) and you return the headers unchanged. This happens because text and content have been decoded, so there will be a mismatch between the header-reported encoding and the actual encoding.

According to the documentation:

In the rare case that you’d like to get the raw socket response from the server, you can access r.raw. If you want to do this, make sure you set stream=True in your initial request.

and

Response.raw is a raw stream of bytes – it does not transform the response content.

So, the following works for gzipped data too:

esreq = requests.Request(method=request.method, url=url,
                         headers=request.headers, data=request.data)
resp = requests.Session().send(esreq.prepare(), stream=True)
return resp.raw.read(), resp.status_code, resp.headers.items()

If you use a shortcut method such as get, it's just:

resp = requests.get(url, stream=True)
return resp.raw.read(), resp.status_code, resp.headers.items()

Solution 3

Flask can return an object of type flask.wrappers.Response.

You can create one of these from your requests.models.Response object r like this:

from flask import Response

return Response(
    response=r.reason,
    status=r.status_code,
    headers=dict(r.headers)
)

Solution 4

I ran into the same scenario, except that in my case my requests.models.Response contained an attachment. This is how I got it to work:

return send_file(BytesIO(result.content), mimetype=result.headers['Content-Type'], as_attachment=True)

Share:
78,944

Related videos on Youtube

Fred Foo
Author by

Fred Foo

Updated on July 09, 2022

Comments

  • Fred Foo
    Fred Foo almost 2 years

    I'm trying to build a simple proxy using Flask and requests. The code is as follows:

    @app.route('/es/<string:index>/<string:type>/<string:id>',
               methods=['GET', 'POST', 'PUT']):
    def es(index, type, id):
        elasticsearch = find_out_where_elasticsearch_lives()
        # also handle some authentication
        url = '%s%s%s%s' % (elasticsearch, index, type, id)
    
        esreq = requests.Request(method=request.method, url=url,
                                 headers=request.headers, data=request.data)
        resp = requests.Session().send(esreq.prepare())
        return resp.text
    

    This works, except that it loses the status code from Elasticsearch. I tried returning resp (a requests.models.Response) directly, but this fails with

    TypeError: 'Response' object is not callable
    

    Is there another, simple, way to return a requests.models.Response from Flask?

  • iman
    iman almost 8 years
    use resp.content instead of resp.text to handle both binary and text responses
  • Tim
    Tim about 5 years
    There is no need for parenthesis around the returned value.
  • conner.xyz
    conner.xyz almost 5 years
    I still get "Received response with content-encoding: gzip, but failed to decode it"... any guidance?
  • Fabio Filippi
    Fabio Filippi about 4 years
    Thanks! This solved also my problems with headers (which somehow created a CORS problem)
  • bigh_29
    bigh_29 almost 3 years
    This is the approach that I decided on. However, I found that I had to pop the Transfer-Encoding item from the response headers. I get an error if I don't: "The response headers can't include 'Content-Length' with chunked encoding"