Skip to content

Remote clients drop preceding URL values for multipart list file inputs #5675

Description

@HarperZ9

Summary

At current main (73c4dbead99be6515fa25fcd91e348ac30f5c22e), a schema-discovered BentoML client sending two URL-backed values for one multipart list[Path] field sends only the final URL value. I reproduced the same loss through the HTTPX sync/async clients and the separate aiohttp/RemoteProxy client.

A direct multipart request with two same-name parts reaches the service as two values, and local Path uploads through the client also preserve both.

Service under test

from pathlib import Path
import bentoml

@bentoml.service(metrics={"enabled": False})
class MultipartService:
    @bentoml.api
    def echo(self, files: list[Path]) -> list[str]:
        return [path.read_text() for path in files]

I used two distinct URL-backed fixtures whose contents were ALPHA and BRAVO. The clients were initialized in remote-schema mode (service is None; schema loaded from /schema.json; endpoint input_spec is None). I replaced only URL retrieval with an in-memory URL-to-UploadFile mapping so the test was deterministic and made no network request; client serialization, ASGI parsing, and service dispatch ran from the pinned source unchanged.

Results

DIRECT_URL_CONTROL        ["ALPHA", "BRAVO"]
LOCAL_FILE_CLIENT_CONTROL ["ALPHA", "BRAVO"]
HTTPX Sync remote client  ["BRAVO"]
HTTPX Async remote client ["BRAVO"]
proxy2 remote client      ["BRAVO"]

The direct control submitted:

files = [
    ("files", (None, "https://files.example.invalid/a.txt")),
    ("files", (None, "https://files.example.invalid/b.txt")),
]

For the client cases, the equivalent call was:

client.echo(files=[url_a, url_b])

Expected

Each URL in a multipart list field should produce one same-name part, so the service should receive both values in order, matching the direct-request and local-file controls.

Source-level cause

Both client implementations iterate the list but assign URL strings through data[name] = file. Because data is a mapping, each URL overwrites the previous value. Concrete local files instead use the repeated-entry files list. On the server side, MultipartSerde.parse_request() uses form.getlist(k), so repeated values are preserved if the client actually sends them.

Affected implementations:

  • src/_bentoml_impl/client/http.py (_build_multipart used by HTTPX sync/async clients)
  • src/_bentoml_impl/client/proxy2.py (duplicated aiohttp/RemoteProxy multipart builder)

This report is limited to remote-schema URL-backed list inputs. Local file uploads and direct repeated multipart requests are unaffected. I am not making a security claim, and this does not appear to be an OpenAPI-schema defect.

I searched open and closed issues/PRs for the builder, URL-backed multipart lists, and last-value overwrite behavior and did not find a direct match. I have a focused service-level regression covering the direct control and all three client paths. Would a narrow fix and regression test be welcome?

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions