swagger-ui: "Try it Now" functionality stripping path prefixes when run behind nginx-ingress reverse proxy in Kubernetes/K8S.
Q&A (please complete the following information)
- OS: Linux (Ubuntu x86_64 LTS 20.04).
- Browser: Firefox.
- Version: v84.0.1 64-bit.
- Method of installation: Helm v3.x in K8S (though trying the manual "dist/index.html" approach yields similar results).
- Swagger-UI version: v3.24.3 (pre-built Docker container), v3.40.0 (plain old "dist/index.html" approach).
- Swagger/OpenAPI version: OpenAPI 3.0.
Content & configuration
- Setup an instance of an application exposed through an nginx-ingress rule in K8S. Sample ingress YAML included below. In this example, the host is
roadrunner.acme.co, and the service is exposed onhttp://roadrunner.acme.com/meep/v1.-
Note: I've found a few other projects on GitHub or via Google searches where the solution is "just expose your app on
hostname/, rather than a custom path invoking nginx rewrites such ashostname/meep/v1. - This is not an option, as that would assume only a single application is exposed via the ingress controller. In a typical/production use case, each micro-service in behind the ingress-control would get its own dedicated base path, like
meep/v1in my example).
-
Note: I've found a few other projects on GitHub or via Google searches where the solution is "just expose your app on
- Download and extract the contents of the "plain old HTML/JS" archive.
- Modify the
dist/index.htmlfile so theURLfield points to the OpenAPI spec of my service (i.e.http://roadrunner.acme.co/meep/v1/openapi.json; the service is a simple Python Flash+Swagger example that auto-exposes theopenapi.jsonendpoint). - I am able to access the Swagger-UI web UI example, interact with it, etc. However, if I attempt to use the "Try It Now" ==> "Execute" functionality, the API calls to the live/running app fail due to a "Network error".
- Upon closer inspection, I see that the "Execute" calls are attempting to communicate with
http://roadrunner.acme.co/v1/rather thanhttp://roadrunner.acme.co/meep/v1(i.e. the re-written portion of the URL is absent). - I was able to arrive at the same result by deploying swagger-ui into the cluster directly via a third-party helm chart.
Example ingress:
---
apiVersion: networking.k8s.io/v1beta1
kind: Ingress
metadata:
name: sample-ingress-meep-meep
annotations:
nginx.ingress.kubernetes.io/rewrite-target: /$1
spec:
rules:
- http:
paths:
- backend:
serviceName: velocitus-good-with-ketchup-ius
servicePort: 80
path: /meep/(v1/.*)
Example Swagger/OpenAPI definition: N/A.
Swagger-UI configuration options:
window.onload = function() {
// Begin Swagger UI call region
const ui = SwaggerUIBundle({
url: "http://roadrunner.acme.co/meep/v1/openapi.json",
dom_id: '#swagger-ui',
deepLinking: true,
presets: [
SwaggerUIBundle.presets.apis,
SwaggerUIStandalonePreset
],
plugins: [
SwaggerUIBundle.plugins.DownloadUrl
],
layout: "StandaloneLayout"
})
// End Swagger UI call region
window.ui = ui
}
Describe the bug you're encountering
- The "Try It Now" ==> "Execute" functionality does not work, but access to the HTML app in general works.
To reproduce...
Example described above.
Expected behavior
- The "Try It Now" ==> "Execute" functionality uses the exact same URL (i.e. hostname plus base path) that is used to access the running OpenAPI-enabled service (maybe using
X-Forwarded-Foror additional parameters supplied to the ingress-controller, so that re-writes don't break functionality).
Additional context or thoughts
There's a hack I've figured out in order to make the "Try It Now" ==> "Execute" functionality work, but the downside (severe), is that I need to supply the external hostname to the swagger-ui app at chart installation time (i.e. when I install swagger-ui, it needs to be provided with the string http://roadrunner.acme.co/meep/v1 at installation time). This isn't a permanent/viable solution though, as various users may "see" a different hostname and base path when accessing this micro-service (e.g. if it's exposed via multiple domains with a common load balancer, or if some people attempt to access it via IP address rather than this specific host name). It also prevents 100% end-to-end automated installation, as someone needs to supply the external FQDN + base path at chart installation time.
Hack-y workaround:
- Deploy an OpenAPI-enabled microservice, expose it via an ingress rule (below). Can be deployed via
kubectl apply --namespace=acme-ns -f ingress-meep.yaml.
ingress-meep.yaml
---
apiVersion: networking.k8s.io/v1beta1
kind: Ingress
metadata:
name: sample-ingress-meep-meep
annotations:
nginx.ingress.kubernetes.io/rewrite-target: /$1
spec:
rules:
- http:
paths:
- backend:
serviceName: velocitus-good-with-ketchup-ius
servicePort: 80
path: /meep/(v1/.*)
- Pre-create an ingress rule for swagger-ui (below). Can be deployed via
kubectl apply --namespace=acme-ns -f ingress-demo.yaml.
ingress-demo.yaml
---
apiVersion: networking.k8s.io/v1beta1
kind: Ingress
metadata:
name: sample-ingress-swaggerui-demo
annotations:
nginx.ingress.kubernetes.io/rewrite-target: /$1
nginx.ingress.kubernetes.io/enable-cors: "true"
spec:
rules:
- http:
paths:
- backend:
serviceName: swagger-demo-swaggerui
servicePort: 8080
path: /swagger-demo/(.*)
- Using the following commands to deploy the chart.
# Make chart "visible" to Helm.
helm repo add cetic https://cetic.github.io/helm-charts
helm repo update
# Delete old copy if present.
helm delete --namespace=acme-ns swagger-demo
# Install chart.
helm install --namespace=acme-ns swagger-demo cetic/swagger-ui \
--set swaggerui.jsonUrl="http://velocitus-good-with-ketchup-ius.acme-ns.svc.cluster.local/v1/openapi.json" \
--set swaggerui.server.url="http://roadrunner.acme.co/meep/v1/"
- Navigate to
<http://roadrunner.acme.co/swagger-demo/>in my browser. Application loads and is visible in my browser. - Change the "Servers" via the drop-down UI menu (top-left) from
v1tohttp://roadrunner.acme.co/meep/v1. Both the general UI features, and the live debug "Try It Now" features work as expected.
Possibly related issues (external projects)
- https://github.com/RicoSuter/NSwag/issues/3192
- https://github.com/RicoSuter/NSwag/issues/1717
- https://github.com/noirbizarre/flask-restplus/issues/310
Hi! Not certain what version of Kubernetes you're using (I'm on Kubernetes 1.20.4 and NGINX Ingress controller v0.44.0),. Adding the nginx.ingress.kubernetes.io/x-forwarded-prefix annotation worked for me.
---
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: sample-ingress-swaggerui-demo
annotations:
nginx.ingress.kubernetes.io/x-forwarded-prefix: "/swagger-demo"
nginx.ingress.kubernetes.io/rewrite-target: /$1
nginx.ingress.kubernetes.io/enable-cors: "true"
spec:
rules:
- http:
paths:
- backend:
serviceName: swagger-demo-swaggerui
servicePort: 8080
path: /swagger-demo/(.*)
Note: I'm using networking.k8s.io/v1
edit:: Documentation for nginx.ingress.kubernetes.io/x-forwarded-prefix
@DanSibbernsen What if you have multiple paths? You don't want to pass x-forwarded-prefix to other paths than swagger-demo-swaggerui.
@80rian Unfortunately my solution doesn't cover that that (for the apps I maintain, we only have 1 path per service, and the only one I cared about for the x-forwarded-prefix was for Swagger). Per this comment, it seems you'd have to define multiple ingresses in order to have different x-forwarded-prefix values passed along.
Gotcha. Thanks!
Hi! I tried to pass different x-forwarded-prefix values to multiple backend paths on the ingress-nginx (v1.0.4) and Kubernetes (1.21.7) through the nginx.ingress.kubernetes.io/x-forwarded-prefix annotation as @DanSibbernsen's comment.
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: ingress-demo
annotations:
kubernetes.io/ingress.class: nginx
nginx.ingress.kubernetes.io/use-regex: "true"
nginx.ingress.kubernetes.io/rewrite-target: /$3
nginx.ingress.kubernetes.io/x-forwarded-prefix: /$1
spec:
rules:
- http:
paths:
- path: /()()(.*)
pathType: Prefix
backend:
service:
name: web
port:
number: 80
- path: /(XXXApi)(/|$)(.*)
pathType: Prefix
backend:
service:
name: xxx-api
port:
number: 80
- path: /(YYYApi)(/|$)(.*)
pathType: Prefix
backend:
service:
name: yyy-api
port:
number: 80
@happyincent Thank you very much!
Someone help solve the problem!
Hello. I ran into a similar problem and google brought me to this thread. I am using kubernetes v1.23.6 and nginx-ingress-controller v1.3.1
If I use host-based routing using the address "api.[SomeDomain].com" then everything works fine and I can access the api and SwaggerUI. But when I try to use path-based Routing "api.[SomeDomain].com/test", then I constantly fail: When going to the address "api.[SomeDomain].com/test/swagger/index.html" I get a SwaggerUI page with the error: "API definition could not be loaded. The status of the response to the sampling error is 404 /swagger/v1/swagger.json". But if I go from the browser to: "api.[SomeDomain].com/test/swagger/v1/swagger.json" - I get normal json in response. Api requests at the address "api.[SomeDomain].com/test/..." also pass a good one. I tried many examples and the internet, but none solved my problem! ** Of course, instead of "[SomeDomain]" I use the real domain name, which is resolved through the global DNS :-)
The example suggested here fom @happyincent - also doesn't work for me.
Ingress:
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: testapi-ingress
annotations:
nginx.ingress.kubernetes.io/use-regex: "true"
nginx.ingress.kubernetes.io/rewrite-target: /$3
nginx.ingress.kubernetes.io/x-forwarded-prefix: /$1
spec:
ingressClassName: nginx
tls:
- hosts:
- api.[SomeDomain].com
secretName: "wildcard-tls-secret"
rules:
- host: api.[SomeDomain].com
http:
paths:
- path: /(test)(/|$)(.*)
pathType: Prefix
backend:
service:
name: testapi-svc
port:
number: 80
Error in SwaggerUi:

same here