Skip to content
New issue

Have a question about this project? # for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “#”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? # to your account

Error in handling duplicate query parameters #56

Closed
yhl-cs opened this issue Mar 24, 2025 · 1 comment · Fixed by #57
Closed

Error in handling duplicate query parameters #56

yhl-cs opened this issue Mar 24, 2025 · 1 comment · Fixed by #57
Labels
bug Something isn't working

Comments

@yhl-cs
Copy link
Contributor

yhl-cs commented Mar 24, 2025

Describe the Bug

When sending an HTTP request with multiple query parameters of the same name (e.g., command=ls&command=-l&command=--full-time&command=/), the parameters are incorrectly processed after passing through a reverse proxy.
Specifically, the proxy merges or drops duplicate parameters, resulting in the backend server receiving malformed parameters (e.g., only command=/).

This issue also affects WebSocket requests.

To Reproduce

  1. Run the server:
# http-server.py
from fastapi import FastAPI, Request

app = FastAPI()

@app.get("/test")
async def handle_request(request: Request):
    return {
        "message": "Request received by server",
        "query_params": request.query_params.__dict__,
    }

if __name__ == "__main__":
    import uvicorn
    uvicorn.run(app, host="127.0.0.1", port=8766)
$ python http-server.py 
INFO:     Started server process [1097915]
INFO:     Waiting for application startup.
INFO:     Application startup complete.
INFO:     Uvicorn running on http://127.0.0.1:8766 (Press CTRL+C to quit)
  1. Run the reverse proxy:
# http_rs.py
from fastapi_proxy_lib.fastapi.app import reverse_http_app

app = reverse_http_app(base_url="http://127.0.0.1:8766/")

if __name__ == "__main__":
    import uvicorn
    uvicorn.run(app, host="0.0.0.0", port=8765)
$ python ./http-rs.py 
INFO:     Started server process [1098305]
INFO:     Waiting for application startup.
INFO:     Application startup complete.
INFO:     Uvicorn running on http://0.0.0.0:8765 (Press CTRL+C to quit)
  1. Send the HTTP request:
$ curl "http://127.0.0.1:8765/test?stdin=false&stdout=true&command=ls&command=-l&command=--full-time&command=%2F"
{"message":"Request received by server","query_params":{"_dict":{"stdin":"false","stdout":"true","command":"/"},"_list":[["stdin","false"],["stdout","true"],["command","/"]]}}
  1. Check the reverse proxy status (receives correct query parameters):
$ python ./http-rs.py 
INFO:     Started server process [1098305]
INFO:     Waiting for application startup.
INFO:     Application startup complete.
INFO:     Uvicorn running on http://0.0.0.0:8765 (Press CTRL+C to quit)
INFO:     127.0.0.1:33962 - "GET /test?stdin=false&stdout=true&command=ls&command=-l&command=--full-time&command=%2F HTTP/1.1" 200 OK
  1. Check the server status (receives incorrect query parameters):
$ python http-server.py 
INFO:     Started server process [1097915]
INFO:     Waiting for application startup.
INFO:     Application startup complete.
INFO:     Uvicorn running on http://127.0.0.1:8766 (Press CTRL+C to quit)
INFO:     127.0.0.1:48436 - "GET /test?stdin=false&stdout=true&command=%2F HTTP/1.1" 200 OK

Expected Behavior

The query parameters should remain unchanged after passing through the reverse proxy. For example:

INFO:     127.0.0.1:58978 - "GET /test?stdin=false&stdout=true&stderr=true&tty=false&command=ls&command=-l&command=--full-time&command=%2F HTTP/1.1" 200 OK

Additional Context

The original request query parameters are stdin=false&stdout=true&command=ls&command=-l&command=--full-time&command=%2F. These are converted into starlette.datastructures.QueryParams 1:

{'_dict': {'stdin': 'command': '/'}, '_list': [('stdin', 'false'), ('stdout', 'true'), ('command', 'ls'), ('command', '-l'), ('command', '--full-time'), ('command', '/')]}

During the initialization of httpx.QueryParams, the code enters the else branch 2. Here, value.items() only iterates over value._dict, resulting in the final httpx.QueryParams data:

{'stdin': ['false'], ('stdout', 'true'), 'command': ['/']}

Theoretically, it should iterate over value._list instead.

Configuration

  • python 3.12.3
  • httpx 0.28.1
  • fastAPI 0.115.6
  • fastAPI-proxy-lib 0.2.0

Footnotes

  1. https://github.com/encode/starlette/blob/master/starlette/datastructures.py#L373

  2. https://github.com/encode/httpx/blob/master/httpx/_urls.py#L436

@yhl-cs yhl-cs added the bug Something isn't working label Mar 24, 2025
@WSH032
Copy link
Owner

WSH032 commented Mar 24, 2025

@yhl-cs Nice catch! I think we just need to change these two pieces of code to request.query_params.multi_items():

Would you like to submit a PR? 🤓

yhl-cs pushed a commit to yhl-cs/fastapi-proxy-lib that referenced this issue Mar 24, 2025
Fix: WSH032#56

Signed-off-by: yhl-cs <yuhl18@zju.edu.cn>
yhl-cs pushed a commit to yhl-cs/fastapi-proxy-lib that referenced this issue Mar 25, 2025
Fix: WSH032#56

Signed-off-by: yhl-cs <yuhl18@zju.edu.cn>
yhl-cs pushed a commit to yhl-cs/fastapi-proxy-lib that referenced this issue Mar 25, 2025
Fix: WSH032#56

Signed-off-by: yhl-cs <yuhl18@zju.edu.cn>
@WSH032 WSH032 closed this as completed in d464f27 Mar 25, 2025
# for free to join this conversation on GitHub. Already have an account? # to comment
Labels
bug Something isn't working
Projects
None yet
Development

Successfully merging a pull request may close this issue.

2 participants