mirror of
https://github.com/Fguarien/FGU-bookfusion.git
synced 2026-10-14 03:50:00 +00:00
fix(api): force dict for form data, drop Authorization on presigned upload
httpx.AsyncClient rejects requests whose body stream is a sync IteratorByteStream. That stream is what httpx builds whenever you pass data as a list of tuples, even when files are present. Convert data to a dict before delegating to httpx (duplicate keys collapse to a list value, which httpx still encodes correctly). The presigned S3 upload URL embeds its own signature in the query string and returns InvalidArgument: Unsupported Authorization Type if any extra Authorization header tags along. Build the request explicitly and drop the client-wide Basic auth header before sending whenever url_override is set. Tested end-to-end against BookFusion via the calibre-web-automated proxy. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
22950370de
commit
56b2a2cafa
2 changed files with 27 additions and 2 deletions
|
|
@ -14,6 +14,11 @@ COPY pyproject.toml README.md ./
|
|||
COPY src ./src
|
||||
|
||||
RUN pip install --upgrade pip && pip install .
|
||||
# httpx 0.28 ships a regression that raises
|
||||
# "Attempted to send an sync request with an AsyncClient instance"
|
||||
# on multipart POSTs (used by bookfusion's /uploads/init). Pin to 0.27.x
|
||||
# until the upstream fix lands.
|
||||
RUN pip install --no-cache-dir "httpx>=0.27,<0.28"
|
||||
|
||||
# Non-root user for read-only library access.
|
||||
RUN useradd -r -u 1000 -m bookfusion
|
||||
|
|
|
|||
|
|
@ -139,14 +139,34 @@ class BookFusionClient:
|
|||
expect_json: bool = True,
|
||||
) -> Any:
|
||||
url = url_override or self._url(path)
|
||||
# httpx builds an IteratorByteStream (sync) whenever `data` is a list
|
||||
# of tuples — even when files are present — and AsyncClient rejects
|
||||
# those at send time. We must always pass a dict; duplicate keys are
|
||||
# collapsed to a list value, which httpx encodes as repeated fields
|
||||
# (form-urlencoded or multipart, depending on `files`).
|
||||
req_data: Any = data
|
||||
if data is not None:
|
||||
req_data = {}
|
||||
for k, v in data:
|
||||
if k in req_data:
|
||||
existing = req_data[k]
|
||||
req_data[k] = existing + [v] if isinstance(existing, list) else [existing, v]
|
||||
else:
|
||||
req_data[k] = v
|
||||
try:
|
||||
resp = await self._client.request(
|
||||
req = self._client.build_request(
|
||||
method,
|
||||
url,
|
||||
params=params,
|
||||
data=data,
|
||||
data=req_data,
|
||||
files=files,
|
||||
)
|
||||
# Presigned upload URLs (e.g. S3) embed their own signature in
|
||||
# the query string and reject any extra Authorization header.
|
||||
# Our BookFusion-wide Basic auth must not leak there.
|
||||
if url_override:
|
||||
req.headers.pop("Authorization", None)
|
||||
resp = await self._client.send(req)
|
||||
except httpx.HTTPError as exc:
|
||||
raise BookFusionError(f"Network error calling {method} {url}: {exc}") from exc
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue