Skip to content

feat: add callback api to WebSocketProxy #41

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

Open
wants to merge 5 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

## [Unreleased]

### Added

- [#41](https://github.com/WSH032/fastapi-proxy-lib/pull/41) - feat: add `callback` api to `WebSocketProxy`. Thanks [@WSH032](https://github.com/WSH032) and [@IvesAwadi](https://github.com/IvesAwadi)!

### Changed

- [#30](https://github.com/WSH032/fastapi-proxy-lib/pull/30) - fix(internal): use `websocket` in favor of `websocket_route`. Thanks [@WSH032](https://github.com/WSH032)!
Expand Down
3 changes: 2 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,8 +32,9 @@ Source Code: <https://github.com/WSH032/fastapi-proxy-lib/>
- [x] Support both **reverse** proxy and **forward** proxy.
- [x] **Transparently** and **losslessly** handle all proxy requests,
Including **HTTP headers**, **cookies**, **query parameters**, **body**, etc.
- [X] WebSocket proxy **callback**.
- [x] Asynchronous streaming transfer, support **file proxy**.
- [x] `fastapi-proxy-lib` value [privacy security](https://wsh032.github.io/fastapi-proxy-lib/Usage/Security/).
- [x] `fastapi-proxy-lib` value [**privacy security**](https://wsh032.github.io/fastapi-proxy-lib/Usage/Security/).

### other features

Expand Down
27 changes: 22 additions & 5 deletions docs/Usage/Advanced.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,8 @@ For the following scenarios, you might prefer [fastapi_proxy_lib.core][]:

- When you need to use proxies with **only** `Starlette` dependencies (without `FastAPI`).
- When you need more fine-grained control over parameters and lifespan event.
- When you need to further process the input and output before and after the proxy (similar to middleware).
- When you need to further process the input and output before and after the http proxy (similar to `middleware`).
- When you need `callback` to modify the websocket proxy messages.

We will demonstrate with `FastAPI`,
but you can completely switch to the `Starlette` approach,
Expand All @@ -19,13 +20,13 @@ Also (without annotations):
- [`ForwardHttpProxy#examples`][fastapi_proxy_lib.core.http.ForwardHttpProxy--examples]
- [`ReverseWebSocketProxy#examples`][fastapi_proxy_lib.core.websocket.ReverseWebSocketProxy--examples]

## Modify request
## Modify HTTP request

In some cases, you may want to make final modifications before sending a request, such as performing behind-the-scenes authentication by modifying the headers of request.

`httpx` provides comprehensive authentication support, and `fastapi-proxy-lib` offers first-class support for `httpx`.

See <https://www.python-httpx.org/advanced/#customizing-authentication>
See <https://www.python-httpx.org/advanced/authentication/>

You can refer following example to implement a simple authentication:

Expand All @@ -35,7 +36,7 @@ from fastapi_proxy_lib.fastapi.app import reverse_http_app


class MyCustomAuth(httpx.Auth):
# ref: https://www.python-httpx.org/advanced/#customizing-authentication
# ref: https://www.python-httpx.org/advanced/authentication/

def __init__(self, token: str):
self.token = token
Expand All @@ -55,7 +56,7 @@ app = reverse_http_app(

visit `/headers` to see the result which contains `"X-Authentication": "bearer_token"` header.

## Modify response
## Modify HTTP response

In some cases, you may want to make final modifications before return the response to the client, such as transcoding video response streams.

Expand Down Expand Up @@ -118,3 +119,19 @@ async def _(request: Request, path: str = ""):
```

visit `/`, you will notice that the response body is printed to the console.

## Modify WebSocket message

In some cases, you might want to modify the content of the messages that the WebSocket proxy receives and sends to the client and target server.

In version `0.2.0` of `fastapi-proxy-lib`, we introduced a [`callback API`][fastapi_proxy_lib.core.websocket.ReverseWebSocketProxy.proxy] for `WebSocketProxy` to allow you to do this.

See example: [ReverseWebSocketProxy#with-callback][fastapi_proxy_lib.core.websocket.ReverseWebSocketProxy--with-callback]

Also:

- RFC: [#40](https://github.com/WSH032/fastapi-proxy-lib/issues/40)
- PR: [#41](https://github.com/WSH032/fastapi-proxy-lib/pull/41)

!!!example
The current implementation still has some defects. Read the [callback-implementation][fastapi_proxy_lib.core.websocket.BaseWebSocketProxy.send_request_to_target--callback-implementation] section, or you might accidentally shoot yourself in the foot.
Comment on lines +123 to +137
Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@IvesAwadi What do you think of this description? Can you provide some specific use cases? This way, I can add them to the documentation.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@IvesAwadi What do you think of this description? Can you provide some specific use cases? This way, I can add them to the documentation.

The description is good and makes sense, took a bit to respond to this (had some issues with my email.)

8 changes: 7 additions & 1 deletion docs/Usage/FastAPI-Helper.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ There are two helper modules to get FastAPI `app`/`router` for proxy convenientl

## app

use `fastapi_proxy_lib.fastapi.app` is very convenient and out of the box, there are three helper functions:
`fastapi_proxy_lib.fastapi.app` is very convenient and out of the box, there are three helper functions:

- [forward_http_app][fastapi_proxy_lib.fastapi.app.forward_http_app]
- [reverse_http_app][fastapi_proxy_lib.fastapi.app.reverse_http_app]
Expand Down Expand Up @@ -46,3 +46,9 @@ For the following scenarios, you might prefer [fastapi_proxy_lib.fastapi.router]
- When you need to [mount the proxy on a route of larger app](https://fastapi.tiangolo.com/tutorial/bigger-applications/).

**^^[Please refer to the documentation of `RouterHelper` for more information :material-file-document: ][fastapi_proxy_lib.fastapi.router.RouterHelper--examples]^^**.

---

## More

**The `Helper Module` might not meet your further customization needs. Please refer to the [Advanced](Advanced.md) section, which is the core of `fastapi-proxy-lib`, for more personalization options.**
2 changes: 2 additions & 0 deletions mkdocs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,8 @@ plugins:
import:
- https://frankie567.github.io/httpx-ws/objects.inv
- https://fastapi.tiangolo.com/objects.inv
- https://anyio.readthedocs.io/en/stable/objects.inv
- https://docs.python.org/3/objects.inv
options:
docstring_style: google
paths: [src]
Expand Down
6 changes: 3 additions & 3 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,8 @@ dependencies = [
"httpx",
"httpx-ws >= 0.4.2",
"starlette",
"typing_extensions >=4.5.0",
"typing_extensions >= 4.12",
"anyio >= 4",
]

[project.optional-dependencies]
Expand Down Expand Up @@ -91,13 +92,12 @@ dependencies = [
# NOTE: 👆

# lint-check
"pyright == 1.1.356", # pyright must be installed in the runtime environment
"pyright == 1.1.372", # pyright must be installed in the runtime environment
# test
"pytest == 7.*",
"pytest-cov == 4.*",
"uvicorn[standard] < 1.0.0", # TODO: Once it releases version 1.0.0, we will remove this restriction.
"httpx[http2]", # we don't set version here, instead set it in `[project].dependencies`.
"anyio", # we don't set version here, because fastapi has a dependency on it
"asgi-lifespan==2.*",
"pytest-timeout==2.*",
]
Expand Down
10 changes: 7 additions & 3 deletions src/fastapi_proxy_lib/core/http.py
Original file line number Diff line number Diff line change
Expand Up @@ -222,7 +222,7 @@ class BaseHttpProxy(BaseProxyModel):
"""

@override
async def send_request_to_target( # pyright: ignore [reportIncompatibleMethodOverride]
async def send_request_to_target(
self, *, request: StarletteRequest, target_url: httpx.URL
) -> StarletteResponse:
"""Change request headers and send request to target url.
Expand Down Expand Up @@ -318,6 +318,8 @@ class ReverseHttpProxy(BaseHttpProxy):

# # Examples

## Basic usage

```python
from contextlib import asynccontextmanager
from typing import AsyncIterator
Expand All @@ -341,7 +343,7 @@ async def close_proxy_event(_: FastAPI) -> AsyncIterator[None]: # (1)!
async def _(request: Request, path: str = ""):
return await proxy.proxy(request=request, path=path) # (3)!

# Then run shell: `uvicorn <your.py>:app --host http://127.0.0.1:8000 --port 8000`
# Then run shell: `uvicorn <your_py>:app --host 127.0.0.1 --port 8000`
# visit the app: `http://127.0.0.1:8000/`
# you will get the response from `http://www.example.com/`
```
Expand Down Expand Up @@ -452,6 +454,8 @@ class ForwardHttpProxy(BaseHttpProxy):

# # Examples

## Basic usage

```python
from contextlib import asynccontextmanager
from typing import AsyncIterator
Expand All @@ -476,7 +480,7 @@ async def close_proxy_event(_: FastAPI) -> AsyncIterator[None]:
async def _(request: Request, path: str = ""):
return await proxy.proxy(request=request, path=path)

# Then run shell: `uvicorn <your.py>:app --host http://127.0.0.1:8000 --port 8000`
# Then run shell: `uvicorn <your_py>:app --host 127.0.0.1 --port 8000`
# visit the app: `http://127.0.0.1:8000/http://www.example.com`
# you will get the response from `http://www.example.com`
```
Expand Down
Loading