The documentation states that the preferred way to define a route is to include a trailing slash:
#app.route('/foo/', methods=['GET'])
def get_foo():
pass
This way, a client can GET /foo or GET /foo/ and receive the same result.
However, POSTed methods do not have the same behavior.
from flask import Flask
app = Flask(__name__)
#app.route('/foo/', methods=['POST'])
def post_foo():
return "bar"
app.run(port=5000)
Here, if you POST /foo, it will fail with method not allowed if you are not running in debug mode, or it will fail with the following notice if you are in debug mode:
A request was sent to this URL (http://localhost:5000/foo) but a redirect was issued automatically by the routing system to "http://localhost:5000/foo/". The URL was defined with a trailing slash so Flask will automatically redirect to the URL with the trailing slash if it was accessed without one. Make sure to directly send your POST-request to this URL since we can't make browsers or HTTP clients redirect with form data reliably or without user interaction
Moreover, it appears that you cannot even do this:
#app.route('/foo', methods=['POST'])
#app.route('/foo/', methods=['POST'])
def post_foo():
return "bar"
Or this:
#app.route('/foo', methods=['POST'])
def post_foo_no_slash():
return redirect(url_for('post_foo'), code=302)
#app.route('/foo/', methods=['POST'])
def post_foo():
return "bar"
Is there any way to get POST to work on both non-trailing and trailing slashes?
Please refer to this post:
Trailing slash triggers 404 in Flask path rule
You can disable strict slashes to support your needs
Globally:
app = Flask(__name__)
app.url_map.strict_slashes = False
... or per route
#app.route('/foo', methods=['POST'], strict_slashes=False)
def foo():
return 'foo'
You can also check this link. There is separate discussion on github on this one. https://github.com/pallets/flask/issues/1783
You can check request.path whether /foo/ or not then redirect it to where you want:
#app.before_request
def before_request():
if request.path == '/foo':
return redirect(url_for('foo'), code=123)
#app.route('/foo/', methods=['POST'])
def foo():
return 'foo'
$ http post localhost:5000/foo
127.0.0.1 - - [08/Mar/2017 13:06:48] "POST /foo HTTP/1.1" 123
Related
I'm trying to create a website with optional url sub-paths:
/user - Returns general information on users
/user/edit - Edits the user
I've tried setting:
config.add_route('user', '/user/{action}')
#view_defaults(route_name="user")
class UserViews():
# not sure what (if anything) to put in #view_config here...
def user_general(self):
return Response("General User Info"
#view_config(match_param="action=edit")
def edit(self):
return Response("Editing user")
However while this works for /user/edit, it returns a 404 for /user
It also fails in the same way if I set 2 explicit routes with a shared path - e.g.:
config.add_route('login', '/user')
config.add_route('edit_user', '/user/edit')
I've tried things like setting match_params="action=" but can't get it to work.
Any ideas on how this can be achieved?
user_general inherits the default route configuration of the class, which requires an {action} match param. When you do not supply that in the request, the route for that view will never match, returning a 404 not found response.
You need to add a decorator with the route_name argument to user_general to override the default route for the view.
#view_config(
route_name="user"
)
def user_general(self):
The following works for me as a complete example with some minor explicit naming conventions.
from wsgiref.simple_server import make_server
from pyramid.config import Configurator
from pyramid.response import Response
from pyramid.view import view_config, view_defaults
#view_defaults(route_name="user_action")
class UserViews():
def __init__(self, context, request):
self.request = request
self.context = context
#view_config(
route_name="user_get",
request_method="GET"
)
def get_user(request):
return Response("I got you, Babe!")
#view_config(
match_param="action=edit"
)
def edit(self):
return Response("Don't ever change, Babe!")
if __name__ == "__main__":
with Configurator() as config:
config.add_route("user_get", "/user")
config.add_route('user_action', '/user/{action}')
config.scan()
app = config.make_wsgi_app()
server = make_server("0.0.0.0", 6543, app)
server.serve_forever()
I'm trying to store JWT Tokens in cookies for a Flask application to restrict some endpoints. An endpoint, "/authorize" , is responsible for setting the cookies then redirect the page to the root endpoint, "/".
from flask import Flask, request, make_response, redirect
#app.route("/authorize", methods=["GET"])
def authorize():
token = request.args.get('token')
expires = request.args.get('expires')
# some code to validate the token
resp_output = make_response(redirect("/"))
resp_output.set_cookie("token", token, expires=expires)
return resp_output
#app.route("/", methods=["GET"])
def index():
token = request.cookies.get("token)
# do something with the token
However, when I tried to deploy this, I ran into some problems with the redirecting and therefore have to change redirect("/") to redirect("https://someaddress.com/)" where https://someaddress.com/ is the address of the flask application. Now when I try to retrieve the token cookies in the root endpoint, it returns None. I suspect it is because the redirection has turnt from an internal one to an external one.
Please help me find a workaround for this. Or if you think I should resolve the problems that lead to the change from internal to external redirection so I can go back to what works. (If anyone can point me to some resources explaining exactly how redirection, or more specifically Flask's redirection, works, I'd really appreciate it.)
Using url_for function from flask should work in your case, as it will look for the link within the app context:
from flask import Flask, request, make_response, redirect, url_for
#app.route("/authorize", methods=["GET"])
def authorize():
token = request.args.get('token')
expires = request.args.get('expires')
# some code to validate the token
resp_output = make_response(redirect(url_for('index')))
resp_output.set_cookie("token", token, expires=expires)
return resp_output
#app.route("/", methods=["GET"])
def index():
token = request.cookies.get("token)
# do something with the token
Btw, I would recommend you pass your authorization logic to a decorator, have a look on authorization decorators using flask.
In case this don't work in production, that can be some setting related to your reverse proxy - like nginx conf file. Let me know if it is the case
on Nginx file on sites-enabled folder etc/nginx/sites-enabled/<project-name>, comment or remove the following line:
proxy_set_header Host $host;
Hope it suits you well!
I'm trying to wrap my Flask server in a class, so as to better fit in the structure of the rest of my application.
I have the below code:
class HTTPServer(object):
def __init__(self):
self.app = Flask(__name__)
self.app.add_url_rule('/', 'index', self.hello_world, methods=['POST'])
self.app.run(port=5050, use_reloader=False)
def hello_world(self, data):
print "Hello, World: {}".format(data)
However, if I send a POST request to localhost:5050/index I get a 404 error.
The Flask log shows the following:
127.0.0.1 - - [30/Aug/2019 11:17:52] "POST /index HTTP/1.1" 404 -
The same happens if I change ['POST'] to ['GET'] in methods and send a GET request.
However, if I remove the methods parameter from add_url_rule() entirely, I can send GET requests and they are handled appropriately.
I didn't understand the endpoint parameter of add_url_rule. It isn't the endpoint as seen by the client, but rather an internal name for the endpoint. The correct method call is:
self.app.add_url_rule('/index', 'hello_world', self.hello_world, methods=['POST'])
I'm building the Flask app with React, I ended up having a problem with routing.
The backend is responsible to be an API, hence some routes look like:
#app.route('/api/v1/do-something/', methods=["GET"])
def do_something():
return something()
and the main route which leads to the React:
#app.route('/')
def index():
return render_template('index.html')
I'm using react-router in the React app, everything works fine, react-router takes me to /something and I get the rendered view, but when I refresh the page on /something then Flask app takes care of this call and I get Not Found error.
What is the best solution? I was thinking about redirecting all calls which are not calling /api/v1/... to / it's not ideal as I will get back the home page of my app, not rendered React view.
We used catch-all URLs for this.
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()
You can also go an extra mile and reuse the Flask routing system to match path to the same routes as client so you can embed the data client will need as JSON inside the HTML response.
Maybe as extension to the answers before. This solved the problem for me:
from flask import send_from_directory
#app.route('/', defaults={'path': ''})
#app.route('/<path:path>')
def serve(path):
path_dir = os.path.abspath("../build") #path react build
if path != "" and os.path.exists(os.path.join(path_dir, path)):
return send_from_directory(os.path.join(path_dir), path)
else:
return send_from_directory(os.path.join(path_dir),'index.html')
For some reason, the catch-all URLs did not work for me. I found that using the flask 404 handler results in the exact same thing. It sees the url and passes it down to react where your router will handle it.
#app.errorhandler(404)
def not_found(e):
return app.send_static_file('index.html')
Just to inform handle error 404 and render_template works perfectly for me.
#app.errorhandler(404)
def not_found(e):
return render_template("index.html")
I have to combine both catch-all and 404 handler for it to work properly. I am hosting a react-app in a subpath with its own redirection handler from react-router.
#app.route('/sub-path', defaults={'path': 'index.html'})
#app.route('/sub-path/<path:path>')
def index(path):
return send_from_directory('../react-dir/build', path)
#app.errorhandler(404)
def not_found(e):
return send_from_directory('../react-dir/build','index.html')
I created a blueprint with a 404 error handler. However, when I go to non-existent urls under the blueprint's prefix, the standard 404 page is shown rather than my custom one. How can I make the blueprint handle 404 errors correctly?
The following is a short app that demonstrates the problem. Navigating to http://localhost:5000/simple/asdf will not show the blueprint's error page.
#!/usr/local/bin/python
# coding: utf-8
from flask import *
from config import PORT, HOST, DEBUG
simplepage = Blueprint('simple', __name__, url_prefix='/simple')
#simplepage.route('/')
def simple_root():
return 'This simple page'
#simplepage.errorhandler(404)
def error_simple(err):
return 'This simple error 404', err
app = Flask(__name__)
app.config.from_pyfile('config.py')
app.register_blueprint(simplepage)
#app.route('/', methods=['GET'])
def api_get():
return render_template('index.html')
if __name__ == '__main__':
app.run(host=HOST,
port=PORT,
debug=DEBUG)
The documentation mentions that 404 error handlers will not behave as expected on blueprints. The app handles routing and raises a 404 before the request gets to the blueprint. The 404 handler will still activate for abort(404) because that is happening after routing at the blueprint level.
This is something that could possibly be fixed in Flask (there's an open issue about it). As a workaround, you can do your own error routing within the top-level 404 handler.
from flask import request, render_template
#app.errorhandler(404)
def handle_404(e):
path = request.path
# go through each blueprint to find the prefix that matches the path
# can't use request.blueprint since the routing didn't match anything
for bp_name, bp in app.blueprints.items():
if path.startswith(bp.url_prefix):
# get the 404 handler registered by the blueprint
handler = app.error_handler_spec.get(bp_name, {}).get(404)
if handler is not None:
# if a handler was found, return it's response
return handler(e)
# return a default response
return render_template('404.html'), 404