You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

C#客户端MultipartFormData经Nginx上传模块转发至ASP.NET异常排查

问题:C# MultipartFormDataContent 与 Nginx Upload Module 兼容问题

我正在尝试通过Nginx Upload Module将C#客户端的图片存储到Nginx服务器,已将Nginx、ASP.NET Core服务、Echo服务部署为Docker容器并组网。使用curl上传文件可正常执行,但C#客户端的请求经Nginx处理后,会向ASP.NET服务转发空请求导致被拒绝。

Nginx配置(基本默认)

load_module modules/ngx_http_upload_module.so;

error_log /var/log/nginx/error.log debug;

events {

}


http {

    server {

        listen 80;

        # Allow file uploads max 1024M for example
        client_max_body_size 1024M;
        upload_buffer_size 10M;

        # POST URL
        location /Upload {
            # Pass altered request body to this location
            upload_pass @after_upload;
            # upload_pass_args on;

            # Store files to this directory
            # upload_store /tmp/nginx_upload/;
            upload_store /upload/;
            # Allow uploaded files to be world readable
            upload_store_access user:rw group:rw all:r;

            # Set specified fields in request body
            upload_set_form_field upload_file_name "$upload_file_name";
            upload_set_form_field upload_content_type "$upload_content_type";
            upload_set_form_field upload_tmp_path "$upload_tmp_path";

            # Inform backend about hash and size of a file
            upload_aggregate_form_field "$upload_field_name.md5" "$upload_file_md5";
            upload_aggregate_form_field "$upload_field_name.size" "$upload_file_size";

            upload_pass_form_field "^submit$|^description$";
            upload_cleanup 400 404 499 500-505;
        }

        location @after_upload {
            # proxy_pass http://commoditytracker;
            proxy_pass http://echo:8080;
            # add_header Content-Type "text/plain;charset=utf-8";
            # return 200 "Upload succeeded";
        }

        root /upload;

        location / {
            root /upload;
        }

        location /upload {
            alias /upload/;
        }
    }
}

C#客户端代码

构建Form Data(测试图片示例):

Console.WriteLine("Hello, World!");
string path = @"C:\Users\User\Downloads\test.jpg";
byte[] bytes = new byte[8] { 0x40, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20 };
if (File.Exists(path))
{
    bytes = File.ReadAllBytes(path);
}

var fileName = "test.jpg";
var contentType = "image/jpeg";


using (var client = new HttpClient())
{
    using (var content = new MultipartFormDataContent("boundary1234"))
    {
        content.Add(new ByteArrayContent(bytes), "file", $"{fileName}");

        List<KeyValuePair<string, string>> b = new List<KeyValuePair<string, string>>();
        var requestParameters = new KeyValuePair<string, string>("basketId", Guid.Empty.ToString());
        b.Add(requestParameters);
        var addMe = new FormUrlEncodedContent(b);

        content.Add(addMe);
        var Message = await client.PostAsync($"http://xxxxxxxxxxxxxx:8081/upload", content);
        Console.WriteLine(Message.StatusCode);
        Console.WriteLine(await Message.Content.ReadAsStringAsync());
    }

    Console.WriteLine("-------------------------------------------");

    using (var content = new MultipartFormDataContent("boundary1234"))
    {
        content.Add(new ByteArrayContent(bytes), "file", $"{fileName}");

        List<KeyValuePair<string, string>> b = new List<KeyValuePair<string, string>>();
        var requestParameters = new KeyValuePair<string, string>("basketId", Guid.Empty.ToString());
        b.Add(requestParameters);
        var addMe = new FormUrlEncodedContent(b);

        content.Add(addMe);
        var Message = await client.PostAsync($"https://xxxxxxxxxxxxxxx/Upload", content);
        Console.WriteLine(Message.StatusCode);
        Console.WriteLine(await Message.Content.ReadAsStringAsync());
        // NavigationManager.NavigateTo("fetchdata");
    }
}

请求结果

C#客户端直接发送至Echo服务的原始请求

{
    "path": "/upload",
    "headers": {
        "host": "xxxxxxxxxxxxxxxx:8081",
        "content-type": "image/jpeg",
        "content-length": "962"
    },
    "method": "POST",
    "body": "--boundary1234\r\nContent-Disposition: form-data; name=file; filename=test.jpg; filename*=utf-8''test.jpg\r\n\r\n[...]\r\n--boundary1234\r\nContent-Type: application/x-www-form-urlencoded\r\nContent-Disposition: form-data\r\n\r\nbasketId=00000000-0000-0000-0000-000000000000\r\n--boundary1234--\r\n",
    "fresh": false,
    "hostname": "xxxxxxxxxxxxxxxx",
    "ip": "xxxxxxxxxxxxxxxx",
    "ips": [],
    "protocol": "http",
    "query": {},
    "subdomains": [],
    "xhr": false,
    "os": {
        "hostname": "xxxxxxxxxxxxxx"
    },
    "connection": {}
}
xxxxxxxxxxxxxxxx - - [16/Jul/2023:10:40:26 +0000] "POST /upload HTTP/1.1" 200 - "-" "-"

Nginx处理后转发至Echo服务的代理请求

{
    "path": "/Upload",
    "headers": {
        "host": "echo:8080",
        "connection": "close",
        "content-length": "108",
        "x-real-ip": "10.0.0.2",
        "x-forwarded-for": "10.0.0.2",
        "x-forwarded-proto": "https",
        "x-forwarded-ssl": "on",
        "x-forwarded-port": "443",
        "x-original-uri": "/Upload",
        "content-type": "multipart/form-data; boundary=\"boundary1234\"",
        "content-disposition": "attachment; filename=test.jpg"
    },
    "method": "POST",
    "body": "--\"boundary1234\"\r\nContent-Disposition: form-data; name=\"<ngx_upload_module_dummy>\"\r\n\r\n\r\n--\"boundary1234\"--\r\n",
    "fresh": false,
    "hostname": "echo",
    "ip": "10.0.0.2",
    "ips": [
        "10.0.0.2"
    ],
    "protocol": "https",
    "query": {},
    "subdomains": [],
    "xhr": false,
    "os": {
        "hostname": "xxxxxxxxxxxxxxxx"
    },
    "connection": {}
}
10.0.0.2 - - [16/Jul/2023:10:40:27 +0000] "POST /Upload HTTP/1.0" 200 - "-" "-"

Curl请求对比结果

Curl直接发送至Echo服务的原始请求

PS C:\Users\User\Downloads>  curl -X 'POST' 'http://xxxxxxxxxxxxx:8081/Upload?basketId=05aa7960-3f4a-42f4-8112-c22b5f96a9b5' -H 'accept: multipart/form-data' -F "upload_file_name=@$(pwd)/test.jpg"
{
    "path": "/Upload",
    "headers": {
        "host": "xxxxxxxxxxxxx:8081",
        "user-agent": "curl/8.0.1",
        "accept": "multipart/form-data",
        "content-length": "877",
        "content-type": "multipart/form-data; boundary=------------------------46a959572bae9574"
    },
    "method": "POST",
    "body": "--------------------------46a959572bae9574\r\nContent-Disposition: form-data; name=\"upload_file_name\"; filename=\"test.jpg\"\r\nContent-Type: image/jpeg\r\n\r\n[...]\r\n--------------------------46a959572bae9574--\r\n",
    "fresh": false,
    "hostname": "xxxxxxxxxxxxxxxxx",
    "ip": "xxxxxxxxxxxxxxxxxxxxxxxxx",
    "ips": [],
    "protocol": "http",
    "query": {
        "basketId": "05aa7960-3f4a-42f4-8112-c22b5f96a9b5"
    },
    "subdomains": [],
    "xhr": false,
    "os": {
        "hostname": "xxxxxxxxxxxxxxxxxxx"
    },
    "connection": {}
}
xxxxxxxxxxxxxxxxxxxxx - - [16/Jul/2023:11:10:05 +0000] "POST /Upload?basketId=05aa7960-3f4a-42f4-8112-c22b5f96a9b5 HTTP/1.1" 200 - "-" "curl/8.0.1"

Nginx处理后转发至Echo服务的代理请求

PS C:\Users\User\Downloads>  curl -X 'POST' 'https://xxxxxxxxxxxxxxx/Upload?basketId=05aa7960-3f4a-42f4-8112-c22b5f96a9b5' -H 'accept: multipart/form-data' -F "upload_file_name=@$(pwd)/test.jpg"
10.0.0.2 - - [16/Jul/2023:11:07:05 +0000] "POST /Upload HTTP/1.0" 200 - "-" "-"
{
    "path": "/Upload",
    "headers": {
        "host": "echo:8080",
        "connection": "close",
        "content-length": "653",
        "x-real-ip": "10.0.0.2",
        "x-forwarded-for": "10.0.0.2",
        "x-forwarded-proto": "https",
        "x-forwarded-ssl": "on",
        "x-forwarded-port": "443",
        "x-original-uri": "/Upload?basketId=05aa7960-3f4a-42f4-8112-c22b5f96a9b5",
        "user-agent": "curl/8.0.1",
        "accept": "multipart/form-data",
        "content-type": "multipart/form-data; boundary=------------------------069346ec29b44fc6"
    },
    "method": "POST",
    "body": "--------------------------069346ec29b44fc6\r\nContent-Disposition: form-data; name=\"upload_file_name\"\r\n\r\ntest.jpg\r\n--------------------------069346ec29b44fc6\r\nContent-Disposition: form-data; name=\"upload_content_type\"\r\n\r\nimage/jpeg\r\n--------------------------069346ec29b44fc6\r\nContent-Disposition: form-data; name=\"upload_tmp_path\"\r\n\r\n/upload/0002424835\r\n--------------------------069346ec29b44fc6\r\nContent-Disposition: form-data; name=\"upload_file_name.md5\"\r\n\r\n77eb043e0f79321adf635dc9d9895b00\r\n--------------------------069346ec29b44fc6\r\nContent-Disposition: form-data; name=\"upload_file_name.size\"\r\n\r\n679\r\n--------------------------069346ec29b44fc6--\r\n",
    "fresh": false,
    "hostname": "echo",
    "ip": "10.0.0.2",
    "ips": [
        "10.0.0.2"
    ],
    "protocol": "https",
    "query": {
        "basketId": "05aa7960-3f4a-42f4-8112-c22b5f96a9b5"
    },
    "subdomains": [],
    "xhr": false,
    "os": {
        "hostname": "xxxxxxxxxxxxxxxxxx"
    },
    "connection": {}
}
10.0.0.2 - - [16/Jul/2023:11:08:37 +0000] "POST /Upload?basketId=05aa7960-3f4a-42f4-8112-c22b5f96a9b5 HTTP/1.0" 200 - "-" "curl/8.0.1"

我猜测是C#客户端生成的MultipartFormData不符合规范,导致Nginx无法正确处理,但无法确定具体原因。已尝试手动设置ContentType、转义文件名、自定义边界等操作均无效,目前采用手动拼接FormData的临时方案可行,但仍想明确MultipartFormDataContent与Nginx-upload不兼容的根本原因。

内容的提问来源于stack exchange,提问作者Chahed

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.15 23:34:48