When Falcon(-Framework) could not find a route for a specific request, 404 is returned. How can I override this default handler? I want to extend the handler with a custom response.
The default handler when no resource matches is the path_not_found responder:
But as you can see in the _get_responder method of falcon API, it can't be override without some monkey patching.
As far as I can see, there are two different ways to use a custom handler:
Subclass the API class, and overwrite the _get_responder method so it calls your custom handler
Use a default route that matches any route if none of the application ones are matched. You probably prefer to use a sink instead of a route, so you capture any HTTP method (GET, POST...) with the same function.
I would recommend the second option, as it looks much neater.
Your code would look like:
import falcon
class HomeResource:
def on_get(self, req, resp):
resp.body = 'Hello world'
def handle_404(req, resp):
resp.status = falcon.HTTP_404
resp.body = 'Not found'
application = falcon.API()
application.add_route('/', HomeResource())
# any other route should be placed before the handle_404 one
application.add_sink(handle_404, '')
There is a better solution here.
def custom_response_handler(req, resp, ex, params):
resp.status = falcon.HTTP_404
resp.text = "custom text response"
app = falcon.App()
app.add_error_handler(HTTPRouteNotFound, custom_response_handler)
Related
I want to create a middleware function in Flask that logs details from the request and the response. The middleware should run after the Response is created, but before it is sent back. I want to log:
The request's HTTP method (GET, POST, or PUT)
The request endpoint
The response HTTP status code, including 500 responses. So, if an exception is raised in the view function, I want to record the resulting 500 Response before the Flask internals send it off.
Some options I've found (that don't quite work for me):
The before_request and after_request decorators. If I could access the request data in after_request, my problems still won't be solved, because according to the documentation
If a function raises an exception, any remaining after_request functions will not be called.
Deferred Request Callbacks - there is an after_this_request decorator described on this page, which decorates an arbitrary function (defined inside the current view function) and registers it to run after the current request. Since the arbitrary function can have info from both the request and response in it, it partially solves my problem. The catch is that I would have to add such a decorated function to every view function; a situation I would very much like to avoid.
#app.route('/')
def index():
#after_this_request
def add_header(response):
response.headers['X-Foo'] = 'Parachute'
return response
return 'Hello World!'
Any suggestions?
My first answer is very hacky. There's actually a much better way to achieve the same result by making use of the g object in Flask. It is useful for storing information globally during a single request. From the documentation:
The g name stands for “global”, but that is referring to the data being global within a context. The data on g is lost after the context ends, and it is not an appropriate place to store data between requests. Use the session or a database to store data across requests.
This is how you would use it:
#app.before_request
def gather_request_data():
g.method = request.method
g.url = request.url
#app.after_request
def log_details(response: Response):
g.status = response.status
logger.info(f'method: {g.method}\n url: {g.url}\n status: {g.status}')
return response
Gather whatever request information you want in the function decorated with #app.before_request and store it in the g object.
Access whatever you want from the response in the function decorated with #app.after_request. You can still refer to the information you stored in the g object from step 1. Note that you'll have to return the response at the end of this function.
you can use flask-http-middleware for it link
from flask import Flask
from flask_http_middleware import MiddlewareManager, BaseHTTPMiddleware
app = Flask(__name__)
class MetricsMiddleware(BaseHTTPMiddleware):
def __init__(self):
super().__init__()
def dispatch(self, request, call_next):
url = request.url
response = call_next(request)
response.headers.add("x-url", url)
return response
app.wsgi_app = MiddlewareManager(app)
app.wsgi_app.add_middleware(MetricsMiddleware)
#app.get("/health")
def health():
return {"message":"I'm healthy"}
if __name__ == "__main__":
app.run()
Every time you make request, it will pass the middleware
Okay, so the answer was staring me in the face the whole time, on the page on Deferred Request Callbacks.
The trick is to register a function to run after the current request using after_this_request from inside the before_request callback. This is the code snippet of what worked for me:
#app.before_request
def log_details():
method = request.method
url = request.url
#after_this_request
def log_details_callback(response: Response):
logger.info(f'method: {method}\n url: {url}\n status: {response.status}')
These are the steps:
Get the required details from the response in the before_request callback and store them in some variables.
Then access what you want of the response in the function you decorate with after_this_request, along with the variables you stored the request details in earlier.
Currently, in our system, we are calling the endpoints even in the same flask application by a HTTP request. All the requests is called through a make_request wrapper method as shown below:
def make_request(url, body, http_type="get"):
http_fn = getattr(requests, http_type)
response = http_fn(url, headers=headers, json=body)
return response.status_code, response
Hence I'm trying to convert all local requests within the same flask application to a direct method call so that any endpoint within the same flask application is called this way:
def make_request(url, body, http_type="get"):
# Figure out If its local request call the function of the endpoint and construct the response
# If not make an http request
return response.status_code, response
EDIT: Tried searching in the url_map to find the method associated with the endpoint but the function returned in not in a callabale state. Any points on how we can call the method from here?
for rule in current_app.url_map.iter_rules():
if my_url in rule.rule:
endpoint = rule.endpoint
for key, view in current_app.view_functions.items():
if key == endpoint:
# Found the view function, need to know how to call
# the right method( GET, POST etc)
view contains the following:
{
'view_class': <class 'endpoints.attribute_endpoints.AttributeEndpoint'>,
'methods': {'GET', 'PUT', 'POST'}, 'provide_automatic_options': None, '__wrapped__': <function View.as_view.<locals>.view at 0x10c9190d0>}
If I understand correctly, what you're trying to achieve is calling a flask endpoint internally without going over http. Look at the solution below and let me know if it is does what you want.
Old Code:
#app.route('/someRoute', methods=['GET'])
def some_route_function():
json_object = request.get_json()
my_number = json_object['myNumber']
my_number = my_number**2
return jsonify(my_number=my_number)
New Code:
def square_number_func(number):
return number**2
#app.route('/someRoute', methods=['GET'])
def some_route_function():
json_object = request.get_json()
my_number = json_object['myNumber']
my_number = square_number_func(my_number)
return jsonify(my_number=my_number)
def my_non_flask_function():
my_number = 17
my_number = square_number_func(my_number)
This way you get the functionality you need without having to rely on Flask's request object, nor having to call flask via http.
Edit: If you need to figure out if it's an internal flask function then you compare it against a list of functions in your local global scope or in your flask app, as it does store your routes. You can even store the function parameters so you know what to call. Finally, you can map each endpoint to another function if you want inside a dictionary or something, such as {some_route_function: square_number_func} so that you can tell which function to substitute for the http call.
As many as HTTP frameworks support using the custom method and custom HTTP status code to be used inside the codes, I wonder that is it possible to do the same thing with Falcon?
I tried:
class MyResource:
def on_obtain(self, req, resp):
# do some thing
and I also add it to the API routes, but when I call this API I get HTTP/1.1 400 Bad Request with this body:
{"title":"Bad request","description":"Invalid HTTP method"}
my question is that is it possible to define obtain on this resource?
I think you need to use Custom Routers. Just an example:
class Resource:
def on_get(self, req, resp):
resp.body = req.method
resp.status = falcon.HTTP_200
def on_example(self, req, resp):
resp.body = req.method
resp.status = falcon.HTTP_200
class MyRouter(routing.DefaultRouter):
# this is just demonstration that my solution works
# I added request method into method_map
def add_route(self, uri_template, method_map, resource):
super().add_route(uri_template, method_map, resource)
method_map['EXAMPLE'] = resource.on_example
def find(self, uri, req=None):
return super().find(uri, req)
api = falcon.API(router=MyRouter()) # just set your router
test = Resource()
api.add_route('/test', test)
Let's check:
curl -X EXAMPLE http://127.0.0.1:8000/test
EXAMPLE⏎
curl http://127.0.0.1:8000/test
GET⏎
So, all what you need is just create Router and implement add_route() / find() methods. Hope you understand what I mean.
I would like to route to a different Flask views based on the Accept HTTP header, for example:
#api.route('/test', accept='text/html')
def test_html():
return "<html><body>Test</body></html>"
#api.route('/test', accept='text/json')
def test_json():
return jsonify(test="Test")
I haven't found relevant option in Werkzeug Rule constructor, which is used by Flask. Is it a missing feature or is it possible to achieve the same effect differently, for example by intercepting and modifying URL path before routing?
I don't want to merge the views into one because it would complicate code significantly, there are many of them and they reside in different blueprints.
I am aware that similar question has been asked, but nobody answered it using Flask. It's possible to do it in different web frameworks, for example in Pyramid using predicates - sample code can be found in this answer.
I know this is an old question but I ended up here looking for something similar so I hope it helps someone else.
flask_accept has the functionality to handle different Accept types through different routes.
from flask import Flask, jsonify
from flask_accept import accept
app = Flask(__name__)
#app.route('/')
#accept('text/html')
def hello_world():
return 'Hello World!'
#hello_world.support('application/json')
def hello_world_json():
return jsonify(result="Hello World!")
if __name__ == '__main__':
app.run()
if you just want to reject requests depending on whether they are a specific data type you could also use Flask-Negotiate
from flask import Flask
from flask_negotiate import consumes, produces
app = Flask(__name__)
#app.route('/consumes_json_only')
#consumes('application/json')
def consumes_json_only():
return 'consumes json only'
When one tries to access the endpoint without a valid Accept header:
$ curl localhost:5000 -I
HTTP 415 (Unsupported Media Type)
I wrote a decorator which does that (copying here for posterity). It's just a rough idea that could be improved further (e.g. returning 406 Not Acceptable response instead of using the default handler when there are no handlers that match given MIME type). More explanations are in the comments.
import functools
from flask import Flask, request, jsonify
app = Flask(__name__)
def accept(func_or_mimetype=None):
"""Decorator which allows to use multiple MIME type handlers for a single
endpoint.
"""
# Default MIME type.
mimetype = 'text/html'
class Accept(object):
def __init__(self, func):
self.default_mimetype = mimetype
self.accept_handlers = {mimetype: func}
functools.update_wrapper(self, func)
def __call__(self, *args, **kwargs):
default = self.default_mimetype
mimetypes = request.accept_mimetypes
best = mimetypes.best_match(self.accept_handlers.keys(), default)
# In case of Accept: */*, choose default handler.
if best != default and mimetypes[best] == mimetypes[default]:
best = default
return self.accept_handlers[best](*args, **kwargs)
def accept(self, mimetype):
"""Register a MIME type handler."""
def decorator(func):
self.accept_handlers[mimetype] = func
return func
return decorator
# If decorator is called without argument list, return Accept instance.
if callable(func_or_mimetype):
return Accept(func_or_mimetype)
# Otherwise set new MIME type (if provided) and let Accept act as a
# decorator.
if func_or_mimetype is not None:
mimetype = func_or_mimetype
return Accept
#app.route('/')
#accept # Or: #accept('text/html')
def index():
return '<strong>foobar</strong>'
#index.accept('application/json')
def index_json():
return jsonify(foobar=True)
#index.accept('text/plain')
def index_text():
return 'foobar\n', 200, {'Content-Type': 'text/plain'}
You can return different response types based on the Accept header using request. Example.
if request.accept_mimetypes['application/json']:
return jsonify(<object>), '200 OK'
There is some way to remove HTTP Header 'Server: TwistedWeb/13.1.0' from responses from a Twisted based web application?
You can rewrite any header by calling the request.setHeader method.
class RootPage(Resource):
def getChild(self, name, request):
request.setHeader('server', 'MyVeryOwnServer/1.0')
return OtherResource(name)
The change applies to any resources on your site; you could put it in your Site class. You want that 404 or 500 errors would also return correct header; so you should set it as earlier as possible but not before it is set by twisted itself (in order to overwrite it):
#!/usr/bin/env python
import sys
from twisted.web import server, resource
from twisted.internet import reactor
from twisted.python import log
class Site(server.Site):
def getResourceFor(self, request):
request.setHeader('server', 'Server/1.9E377')
return server.Site.getResourceFor(self, request)
# example from http://twistedmatrix.com/
class HelloResource(resource.Resource):
isLeaf = True
numberRequests = 0
def render_GET(self, request):
self.numberRequests += 1
request.setHeader("content-type", "text/plain")
return "I am request #" + str(self.numberRequests) + "\n"
log.startLogging(sys.stderr)
reactor.listenTCP(8080, Site(HelloResource()))
reactor.run()
Default Server http header is specified in t.w.server.version.
I know this is an old question, but if you would like to remove the server http header. I am talking about the
request.setHeader('Server', 'SomeServer')
This is set by Twisted Web automagically if you don't specify a value. You can remove it by using the inner Headers class. For example,
request.responseHeaders.removeHeader('Server')
This will remove the Server Http Header.