Skip to content

Commit ebab5ca

Browse files
pavelsrpre-commit-ci[bot]WSH032
authored
docs: add example of Modify (redefine) response only to particular endpoint (#37)
* Add example of redefining particular endpoint * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * docs(CHANGELOG): update `CHANGELOG.md` * docs: move inline code snippets in the documentation to separate files --------- Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> Co-authored-by: WSH032 <614337162@qq.com> Co-authored-by: Sean Wang <126865849+WSH032@users.noreply.github.com>
1 parent fff2a17 commit ebab5ca

File tree

7 files changed

+121
-68
lines changed

7 files changed

+121
-68
lines changed

CHANGELOG.md

+4
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
2323

2424
## [Unreleased]
2525

26+
### Added
27+
28+
- [#37](https://github.com/WSH032/fastapi-proxy-lib/pull/37) - docs: add example of `Modify (redefine) response only to particular endpoint`. Thanks [@pavelsr](https://github.com/pavelsr)!
29+
2630
### Changed
2731

2832
- [#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)!

docs/Usage/Advanced.md

+15-68
Original file line numberDiff line numberDiff line change
@@ -30,27 +30,7 @@ See <https://www.python-httpx.org/advanced/#customizing-authentication>
3030
You can refer following example to implement a simple authentication:
3131

3232
```python
33-
import httpx
34-
from fastapi_proxy_lib.fastapi.app import reverse_http_app
35-
36-
37-
class MyCustomAuth(httpx.Auth):
38-
# ref: https://www.python-httpx.org/advanced/#customizing-authentication
39-
40-
def __init__(self, token: str):
41-
self.token = token
42-
43-
def auth_flow(self, request: httpx.Request):
44-
# Send the request, with a custom `X-Authentication` header.
45-
request.headers["X-Authentication"] = self.token
46-
yield request
47-
48-
49-
app = reverse_http_app(
50-
client=httpx.AsyncClient(auth=MyCustomAuth("bearer_token")),
51-
base_url="http://www.httpbin.org/",
52-
)
53-
33+
--8<-- "docs_src/advanced/modify-request.py"
5434
```
5535

5636
visit `/headers` to see the result which contains `"X-Authentication": "bearer_token"` header.
@@ -64,57 +44,24 @@ See [issue#15](https://github.com/WSH032/fastapi-proxy-lib/issues/15)
6444
You can refer following example to modify the response:
6545

6646
```python
67-
from contextlib import asynccontextmanager
68-
from typing import AsyncIterable, AsyncIterator, Union
69-
70-
from fastapi import FastAPI
71-
from fastapi_proxy_lib.core.http import ReverseHttpProxy
72-
from starlette.requests import Request
73-
from starlette.responses import StreamingResponse
74-
75-
AsyncContentStream = AsyncIterable[Union[str, bytes]]
76-
77-
78-
proxy = ReverseHttpProxy(base_url="http://www.example.com/")
79-
80-
81-
@asynccontextmanager
82-
async def close_proxy_event(_: FastAPI) -> AsyncIterator[None]:
83-
"""Close proxy."""
84-
yield
85-
await proxy.aclose()
86-
87-
88-
app = FastAPI(lifespan=close_proxy_event)
89-
90-
91-
async def new_content(origin_content: AsyncContentStream) -> AsyncContentStream:
92-
"""Fake content processing."""
93-
async for chunk in origin_content:
94-
# do some processing with chunk, e.g transcoding,
95-
# here we just print and return it as an example.
96-
print(chunk)
97-
yield chunk
47+
--8<-- "docs_src/advanced/modify-response.py"
48+
```
9849

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

100-
@app.get("/{path:path}")
101-
async def _(request: Request, path: str = ""):
102-
proxy_response = await proxy.proxy(request=request, path=path)
52+
## Modify (redefine) response only to particular endpoint
10353

104-
if isinstance(proxy_response, StreamingResponse):
105-
# get the origin content stream
106-
old_content = proxy_response.body_iterator
54+
```python
55+
--8<-- "docs_src/advanced/modify-response-particular.py"
56+
```
10757

108-
new_resp = StreamingResponse(
109-
content=new_content(old_content),
110-
status_code=proxy_response.status_code,
111-
headers=proxy_response.headers,
112-
media_type=proxy_response.media_type,
113-
)
114-
return new_resp
58+
In this example all requests except `GET /ip` will be passed to `httpbin.org`:
11559

116-
return proxy_response
60+
```bash
61+
# we assume your proxy server is running on `http://127.0.0.0:8000`
11762

63+
# from `httpbin.org` which is proxied
64+
curl http://127.0.0.0:8000/user-agent # { "user-agent": "curl/7.81.0" }
65+
# from your fastapi app
66+
curl http://127.0.0.0:8000/ip # { "msg":"Method is redefined" }
11867
```
119-
120-
visit `/`, you will notice that the response body is printed to the console.

docs_src/advanced/modify-request.py

+24
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
from collections.abc import Generator
2+
from typing import Any
3+
4+
import httpx
5+
from fastapi_proxy_lib.fastapi.app import reverse_http_app
6+
from httpx import Request
7+
8+
9+
class MyCustomAuth(httpx.Auth):
10+
# ref: https://www.python-httpx.org/advanced/#customizing-authentication
11+
12+
def __init__(self, token: str):
13+
self.token = token
14+
15+
def auth_flow(self, request: httpx.Request) -> Generator[Request, Any, None]:
16+
# Send the request, with a custom `X-Authentication` header.
17+
request.headers["X-Authentication"] = self.token
18+
yield request
19+
20+
21+
app = reverse_http_app(
22+
client=httpx.AsyncClient(auth=MyCustomAuth("bearer_token")),
23+
base_url="http://www.httpbin.org/",
24+
)
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
from collections.abc import AsyncIterator
2+
from contextlib import asynccontextmanager
3+
4+
from fastapi import FastAPI
5+
from fastapi_proxy_lib.core.http import ReverseHttpProxy
6+
from starlette.requests import Request
7+
8+
proxy = ReverseHttpProxy(base_url="http://httpbin.org/")
9+
10+
11+
@asynccontextmanager
12+
async def close_proxy_event(_: FastAPI) -> AsyncIterator[None]:
13+
"""Close proxy."""
14+
yield
15+
await proxy.aclose()
16+
17+
18+
app = FastAPI(lifespan=close_proxy_event)
19+
20+
21+
@app.get("/{path:path}")
22+
@app.post("/{path:path}")
23+
async def _(request: Request, path: str = ""):
24+
if path == "ip" and request.method == "GET":
25+
return {"msg": "Method is redefined"}
26+
else:
27+
return await proxy.proxy(request=request, path=path)

docs_src/advanced/modify-response.py

+47
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
from collections.abc import AsyncIterator
2+
from contextlib import asynccontextmanager
3+
4+
from fastapi import FastAPI
5+
from fastapi_proxy_lib.core.http import ReverseHttpProxy
6+
from starlette.requests import Request
7+
from starlette.responses import AsyncContentStream, StreamingResponse
8+
9+
proxy = ReverseHttpProxy(base_url="http://www.example.com/")
10+
11+
12+
@asynccontextmanager
13+
async def close_proxy_event(_: FastAPI) -> AsyncIterator[None]:
14+
"""Close proxy."""
15+
yield
16+
await proxy.aclose()
17+
18+
19+
app = FastAPI(lifespan=close_proxy_event)
20+
21+
22+
async def new_content(origin_content: AsyncContentStream) -> AsyncContentStream:
23+
"""Fake content processing."""
24+
async for chunk in origin_content:
25+
# do some processing with chunk, e.g transcoding,
26+
# here we just print and return it as an example.
27+
print(chunk)
28+
yield chunk
29+
30+
31+
@app.get("/{path:path}")
32+
async def _(request: Request, path: str = ""):
33+
proxy_response = await proxy.proxy(request=request, path=path)
34+
35+
if isinstance(proxy_response, StreamingResponse):
36+
# get the origin content stream
37+
old_content = proxy_response.body_iterator
38+
39+
new_resp = StreamingResponse(
40+
content=new_content(old_content),
41+
status_code=proxy_response.status_code,
42+
headers=proxy_response.headers,
43+
media_type=proxy_response.media_type,
44+
)
45+
return new_resp
46+
47+
return proxy_response

mkdocs.yml

+1
Original file line numberDiff line numberDiff line change
@@ -130,6 +130,7 @@ watch:
130130
- README.md
131131
- CONTRIBUTING.md
132132
- CHANGELOG.md
133+
- docs_src/
133134

134135
validation:
135136
omitted_files: warn

pyproject.toml

+3
Original file line numberDiff line numberDiff line change
@@ -191,6 +191,9 @@ ignore = [
191191
# "SIM108", # if-else-block-instead-of-if-exp
192192
]
193193

194+
[tool.ruff.lint.per-file-ignores]
195+
"docs_src/**/*.py" = ["D"]
196+
194197
# https://docs.astral.sh/ruff/settings/#pydocstyle
195198
[tool.ruff.lint.pydocstyle]
196199
convention = "google"

0 commit comments

Comments
 (0)