responder icon indicating copy to clipboard operation
responder copied to clipboard

The swagger UI doesn't work when put behind a reverse proxy

Open Hultner opened this issue 6 years ago • 13 comments
trafficstars

This problems occurs for instance when putting NGINX as an API-Gateway infront of the service and serving it from a subpath.

The swagger UI are using absolute paths for it's files, so when accessesing http://nginx/service-path/docs the index.html is referencing http://nginx/static/js/swagger-ui-… when it needs to access http://nginx/service-path/static/js/swagger-ui-….

If I could inject a custom <base>-tag with the real path or maybe if it could be detected from the X-Forwarded-For header we could have it working behind the reverse proxy as well.

Hultner avatar Jan 08 '19 15:01 Hultner

Did take a second look at this today. My first hopes was that changing the static_url-arg in the constructer to include service-path/static but that mounted the files even further down the chain. However I did find another although hacky temporary solution to the problem when reading through the source-code.

api = responder.API(
    title="Demo Service", 
    version="0.1.0", 
    openapi="3.0.0", 
    docs_route="/docs",
    # openapi_route = "./schema.yml",
    # static_route="/api/service-path/static",
)
# HACK: Changes the static route after it's been mounted by whitenoise
# This way the correct path is used by the API.docs()-function.
api.static_route = "/api/service-path/static"

This however takes me to the next problem which I can't work around without patching responder. The schema link is hardcoded to /schema.yml so it always tries to access the root of the server, removing the slash in the swagger ui making it either schema.yml or ./schema.yml makes the swagger UI render successfully.

However I can't use the same kind of hack here since this is hardcoded rather then read from the self.openapi_route-property, if it would I could overwrite the variable in the same way.

https://github.com/kennethreitz/responder/blob/d1e105a29a5c5900ad083fd7c0e03f57a303f393/responder/api.py#L605

Hultner avatar Jan 11 '19 16:01 Hultner

I agree that this would be a nice thing to have.

Having said that, I was able to work around this by making two changes.

First, specify the route prefix in all the places:

DEMO_PATH = '/demo'

API = responder.API(
    title="Demo App",
    version="1.0",
    openapi="3.0.2",
    docs_route=f"{DEMO_PATH}/docs",
    static_route=f"{DEMO_PATH}/static",
    openapi_route=f"{DEMO_PATH}/schema.yml"
)

Second, do some grossness with the apistar template to work around the aforementioned hard-coding (https://github.com/kennethreitz/responder/blob/v1.3.0/responder/api.py#L627) :

RUN find /usr -type f -path *apistar* -path *swagger* -name index.html -exec sed --in-place  --regexp-extended 's|^(\s*)url:(\s*)\"(.*)\"(,?)\s?$|\1url:\2"${DEMO_PATH}\3"\4|g' {} \;

StephenWithPH avatar Mar 15 '19 20:03 StephenWithPH

@taoufik07 Was this resolved? Or why was it closed? Would be nice to have a reference to where it was fixed/reason for closing.

Hultner avatar Apr 30 '19 09:04 Hultner

I am also very interested in the solution, because i am facing the same problem. I have a micronaut application with swagger running behind a reverse proxy.

jensW89 avatar Apr 30 '19 12:04 jensW89

@Hultner @jensW89 sorry guys I thought this was solved in the previous comment :)

taoufik07 avatar Apr 30 '19 17:04 taoufik07

Working on https://github.com/taoufik07/responder/pull/37.

app.py

import responder 

DEMO_PATH = '/demo'

api = responder.API(
    title="Demo App",
    version="1.0",
    openapi="3.0.2",
    docs_route="/docs",
    static_route="/static",
    openapi_route="/schema.yml",
    reverse_proxy_path=DEMO_PATH
    )

@api.route("/")
class Home:
    def on_request(self, req, resp):   # or on_get...
        resp.text = "Home"
        resp.headers.update({'X-Life': '42'})
        resp.status_code = api.status_codes.HTTP_416

@api.route("/{greeting}")
class GreetingResource:
    def on_request(self, req, resp, *, greeting):   # or on_get...
        resp.text = f"{greeting}, world!"
        resp.headers.update({'X-Life': '42'})
        resp.status_code = api.status_codes.HTTP_416

print(api.routes)

if __name__ == "__main__":
    api.run()

terminal

python3.7 app.py

image image

image

iancleary avatar Jul 20 '19 23:07 iancleary

https://github.com/taoufik07/responder/pull/374 should be all set to review @taoufik07 @jensW89 @StephenWithPH @Hultner

Please note I don't use static_dir and static_route extensively, so if you think more or different test coverage is required, let me know.

The above example application is still valid. Test cases have been added

I have a use case to put responder behind traefik's router, so it sounds like we will benefit from this being part of master.

Cheers from Arizona; @taoufik07 thanks for all the help with this PR and prior changes.

iancleary avatar Jul 21 '19 01:07 iancleary

Having the same issue. It would be great if one could configure the swagger path to be something below /api as I'm also serving behind a reverse proxy.

Is there any progress on this?

flowolf avatar Oct 05 '19 19:10 flowolf

Same issue here. I managed to solve this with a hack, it's not a real solution but rather a workaround.

I'm serving swagger behind a nginx reverse proxy on a subpath, so nginx expects https://mydomain.com/api/swagger but swagger (the swagger UI are using absolute paths) sends requests to https://mydomain.com/swagger which nginx doesn't know how to serve.

So I added a new location to mydomain.com config:

location /swagger/ { return 301 https://$server_name/api$request_uri; }

Now when something like https://mydomain.com/swagger comes in, nginx returns https://mydomain.com/api/swagger maybe a rewrite would be better but I didn't try yet.

armoba avatar Oct 17 '19 18:10 armoba

@taoufik07 In the next few days, I will rebase https://github.com/taoufik07/responder/pull/374 from master now that the Schema pattern has moved and the new Router class have been created (https://github.com/taoufik07/responder/issues/356).
I'll propose my edits to the add_route functions and think about how to interface the Router with the ext/Schema patter for the docs route. The api init will retain backwards compatibility.

Open to opinions on how to pass in the configuration, and will default to an extra init parameter for the reverse_proxy_route in my original PR

iancleary avatar Oct 17 '19 19:10 iancleary

@iancleary yes, we would love to have Swagger UI working behind a reverse proxy

taoufik07 avatar Oct 19 '19 11:10 taoufik07

@taoufik07 PR #400 should correctly replay all of #374 onto responder version 2.0.0.

Also, the new Router class makes it so much simpler to maintain and reason about the structure of the code :rocket:

iancleary avatar Oct 20 '19 03:10 iancleary

Hello, regarding PR #400, I no longer am working on a project that uses responder. I used the project in 2019, but my work has gone a different direction and I do not have the time to support PR #400 .

I believe GitHub stores Pull Requests in the history of the upstream project (responder), so the PR content and history should remain if the proposed code is useful for this issue.

I wanted to let you know my intentions. Thanks and all the best with the project! :slightly_smiling_face: Best of luck!

iancleary avatar May 02 '20 00:05 iancleary