how get proper http trace response in fastapi? - python

here is my code:
from fastapi import FastAPI
app = FastAPI()
#app.trace("/")
def test_trace():
...
and this is the response:
curl -v -X TRACE http://127.0.0.1:8000
* Mark bundle as not supporting multiuse
< HTTP/1.1 200 OK
< date: Sat, 03 Sep 2022 15:52:55 GMT
< server: uvicorn
< content-length: 4
< content-type: application/json
<
* Connection #0 to host 127.0.0.1 left intact
but I want something like this with TRACE header in it:
< HTTP/1.1 200 OK
< date: Sat, 03 Sep 2022 15:52:55 GMT
< server: uvicorn
< content-length: 4
< content-type: application/json
< TRACE / HTTP/1.1
< Host: www.ssfkz.si
< User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:91.0) Gecko/20100101 Firefox/91.0

Headers (and pretty much every detail of interest) are customizable through the use of the Response object.
from fastapi import FastAPI, Response
app = FastAPI()
#app.trace("/")
def test_trace(response: Response):
response.headers["TRACE"] = "HTTP/1.1"
response.status_code = 200
...
return {}
Before asking on SO, please make sure to read (and/or search) the official documentation (https://fastapi.tiangolo.com/advanced/response-headers/).
EDIT
Even though the OP mentioned setting the TRACE "header", officially, the implementation of this HTTP method does not mention setting such headers, but rather, returning exactly what was received in the trace request.
An approximate behavior might be achieved by using:
#app.trace("/")
def test_trace(request: Request, response: Response):
for k,v in request.headers.items():
response.headers[k] = v
# repeat for every property that should be echo-ed
response.headers["Content-Type"] = "message/http" # RFC 7231
response.status_code = 200 # RFC 7231
return {}

Related

Python Flask-cors not returning back any of the cors headers in response

I am using Flask-Cors==3.0.3
Here's the way I am setting it up for my app, where the front end is on localhost:9000 on apache while the backend is on localhost:8080:
app = Flask(_name_, template_folder="www/templates", static_folder ="www/static" )
app.config['API_UPLOAD_FOLDER'] = config.API_UPLOAD_FOLDER
app.config['SMOKE_UPLOAD_FOLDER'] = config.SMOKE_UPLOAD_FOLDER
app.config['MONGODB_SETTINGS'] = {
'db': config.MONGO_DBNAME,
'host': config.MONGO_URI
}
app.config['CORS_HEADERS'] = 'Content-Type, auth'
app.config['CORS_RESOURCES'] = {r"/apis/*":{"origins":"http://localhost:9000"}}
app.config['CORS_METHODS'] = "GET,POST,OPTIONS"
app.config['CORS_SUPPORTS_CREDENTIALS'] = True
CORS(app)
and my request is like :
OPTIONS /apis/register HTTP/1.1
Host: localhost:8080
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.13; rv:57.0) Gecko/20100101 Firefox/57.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: en-US,en;q=0.5
Accept-Encoding: gzip, deflate
Access-Control-Request-Method: POST
Access-Control-Request-Headers: content-type
Origin: http://localhost:9000
Connection: close
and the response is :
HTTP/1.1 200 OK
Server: nginx/1.10.1 (Ubuntu)
Date: Tue, 02 Jan 2018 08:52:29 GMT
Content-Type: text/html; charset=utf-8
Content-Length: 0
Connection: close
Allow: POST, OPTIONS
I also tries doing it this way instead of the above and the results were still the same... no CORS headers in the response :(
Cors = CORS(app, resources={r"/apis/*": {"origins": "localhost:9000"}}, supports_credentials=True, methods="GET, POST, OPTIONS", allow_headers="Content-type, auth")
Is there something that I am doing wrong here ?
Was trying more things here as changing this :
app.config['CORS_HEADERS'] = 'Content-Type, auth'
app.config['CORS_RESOURCES'] = {r"/apis/*":{"origins":"http://localhost:9000"}}
app.config['CORS_METHODS'] = "GET,POST,OPTIONS"
app.config['CORS_SUPPORTS_CREDENTIALS'] = True
to this:
app.config['CORS_HEADERS'] = ['Content-Type, auth']
app.config['CORS_RESOURCES'] = {r"/apis/*":{"origins":"http://localhost:9000"}}
app.config['CORS_METHODS'] = ["GET,POST,OPTIONS"]
app.config['CORS_SUPPORTS_CREDENTIALS'] = True
but the same results :(
What did seem to work though was using decorator instead of global app level settings like this:
#cross_origin(supports_credentials=True, methods=["GET, POST, OPTIONS"], headers=["content-type, auth"])
There's a comment in the source-code for flask-cors:
"If wildcard is in the origins, even if 'send_wildcard' is False, simply send the wildcard. Unless supports_credentials is True, since that is forbidded by the spec.."
They refer to the spec here: http://www.w3.org/TR/cors/#resource-requests which explicitly states "Note: The string "*" cannot be used for a resource that supports credentials."
So, the solution would seem to be to not use a wildcard origin.

Python. Cannot make a request to simple site api. Flask and requests

I am trying to create simple API for my site. I created the route with flask:
#api.route('/api/rate&message_id=<message_id>&performer=<performer_login>', methods=['POST'])
def api_rate_msg(message_id, performer_login):
print("RATE API ", message_id, ' ', performer_id)
return 400
print(...) function don't execute...
I use flask-socketio to communicate between client and server.
I send json from client and process it with:
#socket.on('rate')
def handle_rate(data):
print(data)
payload = {'message_id':data['message_id'], 'performer':data['performer']}
r = requests.post('/api/rate', params=payload)
print (r.status_code)
Note, that data variable is sending from client and is correct(I've checked it).
print(r.status_code) don't exec too...
Where I'm wrong? Please, sorry for my bad english :(
This api function must increase rate of message, which stored in mongodb, if interesting.
Don't put &message_id=<message_id>&performer=<performer_login> in your route string. Instead, get these arguments from request.args.
Try it:
from flask import request
...
#api.route('/api/rate', methods=['POST'])
def api_rate_msg():
print(request.args)
return ''
I've tested it with httpie:
$ http -v POST :5000/api/rate message_id==123 performer_login==foo
POST /api/rate?message_id=123&performer_login=foo HTTP/1.1
Accept: */*
Accept-Encoding: gzip, deflate
Connection: keep-alive
Content-Length: 0
Host: localhost:5000
User-Agent: HTTPie/0.9.8
HTTP/1.0 200 OK
Content-Length: 0
Content-Type: text/html; charset=utf-8
Date: Sun, 02 Apr 2017 13:54:40 GMT
Server: Werkzeug/0.11.11 Python/2.7.13
And from flask's log:
ImmutableMultiDict([('message_id', u'123'), ('performer_login', u'foo')])
127.0.0.1 - - [02/Apr/2017 22:54:40] "POST /api/rate?message_id=123&performer_login=foo HTTP/1.1" 200 -
Remove the below part from your api route
&message_id=<message_id>&performer=<performer_login
This is not required in POST request. It helps in GET requests. API call in request is not matching the route definition and therefore you have the current problem

Bottle framework with a tunnel doesn't receive GET arguments

I'm trying to make a simple python script which returns an argument from GET request. The issue is that it does not receive any arguments and returns blank body. There is one peculiar thing, though. In order to test GET requests I use requestmaker.com, hurl.it and apikitchen.com. While requestmaker and apikitchen return an empty body, hurl.it actually returns the required parameter.
I have tried Bottle, Flask and Tornado with the same results. I'm using ngrok for tunneling but I've also tried forwardhq.com.
The code (with bottle framework):
import bottle
from bottle import route, run, request, response
bottle.debug(True)
#route('/')
def home():
return "Great Scott!"
#route('/valley')
def thevalley():
theflux = request.query.flux
return theflux
run(host='0.0.0.0', port=8515, reloader=True)
ngrok status:
Tunnel Status online
Version 1.6/1.5
Forwarding http://88mph.ngrok.com -> 127.0.0.1:8515
The results I get from GET requests:
requestmaker.com
Request Headers Sent:
GET /valley HTTP/1.1
Host: 88mph.ngrok.com
Accept: */*
Content-Length: 10
Content-Type: application/x-www-form-urlencoded
Response Headers:
HTTP/1.1 200 OK
Server: nginx/1.4.3
Date: Sun, 09 Feb 2014 13:51:20 GMT
Content-Type: text/html; charset=UTF-8
Content-Length: 0
Connection: keep-alive
bottle:
127.0.0.1 - - [09/Feb/2014 13:51:20] "GET /valley HTTP/1.1" 200 0
hurl.it
Request:
Accept: */*
Accept-Encoding: gzip, deflate, compress
User-Agent: runscope/0.1
Response:
Connection: keep-alive
Content-Length: 5
Content-Type: text/html; charset=UTF-8
Date: Sun, 09 Feb 2014 14:02:55 GMT
Server: nginx/1.4.3
Body:
121gw
bottle:
127.0.0.1 - - [09/Feb/2014 14:02:55] "GET /valley?flux=121gw HTTP/1.1" 200 5
https://88mph.ngrok.com/valley?flux=121gw
Finally, just entering the URL into the address bar works as well, I get "121gw".
bottle:
127.0.0.1 - - [09/Feb/2014 14:05:46] "GET /valley?flux=121gw HTTP/1.1" 200 5
End
Every request maker can connect to server (200 OK) and even return "Great Scott" when accessing root. However, only webbrowser and hurl return the argument. Any ideas what is at fault here?

Python requests - print entire http request (raw)?

While using the requests module, is there any way to print the raw HTTP request?
I don't want just the headers, I want the request line, headers, and content printout. Is it possible to see what ultimately is constructed from HTTP request?
Since v1.2.3 Requests added the PreparedRequest object. As per the documentation "it contains the exact bytes that will be sent to the server".
One can use this to pretty print a request, like so:
import requests
req = requests.Request('POST','http://stackoverflow.com',headers={'X-Custom':'Test'},data='a=1&b=2')
prepared = req.prepare()
def pretty_print_POST(req):
"""
At this point it is completely built and ready
to be fired; it is "prepared".
However pay attention at the formatting used in
this function because it is programmed to be pretty
printed and may differ from the actual request.
"""
print('{}\n{}\r\n{}\r\n\r\n{}'.format(
'-----------START-----------',
req.method + ' ' + req.url,
'\r\n'.join('{}: {}'.format(k, v) for k, v in req.headers.items()),
req.body,
))
pretty_print_POST(prepared)
which produces:
-----------START-----------
POST http://stackoverflow.com/
Content-Length: 7
X-Custom: Test
a=1&b=2
Then you can send the actual request with this:
s = requests.Session()
s.send(prepared)
These links are to the latest documentation available, so they might change in content:
Advanced - Prepared requests and API - Lower level classes
import requests
response = requests.post('http://httpbin.org/post', data={'key1': 'value1'})
print(response.request.url)
print(response.request.body)
print(response.request.headers)
Response objects have a .request property which is the PreparedRequest object that was sent.
An even better idea is to use the requests_toolbelt library, which can dump out both requests and responses as strings for you to print to the console. It handles all the tricky cases with files and encodings which the above solution does not handle well.
It's as easy as this:
import requests
from requests_toolbelt.utils import dump
resp = requests.get('https://httpbin.org/redirect/5')
data = dump.dump_all(resp)
print(data.decode('utf-8'))
Source: https://toolbelt.readthedocs.org/en/latest/dumputils.html
You can simply install it by typing:
pip install requests_toolbelt
Note: this answer is outdated. Newer versions of requests support getting the request content directly, as AntonioHerraizS's answer documents.
It's not possible to get the true raw content of the request out of requests, since it only deals with higher level objects, such as headers and method type. requests uses urllib3 to send requests, but urllib3 also doesn't deal with raw data - it uses httplib. Here's a representative stack trace of a request:
-> r= requests.get("http://google.com")
/usr/local/lib/python2.7/dist-packages/requests/api.py(55)get()
-> return request('get', url, **kwargs)
/usr/local/lib/python2.7/dist-packages/requests/api.py(44)request()
-> return session.request(method=method, url=url, **kwargs)
/usr/local/lib/python2.7/dist-packages/requests/sessions.py(382)request()
-> resp = self.send(prep, **send_kwargs)
/usr/local/lib/python2.7/dist-packages/requests/sessions.py(485)send()
-> r = adapter.send(request, **kwargs)
/usr/local/lib/python2.7/dist-packages/requests/adapters.py(324)send()
-> timeout=timeout
/usr/local/lib/python2.7/dist-packages/requests/packages/urllib3/connectionpool.py(478)urlopen()
-> body=body, headers=headers)
/usr/local/lib/python2.7/dist-packages/requests/packages/urllib3/connectionpool.py(285)_make_request()
-> conn.request(method, url, **httplib_request_kw)
/usr/lib/python2.7/httplib.py(958)request()
-> self._send_request(method, url, body, headers)
Inside the httplib machinery, we can see HTTPConnection._send_request indirectly uses HTTPConnection._send_output, which finally creates the raw request and body (if it exists), and uses HTTPConnection.send to send them separately. send finally reaches the socket.
Since there's no hooks for doing what you want, as a last resort you can monkey patch httplib to get the content. It's a fragile solution, and you may need to adapt it if httplib is changed. If you intend to distribute software using this solution, you may want to consider packaging httplib instead of using the system's, which is easy, since it's a pure python module.
Alas, without further ado, the solution:
import requests
import httplib
def patch_send():
old_send= httplib.HTTPConnection.send
def new_send( self, data ):
print data
return old_send(self, data) #return is not necessary, but never hurts, in case the library is changed
httplib.HTTPConnection.send= new_send
patch_send()
requests.get("http://www.python.org")
which yields the output:
GET / HTTP/1.1
Host: www.python.org
Accept-Encoding: gzip, deflate, compress
Accept: */*
User-Agent: python-requests/2.1.0 CPython/2.7.3 Linux/3.2.0-23-generic-pae
requests supports so called event hooks (as of 2.23 there's actually only response hook). The hook can be used on a request to print full request-response pair's data, including effective URL, headers and bodies, like:
import textwrap
import requests
def print_roundtrip(response, *args, **kwargs):
format_headers = lambda d: '\n'.join(f'{k}: {v}' for k, v in d.items())
print(textwrap.dedent('''
---------------- request ----------------
{req.method} {req.url}
{reqhdrs}
{req.body}
---------------- response ----------------
{res.status_code} {res.reason} {res.url}
{reshdrs}
{res.text}
''').format(
req=response.request,
res=response,
reqhdrs=format_headers(response.request.headers),
reshdrs=format_headers(response.headers),
))
requests.get('https://httpbin.org/', hooks={'response': print_roundtrip})
Running it prints:
---------------- request ----------------
GET https://httpbin.org/
User-Agent: python-requests/2.23.0
Accept-Encoding: gzip, deflate
Accept: */*
Connection: keep-alive
None
---------------- response ----------------
200 OK https://httpbin.org/
Date: Thu, 14 May 2020 17:16:13 GMT
Content-Type: text/html; charset=utf-8
Content-Length: 9593
Connection: keep-alive
Server: gunicorn/19.9.0
Access-Control-Allow-Origin: *
Access-Control-Allow-Credentials: true
<!DOCTYPE html>
<html lang="en">
...
</html>
You may want to change res.text to res.content if the response is binary.
Here is a code, which makes the same, but with response headers:
import socket
def patch_requests():
old_readline = socket._fileobject.readline
if not hasattr(old_readline, 'patched'):
def new_readline(self, size=-1):
res = old_readline(self, size)
print res,
return res
new_readline.patched = True
socket._fileobject.readline = new_readline
patch_requests()
I spent a lot of time searching for this, so I'm leaving it here, if someone needs.
A fork of #AntonioHerraizS answer (HTTP version missing as stated in comments)
Use this code to get a string representing the raw HTTP packet without sending it:
import requests
def get_raw_request(request):
request = request.prepare() if isinstance(request, requests.Request) else request
headers = '\r\n'.join(f'{k}: {v}' for k, v in request.headers.items())
body = '' if request.body is None else request.body.decode() if isinstance(request.body, bytes) else request.body
return f'{request.method} {request.path_url} HTTP/1.1\r\n{headers}\r\n\r\n{body}'
headers = {'User-Agent': 'Test'}
request = requests.Request('POST', 'https://stackoverflow.com', headers=headers, json={"hello": "world"})
raw_request = get_raw_request(request)
print(raw_request)
Result:
POST / HTTP/1.1
User-Agent: Test
Content-Length: 18
Content-Type: application/json
{"hello": "world"}
💡 Can also print the request in the response object
r = requests.get('https://stackoverflow.com')
raw_request = get_raw_request(r.request)
print(raw_request)
I use the following function to format requests. It's like #AntonioHerraizS except it will pretty-print JSON objects in the body as well, and it labels all parts of the request.
format_json = functools.partial(json.dumps, indent=2, sort_keys=True)
indent = functools.partial(textwrap.indent, prefix=' ')
def format_prepared_request(req):
"""Pretty-format 'requests.PreparedRequest'
Example:
res = requests.post(...)
print(format_prepared_request(res.request))
req = requests.Request(...)
req = req.prepare()
print(format_prepared_request(res.request))
"""
headers = '\n'.join(f'{k}: {v}' for k, v in req.headers.items())
content_type = req.headers.get('Content-Type', '')
if 'application/json' in content_type:
try:
body = format_json(json.loads(req.body))
except json.JSONDecodeError:
body = req.body
else:
body = req.body
s = textwrap.dedent("""
REQUEST
=======
endpoint: {method} {url}
headers:
{headers}
body:
{body}
=======
""").strip()
s = s.format(
method=req.method,
url=req.url,
headers=indent(headers),
body=indent(body),
)
return s
And I have a similar function to format the response:
def format_response(resp):
"""Pretty-format 'requests.Response'"""
headers = '\n'.join(f'{k}: {v}' for k, v in resp.headers.items())
content_type = resp.headers.get('Content-Type', '')
if 'application/json' in content_type:
try:
body = format_json(resp.json())
except json.JSONDecodeError:
body = resp.text
else:
body = resp.text
s = textwrap.dedent("""
RESPONSE
========
status_code: {status_code}
headers:
{headers}
body:
{body}
========
""").strip()
s = s.format(
status_code=resp.status_code,
headers=indent(headers),
body=indent(body),
)
return s
test_print.py content:
import logging
import pytest
import requests
from requests_toolbelt.utils import dump
def print_raw_http(response):
data = dump.dump_all(response, request_prefix=b'', response_prefix=b'')
return '\n' * 2 + data.decode('utf-8')
#pytest.fixture
def logger():
log = logging.getLogger()
log.addHandler(logging.StreamHandler())
log.setLevel(logging.DEBUG)
return log
def test_print_response(logger):
session = requests.Session()
response = session.get('http://127.0.0.1:5000/')
assert response.status_code == 300, logger.warning(print_raw_http(response))
hello.py content:
from flask import Flask
app = Flask(__name__)
#app.route('/')
def hello_world():
return 'Hello, World!'
Run:
$ python -m flask hello.py
$ python -m pytest test_print.py
Stdout:
------------------------------ Captured log call ------------------------------
DEBUG urllib3.connectionpool:connectionpool.py:225 Starting new HTTP connection (1): 127.0.0.1:5000
DEBUG urllib3.connectionpool:connectionpool.py:437 http://127.0.0.1:5000 "GET / HTTP/1.1" 200 13
WARNING root:test_print_raw_response.py:25
GET / HTTP/1.1
Host: 127.0.0.1:5000
User-Agent: python-requests/2.23.0
Accept-Encoding: gzip, deflate
Accept: */*
Connection: keep-alive
HTTP/1.0 200 OK
Content-Type: text/html; charset=utf-8
Content-Length: 13
Server: Werkzeug/1.0.1 Python/3.6.8
Date: Thu, 24 Sep 2020 21:00:54 GMT
Hello, World!

No response with POST request and Content-Type "application/json" in flask

I'm having problems with a Flask view that should return a response with content-type "application/json" in response to a POST request.
Specifically, if I do:
curl -v -d 'foo=bar' http://example.org/jsonpost
to this view:
#app.route('/jsonpost', methods=['GET', 'POST'])
def json_post():
resp = make_response('{"test": "ok"}')
resp.headers['Content-Type'] = "application/json"
return resp
I get some sort of connection reset:
* About to connect() to example.org port 80 (#0)
* Trying xxx.xxx.xxx.xxx... connected
* Connected to example.org (xxx.xxx.xxx.xxx) port 80 (#0)
> POST /routing/jsonpost HTTP/1.1
> User-Agent: curl/7.19.7 (i486-pc-linux-gnu) libcurl/7.19.7 OpenSSL/0.9.8k zlib/1.2.3.3 libidn/1.15
> Host: example.org
> Accept: */*
> Content-Length: 7
> Content-Type: application/x-www-form-urlencoded
>
< HTTP/1.1 200 OK
< Server: nginx/1.2.4
< Date: Thu, 27 Dec 2012 14:07:59 GMT
< Content-Type: application/json
< Content-Length: 14
< Connection: keep-alive
< Set-Cookie: session="..."; Path=/; HttpOnly
< Cache-Control: public
<
* transfer closed with 14 bytes remaining to read
* Closing connection #0
curl: (18) transfer closed with 14 bytes remaining to read
If instead I do:
curl -d 'foo=bar' http://example.org/htmlpost
to:
#app.route('/htmlpost', methods=['GET', 'POST'])
def html_post():
resp = make_response('{"test": "ok"}')
resp.headers['Content-Type'] = "text/html"
return resp
I get the expected the full response (200-ok)
{"test": "ok"}
By the way, if I send a GET request to the same JSON route:
curl http://example.org/jsonpost
I also get the expected response..
Any ideas?
Thanks to Audrius's comments I tracked a possible source of the problem to the interaction between uWSGI and nginx: apparently, if you receive POST data in a request you must read it before returning a response.
This, for example, fixes my issue.
#app.route('/jsonpost', methods=['GET', 'POST'])
def json_post():
if request.method == 'POST':
dummy = request.form
resp = make_response('{"test": "ok"}')
resp.headers['Content-Type'] = "application/json"
return resp
A different solution involves passing --post-buffering 1 to uWSGI as described by uWSGI's author Roberto De Ioris.
I still don't understand why the problem does not present itself with Content-Type set to "text/html"

Categories