Return a requests.Response object from Flask

2019-01-17 06:37发布

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?

3条回答
Animai°情兽
2楼-- · 2019-01-17 07:13

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()
查看更多
啃猪蹄的小仙女
3楼-- · 2019-01-17 07:27

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) where at least one item has to be in the tuple. 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.

查看更多
SAY GOODBYE
4楼-- · 2019-01-17 07:30

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)

查看更多
登录 后发表回答