I have a Flask-Restful API configured with some CORS options:
api = Api()
api.decorators=[cors.crossdomain(origin='*', headers=['accept', 'Content-Type'])]
...
api.init_app(app)
My API accepts POST requests which may fail if the data in the request is invalid:
class myAPI(Resource):
def post(self):
args = request.get_json()
if args.get('something'):
return {'message': 'Request worked, data received!',
'something': args['something']}
else:
abort(500, "Error: Data must contain a 'something' field!")
When I make a successful POST request to my API I can see that the CORS options are properly set:
...
* upload completely sent off: 81 out of 81 bytes
* HTTP 1.0, assume close after body
< HTTP/1.0 200 OK
< Content-Type: application/json
< Content-Length: 205
< Access-Control-Allow-Origin: *
< Access-Control-Allow-Methods: HEAD, GET, POST, OPTIONS
< Access-Control-Max-Age: 21600
< Access-Control-Allow-Headers: ACCEPT, CONTENT-TYPE
< Server: Werkzeug/0.9.4 Python/2.7.6
If, however, the post call in my class exits through the abort method (by purposely sending bad data to the request) then the Access-Control-* fields are all missing from the response:
* upload completely sent off: 75 out of 75 bytes
* HTTP 1.0, assume close after body
< HTTP/1.0 500 INTERNAL SERVER ERROR
< Content-Type: application/json
< Content-Length: 51
< Server: Werkzeug/0.9.4 Python/2.7.6
Is it possible to make the abort method play nice with my CORS rules, or should I create my own full-fledged response and avoid using the abort function?
When you trigger an abort, the error handler registered to the HTTP error code is automatically invoked, and the response is not actually served by your view function.
If you use the CORS middleware which is provided by Flask-Cors, instead of the decorator form, in the case of handled exceptions and aborts, the CORS headers will be injected as expected.
If an unhandled exception occurs, (E.G. there is an error in your code, and a 500 internal server error), Flask bypasses middleware, and after_request handlers are not run.
Full disclosure, I wrote Flask-Cors.
Related
I am managing an app built by third parts in python.
I have this url dispatcher
urls += [(r'/path/objectAlpha/(.*)', objectAlphaHandler)] # this was made by third parts, it is expected to work
and this class
class objectAlphaHandler(BaseHandler):
def __init__(self, application, request, **kwargs):
super(objectAlphaHandler, self).__init__(application, request, **kwargs) # do the init of the basehandler
#gen.coroutine
def post(self, action=''):
response = {}
...
response = yield self.my_method(json_data)
...
self.write(json.dumps(response))
def my_method(self, json_data)
...
I want to check that the app correctly receives the request and returns some response.
So I try to access that url with Postman
request type:
POST
URL:
http://<machine_ip>:<machine_port>/path/objectAlpha/
I get this error from Postman response box
Parse Error: The server returned a malformed response
and when I click on "view in console" I see
POST http://machine_ip>:<machine_port>/path/objectAlpha/
Error: Parse Error: Expected HTTP/
Request Headers
Content-Type: application/json
User-Agent: PostmanRuntime/7.28.4
Accept: */*
Postman-Token: d644d7dd-699b-4d77-b32f-46a575ae31fc
Host: xx.xxx.x.xx:22
Accept-Encoding: gzip, deflate, br
Connection: keep-alive
Request Body
What does Error: Parse Error: Expected HTTP/ mean?
I checked my app logs but it seems it is not handling any request, even if Postman indicates that the server is returning a (malformed) response.
I also tryed to chenge the target url to:
https...
but it returns
Error: write EPROTO 28427890592840:error:100000f7:SSL routines:OPENSSL_internal:WRONG_VERSION_NUMBER:../../third_party/boringssl/src/ssl/tls_record.cc:242:
which I found out it indicates I should stick with HTTP
Then I tried also:
http://<machine_ip>/path/objectAlpha/
and
<machine_ip>/path/objectAlpha/
which generically return:
Error: connect ECONNREFUSED <machine_ip>:80
I also tryed to substitute line
urls += [(r'/path/objectAlpha/(.*)', objectAlphaHandler)]
with
urls += [(r'/path/objectAlpha/', objectAlphaHandler)]
and
urls += [(r'/path/objectAlpha', objectAlphaHandler)]
but none of these worked.
What is wrong? How can I fix it?
UPDATE
Apparently, according to this thread on Postman Github, the problem happens only on Postman Desktop and not on Postman on browser.
So I tryed to send the request form Postman on my browser but I get
Cloud Agent Error: Can not send requests to reserved address. Make sure address is publicly accessible or select a different agent.
because, according to this other thread,
Postman Website cannot send a request to your computer's localhost. It first needs to connect to your PC with the Postman desktop client
and even if I follow the indications in that answer
Run it [ndr. Postman desktop], then go to the Postman workspace in your browser -> send the request and it will work.
I still get the same
Error: Parse Error: Expected HTTP/
on both Postman Desktop and Postman on browser.
UPDATE
Going on debugging, I tryed to cast a curl on that URL from my terminal:
myuser#mymachine-VirtualBox:~$ curl --verbose "http://<target_machine_ip>:<target_machine_port>/path/objectAlpha"
and I got:
* Trying <target_machine_ip>:<target_machine_port>...
* Connected to <target_machine_ip> (<target_machine_ip>) port <target_machine_port> (#0)
> GET /orkpos5/receipt HTTP/1.1
> Host: <target_machine_ip>:<target_machine_port>
> User-Agent: curl/7.74.0
> Accept: */*
>
* Received HTTP/0.9 when not allowed
* Closing connection 0
curl: (1) Received HTTP/0.9 when not allowed
SOLVED
Since the exposed API has been built by third parts for internal use, it is not exposed to the public.
I did not know that, so in the request URL I had put the well-known port number for HTTP requests, the number 22 (see the list of well-known port numbers.).
To solve the problem, in the request URL, I changed the <target_machine_port> to the port on which the API is actually exposed.
I'm experimenting with uploading data to a sanic web server. To do so I issue a POST request using curl. I try to return some data after the POST request. The rationale behind this is to return some ID now representing the upload on the server side. But this doesn't seem to work. Now I'm wondering: Is my program wrong? Does curl not write the output? Or is this a bug in sanic? Could anyone help me here? Thanks!
Here's the Python program:
import signal
import asyncio
import uvloop
import sanic
app = sanic.Sanic(__name__)
loop = asyncio.get_event_loop()
server = app.create_server(host="localhost", port=3002)
task = asyncio.ensure_future(server)
#app.post("/testUpload", stream=True)
async def api_testUpload(request):
async def doStream(response):
while True:
body = await request.stream.get()
if body is None:
break
sanic.response.json({
"result": "good!"
})
return sanic.response.stream(doStream)
signal.signal(signal.SIGINT, lambda s, f: loop.stop())
try:
loop.run_forever()
except:
loop.stop()
You can invoke curl like this:
curl -v --data-binary "#somefile.data" http://localhost:3002/testUpload
And here is what curl writes to STDOUT:
* Trying 127.0.0.1...
* Connected to localhost (127.0.0.1) port 3002 (#0)
> POST /testUpload HTTP/1.1
> Host: localhost:3002
> User-Agent: curl/7.47.0
> Accept: */*
> Content-Length: 334504
> Content-Type: application/x-www-form-urlencoded
> Expect: 100-continue
>
< HTTP/1.1 200 OK
< Keep-Alive: 5
< Transfer-Encoding: chunked
< Content-Type: text/plain; charset=utf-8
<
* Done waiting for 100-continue
* We are completely uploaded and fine
* Connection #0 to host localhost left intact
As you can see, a text/plain response is generated. This should be an application/json with my data, shouldn't it?
This is not the right way to do it. The sanic API does not work that intuitively here. In order to write data after receiving the bytes sent you have to do it this way within the streaming function doStream():
await response.write(json.dumps({
"result": "good!"
}))
If I understand your question, then you do not need to stream at all. It seems to me like what you are attempting to do is:
upload a file
process the file and do something
return a response to say that something was done
Assuming this is the case, then you do not need to stream at all.
import sanic
app = sanic.Sanic(__name__)
def do_something(file):
print(f'doing something to {file.name}')
#app.post("/testUpload")
async def api_testUpload(request):
myfile = request.files.get('myfile')
do_something(myfile)
return sanic.response.json({
'body': myfile.body,
'name': myfile.name,
'type': myfile.type,
})
app.run(host='localhost', port=3002, auto_reload=True)
You can then hit your endpoint with a file ...
curl -i -F "myfile=#/tmp/somefile.txt" http://localhost:3002/testUpload
And you should see in your logs:
[2018-10-25 10:09:56 +0300] [16051] [INFO] Goin' Fast # http://localhost:3002
[2018-10-25 10:09:56 +0300] [16055] [INFO] Starting worker [16055]
doing something to somefile.txt
[2018-10-25 10:09:59 +0300] - (sanic.access)[INFO][127.0.0.1:39306]: POST http://localhost:3002/testUpload 200 62
And your return
HTTP/1.1 200 OK
Connection: keep-alive
Keep-Alive: 5
Content-Length: 62
Content-Type: application/json
{"body":"FOOBAR\n","name":"somefile.txt","type":"text\/plain"}
Now, I suppose if you are uploading a file, and you wanted to stream the response of that upload as it was happening to that request or another, that would be a different issue.
I hope this helps.
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
I am trying to post data to my server from my microcontroller. I need to send raw http data from my controller and this is what I am sending below:
POST /postpage HTTP/1.1
Host: https://example.com
Accept: */*
Content-Length: 18
Content-Type: application/json
{"cage":"abcdefg"}
My server requires JSON encoding and not form encoded request.
For the above request sent, I get an 400 error from the server, HTTP/1.1 400 Bad Request
However, when I try to reach the post to my server via a python script via my laptop, I am able to get a proper response.
import requests
url='https://example.com'
mycode = 'abcdefg'
def enter():
value = requests.post('url/postpage',
params={'cage': mycode})
print vars(value)
enter()
Can anyone please let me know where I could be going wrong in the raw http data I'm sending above ?
HTTP specifies the separator between headers as a single newline, and requires a double newline before the content:
POST /postpage HTTP/1.1
Host: https://example.com
Accept: */*
Content-Length: 18
Content-Type: application/json
{"cage":"abcdefg"}
If you don’t think you’ve got all of the request right, try seeing what was sent by Python:
response = ...
request = response.request # request is a PreparedRequest.
headers = request.headers
url = request.url
Read the docs for PreparedRequest for more information.
To pass a parameter, use this Python:
REQUEST = 'POST /postpage%s HTTP/1.1\r\nHost: example.com\r\nContent-Length: 0\r\nConnection: keep-alive\r\nAccept-Encoding: gzip, deflate\r\nAccept: */*\r\nUser-Agent: python-requests/2.4.3 CPython/2.7.9 Linux/4.4.11-v7+\r\n\r\n';
query = ''
for k, v in params.items():
query += '&' + k + '=' + v # URL-encode here if you want.
if len(query): query = '?' + query[1:]
return REQUEST % query
I'm implementing swagger validation middleware for falcon framework. However, there's a problem with unsuccessful requests.
If a falcon HTTP error is raised in responder or even before coming to a resource (for example in case of 404 Not Found), response still has status code 200 and empty body when it comes to process_response middleware method.
I can't undertsand if it's a bug or a feature.
When I remove validation middleware, everything works as expected. When there's a swagger validation middleware, all responses in process_response have status code 200, including unsuccessful.
I want to skip swagger validation/serialization of response in case of HTTP 404, 401, 400 etc. When a response comes to process_response, how do I find out that an error was raised if the status code is 200? How do I process only successful responses in middleware?
Also, at which point does falcon set the proper response status code and body?
python 3.5, falcon==1.0.0
Any help is much appreciated.
UPD: minimal working example:
app.py:
import falcon
class Validator:
def process_resource(self, req, resp, resource, params):
raise falcon.HTTPBadRequest("Too bad", "All requests are bad")
def process_response(self, req, resp, resource):
print(resp.status, resp.body)
class Resource:
def on_get(self, req, resp):
pass
application = falcon.API(middleware=[Validator()])
application.add_route('/test', Resource())
$ http http http://127.0.0.1:8000/test returns:
HTTP/1.1 400 Bad Request
Connection: close
Date: Tue, 28 Jun 2016 15:09:04 GMT
Server: gunicorn/19.6.0
content-length: 69
content-type: application/json; charset=UTF-8
vary: Accept
{
"description": "All requests are bad",
"title": "Too bad"
}
while web app prints 200 OK None.