Redirect HTTP to HTTPS on Flask+Heroku - python

When I attempt to redirect incoming traffic to https I get an infinite redirect loop.
#app.route('/checkout/')
def checkout():
checkout = "https://myapp.herokuapp.com/checkout/"
if checkout != request.url:
print checkout, request.url
return redirect(checkout)
return render_template('checkout.html', key=keys['publishable_key'])
The request.url is never changed to prefix https. I want to use heroku's piggyback ssl to minimize cost.

1) Do "pip install flask-sslify"
(github is here: https://github.com/kennethreitz/flask-sslify)
2) Include the following lines:
from flask_sslify import SSLify
if 'DYNO' in os.environ: # only trigger SSLify if the app is running on Heroku
sslify = SSLify(app)

On Heroku, SSL (https) is terminated before it reaches your application, so you app never actually sees SSL traffic. To check whether a request was made with https, you instead have to inspect the x-forwarded-proto header. More info here: How to make python on Heroku https only?
UPDATE: For your use, you should just check request.url for "myapp.herokuapp.com/checkout/"; and verify that the header is "https"

I tried SSLify, url_for _scheme, and setting a PREFERRED_URL_SCHEME; however none worked out, at the release level at least.. (worked fine locally) Then I thought;
#app.before_request
def beforeRequest():
if not request.url.startswith('https'):
return redirect(request.url.replace('http', 'https', 1))
This is essentially another way to get it done without any configurations, or extensions.

I was able to repurpose the flask-sslify code for a single view. Just needed to check whether or not the request was being made with SSL and add proper headers to the response. https://github.com/kennethreitz/flask-sslify
#app.route('/checkout/')
def checkout():
checkout = "https://myapp.herokuapp.com/checkout/"
if request.headers.get('X-Forwarded-Proto', 'http') == 'https':
resp = make_response(render_template('checkout.html', key=keys['publishable_key']))
return set_hsts_header(resp)
return redirect(checkout, code=302)
def set_hsts_header(response):
"""Adds HSTS header to each response."""
response.headers.setdefault('Strict-Transport-Security', hsts_header)
return response
def hsts_header():
"""Returns the proper HSTS policy."""
hsts_policy = 'max-age={0}'.format(31536000) #year in seconds
if self.hsts_include_subdomains:
hsts_policy += '; includeSubDomains'
return hsts_policy

You just need to check the X-Forwarded-Proto header. If its false, redirect to the equivalent https url.
Here the code to enforce https for all calls on a flask app running on heroku:
#app.before_request
def enforceHttpsInHeroku():
if request.headers.get('X-Forwarded-Proto') == 'http':
url = request.url.replace('http://', 'https://', 1)
code = 301
return redirect(url, code=code)

You can do something like this:
#app.before_request
def before_request():
if 'DYNO' in os.environ: # Only runs when on heroku
if request.url.startswith('http://'):
url = request.url.replace('http://', 'https://', 1)
code = 301
return redirect(url, code=code)

On my answer to another question I have stated the upto date Flask recommendations. Use Talisman instead of SSLify.
For Flask use Talisman. Flask, Heroku and SSLify
documentations favor the use of Talisman over SSLify because the
later is no longer maintained.
From SSLify:
The extension is no longer maintained, prefer using Flask-Talisman as
it is encouraged by the Flask Security Guide.
Install via pip:
$ pip install flask-talisman
Instatiate the extension (example):
from flask import Flask
from flask_talisman import Talisman
app = Flask(__name__)
if 'DYNO' in os.environ:
Talisman(app)
Talisman enables CSP (Content Security Policy) by default only
allowing resources from the same domain to be loaded. If you want to
disable it and deal with the implications:
Talisman(app, content_security_policy=None)
If you don't want to disable it you have set the
content_security_policy argument to allow resources from external
domains, like CDNs, for instance. For that refer to the
documentation.

Related

Flask - Uwsgi - Not found error [duplicate]

I have a prefix that I want to add to every route. Right now I add a constant to the route at every definition. Is there a way to do this automatically?
PREFIX = "/abc/123"
#app.route(PREFIX + "/")
def index_page():
return "This is a website about burritos"
#app.route(PREFIX + "/about")
def about_page():
return "This is a website about burritos"
You can put your routes in a blueprint:
bp = Blueprint('burritos', __name__,
template_folder='templates')
#bp.route("/")
def index_page():
return "This is a website about burritos"
#bp.route("/about")
def about_page():
return "This is a website about burritos"
Then you register the blueprint with the application using a prefix:
app = Flask(__name__)
app.register_blueprint(bp, url_prefix='/abc/123')
The answer depends on how you are serving this application.
Sub-mounted inside of another WSGI container
Assuming that you are going to run this application inside of a WSGI container (mod_wsgi, uwsgi, gunicorn, etc); you need to actually mount, at that prefix the application as a sub-part of that WSGI container (anything that speaks WSGI will do) and to set your APPLICATION_ROOT config value to your prefix:
app.config["APPLICATION_ROOT"] = "/abc/123"
#app.route("/")
def index():
return "The URL for this page is {}".format(url_for("index"))
# Will return "The URL for this page is /abc/123/"
Setting the APPLICATION_ROOT config value simply limit Flask's session cookie to that URL prefix. Everything else will be automatically handled for you by Flask and Werkzeug's excellent WSGI handling capabilities.
An example of properly sub-mounting your app
If you are not sure what the first paragraph means, take a look at this example application with Flask mounted inside of it:
from flask import Flask, url_for
from werkzeug.serving import run_simple
from werkzeug.middleware.dispatcher import DispatcherMiddleware
app = Flask(__name__)
app.config['APPLICATION_ROOT'] = '/abc/123'
#app.route('/')
def index():
return 'The URL for this page is {}'.format(url_for('index'))
def simple(env, resp):
resp(b'200 OK', [(b'Content-Type', b'text/plain')])
return [b'Hello WSGI World']
app.wsgi_app = DispatcherMiddleware(simple, {'/abc/123': app.wsgi_app})
if __name__ == '__main__':
app.run('localhost', 5000)
Proxying requests to the app
If, on the other hand, you will be running your Flask application at the root of its WSGI container and proxying requests to it (for example, if it's being FastCGI'd to, or if nginx is proxy_pass-ing requests for a sub-endpoint to your stand-alone uwsgi / gevent server then you can either:
Use a Blueprint, as Miguel points out in his answer.
or use the DispatcherMiddleware from werkzeug (or the PrefixMiddleware from su27's answer) to sub-mount your application in the stand-alone WSGI server you're using. (See An example of properly sub-mounting your app above for the code to use).
You should note that the APPLICATION_ROOT is NOT for this purpose.
All you have to do is to write a middleware to make the following changes:
modify PATH_INFO to handle the prefixed url.
modify SCRIPT_NAME to generate the prefixed url.
Like this:
class PrefixMiddleware(object):
def __init__(self, app, prefix=''):
self.app = app
self.prefix = prefix
def __call__(self, environ, start_response):
if environ['PATH_INFO'].startswith(self.prefix):
environ['PATH_INFO'] = environ['PATH_INFO'][len(self.prefix):]
environ['SCRIPT_NAME'] = self.prefix
return self.app(environ, start_response)
else:
start_response('404', [('Content-Type', 'text/plain')])
return ["This url does not belong to the app.".encode()]
Wrap your app with the middleware, like this:
from flask import Flask, url_for
app = Flask(__name__)
app.debug = True
app.wsgi_app = PrefixMiddleware(app.wsgi_app, prefix='/foo')
#app.route('/bar')
def bar():
return "The URL for this page is {}".format(url_for('bar'))
if __name__ == '__main__':
app.run('0.0.0.0', 9010)
Visit http://localhost:9010/foo/bar,
You will get the right result: The URL for this page is /foo/bar
And don't forget to set the cookie domain if you need to.
This solution is given by Larivact's gist. The APPLICATION_ROOT is not for this job, although it looks like to be. It's really confusing.
This is more of a python answer than a Flask/werkzeug answer; but it's simple and works.
If, like me, you want your application settings (loaded from an .ini file) to also contain the prefix of your Flask application (thus, not to have the value set during deployment, but during runtime), you can opt for the following:
def prefix_route(route_function, prefix='', mask='{0}{1}'):
'''
Defines a new route function with a prefix.
The mask argument is a `format string` formatted with, in that order:
prefix, route
'''
def newroute(route, *args, **kwargs):
'''New function to prefix the route'''
return route_function(mask.format(prefix, route), *args, **kwargs)
return newroute
Arguably, this is somewhat hackish and relies on the fact that the Flask route function requires a route as a first positional argument.
You can use it like this:
app = Flask(__name__)
app.route = prefix_route(app.route, '/your_prefix')
NB: It is worth nothing that it is possible to use a variable in the prefix (for example by setting it to /<prefix>), and then process this prefix in the functions you decorate with your #app.route(...). If you do so, you obviously have to declare the prefix parameter in your decorated function(s). In addition, you might want to check the submitted prefix against some rules, and return a 404 if the check fails. In order to avoid a 404 custom re-implementation, please from werkzeug.exceptions import NotFound and then raise NotFound() if the check fails.
So, I believe that a valid answer to this is: the prefix should be configured in the actual server application that you use when development is completed. Apache, nginx, etc.
However, if you would like this to work during development while running the Flask app in debug, take a look at this gist.
Flask's DispatcherMiddleware to the rescue!
I'll copy the code here for posterity:
"Serve a Flask app on a sub-url during localhost development."
from flask import Flask
APPLICATION_ROOT = '/spam'
app = Flask(__name__)
app.config.from_object(__name__) # I think this adds APPLICATION_ROOT
# to the config - I'm not exactly sure how!
# alternatively:
# app.config['APPLICATION_ROOT'] = APPLICATION_ROOT
#app.route('/')
def index():
return 'Hello, world!'
if __name__ == '__main__':
# Relevant documents:
# http://werkzeug.pocoo.org/docs/middlewares/
# http://flask.pocoo.org/docs/patterns/appdispatch/
from werkzeug.serving import run_simple
from werkzeug.wsgi import DispatcherMiddleware
app.config['DEBUG'] = True
# Load a dummy app at the root URL to give 404 errors.
# Serve app at APPLICATION_ROOT for localhost development.
application = DispatcherMiddleware(Flask('dummy_app'), {
app.config['APPLICATION_ROOT']: app,
})
run_simple('localhost', 5000, application, use_reloader=True)
Now, when running the above code as a standalone Flask app, http://localhost:5000/spam/ will display Hello, world!.
In a comment on another answer, I expressed that I wished to do something like this:
from flask import Flask, Blueprint
# Let's pretend module_blueprint defines a route, '/record/<id>/'
from some_submodule.flask import module_blueprint
app = Flask(__name__)
app.config['APPLICATION_ROOT'] = '/api'
app.register_blueprint(module_blueprint, url_prefix='/some_submodule')
app.run()
# I now would like to be able to get to my route via this url:
# http://host:8080/api/some_submodule/record/1/
Applying DispatcherMiddleware to my contrived example:
from flask import Flask, Blueprint
from flask.serving import run_simple
from flask.wsgi import DispatcherMiddleware
# Let's pretend module_blueprint defines a route, '/record/<id>/'
from some_submodule.flask import module_blueprint
app = Flask(__name__)
app.config['APPLICATION_ROOT'] = '/api'
app.register_blueprint(module_blueprint, url_prefix='/some_submodule')
application = DispatcherMiddleware(Flask('dummy_app'), {
app.config['APPLICATION_ROOT']: app
})
run_simple('localhost', 5000, application, use_reloader=True)
# Now, this url works!
# http://host:8080/api/some_submodule/record/1/
Another completely different way is with mountpoints in uwsgi.
From the doc about Hosting multiple apps in the same process (permalink).
In your uwsgi.ini you add
[uwsgi]
mount = /foo=main.py
manage-script-name = true
# also stuff which is not relevant for this, but included for completeness sake:
module = main
callable = app
socket = /tmp/uwsgi.sock
If you don't call your file main.py, you need to change both the mount and the module
Your main.py could look like this:
from flask import Flask, url_for
app = Flask(__name__)
#app.route('/bar')
def bar():
return "The URL for this page is {}".format(url_for('bar'))
# end def
And a nginx config (again for completeness):
server {
listen 80;
server_name example.com
location /foo {
include uwsgi_params;
uwsgi_pass unix:///temp/uwsgi.sock;
}
}
Now calling example.com/foo/bar will display /foo/bar as returned by flask's url_for('bar'), as it adapts automatically. That way your links will work without prefix problems.
from flask import Flask
app = Flask(__name__)
app.register_blueprint(bp, url_prefix='/abc/123')
if __name__ == "__main__":
app.run(debug='True', port=4444)
bp = Blueprint('burritos', __name__,
template_folder='templates')
#bp.route('/')
def test():
return "success"
I needed similar so called "context-root". I did it in conf file under /etc/httpd/conf.d/ using WSGIScriptAlias :
myapp.conf:
<VirtualHost *:80>
WSGIScriptAlias /myapp /home/<myid>/myapp/wsgi.py
<Directory /home/<myid>/myapp>
Order deny,allow
Allow from all
</Directory>
</VirtualHost>
So now I can access my app as : http://localhost:5000/myapp
See the guide - http://modwsgi.readthedocs.io/en/develop/user-guides/quick-configuration-guide.html
My solution where flask and PHP apps coexist
nginx and PHP5.6
KEEP Flask in root and PHP in subdirectories
sudo vi /etc/php/5.6/fpm/php.ini
Add 1 line
cgi.fix_pathinfo=0
sudo vi /etc/php/5.6/fpm/pool.d/www.conf
listen = /run/php/php5.6-fpm.sock
uwsgi
sudo vi /etc/nginx/sites-available/default
USE NESTED LOCATIONS for PHP and let FLASK remain in root
server {
listen 80 default_server;
listen [::]:80 default_server;
# SSL configuration
#
# listen 443 ssl default_server;
# listen [::]:443 ssl default_server;
#
# Note: You should disable gzip for SSL traffic.
# See: https://bugs.debian.org/773332
#
# Read up on ssl_ciphers to ensure a secure configuration.
# See: https://bugs.debian.org/765782
#
# Self signed certs generated by the ssl-cert package
# Don't use them in a production server!
#
# include snippets/snakeoil.conf;
root /var/www/html;
# Add index.php to the list if you are using PHP
index index.html index.htm index.php index.nginx-debian.html;
server_name _;
# Serve a static file (ex. favico) outside static dir.
location = /favico.ico {
root /var/www/html/favico.ico;
}
# Proxying connections to application servers
location / {
include uwsgi_params;
uwsgi_pass 127.0.0.1:5000;
}
location /pcdp {
location ~* \.php$ {
try_files $uri =404;
fastcgi_split_path_info ^(.+\.php)(/.+)$;
fastcgi_pass unix:/var/run/php/php5.6-fpm.sock;
fastcgi_index index.php;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
include fastcgi_params;
}
}
location /phpmyadmin {
location ~* \.php$ {
try_files $uri =404;
fastcgi_split_path_info ^(.+\.php)(/.+)$;
fastcgi_pass unix:/var/run/php/php5.6-fpm.sock;
fastcgi_index index.php;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
include fastcgi_params;
}
}
# pass the PHP scripts to FastCGI server listening on 127.0.0.1:9000
#
#location ~ \.php$ {
# include snippets/fastcgi-php.conf;
#
# # With php7.0-cgi alone:
# fastcgi_pass 127.0.0.1:9000;
# # With php7.0-fpm:
# fastcgi_pass unix:/run/php/php7.0-fpm.sock;
#}
# deny access to .htaccess files, if Apache's document root
# concurs with nginx's one
#
#location ~ /\.ht {
# deny all;
#}
}
READ carefully
https://www.digitalocean.com/community/tutorials/understanding-nginx-server-and-location-block-selection-algorithms
We need to understand location matching
(none): If no modifiers are present, the location is interpreted as a prefix match. This means that the location given will be matched against the beginning of the request URI to determine a match.
=: If an equal sign is used, this block will be considered a match if the request URI exactly matches the location given.
~: If a tilde modifier is present, this location will be interpreted as a case-sensitive regular expression match.
~*: If a tilde and asterisk modifier is used, the location block will be interpreted as a case-insensitive regular expression match.
^~: If a carat and tilde modifier is present, and if this block is selected as the best non-regular expression match, regular expression matching will not take place.
Order is important, from nginx's "location" description:
To find location matching a given request, nginx first checks locations defined using the prefix strings (prefix locations). Among them, the location with the longest matching prefix is selected and remembered. Then regular expressions are checked, in the order of their appearance in the configuration file. The search of regular expressions terminates on the first match, and the corresponding configuration is used. If no match with a regular expression is found then the configuration of the prefix location remembered earlier is used.
It means:
First =. ("longest matching prefix" match)
Then implicit ones. ("longest matching prefix" match)
Then regex. (first match)
For people still struggling with this, the first example does work, but the full example is here if you have a Flask app that is not under your control:
from os import getenv
from werkzeug.middleware.dispatcher import DispatcherMiddleware
from werkzeug.serving import run_simple
from custom_app import app
application = DispatcherMiddleware(
app, {getenv("REBROW_BASEURL", "/rebrow"): app}
)
if __name__ == "__main__":
run_simple(
"0.0.0.0",
int(getenv("REBROW_PORT", "5001")),
application,
use_debugger=False,
threaded=True,
)
In flask blueprint, we can use -
app = Flask(__name__)
app.config['APPLICATION_ROOT'] = '/prefix-text'
Anyone looking to do in flask-restful can make use of -
doc link
app = Flask(__name__)
api = Api(app, prefix='/pefix-text')
Now, all your routes will be prefixed with /prefix-text. Just make sure you use url_for('link') in places where you might have simply used a /link.
I think su27's answer is right. And I am using gevent, here is my code and it works fine:
from gevent import pywsgi
# your flask code ...
# app = Flask(__name__)
if __name__ == "__main__":
class MyHandler(pywsgi.WSGIHandler):
def get_environ(self):
prefix = "/your_prefix"
env = super().get_environ()
if env['PATH_INFO'].startswith(prefix):
env['PATH_INFO'] = env['PATH_INFO'][len(prefix):]
env['SCRIPT_NAME'] = prefix
return env
server = pywsgi.WSGIServer(('', 8080), app, handler_class=MyHandler)
server.serve_forever()
From all the answers I have seen above, they are either too simplistic or over complicating.
That said, I like to accomplish it using nested blueprints:
from .blueprints import blueprint1, blueprint2, blueprint3, etc
app = Flask(__name__)
url_prefix = "/abc/123"
parent = Blueprint('index', __name__, url_prefix=url_prefix)
index.register_blueprint(blueprint1)
index.register_blueprint(blueprint2)
index.register_blueprint(blueprint3)
app.register_blueprint(index)
This way, you basically link your child blueprints to a parent blueprint, where you define the prefix. This is documented here.
With your example, you would simply rewrite it to:
blueprint1 = Blueprint('blueprint1', __name__)
#blueprint1.route("/")
def index_page():
return "Index page"
#blueprint1.route("/about")
def about_page():
return "About page"
If your purpose is to add the prefix in some way,
take a look at the answer https://stackoverflow.com/a/73883005/553095
and https://github.com/mskimm/prefixed-superset
If you want to handle the prefix when using Nginx as a reverse proxy. Werkzeug's ProxyFix middleware will be a simpler solution:
from werkzeug.middleware.proxy_fix import ProxyFix
from flask import Flask
app = Flask(__name__)
app.wsgi_app = ProxyFix(
app.wsgi_app, x_for=1, x_proto=1, x_host=1, x_prefix=1
)
Werkzeug will read the X-Forwarded-Prefix header and set it to SCRIPT_NAME. So be sure to set the X-Forwarded-Prefix header in Nginx config:
server {
listen 80;
server_name _;
location /api {
proxy_pass http://127.0.0.1:5000/;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header X-Forwarded-Host $host;
proxy_set_header X-Forwarded-Prefix /api;
}
}
See more details at https://stackoverflow.com/a/75123044/5511849

python flask redirect to https from http

I have a website build using python3.4 and flask...I have generated my own self-signed certificate and I am currently testing my website through localhost.
I am using the python ssl module along with this flask extension: https://github.com/kennethreitz/flask-sslify
context = ('my-cert.pem', 'my-key.pem')
app = Flask(__name__)
sslify = SSLify(app)
...
if __name__ == '__main__':
app.debug = False
app.run(
host="127.0.0.1",
port=int("5000"),
ssl_context=context
)
This does not seem to be working however. I took a look in the sslify source code and this line does not seem to be working
def init_app(self, app):
"""Configures the configured Flask app to enforce SSL."""
app.before_request(self.redirect_to_ssl)
app.after_request(self.set_hsts_header)
Specifically the function call to redirect_to_ssl (I added my own print statement under the redirect_to_ssl function and my statement was never printed)
def redirect_to_ssl(self):
print("THIS IS WORKING")
"""Redirect incoming requests to HTTPS."""
Should we redirect?
criteria = [
request.is_secure,
current_app.debug,
request.headers.get('X-Forwarded-Proto', 'http') == 'https'
]
if not any(criteria) and not self.skip:
if request.url.startswith('http://'):
url = request.url.replace('http://', 'https://', 1)
code = 302
if self.permanent:
code = 301
r = redirect(url, code=code)
return r
I am pretty new to python. Any ideas?
To me, it appears you're making it more complicated than it needs to be. Here is the code I use in my views.py script to force user to HTTPS connections:
#app.before_request
def before_request():
if not request.is_secure:
url = request.url.replace('http://', 'https://', 1)
code = 301
return redirect(url, code=code)
According with the docs, after pip install Flask-SSLify you only need to insert the following code:
from flask import Flask
from flask_sslify import SSLify
app = Flask(__name__)
sslify = SSLify(app)
I have done it and it works very well. Am I missing something in the discussion ?
The Flask Security Guide recommends using Flask-Talisman.
$ pip install flask-talisman
Usage example:
from flask import Flask
from flask_talisman import Talisman
app = Flask(__name__)
Talisman(app)
It forces HTTPS by default (from the README):
force_https, default True, forces all non-debug connects to https.
Personally, I got some errors relating to CSP (Content Security Policy) which I disabled with:
Talisman(app, content_security_policy=None)
But use this at your own risk :)
Thanks to answer from Kelly Keller-Heikkila and comment by jaysqrd I ended up doing this in my Flask app:
from flask import request, redirect
...
#app.before_request
def before_request():
if app.env == "development":
return
if request.is_secure:
return
url = request.url.replace("http://", "https://", 1)
code = 301
return redirect(url, code=code)
I tried the flask_sslify solution suggested by Rodolfo Alvarez but ran into this issue and went with the above solution instead.
If the app is running in development mode or the request is already on https there's no need to redirect.
Here is a flask solution if you are on aws and behind a load balancer. Place it in your views.py
#app.before_request
def before_request():
scheme = request.headers.get('X-Forwarded-Proto')
if scheme and scheme == 'http' and request.url.startswith('http://'):
url = request.url.replace('http://', 'https://', 1)
code = 301
return redirect(url, code=code)
The standard solution is to wrap the request with an enforce_ssl decorator that after checking some flags in the app configuration (flags you can set depending on your debugging needs) modifies the request's url with request.url.
As it is written here.
You can modify the code to make it working with before_request as suggested by #kelly-keller-heikkila
I use a simple extra app that runs on port 80 and redirect people to https:
from flask import Flask,redirect
app = Flask(__name__)
#app.route('/')
def hello():
return redirect("https://example.com", code=302)
if __name__ == '__main__':
app.run(host='0.0.0.0', port=80)
An alternative to the other answers that I've been able to use with great success:
from http import HTTPStatus
from typing import Optional
from flask import Response, redirect, request, url_for
def https_redirect() -> Optional[Response]:
if request.scheme == 'http':
return redirect(url_for(request.endpoint,
_scheme='https',
_external=True),
HTTPStatus.PERMANENT_REDIRECT)
# ..
if app.env == 'production':
app.before_request(https_redirect)
# ..
On app engine flex, add:
from werkzeug.middleware.proxy_fix import ProxyFix
def create_app(config=None):
app = Flask(__name__)
app.wsgi_app = ProxyFix(app.wsgi_app)
In addition to the solution of:
#app.before_request
def before_request():
if not request.is_secure:
url = request.url.replace('http://', 'https://', 1)
code = 301
return redirect(url, code=code)
Otherwise it'll cause infinite redirects since SSL is unwrapped behind the proxy but is noted in the headers.
I ran into the same solution running a Flask application in AWS Elastic Beanstalk behind a load balancer. The following AWS docs provided two steps to configure the environment for http redirects: https://docs.aws.amazon.com/elasticbeanstalk/latest/dg/configuring-https-httpredirect.html Following both steps fixed my issue.
One thing to note is that you'll have to create the .ebextenions folder at the root level of your application source bundle and add the config file to that .ebextensions folder. The readme here: https://github.com/awsdocs/elastic-beanstalk-samples explains this in a bit more detail.
For some reason it seems, requests from a Private AWS API Gateway with a VPC endpoint don't include the "X-Forwarded-Proto" header. This can break some of the other solutions (either it doesn't work or it continuously redirects to the same url). The following middleware forces https on most flask generated internal redirects:
class ForceHttpsRedirects:
def __init__(self, app):
self.app = app
def __call__(self, environ, start_response):
environ["wsgi.url_scheme"] = "https"
return self.app(environ, start_response)
# Usage
app = flask.Flask(__name__)
app.wsgi_app = ForceHttpsRedirects(app.wsgi_app) # Add middleware to force all redirects to https
Use:
app.run(port="443")
All modern browsers automatically use HTTPS when the port is 443 or 8443.
I'm using cloud foundry python app which is behind a load balancer (like https://stackoverflow.com/users/5270172/kelly-keller-heikkila said) .
This resolution helped me by adding (_external and _Scheme to the url_for function). https://github.com/pallets/flask/issues/773
I had the same issue and mine is a brute-force solution, but it works.
Heroku in the past suggested flask_sslify, which is not maintained anymore. Nowadays the proper way in Flask should be flask-talisman, but I tried it and it has bad interactions with boostrap templates.
I tried the anulaibar solution but it did not always worked for me.
The following is what I came up with:
#app.before_request
def before_request():
# If the request is sicure it should already be https, so no need to redirect
if not request.is_secure:
currentUrl = request.url
if currentUrl.startswith('http://'):
# http://example.com -> https://example.com
# http://www.example.com -> https://www.example.com
redirectUrl = currentUrl.replace('http://', 'https://', 1)
elif currentUrl.startswith('www'):
# Here we redirect the case in which the user access the site without typing any http or https
# www.example.com -> https://www.example.com
redirectUrl = currentUrl.replace('www', 'https://www', 1)
else:
# I do not now when this may happen, just for safety
redirectUrl = 'https://www.example.com'
code = 301
return redirect(redirectUrl, code=code)
I have the domain registered in godaddy which is also redirecting to https://www.example.com.
In my case Flask app is sitting behind AWS API Gateway and solutions with #app.before_request were giving me permanent redirects.
The following simple solution finally worked:
#app.after_request
def adjust_response(response):
....
if response.location:
if app.env != "development":
response.location = response.location.replace("http://", "https://", 1)
return response

Flask RESTful cross-domain issue with Angular: PUT, OPTIONS methods

I've developed a small write-only REST api with Flask Restful that accepts PUT request from a handful of clients that can potentially have changing IP addresses. My clients are embedded Chromium clients running an AngularJS front-end; they authenticate with my API with a simple magic key -- it's sufficient for my very limited scale.
I'm testing deploying my API now and I notice that the Angular clients are attempting to send an OPTIONS http methods to my Flask service. My API meanwhile is replying with a 404 (since I didn't write an OPTIONS handler yet, only a PUT handler). It seems that when sending cross-domain requests that are not POST or GET, Angular will send a pre-flight OPTIONS method at the server to make sure the cross-domain request is accepted before it sends the actual request. Is that right?
Anyway, how do I allow all cross-domain PUT requests to Flask Restful API? I've used cross-domaion decorators with a (non-restful) Flask instance before, but do I need to write an OPTIONS handler as well into my API?
With the Flask-CORS module, you can do cross-domain requests without changing your code.
from flask.ext.cors import CORS
app = Flask(__name__)
cors = CORS(app, resources={r"/api/*": {"origins": "*"}})
https://pypi.python.org/pypi/Flask-Cors
https://github.com/corydolphin/flask-cors
Update
As Eric suggested, the flask.ext.cors module is now deprecated, you should rather use the following code:
from flask_cors import CORS
app = Flask(__name__)
cors = CORS(app, resources={r"/api/*": {"origins": "*"}})
You can use the after_request hook:
#app.after_request
def after_request(response):
response.headers.add('Access-Control-Allow-Origin', '*')
response.headers.add('Access-Control-Allow-Headers', 'Content-Type,Authorization')
response.headers.add('Access-Control-Allow-Methods', 'GET,PUT,POST,DELETE')
return response
I resolved the issue by rewriting my Flask backend to answer with an Access-Control-Allow-Origin header in my PUT response. Furthermore, I created an OPTIONS handler in my Flask app to answer the options method by following what I read in the http RFC.
The return on the PUT method looks like this:
return restful.request.form, 201, {'Access-Control-Allow-Origin': '*'}
My OPTIONS method handler looks like this:
def options (self):
return {'Allow' : 'PUT' }, 200, \
{ 'Access-Control-Allow-Origin': '*', \
'Access-Control-Allow-Methods' : 'PUT,GET' }
#tbicr is right: Flask DOES answer the OPTIONS method automatically for you. However, in my case it wasn't transmitting the Access-Control-Allow-Origin header with that answer, so my browser was getting a reply from the api that seemed to imply that cross-domain requests were not permitted. I overloaded the options request in my case and added the ACAO header, and the browser seemed to be satisfied with that, and followed up OPTIONS with a PUT that also worked.
How about this workaround:
from flask import Flask
from flask.ext import restful
from flask.ext.restful import Api
from flask.ext.sqlalchemy import SQLAlchemy
app = Flask(__name__)
app.config.from_object('config')
#flask-sqlalchemy
db = SQLAlchemy(app)
#flask-restful
api = restful.Api(app)
#app.after_request
def after_request(response):
response.headers.add('Access-Control-Allow-Origin', '*')
response.headers.add('Access-Control-Allow-Headers', 'Content-Type,Authorization')
response.headers.add('Access-Control-Allow-Methods', 'GET,PUT,POST,DELETE')
return response
import views
I took this from this tutorial. Works very good. actually, i think this is the best approach I've seen so far.
Returning {'Access-Control-Allow-Origin': '*'} on each endpoint, doesn't seems to be efficient since you have to add it on every endpoint. a bit anoying..., at least for me.
I tried #cors.crossdomain(origin='*') but, looks like it only works with GET request.
You're right, OPTIONS method called every time before real request in browser. OPTIONS response have allowed methods and headers. Flask automatically process OPTIONS requests.
To get access for cross domain request you API must have Access-Control-Allow-Origin header. It can contain specific domains, but if you want allow requests from any domains you can set it to Access-Control-Allow-Origin: *.
To set up CORS for flask you can look at one extension code or try use this extension: https://github.com/wcdolphin/flask-cors/blob/master/flask_cors.py.
To set up CORS for flask-restful look this pull requests: https://github.com/twilio/flask-restful/pull/122 and https://github.com/twilio/flask-restful/pull/131. But looks like flask-restful does not support it by default yet.
Just an update to this comment. Flask CORS is the way to go, but the flask.ext.cors is deprecated.
use:
from flask_cors import CORS
To allow remote CORS requests on your web service api, you can simply initialize your flask restful API like this:
from flask import Flask
from flask_restful import reqparse, abort, Api, Resource
from flask_cors import CORS
app = Flask(__name__)
cors = CORS(app, resources={r"*": {"origins": "*"}})
api = Api(app)
This adds the CORS header to your api instance and allows a CORS request on every path from every origin.
I like to use a decoration to solve.
def cross_origin(origin="*"):
def cross_origin(func):
#functools.wraps(func)
def _decoration(*args, **kwargs):
ret = func(*args, **kwargs)
_cross_origin_header = {"Access-Control-Allow-Origin": origin,
"Access-Control-Allow-Headers":
"Origin, X-Requested-With, Content-Type, Accept"}
if isinstance(ret, tuple):
if len(ret) == 2 and isinstance(ret[0], dict) and isinstance(ret[1], int):
# this is for handle response like: ```{'status': 1, "data":"ok"}, 200```
return ret[0], ret[1], _cross_origin_header
elif isinstance(ret, basestring):
response = make_response(ret)
response.headers["Access-Control-Allow-Origin"] = origin
response.headers["Access-Control-Allow-Headers"] = "Origin, X-Requested-With, Content-Type, Accept"
return response
elif isinstance(ret, Response):
ret.headers["Access-Control-Allow-Origin"] = origin
ret.headers["Access-Control-Allow-Headers"] = "Origin, X-Requested-With, Content-Type, Accept"
return ret
else:
raise ValueError("Cannot handle cross origin, because the return value is not matched!")
return ret
return _decoration
return cross_origin
And then, Use decoration in your restful api.
class ExampleRestfulApi(Resource)
#cross_origin()
def get(self):
# allow all cross domain access
pass
#cross_origin(origin="192.168.1.100")
def post(self):
# allow 192.168.1.100 access
pass
I was facing the multiple types of CORS issue while connecting to my flask rest api from angular and tried almost all the approaches.
If you want to give access to all the websites without any restriction you can add below code in you app.py script :
from flask_cors import CORS , cross_origin
cors = CORS(app, resources={r"/*": {"origins": "*"}})
this will work, but its recommended to have some security which you can always edit in origin

GAE app raising DeadlineExceededError locally and DownloadError deployed

This is very weird. The title says most of it, my code should say the rest. Here's my main.py file:
from google.appengine.api import urlfetch
import webapp2
import jinja2
import json
import os
jinja_environment = jinja2.Environment(
loader=jinja2.FileSystemLoader(os.path.dirname(__file__)))
class MainPage(webapp2.RequestHandler):
def get(self):
response = urlfetch.fetch("http://localhost:8080/api/helloworld?name=totty", method=urlfetch.GET)
if response.status_code == 200:
result = json.loads(response.content)
template_values = {'response': result['msg']}
template = jinja_environment.get_template('index.html')
self.response.out.write(template.render(template_values))
app = webapp2.WSGIApplication(
[('/', MainPage)],
debug=True)
Here's my api.py file:
import webapp2
import json
class HelloWorld(webapp2.RequestHandler):
def get(self):
name = self.request.get('name') or 'world'
msg = "Hello {}!".format(name)
payload = json.dumps({'msg': msg})
# payload = json.dumps({'dir': str(dir(self.request)), 'body': str(self.request.body), 'name': str(self.request.arguments())})
self.response.headers['Content-Type'] = 'application/json'
self.response.write(payload)
app = webapp2.WSGIApplication(
[('/api/helloworld', HelloWorld)],
debug=True)
And in case my app.yaml file would help:
application: stacksort
version: 1
runtime: python27
api_version: 1
threadsafe: true
handlers:
- url: /api/.*
script: api.app
- url: /.*
script: main.app
libraries:
- name: webapp2
version: latest
- name: jinja2
version: latest
Nothing changes even if I add deadline=30 to my urlfetch call. I tested the API using httpie and JQuery and it works perfectly fine and returns in under 5 seconds.
I looked at the other questions, but I'm still stumbling into the dark. Help, tips or refactoring would be appreciated.
I plan to add calls to the StackEchange Search API, so I suspect the problem might come in that time also. If there's a better way to do this, please tell. Thanks.
You are trying to fetch a URL to and from your application which is strongly discouraged on Google App Engine.
Locally you won't be able to call the development server because it serves only one request at a time. Multi-threading is not observed.
Note: the new experimental development server is now able to handle multiple requests at a time.
Multithreaded serving for better performance for complex applications and more correct semantics e.g. accessing your own application through urlfetch no longer deadlocks.
On production environment, GAE prevents the fetch service to call the same application.
To prevent an app from causing an endless recursion of requests, a request handler is not allowed to fetch its own URL. It is still possible to cause an endless recursion with other means, so exercise caution if your app can be made to fetch requests for URLs supplied by the user.
While upgrading the SDK, I noticed this addition to the DevServer page:
Note: dev_appserver.py can only serve one request at a time. If your
application makes URL fetch requests to itself while processing a
request, these requests will fail when using the development web
server. (They will not fail when running on App Engine.) To test such
requests, you can run a second instance of dev_appserver.py on a
different port, then code your application to use the other server
when making requests to itself.
So I guess that solves my problem (or at least gives a satisfactory explanation for it).

Creating a dynamic redirect by using any of Flask, Pyramid, or Bottle?

I want to create a webapp that dynamically redirects to a URL, based on address that user typed. When a user visit my site by a address like this:
http://mydomain1.com/a1b2c3d4
I want redirect this user to URL:
http://mydomain2.com/register.php?id=a1b2c3d4&from=mydomain1.com
Yay, I love a good fight!
from pyramid.config import Configurator
from pyramid.httpexceptions import HTTPFound
from paste.httpserver import serve
config = Configurator()
config.add_route('redirect', '/{arg}')
def redirect_view(request):
dst = 'http://mydomain2.com/register.php?id={id}&from={host}'
args = {
'id': request.matchdict['arg'],
'host': request.host,
}
return HTTPFound(dst.format(**args))
config.add_view(redirect_view, route_name='redirect')
serve(config.make_wsgi_app(), host='0.0.0.0', port=80)
Here goes my attempt, I'm almost newbie in flask, so it should have room to improve
from flask import Flask, redirect, request
app = Flask(__name__)
host = 'domain2.org'
#app.route('/<path>')
def redirection(path):
return redirect('http://'+host+'/register.php?id='+path+'&from='+request.host)
if __name__ == '__main__':
app.run()
Edited to add the host to the from parameter
My solution was to use a Werkzeug rule using the path type :
host = 'domain2.org'
#app.route('/<path:path>')
def redirection(path):
return redirect('http://%s/%s' % (host, path), code=301)
This can be useful if you move a site and want another site instead with redirection on others pages.
There's a pyramid_rewrite extension (https://pypi.python.org/pypi/pyramid_rewrite/) that looks unmaintained, but seems to work. I had a use case it didn't handle, though: using Configure.include() with the route_prefix parameter.
It occurred to me that the usual approach is to do URL rewrites in the server, and I was using a WSGI server from the Python standard library. How hard could it be?
Make a custom request handler class:
from wsgiref.simple_server import make_server, WSGIRequestHandler
class MyReqHandler(WSGIRequestHandler):
def get_environ(self):
env = WSGIRequestHandler.get_environ(self)
if env['PATH_INFO'].startswith('/foo'):
env['PATH_INFO'] = env['PATH_INFO'].replace('foo', 'bar', 1)
return env
Pass it to make_server() when creating your server:
srvr = make_server('0.0.0.0', 6543, app, handler_class=MyReqHandler)
It works!
Straight-up substitution is all I needed for the problem at hand. Extending it to use regular expressions and exposing it via a nice API would be pretty straightforward.
I have another solution, that is straight-up pyramid, so it will work with some other wsgi server:
from pyramid.events import NewRequest, subscriber
#subscriber(NewRequest)
def mysubscriber(event):
req = event.request
if req.path_info.startswith('/~cfuller'):
req.path_info = req.path_info.replace('foo', 'bar', 1)
That's the declarative way, and it requires a config.scan(). Imperitively, you'd do something like
config.add_subscriber(mysubscriber, NewRequest)
See http://docs.pylonsproject.org/projects/pyramid/en/1.5-branch/narr/events.html for the skinny on events.

Categories