Skip to content

Images not getting passed in Mesh setups (Includes possible fix) #366

Description

@v-mwalk

Area of Concern

  • Server
  • Installer
  • Server Dashboard or Explorer
  • Installation issue of one or more Modules. Please post the issue on the module's Issue list directly
  • Behaviour of one or more Modules Please post the issue on the module's Issue list directly
  • Something else

Server Info

  • Version: CodeProject.AI 2.9.5
  • Operating System & architecture: Reproduced across a Mesh pair - Windows x64 (forwarding server) and Debian/Raspberry Pi Linux-Arm64 (receiving server)
  • System RAM: 32Gb (Windows forwarding server)
  • Video card: N/A - not relevant to this bug
  • Using Docker? No (native installs on both servers)

Describe the bug
When a mesh request is received img is None - no image is set over the mesh request.

Detail
When Mesh request forwarding is enabled (AllowRequestForwarding: true) and a multipart/form-data request (e.g. a POST /v1/vision/detection image upload) is forwarded from one server to another via ProxyController.ForwardAsync(), the image/file data does not survive the forward. The receiving server gets a syntactically valid detect command but with no files attached, so any module that requires an image (e.g. ObjectDetectionCoral) receives img = None and fails or returns "No image provided".

Cause
I believe the cause is (src/server/Controllers/ProxyController.cs; ForwardAsync()):

HttpRequestMessage newRequest = new HttpRequestMessage(HttpMethod.Post, url)
{
    Content = new StreamContent(originalRequest.Body)
};

foreach (KeyValuePair<string, Microsoft.Extensions.Primitives.StringValues> header in originalRequest.Headers)
    newRequest.Headers.TryAddWithoutValidation(header.Key, header.Value.AsEnumerable());

This loop copies every header from the original incoming request - including Content-Type, which for a multipart upload carries the required boundary=... parameter - onto newRequest.Headers (the HttpRequestMessage header collection).

But content-specific headers (Content-Type, Content-Length, etc.) are only honoured by HttpClient when set on newRequest.Content.Headers (HttpContentHeaders), not on HttpRequestMessage.Headers.

Since StreamContent is constructed here with no explicit Content.Headers.ContentType assignment, the outgoing forwarded request never gets a usable Content-Type: multipart/form-data; boundary=... header on the wire. The receiving server's Kestrel pipeline then cannot recognise/parse the body as form data, Request.Form.Files comes back empty, and any file/image field is lost.

Note, there is a secondary effect that makes this harder to diagnose! lol. In DispatchRemoteRequest(), any forwarded response with success: false has it's recorded elapsed time being overridden to a flat 30 seconds before being fed into _meshManager.AddResponseTime(...):

if (!responseObject!.ContainsKey("success") || !(bool)responseObject["success"]!)
{
    elapsedMs = 30_000;
}

Im guessing this is to push dodgy remote servers out of routing contention? However, because the above bug makes every forwarded image request fail, every one gets this fake 30s timing recorded!! That so threw me for a while because the Mesh dashboard's tracked effectiveResponseTime for the route climbs toward 30 seconds and looks exactly like a network timeout/slow-server problem, when the real round trip is actually fast (a few hundred ms) and the actual issue is the lost multipart boundary.

Expected behavior
A forwarded multipart/form-data request (including any attached image/file) should arrive at the target Mesh server intact, with the same Content-Type/boundary as the original request, so the receiving server's module can process the image normally.

Screenshots
N/A

Additional context
My suggested fix is (Been out of cs for a few years, so no ide setup to try/debug sorry :-( ), in ForwardAsync(): fall back to newRequest.Content.Headers for any header that newRequest.Headers.TryAddWithoutValidation rejects (which correctly happens for known content-only headers like Content-Type/Content-Length). IE:

foreach (KeyValuePair<string, Microsoft.Extensions.Primitives.StringValues> header in originalRequest.Headers)
{
    if (!newRequest.Headers.TryAddWithoutValidation(header.Key, header.Value.AsEnumerable()))
        newRequest.Content!.Headers.TryAddWithoutValidation(header.Key, header.Value.AsEnumerable());
}

This ensures Content-Type (with its multipart boundary) and Content-Length land on the HttpContent object where HttpClient actually reads them from when serialising the outgoing request. Once fixed, the secondary 30s-penalty behaviour should also stop firing for image-forwarding requests, since they'll start succeeding.

Reproduction steps:

  1. Set up two CodeProject.AI Server instances with Mesh enabled, one forwarding-allowed (AllowRequestForwarding: true), one accepting (AcceptForwardedRequests: true), each aware of the other via KnownMeshHostnames.
  2. Ensure the forwarding server's mesh routing selects the remote server for vision/detection (e.g. by having no local module capable of handling that route, or by response-time metrics favouring the remote server).
  3. POST a multipart/form-data image detection request to the forwarding server's /v1/vision/detection endpoint.
  4. On the receiving server, observe the module log: the detect command arrives with no files/empty image, even though a valid image was attached to the original request.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Fields

    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions