I have to upload a file to an embedded device running an API. First, I tried to write methods to do this in a test project. This works great:
private static async Task<HttpResponseMessage?> POST_internal(string url, HttpContent payLoad)
{
var httpClient = new HttpClient();
httpClient.BaseAddress = new Uri(url);
httpClient.DefaultRequestHeaders.Accept.Clear();
httpClient.DefaultRequestHeaders.Add("Accept", "*/*");
try
{
var result = await httpClient.PostAsync(url, payLoad);
httpClient.Dispose();
return result;
}
catch (Exception)
{
httpClient.Dispose();
return null;
}
}
public static void POST_upload_token(IPAddress deviceIpAddress, string filename)
{
var requestURL = $"http://{deviceIpAddress}/api/upload_token";
using var payload = new MultipartFormDataContent();
payload.Add(new StreamContent(System.IO.File.OpenRead(filename)), "\"file\"", $"\"{Path.GetFileName(filename)}\"");
var response = POST_internal(requestURL, payload).Result;
if(response != null && response.IsSuccessStatusCode)
return;
throw new Exception($"Failed to post upload token to device.\nResponse code: {(response != null ? response.StatusCode : "Response was null")}\nResponse text {(response != null ? response.Content.ReadAsStringAsync().Result : "Response was null")}");
}
However, when adding this code to the main project, it does not work. Either the API responds with 400 Bad Request, or the httpClient.PostAsync() call throws an exception (Unable to read data from the transport connection: An existing connection was forcibly closed by the remote host.)
The only difference between the test project and the main project are the .NET versions. The test project was .NET8, while the main project is .NET Framework 4.8.
When checking the traffic in Wireshark, I believe to have found the problem:
The request from the .NET8 program sends the file data directly with the first request
$X.$EJ@P8_8LPlPOST /api/upload_token HTTP/1.1
Host: 192.168.0.142
Accept: */*
Content-Type: multipart/form-data; boundary="afbc983b-2675-4133-b333-11fb5b878ca8"
Content-Length: 493
--afbc983b-2675-4133-b333-11fb5b878ca8
Content-Disposition: form-data; name="file"; filename="token_8961.bin"; filename*=utf-8''%22token_8961.bin%22
[FILE DATA]
--afbc983b-2675-4133-b333-11fb5b878ca8--
While the .NET Framework 4.8 program tries to split the data into 2 packets:
$X.$EZ@ XPPVpCs\PPOST /api/upload_token HTTP/1.1
Accept: */*
Content-Type: multipart/form-data; boundary="4855026b-b3a3-42df-8abe-d56028e7c47f"
Host: 192.168.0.142
Content-Length: 493
Expect: 100-continue
Connection: Keep-Alive
and
$X.$EW@Psft=,Pw--2fc0624c-9294-44ef-949d-0988aca1b042
Content-Disposition: form-data; name="file"; filename="token_8961.bin"; filename*=utf-8''%22token_8961.bin%22
[FILE DATA]
--2fc0624c-9294-44ef-949d-0988aca1b042--
But between the 2 packets the API already responds with a 400:
$$X.E5WP=sftP
WHTTP/1.1 400 Bad Request
Connection: close
Content-Type: application/json
{"error":"Invalid filename. Expected TOKEN_8961.BIN"}
I already tried:
Set the Expect100Continue flag to false:
System.Net.ServicePointManager.Expect100Continue = false;
- Removes the
Expect: 100-continueHeader, but still splits the request
Remove the Expect: 100-continue and Connection: Keep-Alive Headers:
var request = new HttpRequestMessage(HttpMethod.Post, requestURL)
{
Content = payload
};
request.Headers.ConnectionClose = true;
request.Headers.ExpectContinue = false;
HttpClient.SendAsync(request);
- Removes both Headers, but still splits the request
Note: curl command to achieve the request that works:
curl -X POST http://192.168.0.142/api/upload_token -F "file=@token_8961.bin;type=application/octet-stream"
So my question is: Is there any way to make the .NET Framework4.8 HttpClient send the file data directly in the initial request?
.Resultinstead ofawait?