fastapi-proxy-lib icon indicating copy to clipboard operation
fastapi-proxy-lib copied to clipboard

Incoming query params without value are modified

Open wabiloo opened this issue 4 months ago • 2 comments

Describe the bug

I am working with a remote service that expects (among other things) a query parameter without value, eg "&bla". However when going through the reverse proxy, the call made adds an = sign, which renders the call invalid for that remote service (over which I have no control)

To Reproduce

  1. create a file main.py
from fastapi_proxy_lib.core.http import ReverseHttpProxy
from httpx import AsyncClient
from starlette.requests import Request
import logging 

logging.basicConfig(level=logging.DEBUG)

proxy = ReverseHttpProxy(AsyncClient(), base_url="https://www.httpbin.org/")

app = FastAPI()

@app.get("/get")
async def get(request: Request):
    return await proxy.proxy(request=request, path="get")
  1. Run with uvicorn main:app --host 127.0.0.1 --port 8000
  2. Call http://127.0.01:8000/get?bla
  3. Notice in the logs that the call is made to https://www.httpbin.org/get?bla=, instead of https://www.httpbin.org/get?bla

Expected behavior

Query parameters should remain unchanged, even if they have no value

Configuration

  • Python version: 13
  • OS version: MacOS 15.5
  • deps version: annotated-types==0.7.0 anyio==4.6.2.post1 Brotli==1.1.0 certifi==2024.8.30 charset-normalizer==3.4.0 click==8.1.7 dnspython==2.7.0 email_validator==2.2.0 fastapi==0.111.1 fastapi-cli==0.0.5 fastapi-proxy-lib==0.3.0 h11==0.14.0 httpcore==1.0.6 httptools==0.6.4 httpx==0.27.2 httpx-ws==0.7.2 idna==3.10 Jinja2==3.1.4 loguru==0.7.2 markdown-it-py==3.0.0 MarkupSafe==3.0.2 mdurl==0.1.2 pydantic==2.9.2 pydantic_core==2.23.4 Pygments==2.18.0 python-dotenv==1.0.1 python-multipart==0.0.17 PyYAML==6.0.2 requests==2.32.3 rich==13.9.4 shellingham==1.5.4 sniffio==1.3.1 starlette==0.37.2 typer==0.13.0 typing_extensions==4.12.2 urllib3==2.2.3 uvicorn==0.29.0 uvloop==0.21.0 watchfiles==0.24.0 websockets==13.1 wsproto==1.2.0

wabiloo avatar Aug 14 '25 15:08 wabiloo

I believe the issue comes from the way the request is built, through the use of query_params

proxy_request = client.build_request(
            method=request.method,
            url=target_url,
            params=tuple(request.query_params.multi_items()),
            headers=proxy_header,
            content=request_content,  # FIXME: 一个已知问题是,流式响应头包含'transfer-encoding': 'chunked',但有些服务器会400拒绝这个头
            # cookies=request.cookies,  # NOTE: headers中已有的cookie优先级高,所以这里不需要
        )

wabiloo avatar Aug 14 '25 16:08 wabiloo

Hi @wabiloo, thank you for reporting this. I would say the current behavior is expected.

The use of &bla as a query parameter is ambiguous. Different frameworks may interpret it differently: as {"bla": ""}, {"bla": None}, or {"bla": True}. fastapi interprets it as {"bla": ""}. I'm not sure how your "remote service" interprets it.

We can't improve this situation because fastapi cannot distinguish between &bla and &bla=. For your specific case, you may need to use:

You might need to manually construct the URL, since as far as I know, using httpx.Request.params will not generate a URL like https://www.httpbin.org/get?bla (without the =).

Alternatively, you could contact the remote service and ask them to handle this case more flexibly.

WSH032 avatar Aug 15 '25 03:08 WSH032