Files
connexion/examples/reverseproxy/app.py
Robbe Sneyders edb0381af3 Implement user facing interface for ConnexionMiddleware (#1621)
This PR adds an interface for the ConnexionMiddleware, similar to the
interface of the Connexion Apps.

The Connexion Apps are now a simple wrapper around the
ConnexionMiddleware and framework app, delegating the work to the
middleware. This enables a similar interface and behavior for users when
using either the middleware or apps.

The arguments are repeated everywhere there is a user interface, but are
parsed in a central place. Repeating the arguments is not DRY, but
needed to provide users with IDE autocomplete, typing, etc. They are
parsed in a single `_Options` class, which also provides a mechanism to
set default options on an App level, and override them on the more
granular API level.

This makes the long list of provided parameters a lot more manageable,
so I would like to use it for the `Jsonifier` as well, and re-add the
`debug` and `extra_files` arguments which I have dropped in previous
PRs. I'll submit a separate PR for this.

I renamed the `options` parameter to `swagger_ui_options` since it only
contains swagger UI options. This is a breaking change though, and we'll
need to highlight this upon release.

We still have quite a lot of `App`, `MiddlewareApp`, and abstract
classes. It would be great if we could find a way to reduce those
further, or at least find better naming to make it more clear what each
one does 🙂 .

Finally, I added examples on how the middleware can be used with third
party frameworks under `examples/frameworks`. Currently there's an
example for Starlette and Quart, but this should be easy to extend. They
also show how the `ASGIDecorator` and `StarletteDecorator` from my
previous PR can be used.
2023-01-26 14:40:29 +01:00

84 lines
2.6 KiB
Python
Executable File

"""
example of connexion running behind a path-altering reverse-proxy
NOTE this demo is not secure by default!!
You'll want to make sure these headers are coming from your proxy, and not
directly from users on the web!
"""
import logging
from pathlib import Path
import connexion
import uvicorn
from starlette.types import Receive, Scope, Send
class ReverseProxied:
"""Wrap the application in this middleware and configure the
reverse proxy to add these headers, to let you quietly bind
this to a URL other than / and to an HTTP scheme that is
different than what is used locally.
In nginx:
location /proxied {
proxy_pass http://192.168.0.1:5001;
proxy_set_header Host $host;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header X-Forwarded-Path /proxied;
}
:param app: the WSGI application
:param root_path: override the default script name (path)
:param scheme: override the default scheme
:param server: override the default server
"""
def __init__(self, app, root_path=None, scheme=None, server=None):
self.app = app
self.root_path = root_path
self.scheme = scheme
self.server = server
async def __call__(self, scope: Scope, receive: Receive, send: Send):
logging.warning(
"this demo is not secure by default!! "
"You'll want to make sure these headers are coming from your proxy, "
"and not directly from users on the web!"
)
root_path = scope.get("root_path") or self.root_path
for header, value in scope.get("headers", []):
if header == b"x-forwarded-path":
root_path = value.decode()
break
if root_path:
scope["root_path"] = "/" + root_path.strip("/")
path_info = scope.get("PATH_INFO", scope.get("path"))
if path_info.startswith(root_path):
scope["PATH_INFO"] = path_info[len(root_path) :]
scope["scheme"] = scope.get("scheme") or self.scheme
scope["server"] = scope.get("server") or (self.server, None)
return await self.app(scope, receive, send)
def hello():
return "hello"
def create_app():
app = connexion.FlaskApp(__name__, specification_dir="spec")
app.add_api("openapi.yaml")
app.add_api("swagger.yaml")
app.middleware = ReverseProxied(app.middleware, root_path="/reverse_proxied/")
return app
if __name__ == "__main__":
uvicorn.run(
f"{Path(__file__).stem}:create_app", factory=True, port=8080, proxy_headers=True
)