Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Mounting flask under subpath through nginx #527

Closed
andybarilla opened this issue Oct 11, 2017 · 6 comments · Fixed by #823
Closed

Mounting flask under subpath through nginx #527

andybarilla opened this issue Oct 11, 2017 · 6 comments · Fixed by #823

Comments

@andybarilla
Copy link

Sorry if this is obvious and I just missed how to do it. My flask app is the rest API so http://localhost:5000/swagger.json is correct. However, if I mount it under /api via a proxy_pass in nginx, the swagger ui is still looking for /swagger.json instead of /api/swagger.json

Setting base_path relocates the whole api so I end up with /api/api/my-rest-call once mounted in nginx.

Is there a way to specify the path where the swagger.json file is located? For now I created a rewrite rule on nginx but that is not a long term solution in case multiple Connexion apps are running under the same nginx instance.

@andybarilla
Copy link
Author

While not idea, to work around this I did added the base_path to mount the Connexion app under it's final path:

    cnx_app.add_api('my_api_v1.yaml', base_path='/api')

And then modified the nginx.conf to pass the entire path through the proxy by not including a path in the proxy_pass command:

   location /api {
       # Define the location of the proxy server to send the request to
       proxy_pass http://web:8000;
 
       # Redefine the header fields that NGINX sends to the upstream server
       proxy_set_header Host $host;
       proxy_set_header X-Real-IP $remote_addr;
       proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
 
       # Define the maximum file size on file uploads
       client_max_body_size 5M;
   }

@kopf
Copy link

kopf commented Oct 19, 2017

I'm currently struggling with the same problem.

When using pure flask, it's common to use this ReverseProxied middleware pattern. When using this, the nginx config looks something like this:

location /component {
        proxy_pass http://endpoint;
        proxy_set_header Host $host;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Scheme $scheme;
        proxy_set_header X-Script-Name /component;
}

When running without a reverse proxy (e.g. with flask run), the component's URL routes can be accessed directly - e.g. GET http://localhost:5000/api/v1/users. When running behind the reverse proxy, the routes are accessible under the subpath - e.g. GET http://localhost/component/api/v1/users.

More importantly, flask's url_for will generate the correct URL routes, depending on whether the request came in through a reverse proxy or not. This is due to the fact that the middleware is executed at request time.

connexion's base_path, on the other hand, has to be set at run time.

This is problematic, as the entire base_path (including any reverse proxy paths) isn't known at run-time, nor should it be - instead it should be possible to get connexion to take heed of the incoming X-Script-Name header and react accordingly. Setting something that can be read at run-time (e.g. environment variables or an app.config variable) is messy as it splits this configuration between both the reverse proxy and the application code, when really this should just be something that's handled on the fly (as with the ReverseProxied pattern).

What is the recommended way of doing this?

@ChristianSauer
Copy link

Has anybody found a good solution?

@MattF-NSIDC
Copy link

MattF-NSIDC commented Apr 23, 2018

@ChristianSauer I struggled with this for a while with both connexion and flask-restplus. I have bad news and good news. flask-restplus uses the WSGI SCRIPT_NAME environ variable to find swagger.json and static resources. You can use flask snippet #35 to make this work with Flask and thereby flask-restplus, including swaggerUI. Connexion, as @kopf noted, has this base_path concept that doesn't take environ['SCRIPT_NAME'] into account. I've tested this with a Hello World app for Connexion and swagger-restplus here:

https://bitbucket.org/MattF-NSIDC/sscce_flask-rproxy

My example goes a little bit further and can run under a proxy container exposed at an arbitrary port.

Look at the README for instructions to test for yourself. Looks like flask-restplus is accomplishing this by returning a swagger UI for each request and passing in a URL generated by flask; specs_url here: https://github.com/noirbizarre/flask-restplus/blob/d749772383f6c84b67c1f187e83b5c8b9177ab73/flask_restplus/templates/swagger-ui.html#L53.

EDIT: Man, I shouldn't post to github late at night. To correct what I said above about connexion/base_path: base_path is a key in swagger.json. It's not specific to connexion. BUT, restplus generates a swagger.json on a per-request basis and prefixes script_name to base_path each time. So one API instance could be proxied behind many URLs and still get the base_path right every time, provided a properly configured webserver. Connexion doesn't have the request context available when generating swagger.json to, for example, read headers from the webserver or script_name from the wsgi object.

More on wsgi variables like script_name: https://www.python.org/dev/peps/pep-0333/#environ-variables

@dtkav
Copy link
Collaborator

dtkav commented Jan 13, 2019

Hey folks - please have a look at #823 - It aims to solve this problem and includes an example for both flask and aiohttp.

I'd love your feedback. Thanks!

@kopf
Copy link

kopf commented Jan 13, 2019

Fixed link: #823

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging a pull request may close this issue.

6 participants