switch from aiohttp to httpx

This commit is contained in:
2024-10-24 23:18:23 +08:00
parent 2ca7f8e908
commit e2e4083321
8 changed files with 126 additions and 125 deletions

View File

@@ -9,7 +9,7 @@ RUN adduser -D luser
USER luser USER luser
WORKDIR /home/luser WORKDIR /home/luser
COPY --chown=luser:users ./requirements-dev.txt ./requirements-dev.txt COPY --chown=luser:users ./requirements-dev.txt ./requirements-dev.txt
COPY --chown=luser:users ./requirements-dev.txt ./requirements-run.txt COPY --chown=luser:users ./requirements-run.txt ./requirements-run.txt
WORKDIR /home/luser/ WORKDIR /home/luser/
RUN python -m venv .venv RUN python -m venv .venv
RUN --mount=type=cache,target=/home/luser/.cache/pip,uid=1000,gid=1000 .venv/bin/pip wheel -w /home/luser/wheel -r requirements-dev.txt pygraphviz RUN --mount=type=cache,target=/home/luser/.cache/pip,uid=1000,gid=1000 .venv/bin/pip wheel -w /home/luser/wheel -r requirements-dev.txt pygraphviz

View File

@@ -29,7 +29,7 @@ dependencies = [
"PyYAML", "PyYAML",
"pygraphviz", "pygraphviz",
"aiofiles", "aiofiles",
"aiohttp[speedups]" "httpx[http2]"
] ]
[project.optional-dependencies] [project.optional-dependencies]

View File

@@ -2,38 +2,27 @@
# This file is autogenerated by pip-compile with Python 3.10 # This file is autogenerated by pip-compile with Python 3.10
# by the following command: # by the following command:
# #
# pip-compile --extra-index-url=https://gitea.woggioni.net/api/packages/woggioni/pypi/simple --extra=dev --output-file=requirements-dev.txt --strip-extras pyproject.toml # pip-compile --extra-index-url=https://gitea.woggioni.net/api/packages/woggioni/pypi/simple --extra=dev --output-file=requirements-dev.txt pyproject.toml
# #
--extra-index-url https://gitea.woggioni.net/api/packages/woggioni/pypi/simple --extra-index-url https://gitea.woggioni.net/api/packages/woggioni/pypi/simple
aiodns==3.2.0
# via aiohttp
aiofiles==24.1.0 aiofiles==24.1.0
# via bugis (pyproject.toml) # via bugis (pyproject.toml)
aiohappyeyeballs==2.4.3 anyio==4.6.2.post1
# via aiohttp # via httpx
aiohttp==3.10.10
# via bugis (pyproject.toml)
aiosignal==1.3.1
# via aiohttp
asttokens==2.4.1 asttokens==2.4.1
# via stack-data # via stack-data
async-timeout==4.0.3
# via aiohttp
attrs==24.2.0
# via aiohttp
backports-tarfile==1.2.0 backports-tarfile==1.2.0
# via jaraco-context # via jaraco-context
brotli==1.1.0
# via aiohttp
build==1.2.2.post1 build==1.2.2.post1
# via bugis (pyproject.toml) # via bugis (pyproject.toml)
certifi==2024.8.30 certifi==2024.8.30
# via requests
cffi==1.17.1
# via # via
# cryptography # httpcore
# pycares # httpx
# requests
cffi==1.17.1
# via cryptography
charset-normalizer==3.4.0 charset-normalizer==3.4.0
# via requests # via requests
click==8.1.7 click==8.1.7
@@ -47,19 +36,30 @@ decorator==5.1.1
docutils==0.21.2 docutils==0.21.2
# via readme-renderer # via readme-renderer
exceptiongroup==1.2.2 exceptiongroup==1.2.2
# via ipython # via
# anyio
# ipython
executing==2.1.0 executing==2.1.0
# via stack-data # via stack-data
frozenlist==1.4.1
# via
# aiohttp
# aiosignal
granian==1.6.1 granian==1.6.1
# via bugis (pyproject.toml) # via bugis (pyproject.toml)
h11==0.14.0
# via httpcore
h2==4.1.0
# via httpx
hpack==4.0.0
# via h2
httpcore==1.0.6
# via httpx
httpx[http2]==0.27.2
# via bugis (pyproject.toml)
hyperframe==6.0.1
# via h2
idna==3.10 idna==3.10
# via # via
# anyio
# httpx
# requests # requests
# yarl
importlib-metadata==8.5.0 importlib-metadata==8.5.0
# via # via
# keyring # keyring
@@ -94,10 +94,6 @@ more-itertools==10.5.0
# via # via
# jaraco-classes # jaraco-classes
# jaraco-functools # jaraco-functools
multidict==6.1.0
# via
# aiohttp
# yarl
mypy==1.12.1 mypy==1.12.1
# via bugis (pyproject.toml) # via bugis (pyproject.toml)
mypy-extensions==1.0.0 mypy-extensions==1.0.0
@@ -114,16 +110,12 @@ pkginfo==1.10.0
# via twine # via twine
prompt-toolkit==3.0.48 prompt-toolkit==3.0.48
# via ipython # via ipython
propcache==0.2.0
# via yarl
ptyprocess==0.7.0 ptyprocess==0.7.0
# via pexpect # via pexpect
pure-eval==0.2.3 pure-eval==0.2.3
# via stack-data # via stack-data
pwo==0.0.3 pwo==0.0.3
# via bugis (pyproject.toml) # via bugis (pyproject.toml)
pycares==4.4.0
# via aiodns
pycparser==2.22 pycparser==2.22
# via cffi # via cffi
pygments==2.18.0 pygments==2.18.0
@@ -154,6 +146,10 @@ secretstorage==3.3.3
# via keyring # via keyring
six==1.16.0 six==1.16.0
# via asttokens # via asttokens
sniffio==1.3.1
# via
# anyio
# httpx
stack-data==0.6.3 stack-data==0.6.3
# via ipython # via ipython
tomli==2.0.2 tomli==2.0.2
@@ -169,8 +165,8 @@ twine==5.1.1
# via bugis (pyproject.toml) # via bugis (pyproject.toml)
typing-extensions==4.7.1 typing-extensions==4.7.1
# via # via
# anyio
# ipython # ipython
# multidict
# mypy # mypy
# pwo # pwo
# rich # rich
@@ -184,7 +180,5 @@ watchdog==5.0.3
# via bugis (pyproject.toml) # via bugis (pyproject.toml)
wcwidth==0.2.13 wcwidth==0.2.13
# via prompt-toolkit # via prompt-toolkit
yarl==1.16.0
# via aiohttp
zipp==3.20.2 zipp==3.20.2
# via importlib-metadata # via importlib-metadata

View File

@@ -2,65 +2,59 @@
# This file is autogenerated by pip-compile with Python 3.10 # This file is autogenerated by pip-compile with Python 3.10
# by the following command: # by the following command:
# #
# pip-compile --extra-index-url=https://gitea.woggioni.net/api/packages/woggioni/pypi/simple --extra=run --output-file=requirements-run.txt --strip-extras pyproject.toml # pip-compile --extra-index-url=https://gitea.woggioni.net/api/packages/woggioni/pypi/simple --extra=run --output-file=requirements-run.txt pyproject.toml
# #
--extra-index-url https://gitea.woggioni.net/api/packages/woggioni/pypi/simple --extra-index-url https://gitea.woggioni.net/api/packages/woggioni/pypi/simple
aiodns==3.2.0
# via aiohttp
aiofiles==24.1.0 aiofiles==24.1.0
# via bugis (pyproject.toml) # via bugis (pyproject.toml)
aiohappyeyeballs==2.4.3 anyio==4.6.2.post1
# via aiohttp # via httpx
aiohttp==3.10.10 certifi==2024.8.30
# via bugis (pyproject.toml) # via
aiosignal==1.3.1 # httpcore
# via aiohttp # httpx
async-timeout==4.0.3
# via aiohttp
attrs==24.2.0
# via aiohttp
brotli==1.1.0
# via aiohttp
cffi==1.17.1
# via pycares
click==8.1.7 click==8.1.7
# via granian # via granian
frozenlist==1.4.1 exceptiongroup==1.2.2
# via # via anyio
# aiohttp
# aiosignal
granian==1.6.1 granian==1.6.1
# via bugis (pyproject.toml) # via bugis (pyproject.toml)
h11==0.14.0
# via httpcore
h2==4.1.0
# via httpx
hpack==4.0.0
# via h2
httpcore==1.0.6
# via httpx
httpx[http2]==0.27.2
# via bugis (pyproject.toml)
hyperframe==6.0.1
# via h2
idna==3.10 idna==3.10
# via yarl # via
# anyio
# httpx
markdown==3.7 markdown==3.7
# via bugis (pyproject.toml) # via bugis (pyproject.toml)
multidict==6.1.0
# via
# aiohttp
# yarl
propcache==0.2.0
# via yarl
pwo==0.0.3 pwo==0.0.3
# via bugis (pyproject.toml) # via bugis (pyproject.toml)
pycares==4.4.0
# via aiodns
pycparser==2.22
# via cffi
pygments==2.18.0 pygments==2.18.0
# via bugis (pyproject.toml) # via bugis (pyproject.toml)
pygraphviz==1.14 pygraphviz==1.14
# via bugis (pyproject.toml) # via bugis (pyproject.toml)
pyyaml==6.0.2 pyyaml==6.0.2
# via bugis (pyproject.toml) # via bugis (pyproject.toml)
sniffio==1.3.1
# via
# anyio
# httpx
typing-extensions==4.7.1 typing-extensions==4.7.1
# via # via
# multidict # anyio
# pwo # pwo
uvloop==0.21.0 uvloop==0.21.0
# via granian # via granian
watchdog==5.0.3 watchdog==5.0.3
# via bugis (pyproject.toml) # via bugis (pyproject.toml)
yarl==1.16.0
# via aiohttp

View File

@@ -6,55 +6,49 @@
# #
--extra-index-url https://gitea.woggioni.net/api/packages/woggioni/pypi/simple --extra-index-url https://gitea.woggioni.net/api/packages/woggioni/pypi/simple
aiodns==3.2.0
# via aiohttp
aiofiles==24.1.0 aiofiles==24.1.0
# via bugis (pyproject.toml) # via bugis (pyproject.toml)
aiohappyeyeballs==2.4.3 anyio==4.6.2.post1
# via aiohttp # via httpx
aiohttp==3.10.10 certifi==2024.8.30
# via bugis (pyproject.toml)
aiosignal==1.3.1
# via aiohttp
async-timeout==4.0.3
# via aiohttp
attrs==24.2.0
# via aiohttp
brotli==1.1.0
# via aiohttp
cffi==1.17.1
# via pycares
frozenlist==1.4.1
# via # via
# aiohttp # httpcore
# aiosignal # httpx
exceptiongroup==1.2.2
# via anyio
h11==0.14.0
# via httpcore
h2==4.1.0
# via httpx
hpack==4.0.0
# via h2
httpcore==1.0.6
# via httpx
httpx==0.27.2
# via bugis (pyproject.toml)
hyperframe==6.0.1
# via h2
idna==3.10 idna==3.10
# via yarl # via
# anyio
# httpx
markdown==3.7 markdown==3.7
# via bugis (pyproject.toml) # via bugis (pyproject.toml)
multidict==6.1.0
# via
# aiohttp
# yarl
propcache==0.2.0
# via yarl
pwo==0.0.3 pwo==0.0.3
# via bugis (pyproject.toml) # via bugis (pyproject.toml)
pycares==4.4.0
# via aiodns
pycparser==2.22
# via cffi
pygments==2.18.0 pygments==2.18.0
# via bugis (pyproject.toml) # via bugis (pyproject.toml)
pygraphviz==1.14 pygraphviz==1.14
# via bugis (pyproject.toml) # via bugis (pyproject.toml)
pyyaml==6.0.2 pyyaml==6.0.2
# via bugis (pyproject.toml) # via bugis (pyproject.toml)
sniffio==1.3.1
# via
# anyio
# httpx
typing-extensions==4.7.1 typing-extensions==4.7.1
# via # via
# multidict # anyio
# pwo # pwo
watchdog==5.0.3 watchdog==5.0.3
# via bugis (pyproject.toml) # via bugis (pyproject.toml)
yarl==1.16.0
# via aiohttp

View File

@@ -14,14 +14,14 @@ from pwo import Maybe
from .server import Server from .server import Server
from asyncio import get_running_loop from asyncio import get_running_loop
from .asgi_utils import decode_headers from .asgi_utils import decode_headers
from typing import Optional from typing import Optional, Awaitable, Callable, Any, Mapping
log = logging.getLogger('access') log = logging.getLogger('access')
log.propagate = False log.propagate = False
_server : Optional[Server] = None _server : Optional[Server] = None
async def application(scope, receive, send): async def application(scope, receive, send : Callable[[Mapping[str, Any]], Awaitable[None]]):
global _server global _server
if scope['type'] == 'lifespan': if scope['type'] == 'lifespan':
while True: while True:

View File

@@ -1,17 +1,19 @@
from typing import TYPE_CHECKING from typing import TYPE_CHECKING
from aiofiles import open as async_open from aiofiles import open as async_open
from aiohttp import ClientSession
from .configuration import Configuration from .configuration import Configuration
from yarl import URL
if TYPE_CHECKING: if TYPE_CHECKING:
from _typeshed import StrOrBytesPath from _typeshed import StrOrBytesPath
from httpx import AsyncClient, URL
from typing import Callable, Awaitable
from urllib.parse import urljoin
async def render_plant_uml(path: 'StrOrBytesPath') -> bytes: chunk_size = 0x10000
async with ClientSession() as session: async def render_plant_uml(client: AsyncClient, path: 'StrOrBytesPath', send : Callable[[bytes], Awaitable[None]]):
url = URL(Configuration.instance.plant_uml_server_address) / 'svg' url = URL(urljoin(Configuration.instance.plant_uml_server_address, 'svg'))
async with async_open(path, 'rb') as file: async with async_open(path, 'rb') as file:
source = await file.read() source = await file.read()
async with session.post(url, data=source) as response: response = await client.post(url, content=source)
response.raise_for_status() response.raise_for_status()
return await response.read() async for chunk in response.aiter_bytes(chunk_size=chunk_size):
await send(chunk)

View File

@@ -6,7 +6,7 @@ from io import BytesIO
from mimetypes import init as mimeinit, guess_type from mimetypes import init as mimeinit, guess_type
from os import getcwd from os import getcwd
from os.path import join, normpath, splitext, relpath, basename from os.path import join, normpath, splitext, relpath, basename
from typing import Callable, TYPE_CHECKING, Optional, Awaitable, AsyncGenerator, Any from typing import Callable, TYPE_CHECKING, Optional, Awaitable, AsyncGenerator, Any, Mapping
import pygraphviz as pgv import pygraphviz as pgv
from aiofiles import open as async_open from aiofiles import open as async_open
@@ -14,6 +14,7 @@ from aiofiles.base import AiofilesContextManager
from aiofiles.os import listdir from aiofiles.os import listdir
from aiofiles.ospath import exists, isdir, isfile, getmtime from aiofiles.ospath import exists, isdir, isfile, getmtime
from aiofiles.threadpool.binary import AsyncBufferedReader from aiofiles.threadpool.binary import AsyncBufferedReader
from httpx import AsyncClient
from pwo import Maybe from pwo import Maybe
from .asgi_utils import encode_headers from .asgi_utils import encode_headers
@@ -52,7 +53,10 @@ def is_plant_uml(filepath):
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
class Server: class Server:
_loop : AbstractEventLoop root_dir: 'StrOrBytesPath'
prefix: Optional['StrOrBytesPath']
_loop: AbstractEventLoop
_client: AsyncClient
def __init__(self, def __init__(self,
root_dir: 'StrOrBytesPath' = getcwd(), root_dir: 'StrOrBytesPath' = getcwd(),
@@ -63,8 +67,15 @@ class Server:
self.file_watcher = FileWatcher(cwd) self.file_watcher = FileWatcher(cwd)
self.prefix = prefix and normpath(f'{prefix.decode()}') self.prefix = prefix and normpath(f'{prefix.decode()}')
self._loop = loop self._loop = loop
self._client = AsyncClient()
async def handle_request(self, method: str, url_path: str, etag: Optional[str], query_string: Optional[str], send): async def handle_request(self,
method: str,
url_path: str,
etag: Optional[str],
query_string: Optional[str],
send: Callable[[Mapping[str, Any]], Awaitable[None]]
):
if method != 'GET': if method != 'GET':
await send({ await send({
'type': 'http.response.start', 'type': 'http.response.start',
@@ -88,7 +99,7 @@ class Server:
lambda: completed_future(mtime) lambda: completed_future(mtime)
) )
if etag and etag == digest: if etag and etag == digest:
await self.not_modified(send, digest, ('Cache-Control', 'must-revalidate, max-age=86400')) await self.not_modified(send, digest, 'must-revalidate, max-age=86400')
return return
elif content: elif content:
mime_type = guess_type(basename(url_path))[0] or 'application/octet-stream' mime_type = guess_type(basename(url_path))[0] or 'application/octet-stream'
@@ -163,7 +174,6 @@ class Server:
}) })
elif is_plant_uml(path): elif is_plant_uml(path):
logger.debug("Starting PlantUML rendering for file '%s'", path) logger.debug("Starting PlantUML rendering for file '%s'", path)
body = await render_plant_uml(path)
logger.debug("Completed PlantUML rendering for file '%s'", path) logger.debug("Completed PlantUML rendering for file '%s'", path)
await send({ await send({
'type': 'http.response.start', 'type': 'http.response.start',
@@ -174,9 +184,15 @@ class Server:
'Cache-Control': 'no-cache' 'Cache-Control': 'no-cache'
}) })
}) })
await render_plant_uml(self._client, path, lambda chunk: send({
'type': 'http.response.body',
'body': chunk,
'more_body': True
}))
await send({ await send({
'type': 'http.response.body', 'type': 'http.response.body',
'body': body 'body': '',
'more_body': False
}) })
else: else:
async def read_file(file_path): async def read_file(file_path):
@@ -373,4 +389,5 @@ class Server:
return result return result
async def stop(self): async def stop(self):
await self.file_watcher.stop() await self.file_watcher.stop()
await self._client.aclose()