flask server with ssl_context freezes if it receives http request
Asked Answered
R

2

8

I'm trying to create a simple flask server that redirects any http requests to https. I've created a certificate and key file and registered a before_request hook to see if the request is secure and redirect appropriately, following advise this SO answer.

The flask server responds to https requests as expected. However, when I send an http request, the before_request hook never gets called and ther server hangs forever. If I send the http request from the browser, I see an "ERR_EMPTY_RESPONSE". The server doesn't even respond to https requests afterwards. No logs are printed either.

Running the app with gunicorn didn't help either. The only difference was that gunicorn is able to detect that the worker is frozen and eventually kills and replaces it. I've also tried using flask-talisman, with the same results.

Below is the code I'm running

### server.py
from flask import Flask, request, redirect


def verify_https():
    if not request.is_secure:
        url = request.url.replace("http://", "https://", 1)
        return redirect(url, 301)


def create_flask_app():
    app = Flask(__name__)
    app.before_request(verify_https)
    app.add_url_rule('/', 'root', lambda: "Hello World")
    return app


if __name__ == '__main__':
    app = create_flask_app()
    app.run(
        host="0.0.0.0",
        port=5000,
        ssl_context=('server.crt', 'server.key')
    )

Running it with either python3.8 server.py or gunicorn --keyfile 'server.key' --certfile 'server.crt' --bind '0.0.0.0:5000' 'server:create_flask_app()' and opening a browser window to localhost:5000 causes the server to hang.

Republicanize answered 30/7, 2021 at 3:47 Comment(5)
I have the same issue but with all https requests and only with Chrome. I get the security warning and after that the flask development server does not respond anymore. Does not happen with Firefox. Did you ever find a solution for your problem? It might give me a hint with mine.Trusting
@AhmedFasih I can't reproduce this.Hauteur
Reproduction steps: (1) create keys openssl req -x509 -nodes -days 365 -newkey rsa:2048 -keyout server.key -out server.crt, (2) run the script in the question, python server.py, (3) observe that both browser (Chrome) and curl -k https://localhost:5000 both work, (4) open localhost:5000 in browser (Chrome), and observe "This page isn't working". Now if you return to step (3) and try the https URL in curl or browser, there's no response and the server has hung.Appropriate
@Hauteur but do note, I tried the above reproduction steps in Firefox and couldn't kill the Flask server by requesting the http URL. It only happens with Chrome hmm. If you're able to test this with Chrome, can you check my reproduction steps? With python --version → Python 3.8.6 and python -c 'import flask;print(flask.__version__)' → 2.1.2.Appropriate
I don't think you can make a port receiving both http and https. It's normal to hang if your service expects TLS handshake and the client won't.Marder
E
3

Talking about freezes, its not. Flask and gunicorn can serve only one variant of connection. So it's not freezing because your browser canceled the request and is idling.

I think it is better to use a faster web server, for example, Nginx, if you want to change HTTP to HTTPS. I would recommend it to you.

But it's possible to trigger your verify_https function if you run multiple instances of gunicorn at the same time.
I took your example, generated a certificate, and then run this script in my console (it contains a background job and can be runned in twoo separate ter)

gunicorn --bind '0.0.0.0:80' 'server:create_flask_app()' & gunicorn --certfile server.crt --keyfile server.key --bind '0.0.0.0:443' 'server:create_flask_app()'

now chrome goes to the secure page as expected.

Educationist answered 17/10, 2022 at 20:37 Comment(2)
I don't think that's exactly correct. For example, if I start the server with either gunicorn or flask and send a http request, the server stops responding to https requests as well, unlike when it's just idling. If run with gunicorn, the worker process eventually gets killed and replaced. I do agree that something like nginx would be a more appropriate solution for a production server, but this was just meant for a local server (something similar to a jupiter notebook) and nginx is pretty overkill.Republicanize
Running two separate gunicorn processes that bind to two different ports also defeats the purpose, because the https server still hangs if the user accidentally sends a http request to that port. My main concern wasn't really having the server run on http, but rather preventing http requests from hanging the https server.Republicanize
B
1

Typically servers don't listen for both http and https on the same port. I have a similar requirement for my personal portfolio, but I use nginx to forward http requests (port 80) to https (port 443) and then the https server passes it off to my uwsgi backend, which listens on port 3031. That's probably more complex than you need, but a possible solution. If you go that route I would recommend letsencrypt for your certificate needs. It will set up the certificates AND the nginx.conf for you.

If you don't want to go the full nginx/apache route I think your easiest solution is the one suggested here on that same thread that you linked.

Barolet answered 14/10, 2022 at 19:28 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.