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.
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 am using Flask to build a ReST/JSON API. I want all of my responses to be JSON, even when they're error responses.
I have already made my app return uniform JSON responses for successful operations and most errors. Here's the relevant portion of my code:
from flask import Flask, jsonify
from werkzeug import exceptions
app = Flask(__name__)
def make_json_response(status, message, content):
"""Function to generate uniform JSON responses across the API"""
response = {
"api_version": __version__,
"content": content, # The data the API is supposed to return
"message": message, # Content description, error message, etc.
"request_id": g.get("request_id", None),
"status": status, # HTTP Status
}
return jsonify(response), status
def json_error_handler(error=exceptions.InternalServerError, message=None):
"""Makes JSON error messages. By default, Flask will return HTML error
messages, but we want this API to return valid JSON even on errors."""
return make_json_response(
status=error.code, message=message or error.description, content=None)
# Registering JSON error handlers for each HTTP error that we might reasonably throw.
# Covers 400, 401, 403, 404, 405, 406, 408, 409, 410, 411, 412, 413, 414, 415, 416, 417, 418, 422, 428, 429, 431, 500, 501, 502, 503, 504, 505
for error_class in exceptions.default_exceptions.values():
app.register_error_handler(error_class, json_error_handler)
So that's all working. However, 300-3XX responses are not addressed. From what I can tell, redirect errors are not raised in the same way that the other errors are. For example, a 500 may strike at any time when there's a bug in my code, but 300s are a routing decision, and happen before my code is reached.
Flask very helpfully generates automatic redirect responses. For example, if a user tries to GET /jobs, but the real path is /jobs/, Flask will reply with a redirect with no additional work on my end:
$ curl -i https://api.example.com/jobs
HTTP/1.1 301 MOVED PERMANENTLY
Server: gunicorn/19.6.0
Date: Thu, 01 Jun 2017 14:40:18 GMT
X-Cnection: close
Content-Type: text/html; charset=utf-8
Location: http://api.example.com/jobs/
Content-Length: 295
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2 Final//EN">
<title>Redirecting...</title>
<h1>Redirecting...</h1>
<p>You should be redirected automatically to target URL: http://api.example.com/jobs/. If not click the link.
This is desirable behavior. The only issue is that the generated response does not conform to the JSON response pattern used by the rest of the API's responses. This could cause clients that are expecting JSON responses to explode. (Now, any client brittle enough to have that issue should really know the correct URIs to begin with, but still, I would like my API responses to be uniform and helpful.)
From what I can tell, behind the scenes, Flask uses werkzeug.utils.redirect to generate the redirect, but I don't see an obvious way to monkey-patch my own redirect function into my Flask app.
Is there a way to change the automatically-generated Flask redirect responses?
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 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.
I am trying to perform a simple action:
POST to a URL
Return HTTP 303 (SeeOther)
GET from new URL
From what I can tell, this is a pretty standard practice:
http://en.wikipedia.org/wiki/Post/Redirect/Get
Also, it would seem that SeeOther is designed to work this way:
http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html#sec10.3.4
I'm using web.py as my server-side controller, but I suspect that it's not the issue. If I GET, SeeOther works flawlessly as expected. If I POST to the same URL, the browser fails to redirect or load anything at all.
Thinking it was a browser issue, I tried both IE9 and Google Chrome (v23 ish). Both have the same issue.
Thinking web.py might be serving the page incorrectly, or generating a bad URL, I used telnet to examine the headers. I found this:
HTTP GET (this works in the browser):
GET /Users/1 HTTP/1.1
HOST: domain.com
HTTP/1.1 303 See Other
Date: Mon, 24 Dec 2012 18:07:55 GMT
Server: Apache/2
Cache-control: no-cache
Location: http://domain.com/Users
Content-Length: 0
Content-Type: text/html
HTTP POST (this does not work in the browser):
POST /Users/1 HTTP/1.1
HOST: domain.com
HTTP/1.1 303 See Other
Date: Mon, 24 Dec 2012 18:12:35 GMT
Server: Apache/2
Cache-control: no-cache
Location: http://domain.com/Users
Content-Length: 0
Content-Type: text/html
Another thing that could be throwing a wrench in the works:
I'm using mod-rewrite so that the user-visible domain.com/Users/1 is actually domain.com/control.py/Users/1
There may be more information/troubleshooting that I have, but I'm drawing a blank right now.
The Question:
Why does this work with a GET request, but not a POST request? Am I missing a response header somewhere?
EDIT:
Using IE9 Developer Tools and Chrome's Inspector, it looks like the 303 isn't coming back to the browser after a POST. However, I can see the 303 come in when I do a GET request.
However, after looking more closely at Chrome's Inspector, I saw the ability to log every request (don't clear w/ each page call). This allowed me to see that for some reason, my POST request looks like it's failing. Again - GET works just fine.
It's entirely possible that this isn't your issue, but since you don't have your code posted I'll take a shot (just in case).
Since you're using web.py, do you have the POST method defined on your object?
i.e.
urls = (
'/page', 'page'
)
class page:
def POST(self):
# Do something
def GET(self):
# Do something else