pythonflasksslpython-requestsgunicorn

HTTP request fails in Flask request, succeeds from command line


In trying to write a flask app, I ran into a problem making an outbound HTTP call using requests to an API while processing the request. If I run the same function from the python command line, it works. Why?

Update If I add gunicorn and make the call to flask via gunicorn, it works!

I've simplified my flask app and I get the same error.

import requests
from flask import Flask

app = Flask(__name__)

@app.route("/")
def hello_world():
    try:
        response = requests.get("https://httpbin.org/get")
        data = response.json()
    except Exception as e:
        print(f"Caught exception {e}")
        data = "no data: error"
    return(f'<pre>{data}</pre>')

Saved as sslerror.py, requires installing flask and requests.

Web Request

I run the flask app using flask --app sslerror run. Flask listens at http://127.0.0.1:5000. When I visit that in my browser, I get this error:

Caught exception HTTPSConnectionPool(host='httpbin.org', port=443): Max retries exceeded with url: /get (Caused by SSLError(SSLError(0, 'unknown error (_ssl.c:3098)')))`

Python REPL

From the command line, on the other hand:

>>> from sslerror import hello_world
>>> hello_world()
"<pre>{'args': {}, 'headers': {'Accept': '*/*', 'Accept-Encoding': 'gzip, deflate', 'Host': 'httpbin.org', 'User-Agent': 'python-requests/2.31.0', 'X-Amzn-Trace-Id': 'Root=1-652f409a-abcdefabcdef'}, 'origin': '108.x.x.x', 'url': 'https://httpbin.org/get'}</pre>"

Adding Gunicorn

$ pip install gunicorn
$ gunicorn -w 1 sslerror:app

Gunicorn listens at http://127.0.0.1:8000. When I visit that, I see results in my browser

Environment info: MacOS 13.6 (Ventura) on Apple m2 chip. I'm using python 3.11.5 from pyenv.

I've also tried adding verify=False and allow_redirects=False to the get() call. The result was the same each time.

WHY does the outbound HTTP request work from the command line but not in the context of the flask request?

Update: I also tried in new virtualenvs with python 3.8 (from pyenv) and 3.11 from homebrew. Same behavior.

Update 2: I added gunicorn and it works?!

Update 3: I tried swapping out requests and using httpx. Breaks the same way. This is smelling like a flask issue.

Update 4: I'm now also getting this when making an HTTP call from a django runserver process. No flask in the mix for that.

Update 5: If I change the URL in the requests.get() call to use http rather than https, it works through flask. That doesn't solve the problem since service I really want to connect to only uses https. I mention it here as a data point that might help determine the cause.


Solution

  • I may have finally solved this by fully uninstalling homebrew (using homebrew's uninstaller) and then reinstalling necessary stuff.

    My hypothesis is that my homebrew binaries were transferred from a previous mac (using apple's transfer tool) with an M1 chip. Perhaps there was a binary incompatibility with the M2. By forcing a full reinstall, everything was compiled natively.


    older possible solution

    I found a solution that works sometimes thanks to this answer to a different Stack Overflow question:

    CERT_PATH=$(python -m certifi)
    export SSL_CERT_FILE=${CERT_PATH}
    export REQUESTS_CA_BUNDLE=${CERT_PATH}
    

    (Although I do not understand why I got different behavior in flask vs REPL)