flask-restplus
flask-restplus copied to clipboard
Swagger UI refers to http://localhost:5000/swagger.json
We deploy flask-restplus behind a proxying firewall.
Problem here is that the swagger.html generates this:
url: "http://127.0.0.1:5000/swagger.json"
This is fine for local deployments, but blocked when proxied through a firewall.
The problem come from api.py:
@property
def specs_url(self):
'''
The Swagger specifications absolute url (ie. swagger.json
)
:rtype: str
'''
return url_for(self.endpoint('specs'), _external=True)
Using _external=False results in '/swagger.json' and solves this issue, and nicely serves swagger UI via the proxying firewall.
I think your issue is Flask related. Have a look at #132.
I have the same issue, I've spent the whole day debugging it and hopefully got somewhere, my case:
I have two flask-restplus apps running, one in localhost:5000 and other in localhost:5100. I am using the example on the README.md for both of them. I am deploying them behind nginx.
My intention is that localhost/contacts proxies to localhost:5000 and localhost/notifications proxies to localhost:5100.
With the below changes, things work almost as expected:
- When accessing localhost/contacts/todos/ it's all good;
- When accessing localhost/contacts/todos I get redirect to localhost/contacts/todos/ thanks to the proxy_redirect line;
- When accessing localhost/contacts it fails to load swagger ui. It tries to read swagger.json from localhost/swagger.json instead of localhost/contacts/swagger.json. I can't for the life of me make it work with nginx changes only.
server {
listen 80;
server_name localhost;
location /contacts/ {
proxy_pass http://localhost:5000/;
proxy_redirect http://localhost http://localhost/contacts;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header Referer $http_referer;
}
}
The solution was to follow http://flask.pocoo.org/snippets/35/ and add the following to nginx block:
proxy_set_header X-Scheme $scheme;
proxy_set_header X-Script-Name /contacts/;
And then call flask-restplus with
class ReverseProxied(object):
def __init__(self, app):
self.app = app
def __call__(self, environ, start_response):
script_name = environ.get('HTTP_X_SCRIPT_NAME', '')
if script_name:
environ['SCRIPT_NAME'] = script_name
path_info = environ['PATH_INFO']
if path_info.startswith(script_name):
environ['PATH_INFO'] = path_info[len(script_name):]
scheme = environ.get('HTTP_X_SCHEME', '')
if scheme:
environ['wsgi.url_scheme'] = scheme
return self.app(environ, start_response)
app = Flask(__name__)
app.wsgi_app = ReverseProxied(app.wsgi_app)
api = Api(app, version='1.0', title='TodoMVC API',
description='A simple Notification API',
)
Needless to say I think it's an awful solution because something that should be solved in the web server layer has to be solved in a middleware / application layer. What if I don't have access to the flask-restplus server code?
If swagger.json worked with nginx changes only life would be good. I think what I am trying to achieve is a very common case. Any thoughts @ziirish and @noirbizarre ? And thanks for the work on flask-restplus, it's a very good piece of software :)
I did the worst fix ever but hopefully it can spark a better implementation.
I've created a flask config called SWAGGER_BASEPATH which basically is concatenated to generate the swagger.json URL.
So in my case:
app = Flask(__name__)
app.config['SWAGGER_BASEPATH'] = '/contacts'
api = Api(app, version='1.0', title='TodoMVC API',
description='A simple Contacts API',
)
And then everything works like a charm without needing to use ReverseProxied, CORS or anything else.
My small implementations (happy to adapt or if the thing already existed please point me to it):
in flask_restplus/api.py:
@property
def specs_url(self):
'''
The Swagger specifications absolute url (ie. `swagger.json`)
:rtype: str
'''
url = url_for(self.endpoint('specs'), _external=True)
if self.app.config.get('SWAGGER_BASEPATH', ''):
prefix = url.split('/swagger.json')[0]
url = prefix + self.app.config.get('SWAGGER_BASEPATH', '') + '/swagger.json'
return url
in flask_restplus/swagger.py:
basepath = current_app.config.get('SWAGGER_BASEPATH', '')
if not basepath:
basepath = self.api.base_path
if len(basepath) > 1 and basepath.endswith('/'):
basepath = basepath[:-1]
And that's it.
I don't understand why you want to patch a framework when this should be handled by your application code.
In this case, why won't you patch Flask directly?
@rndrestore issue is typically handled by the ProxyFix
thing (the Flask documentation gives you the answer which is IMO the good one).
Your issue is a bit different because you host your application behind a sub-root path. Again, the snippet you mentioned is probably the best answer you can get.
Again, I don't understand why you want to patch the framework. The issue is related to your own environment. Most people won't care about this use-case. Besides, you can (and must) handle this at a different layer in your application stack.
For the record, here is how I handle your use-case:
This way you can either configure your reverse-proxy to send the appropriate header, or you can add a new option to your application.
And I confirm it works with Flask-Restplus since my application heavily relies on this framework.
I have played with what you mentioned @ziirish , the problem is that I have to duplicate the code (or add an extra lib) / call a reverse proxy on each micro service using flask restplus.
It's also doing things in the middleware layer (slow - python) instead of nginx (fast).
It's a framework that gives a swagger doc (which is amazing) but IMO it has to be configurable to work under reverse proxies without needing to change the application code or add boilerplate to every middleware running a flask-restplus application.
e.g: say I have 500 microservices, with the configuration I can get that path from an environment variable and have it set with Ansible and be done at deployment.
Without it I need to always remember to add a ReverseProxy when using flask restplus, add it in 500 places and make 500 services run slower.
In the end, in my opinion, looks like an immense overkill to add a whole WSGI middlware just to be able to access the auto generated docs, everything else works perfectly fine just with NGINX and no changes in the application layer.
I understand your situation, but I'm pretty sure Flask-Restplus is not the right place for your patch.
Because this is something related to your own environment, I think it might be a good idea to write your own framework around Flask and Flask-Restplus which implements your fixes.
That's how we always did at my past and current jobs.
@ziirish having said that it wouldn't hurt to have some reverse proxy examples in the repo for apache/nginx/haproxy that are verified working with restplus.
If users are repeatedly addressing similar issues, these should at minimum be added to the docs, even if they are deemed beyond the scope of a product. Otherwise every adopter is left to cobble a snowflake solution with no benefit from those that have addressed this. @ziirish expresses this implicitly:
"good idea to write your own framework around Flask and Flask-Restplus which implements your fixes. That's how we always did at my past and current jobs."
Schedules don't allow for that. It'll remain proprietary code which everyone needs to maintain in isolation.
I also find the docs here to stop short of the ideal which, IMO, should include some examples that do go beyond the hard line that defines the projects edge boundaries.
I think I found some way instead of modifying the framework.
I try to derived custom API from flask_restplus.API, and then change _external
from True
to False
, modify the result of specs_url to relative url.
that seems work for me.
class Custom_API(Api):
@property
def specs_url(self):
'''
The Swagger specifications absolute url (ie. `swagger.json`)
:rtype: str
'''
return url_for(self.endpoint('specs'), _external=False)
Wish good luck!
@goodloop 's solution works (at least for ngrok).
@goodloop - great stuff! Does work!
@goodloop it works, great!!
I redirected two nginx location. Everything works.
location /swagger.json { proxy_pass http://content:5000/swagger.json; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Host $server_name; }
location /swaggerui/ {
proxy_pass <http://content:5000/>swaggerui/;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Host $server_name;
}
I am forking flask-restplus now to make this change to the main code. I plan to add a behind_proxy=True keyword parameter to the Api(...., behind_proxy=True) object.
I need this feature to use an nginx reverse proxy to serve many REST+ APIs inside kubernetes. I think this will be a popular fix.
We deploy flask-restplus behind a proxying firewall.
Problem here is that the swagger.html generates this:
url: "http://127.0.0.1:5000/swagger.json"
This is fine for local deployments, but blocked when proxied through a firewall.
The problem come from api.py:
@Property def specs_url(self): ''' The Swagger specifications absolute url (ie.
swagger.json
):rtype: str ''' return url_for(self.endpoint('specs'), _external=True)
Using _external=False results in '/swagger.json' and solves this issue, and nicely serves swagger UI via the proxying firewall.
Hello,
It's been a long time since this issue has been opened. The least thing we can do is to document the available solutions.
We'll then be discussing how we could improve your experience.
I think I found some way instead of modifying the framework. I try to derived custom API from flask_restplus.API, and then change
_external
fromTrue
toFalse
, modify the result of specs_url to relative url. that seems work for me.class Custom_API(Api): @property def specs_url(self): ''' The Swagger specifications absolute url (ie. `swagger.json`) :rtype: str ''' return url_for(self.endpoint('specs'), _external=False)
Wish good luck! I tried but failed. Is it the only place we should modify? Do we have to do something in nginx conf?
class Custom_API(Api): @property def specs_url(self): ''' The Swagger specifications absolute url (ie. `swagger.json`) :rtype: str ''' return url_for(self.endpoint('specs'), _external=False)
Can you reference the rest of the Flask code that you are using to get this to work? I am a little confused what object you pass to the Custom_API class.
Edit: I was able to answer my own question. See here if anyone is confused like I was: https://github.com/noirbizarre/flask-restplus/issues/543
I did a solution for this in my project.
Swagger gets the host information from SERVER_NAME env. But if you change the SERVER_NAME to a different name from your server, you'll not able to access it.
So I've created a 'new' env named SWAGGER_HOST and changed the following line to get this new parameter instead SERVER_NAME and working as expected.
https://github.com/noirbizarre/flask-restplus/blob/a7e363a8352efc70c8d160ef9526dc4572733a1e/flask_restplus/swagger.py#L226
Is there any reason @goodloop's solution cannot be integrated into Flask-RestPlus? It works locally and behind a proxy. Would you accept a pull-request containing this fix, together with documentation on how to use Flask-RestPlus behind and nginx reverse-proxy with HTTPS?
To clarify what @goodloop explained:
DOCS_ROUTES = Blueprint('swagger', __name__)
class CustomAPI(Api):
@property
def specs_url(self):
'''
The Swagger specifications absolute url (ie. `swagger.json`)
:rtype: str
'''
return url_for(self.endpoint('specs'), _external=False)
API = CustomAPI(DOCS_ROUTES, title='My Flask API',
version='1.0',
description='My Flask API Project')
It works with flask==1.1.1 and flask_restplus==0.13.0 behind a Nginx proxy with this example config:
server {
listen 80;
server_name mydomain.com;
return 301 https://$host$request_uri;
}
server {
listen 443 ssl http2;
server_name mydomain.com;
[SSL and other config stuff...]
location ^~ / {
proxy_pass http://my_app:5000/;
proxy_redirect off;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_connect_timeout 600;
proxy_send_timeout 600;
proxy_read_timeout 600;
send_timeout 600;
}
}
For anyone who drops here, this project has been replaced by flask-restx as mentioned in #770.
How do i import app in blueprints for swagger?
api_service.py
from flasgger import Swagger
from flask import Blueprint
from flask_restx import Api
api_service = Api(Blueprint('api_service', __name__))
swagger = Swagger(app) #??
service.py
from app.service.api_service import api_service
app = Flask(__name__)
app.register_blueprint(api_service, url_prefix='/api')
swagger = Swagger(app)
1 - please open restx questions against flask-restx not flask-restplus
2 - if you're using flask-restx for swagger, you don't need flasgger's Swagger. Not really sure what is trying to be accomplished there. flask-restx (and flask-restplus) generate your swagger/openapi docs for you, and vendor in the swagger UI.
You can follow this solution: https://stackoverflow.com/questions/47508257/serving-flask-restplus-on-https-server
- Make sure in your chrome developertools->Network tab that whenever you reload the page(in https server) that shows the swagger UI, you get a mixed content error for swagger.json request.
- The solution in stackoverflow post solves the issue when deployed on an https server but locally it might give issue. For that you can use the environment variable trick. Set an environment variable on your https server while deploying your app. Check for that environment variable before applying the solution. It might look similar to this:
import os
from flask import url_for
from flask_restplus import Api
app = Flask( __name__)
if os.environ.get('CUSTOM_ENV_VAR'):
@property
def specs_url(self):
return url_for(self.endpoint('specs'), _external=True, _scheme='https')
Api.specs_url = specs_url
api = Api(app)
Now when you run locally this hack won't be applied and swagger.json would be served through http and in your server it would be served via https.
location /swaggerui/ { proxy_pass http://content:5000/swaggerui/; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Host $server_name; }
This solved the issue I was having. Thank you!
I don't agree with have to configure my proxy to pass to /swaggerui, what about when I have multiple microservices behind the same proxy? Please considere use the prefix, to set /${prefix}/swaggerui, thank you!