I'm writing a proxy in Flask. The proxy should forward all requests except one small subset, for which some additional checks should be performed.
Since there is a variety of routes I found this example in the docs
to match all URLs:
from flask import Flask
app = Flask(__name__)
#app.route('/', defaults={'path': ''})
#app.route('/<path:path>')
def catch_all(path):
return 'You want path: %s' % path
if __name__ == '__main__':
app.run()
What I'm wondering is what is the best way to have one view function that handles this subset of routes, and another view function that handles all others ?
Flask has decorators to perform action before handling the request.
#app.after_request
def catch_all(response):
# * additional checks here *
You can stop the normal handling by returning data
If any of these function returns a value it’s handled as if it was the return value from the view and further request handling is stopped.
It is often use for authentication or permission checks.
Related
There is a catch-all URL example for Flask:
from flask import Flask
app = Flask(__name__)
#app.route('/', defaults={'path': ''})
#app.route('/<path:path>')
def catch_all(path):
return 'You want path: %s' % path
if __name__ == '__main__':
app.run()
Decorators can be translated to the following to code look more similar to Flask-RESTful achieving the same functionality:
app.add_url_rule('/', 'catch_all', catch_all, defaults={'path': ''})
app.add_url_rule('/<path:path>', 'catch_all', catch_all, defaults={'path': ''})
If I'm right, this can be further translated to an equivalent Flask-RESTful app (at least debuging shows it creates the same URL routes):
class RESTapp(Resource):
def get(self, path):
# do get something
api.add_resource(RESTapp, '/', '/<path:path>', defaults={'path': ''})
The problem is that this app redirects all URLs to / and I can not get the requested path in get() function. I want to handle all paths ( / and '/') in the same function as in Flask, but using Flask-RESTful.
Similar questions:
Catch-All URL in flask-restful The Asker does not want to catch / or at least not in the same functions as other URL-s.
Flask restful API urls The Answerer proposes two classess as two resources. I have to initialize the class through resource_class_kwargs keyword argument and I want to keep only one instance, so it will not be good for me.
What I've tried:
Create two add_resource calls for the same class. It end with error.
Debug add_resource. It shows that it creates a resource view function from the Endpoint and that is given to the add_url_rule function. Else it works the same as the two subsequent add_url_rule functions.
By trial and error I've figured out the solution, which is neither documented nor looks like to the expected way of doing it (simmilarly as in Flask, showed in the question).
One must supply a Pythonic default argument to get() and other functions: get(stuff='DEF_VAL')
Full example which is working:
from flask import Flask
from flask_restful import Api, Resource
app = Flask(__name__)
api = Api(app)
class RESTapp(Resource):
#staticmethod
def get(path=''): # <-- You should provide the default here not in api.add_resource()!
return 'You want path: \'%s\'' % path # do get something
api.add_resource(RESTapp, '/', '/<path:path>') # Here just the URLs must be the arguments!
if __name__ == '__main__':
app.run()
Is there a way to inject a Flask request object into a different Flask app. This is what I'm trying to do:
app = flask.Flask(__name__)
#app.route('/foo/<id>')
def do_something(id):
return _process_request(id)
def say_hello(request):
# request is an instance of flask.Request.
# I want to inject it into 'app'
I'm trying this with Google Cloud Functions, where say_hello() is a function that is invoked by the cloud runtime. It receives a flask.Request as the argument, which I want to then process through my own set of routes.
I tried the following, which doesn't work:
def say_hello(request):
with app.request_context(request.environ):
return app.full_dispatch_request()
This responds with 404 errors for all requests.
Edit:
The simple way to implement say_hello() is as follows:
def say_hello(request):
if request.method == 'GET' and request.path.startswith('/foo/'):
return do_something(_get_id(request.path))
flask.abort(404)
This essentially requires me to write the route matching logic myself. I'm wondering if there's a way to avoid doing that, and instead use Flask's built-in decorators and routing capabilities.
Edit 2:
Interestingly, dispatching across apps work locally:
app = flask.Flask(__name__)
# Add app.routes here
functions = flask.Flask('functions')
#functions.route('/', defaults={'path': ''})
#functions.route('/<path:path>', methods=['GET', 'POST', 'PUT', 'DELETE'])
def catch_all(path):
with app.request_context(flask.request.environ):
return app.full_dispatch_request()
if __name__ == '__main__':
functions.run()
But the same technique doesn't seem to work on GCF.
I wouldn't recommend this method, but this is technically possible by abusing the request stack and rewriting the current request and re-dispatching it.
However, you'll still need to do some type of custom "routing" to properly set the url_rule, as the incoming request from GCF won't have it (unless you explicitly provide it via the request):
from flask import Flask, _request_ctx_stack
from werkzeug.routing import Rule
app = Flask(__name__)
#app.route('/hi')
def hi(*args, **kwargs):
return 'Hi!'
def say_hello(request):
ctx = _request_ctx_stack.top
request = ctx.request
request.url_rule = Rule('/hi', endpoint='hi')
ctx.request = request
_request_ctx_stack.push(ctx)
return app.dispatch_request()
I am using flask-mwoauth to create a simple application in Flask using OAuth authentication on Mediawiki (and Wikipedia in particular).
flask-mwoauth is a blueprint that provides some convenience methods to interact with Mediawiki Extensions:OAuth and adds the following URIs:
/login - runs the OAuth handshake and returns the user to /
/login?next=/someurl will return the user to /someurl
/logout - clears the users' access tokens
/logout?next=/someurl will return the user to /someurl
/oauth-callback - callback from MW to finish the handshake
The users' OAuth key and secret are stored in the session.
I would like to be able to create custom responses for some of this custom URIs. Take for example /logout, the definition of the response in very simple (__init__.py#L56):
#self.bp.route('/logout')
def logout():
session['mwo_token'] = None
session['username'] = None
if 'next' in request.args:
return redirect(request.args['next'])
return "Logged out!"
I would like to define in my application the route /logout with a custom response (for example, rendering a template), however if I use the blueprint then the route #app.route("/logout") is ignored.
What I would like to know if it is possible to "extend" the blueprint in the sense that I can define a route /logout in my app, call the original method from the blueprint and then serve a customized response.
If you want to completely redefine behavior of route the best way is override MWOAuth class. Here an example which works:
import os
from flask import Flask, Blueprint
from flask_mwoauth import MWOAuth
app = Flask(__name__)
app.secret_key = os.urandom(24)
class MyMWOAuth(MWOAuth):
def __init__(self,
base_url='https://www.mediawiki.org/w',
clean_url="Deprecated",
default_return_to='index',
consumer_key=None,
consumer_secret=None,
name="Deprecated"):
# I skipped other rows. It's just an example
self.bp = Blueprint('mwoauth', __name__)
# By the way you can customize here login and oauth-callback
#self.bp.route('/logout')
def logout():
# your custom logic here...
return "My custom logout"
mwoauth = MyMWOAuth(consumer_key='test', consumer_secret='test')
app.register_blueprint(mwoauth.bp)
if __name__ == "__main__":
app.run(debug=True, threaded=True)
Let's open /logout. You will see My custom logout.
As you can see registration of BluePrint routes takes place in init method of MWOAuth.
The second way is to use request callbacks. Here an example which demonstrates the change in the body of the response after logout.
from flask import g, request
def after_this_request(f):
if not hasattr(g, 'after_request_callbacks'):
g.after_request_callbacks = []
g.after_request_callbacks.append(f)
return f
#app.after_request
def call_after_request_callbacks(r):
for callback in getattr(g, 'after_request_callbacks', ()):
callback(r)
return r
#app.before_request
def before_logout():
#after_this_request
def after_logout(response):
# check if called route == '/logout'
# in our case response.data == 'Logged out!'
# see: https://github.com/valhallasw/flask-mwoauth/blob/master/flask_mwoauth/__init__.py#L48
if request.url_rule.endpoint == 'mwoauth.logout':
# custom logic here...
# for example I change data in request
response.data = 'Data from after_logout'
Let's open /logout. You will see Data from after_logout.
Hope this helps.
The Flask documentation shows:
add_url_rule(*args, **kwargs)
Connects a URL rule. Works exactly like the route() decorator.
If a view_func is provided it will be registered with the endpoint.
endpoint – the endpoint for the registered URL rule. Flask itself assumes the name of the view function as endpoint
What exactly is meant by an "endpoint"?
How Flask Routing Works
The entire idea of Flask (and the underlying Werkzeug library) is to map URL paths to some logic that you will run (typically, the "view function"). Your basic view is defined like this:
#app.route('/greeting/<name>')
def give_greeting(name):
return 'Hello, {0}!'.format(name)
Note that the function you referred to (add_url_rule) achieves the same goal, just without using the decorator notation. Therefore, the following is the same:
# No "route" decorator here. We will add routing using a different method below.
def give_greeting(name):
return 'Hello, {0}!'.format(name)
app.add_url_rule('/greeting/<name>', 'give_greeting', give_greeting)
Let's say your website is located at 'www.example.org' and uses the above view. The user enters the following URL into their browser:
http://www.example.org/greeting/Mark
The job of Flask is to take this URL, figure out what the user wants to do, and pass it on to one of your many python functions for handling. It takes the path:
/greeting/Mark
...and matches it to the list of routes. In our case, we defined this path to go to the give_greeting function.
However, while this is the typical way that you might go about creating a view, it actually abstracts some extra info from you. Behind the scenes, Flask did not make the leap directly from URL to the view function that should handle this request. It does not simply say...
URL (http://www.example.org/greeting/Mark) should be handled by View Function (the function "give_greeting")
Actually, it there is another step, where it maps the URL to an endpoint:
URL (http://www.example.org/greeting/Mark) should be handled by Endpoint "give_greeting".
Requests to Endpoint "give_greeting" should be handled by View Function "give_greeting"
Basically, the "endpoint" is an identifier that is used in determining what logical unit of your code should handle the request. Normally, an endpoint is just the name of a view function. However, you can actually change the endpoint, as is done in the following example.
#app.route('/greeting/<name>', endpoint='say_hello')
def give_greeting(name):
return 'Hello, {0}!'.format(name)
Now, when Flask routes the request, the logic looks like this:
URL (http://www.example.org/greeting/Mark) should be handled by Endpoint "say_hello".
Endpoint "say_hello" should be handled by View Function "give_greeting"
How You Use the Endpoint
The endpoint is commonly used for the "reverse lookup". For example, in one view of your Flask application, you want to reference another view (perhaps when you are linking from one area of the site to another). Rather than hard-code the URL, you can use url_for(). Assume the following
#app.route('/')
def index():
print url_for('give_greeting', name='Mark') # This will print '/greeting/Mark'
#app.route('/greeting/<name>')
def give_greeting(name):
return 'Hello, {0}!'.format(name)
This is advantageous, as now we can change the URLs of our application without needing to change the line where we reference that resource.
Why not just always use the name of the view function?
One question that might come up is the following: "Why do we need this extra layer?" Why map a path to an endpoint, then an endpoint to a view function? Why not just skip that middle step?
The reason is because it is more powerful this way. For example, Flask Blueprints allow you to split your application into various parts. I might have all of my admin-side resources in a blueprint called "admin", and all of my user-level resources in an endpoint called "user".
Blueprints allow you to separate these into namespaces. For example...
main.py:
from flask import Flask, Blueprint
from admin import admin
from user import user
app = Flask(__name__)
app.register_blueprint(admin, url_prefix='admin')
app.register_blueprint(user, url_prefix='user')
admin.py:
admin = Blueprint('admin', __name__)
#admin.route('/greeting')
def greeting():
return 'Hello, administrative user!'
user.py:
user = Blueprint('user', __name__)
#user.route('/greeting')
def greeting():
return 'Hello, lowly normal user!'
Note that in both blueprints, the '/greeting' route is a function called "greeting". If I wanted to refer to the admin "greeting" function, I couldn't just say "greeting" because there is also a user "greeting" function. Endpoints allow for a sort of namespacing by having you specify the name of the blueprint as part of the endpoint. So, I could do the following...
print url_for('admin.greeting') # Prints '/admin/greeting'
print url_for('user.greeting') # Prints '/user/greeting'
Endpoint is the name used to reverse-lookup the url rules with url_for and it defaults to the name of the view function.
Small example:
from flask import Flask, url_for
app = Flask(__name__)
# We can use url_for('foo_view') for reverse-lookups in templates or view functions
#app.route('/foo')
def foo_view():
pass
# We now specify the custom endpoint named 'bufar'. url_for('bar_view') will fail!
#app.route('/bar', endpoint='bufar')
def bar_view():
pass
with app.test_request_context('/'):
print url_for('foo_view')
print url_for('bufar')
# url_for('bar_view') will raise werkzeug.routing.BuildError
print url_for('bar_view')
If you have same class name and want to map with multiple routes, then specify the endpoint, so that framework will differentiate between two:
class ClassName(Resource):
def get(self):
if request.endpoint!='hello':
return {"data": "Hello"}
elif:
return {"data" : "World"}
api.add_resource(ClassName, '/rout1', endpoint = "world")
api.add_resource(ClassName, '/rout2', endpoint="hello")
#app.route('/') #Endpoint
def a_function(): #View function
return 'view'
Inside Flask, every endpoint with its request methods mapped to a view function. When you use app.route decorator you are actually adding a URL rule.
I am trying to setup variable route handling in a Flask application such as described in this answer: Dynamic Subdomain Handling in a Web App (Flask)
However, I want to be able to recognize certain subdomains BEFORE they are caught by the variable route so I can use the flask-restful api extension (Routing with RESTful).
For example, I have tried the following:
#app.route('/', subdomain="<user>", defaults={'path':''})
#app.route('/<path:path>', subdomain="<user>")
def user_profile(user,path):
pass
class Api(restful.Resource):
def get(self):
#Do Api things.
api.add_resource(Api, '/v1', subdomain="api")
When I test this, all of URLs go to the variable route handler and call user_prof(). I tried putting the api route first and the standard #app.route rule second and vice versa but there was no change.
Am I missing some other parameter or need to go deeper in Flask to make this happen?
Update:
The URL patterns I am trying to match are like this:
user1.mysite.com -> handled by user_profile()
user2.mysite.com -> handled by user_profile()
any_future_string.mysite.com -> handled by user_profile()
api.mysite.com/v1 -> handled by Api class
Other cases include:
www.mysite.com -> handled by index_display()
mysite.com -> handled by index_display()
#app.before_request
def before_request():
if 'api' == request.host[:-len(app.config['SERVER_NAME'])].rstrip('.'):
redirect(url_for('api'))
#app.route('/', defaults={'path': ''}, subdomain='api')
#app.route('/<path:path>', subdomain='api')
def api(path):
return "hello"
This should work. Add your api version to the path if needed or that could be processed by your API class.
To keep it simple, I redesigned the logic of my application into two distinct parts.
This way the Flask application only handles the API endpoint logic. The user profile logic is handled by another application. I can now add multiple Resources to the API application without worry about breaking the routing.