Skip to content

Commit

Permalink
Support extend default cdn hosts
Browse files Browse the repository at this point in the history
  • Loading branch information
waketzheng committed Dec 29, 2023
1 parent 7a53701 commit eca5cfb
Show file tree
Hide file tree
Showing 11 changed files with 106 additions and 15 deletions.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,7 @@ async def redoc_html():
# http://my-cdn.com/swagger-ui@latest/swagger-ui-bundle.js
# http://my-cdn.com/swagger-ui@latest/swagger-ui.css
# render /redoc with: `http://my-cdn.com/redoc/next/redoc.standalone.js`
monkey_patch_for_docs_ui(app, cdn_host=('http://my-cdn.com', ('/swagger-ui@latest/', '/redoc/next/')))
monkey_patch_for_docs_ui(app, docs_cdn_host=('http://my-cdn.com', ('/swagger-ui@latest/', '/redoc/next/')))
```

## License
Expand Down
11 changes: 6 additions & 5 deletions README.zh-hans.md
Original file line number Diff line number Diff line change
Expand Up @@ -35,16 +35,17 @@ monkey_patch_for_docs_ui(app)
没有的话,使用协程并发对比https://cdn.jsdelivr.net、https://unpkg.com、https://cdnjs.cloudflare.com
三个CDN的响应速度,然后自动采用速度最快的那个。

## 其他CDN
## 加入其他CDN作为备选

- 参考:https://github.com/lecepin/blog/blob/main/%E5%9B%BD%E5%86%85%E9%AB%98%E9%80%9F%E5%89%8D%E7%AB%AF%20Unpkg%20CDN%20%E6%9B%BF%E4%BB%A3%E6%96%B9%E6%A1%88.md
```py
from fastapi_cdn_host import CdnHostEnum
monkey_patch_for_docs_ui(
app,
cdn_host=(
'https://cdn.bootcdn.net/ajax/libs', ('/swagger-ui/{version}/', '') # BootCDN
# 'https://cdn.staticfile.org', ('/swagger-ui/{version}/', '') # 七牛云
# 'https://lf9-cdn-tos.bytecdntp.com/cdn/expire-1-M', ('/swagger-ui/{version}/', '') # 字节
docs_cdn_host=CdnHostEnum.extend(
('https://cdn.bootcdn.net/ajax/libs', ('/swagger-ui/{version}/', '')), # BootCDN
('https://cdn.staticfile.org', ('/swagger-ui/{version}/', '')), # 七牛云
('https://lf9-cdn-tos.bytecdntp.com/cdn/expire-1-M', ('/swagger-ui/{version}/', '')), # 字节
)
)
```
Expand Down
19 changes: 15 additions & 4 deletions fastapi_cdn_host/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,10 +22,11 @@
Annotated[str, "redoc path or url info(must startswith '/')"],
]
CdnDomainType = Annotated[str, "Host for swagger-ui/redoc"]
StrictCdnHostInfoType = Tuple[CdnDomainType, CdnPathInfoType]
CdnHostInfoType = Union[
Annotated[CdnDomainType, f"Will use DEFAULT_ASSET_PATH: {DEFAULT_ASSET_PATH}"],
Tuple[CdnDomainType, CdnPathInfoType],
Tuple[CdnDomainType, Annotated[str, "In case of swagger/redoc has the same path"]],
StrictCdnHostInfoType,
]


Expand All @@ -37,6 +38,10 @@ class CdnHostEnum(Enum):
OFFICIAL_REDOC,
)

@classmethod
def extend(cls, *host: StrictCdnHostInfoType) -> List[CdnHostInfoType]:
return [*host, *cls]


@dataclass
class AssetUrl:
Expand Down Expand Up @@ -118,6 +123,8 @@ def run(self) -> AssetUrl:
cdn_host = cdn_host.value
if isinstance(cdn_host, str):
return self.build_asset_url(cdn_host, favicon_url=favicon)
if isinstance(cdn_host, list) and isinstance(cdn_host[0], tuple):
return self.run_async(self.sniff_the_fastest, favicon, cdn_host)
cdn_host, asset_path = cdn_host
if isinstance(asset_path, str):
asset_path = (asset_path, asset_path)
Expand Down Expand Up @@ -160,8 +167,10 @@ def build_race_data(
return css_urls, they

@classmethod
async def sniff_the_fastest(cls, favicon_url=None) -> AssetUrl:
css_urls, they = cls.build_race_data(list(CdnHostEnum))
async def sniff_the_fastest(
cls, favicon_url=None, choices=list(CdnHostEnum)
) -> AssetUrl:
css_urls, they = cls.build_race_data(choices)
fast_css_url = await HttpSpider.find_fastest_host(css_urls)
fast_host, fast_asset_path = they[css_urls.index(fast_css_url)]
logger.info(f"Select cdn: {fast_host[0]} to serve swagger css/js")
Expand Down Expand Up @@ -345,7 +354,9 @@ def detect_local_file(

def monkey_patch_for_docs_ui(
app: FastAPI,
docs_cdn_host: Union[CdnHostEnum, CdnHostInfoType, Path, None] = None,
docs_cdn_host: Union[
CdnHostEnum, List[CdnHostInfoType], CdnHostInfoType, Path, None
] = None,
favicon_url: Union[str, None] = None,
) -> None:
"""Use local static files or the faster CDN host for docs asset(swagger-ui)
Expand Down
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[tool.poetry]
name = "fastapi-cdn-host"
version = "0.3.1"
version = "0.3.2"
description = ""
authors = ["Waket Zheng <[email protected]>"]
readme = "README.md"
Expand Down
1 change: 1 addition & 0 deletions scripts/test.sh
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ cd ../private_cdn && coverage run -m pytest test_*.py
cd ../cdn_with_default_asset_path && coverage run -m pytest test_*.py
cd ../explicit_cdn_host && coverage run -m pytest test_*.py
cd ../simple_asset_path && coverage run -m pytest test_*.py
cd ../extend_choices && coverage run -m pytest test_*.py

cd ../.. && coverage combine tests/*/.coverage
coverage report -m
33 changes: 33 additions & 0 deletions tests/extend_choices/main.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
#!/usr/bin/env python
from fastapi import FastAPI, Request
from fastapi.responses import RedirectResponse

from fastapi_cdn_host import CdnHostEnum, monkey_patch_for_docs_ui

app = FastAPI(title="FastAPI CDN host test")


@app.get("/", include_in_schema=False)
async def to_docs():
return RedirectResponse("/docs")


@app.get("/app")
async def get_app(request: Request) -> dict:
return {"routes": str(request.app.routes)}


monkey_patch_for_docs_ui(
app,
docs_cdn_host=CdnHostEnum.extend(
(
"https://cdn.bootcdn.net/ajax/libs",
("/swagger-ui/{version}/", ""),
), # BootCDN
("https://cdn.staticfile.org", ("/swagger-ui/{version}/", "")), # 七牛云
(
"https://lf9-cdn-tos.bytecdntp.com/cdn/expire-1-M",
("/swagger-ui/{version}/", ""),
), # 字节
),
)
45 changes: 45 additions & 0 deletions tests/extend_choices/test_extend.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
# mypy: no-disallow-untyped-decorators
import pytest
from httpx import AsyncClient
from main import app

from fastapi_cdn_host.client import CdnHostBuilder, CdnHostEnum


@pytest.fixture(scope="module")
def anyio_backend():
return "asyncio"


@pytest.fixture(scope="module")
async def client():
async with AsyncClient(app=app, base_url="http://test") as c:
yield c


@pytest.mark.anyio
async def test_docs(client: AsyncClient): # nosec
urls = await CdnHostBuilder.sniff_the_fastest(
choices=CdnHostEnum.extend(
(
"https://cdn.bootcdn.net/ajax/libs",
("/swagger-ui/{version}/", ""),
),
("https://cdn.staticfile.org", ("/swagger-ui/{version}/", "")),
(
"https://lf9-cdn-tos.bytecdntp.com/cdn/expire-1-M",
("/swagger-ui/{version}/", ""),
),
)
)
response = await client.get("/docs")
text = response.text
assert response.status_code == 200, text
assert urls.js in text
assert urls.css in text
response2 = await client.get("/redoc")
text2 = response2.text
assert response2.status_code == 200, text2
assert urls.redoc in text2
response = await client.get("/app")
assert response.status_code == 200
2 changes: 1 addition & 1 deletion tests/favicon_online_cdn/test_race_favicon.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,11 +19,11 @@ async def client():

@pytest.mark.anyio
async def test_docs(client: AsyncClient): # nosec
urls = await CdnHostBuilder.sniff_the_fastest()
response = await client.get("/docs")
text = response.text
assert response.status_code == 200, text
assert '"https://ubuntu.com/favicon.ico"' in text
urls = await CdnHostBuilder.sniff_the_fastest()
assert urls.js in text
assert urls.css in text
response2 = await client.get("/redoc")
Expand Down
2 changes: 1 addition & 1 deletion tests/online_cdn/test_online_race.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,11 +21,11 @@ async def client():

@pytest.mark.anyio
async def test_docs(client: AsyncClient): # nosec
urls = await CdnHostBuilder.sniff_the_fastest()
response = await client.get("/docs")
text = response.text
assert response.status_code == 200, text
assert default_favicon_url in text
urls = await CdnHostBuilder.sniff_the_fastest()
assert urls.js in text
assert urls.css in text
response2 = await client.get("/redoc")
Expand Down
2 changes: 1 addition & 1 deletion tests/root_path_without_static/test_root_path.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,11 +21,11 @@ async def client():

@pytest.mark.anyio
async def test_docs(client: AsyncClient): # nosec
urls = await CdnHostBuilder.sniff_the_fastest()
response = await client.get("/docs")
text = response.text
assert response.status_code == 200, text
assert default_favicon_url in text
urls = await CdnHostBuilder.sniff_the_fastest()
assert f'"{urls.js}"' in text
assert f'"{urls.css}"' in text
response2 = await client.get("/redoc")
Expand Down
2 changes: 1 addition & 1 deletion tests/static_favicon_without_swagger_ui/test_favicon.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,12 +21,12 @@ async def client():

@pytest.mark.anyio
async def test_docs(client: AsyncClient): # nosec
urls = await CdnHostBuilder.sniff_the_fastest()
response = await client.get("/docs")
text = response.text
assert response.status_code == 200, text
assert default_favicon_url not in text
assert '"/static/favicon.ico"' in text
urls = await CdnHostBuilder.sniff_the_fastest()
assert f'"{urls.js}"' in text
assert f'"{urls.css}"' in text
response2 = await client.get("/redoc")
Expand Down

0 comments on commit eca5cfb

Please sign in to comment.