Difference in multipart/form-data usage in versions 106 and 107 - c#

I tried to update RestSharp version from 104 to 108.
Other APIs (application/json) fine.
But in multipart/form-data to transfer file, On server side, there is no file.
I tried to make them as similar as possible. But it still didn't work.
So I thought that there will be some changes as the version changes.
I found a version that doesn't work.
Working : Version 104.4.0, 106.15.0
Not working : Version 107.3.0, 108.0.3
Is there anything else I need to do to transfer files from version 107?
Version 106.15.0
var client = new RestClient("https://192.168.0.1/");
client.Timeout = -1;
var request = new RestRequest("file", Method.POST);
request.AlwaysMultipartFormData = true;
request.AddHeader("Authorization", "Auth-Token" + token);
request.AddHeader("Content-Type", "multipart/form-data");
request.AddHeader("Accept", "application/json");
request.AddFile("file", path);
System.Net.ServicePointManager.ServerCertificateValidationCallback = delegate { return true; };
var response = client.Execute(request);
Console.WriteLine(response.Content);
Capture from postman console
POST /file HTTP/1.1
Authorization: Auth-Token ----
Postman-Token: ecff7025-298c-4b29-8b15-ebd0d679aad8
Host: 192.168.0.1
Content-Type: multipart/form-data; boundary=--------------------------077067924352348455764323
Content-Length: 2409
----------------------------077067924352348455764323
Content-Disposition: form-data; name="file"; filename="filename.txt"
<filename.txt>
----------------------------077067924352348455764323--
Version 107.3.0
var options = new RestClientOptions("https://192.168.0.1/")
{
RemoteCertificateValidationCallback = (sender, certificate, chain, sslPolicyErrors) => true,
ConfigureMessageHandler = handler =>
new HttpTracerHandler(handler, new ConsoleLogger(), HttpMessageParts.All),
};
var client = new RestClient(options);
var request = new RestRequest("file", Method.Post);
request.AlwaysMultipartFormData = true;
request.AddHeader("Authorization", "Auth-Token" + token);
request.AddHeader("Content-Type", "multipart/form-data");
request.AddHeader("Accept", "application/json");
request.AddFile("file", path);
var response = client.ExecuteAsync(request).Result;
Console.WriteLine(response.Content);
Capture from HttpTracer
==================== HTTP REQUEST: [POST] ====================
POST https://192.168.0.1/file
Authorization: Auth-Token ----
Accept: application/json
User-Agent: RestSharp/0.0.0.0
--94000a34-a3fa-4eeb-b803-52eea1c7cbc9
Content-Type: application/octet-stream
Content-Disposition: form-data; name="file"; filename="filename.txt"
/////////////////////////////////
File contents ...
/////////////////////////////////
--94000a34-a3fa-4eeb-b803-52eea1c7cbc9--
Add :
nginx error log
upstream timed out (110: Connection timed out) while reading response header from upstream
Edit :
Added captured data of Post messages using postman and httptracer.
Edit2 :
Added nginx error log

Related

What is the equivalent HttpClient JSON POST to this RestClient JSON POST?

Given:
Uri location = ...; // Remote 3rd party HTTP Rest API
string body = "SOME JSON";
The following RestClient code generates HTTP traffic that is accepted by the server.
var client = new RestClient(location);
var request = new RestRequest(Method.POST);
request.AddHeader("cache-control", "no-cache");
request.AddHeader("content-type", "application/json; charset=utf-8");
request.AddParameter("application/json; charset=utf-8", body,
ParameterType.RequestBody);
var restResponse = client.Execute(request);
However, the HttpClient code below must be generating different HTTP traffic (indicated by the server rejecting the request).
using (var client = new HttpClient())
{
var request = new HttpRequestMessage();
request.Method = HttpMethod.Post;
request.RequestUri = location;
var bodyContent = new StringContent(body, Encoding.UTF8, "application/json");
request.Content = bodyContent;
request.Headers.Add("cache-control", "no-cache");
client.Timeout = TimeSpan.FromMinutes(5.0);
var response = await client.SendAsync(request);
}
Why is the HttpClient code serializing differently?
A simple way to find the precise differences is to run Fiddler or another debugging proxy and check the raw request. Here's what I got with HttpClient:
POST http://example.com/ HTTP/1.1
cache-control: no-cache
Content-Type: application/json; charset=utf-8
Host: example.com
Content-Length: 4
Expect: 100-continue
Connection: Keep-Alive
test
And with RestSharp:
POST http://example.com/ HTTP/1.1
cache-control: no-cache
Content-Type: application/json; charset=utf-8
Accept: application/json, text/json, text/x-json, text/javascript, application/xml, text/xml
User-Agent: RestSharp/106.6.9.0
Host: example.com
Content-Length: 4
Accept-Encoding: gzip, deflate
Connection: Keep-Alive
test
Your results may differ depending on your system configuration, versions etc., so you should try it yourself to make sure.

Xamarin.Android: HttpClient with MultipartFormDataContent returns 400 Bad Request

I develop a Xamarin.Android app and I need to update an audio recorded file.
For this, my client has provided me an API like this:
URL: /api/recordings/
Method: POST
Data format: multipart/form-data
Fields: some text fields and the audio file to send
When I test it on Postman, it works fine. The generate code snippets are:
POST /api/recordings/ HTTP/1.1
Host: X.X.X.X
Cache-Control: no-cache
Postman-Token: xxxxx-xxxx-xxxx-xxxx-xxxxx
Content-Type: multipart/form-data; boundary=----WebKitFormBoundary7MA4YWxkTrZu0gW
------WebKitFormBoundary7MA4YWxkTrZu0gW
Content-Disposition: form-data; name="param1"
param1 name
------WebKitFormBoundary7MA4YWxkTrZu0gW
Content-Disposition: form-data; name="param2"
param2 name
------WebKitFormBoundary7MA4YWxkTrZu0gW
Content-Disposition: form-data; name="audio_file"; filename="xxx.wav"
Content-Type: audio/wav
But when I try from the app, I get an 400 - Bad Request error.
First attempt:
I first tried with the following basic code:
using (HttpClient client = new HttpClient())
{
try
{
MultipartFormDataContent formData = new MultipartFormDataContent();
formData.Add(new StringContent("param1 name"), "param1");
formData.Add(new StringContent("param2 name"), "param2");
var response = await client.PostAsync(url, formData);
Logger.Log("Response : " + responseObj.ToString());
}
catch (Exception ex)
{
Logger.Log("Exception : " + ex.ToString());
}
}
But there is a 400 - Bad Request error:
Response : StatusCode: 400, ReasonPhrase: 'Bad Request', Version: 1.1, Content: System.Net.Http.StreamContent, Headers:
{
Server: nginx/1.15.0
Date: Mon, 25 Jun 2018 23:25:41 GMT
Connection: keep-alive
Vary: Cookie
X-Frame-Options: SAMEORIGIN
Content-Type: application/json
Content-Length: 85
Allow: POST, OPTIONS
}
Second attempt:
Then I've tried the following snippet that I found there:
string servResp = "";
string boundary = "----CustomBoundary" + DateTime.Now.Ticks.ToString("x");
using (var content = new MultipartFormDataContent(boundary))
{
content.Headers.Remove("Content-Type");
content.Headers.TryAddWithoutValidation("Content-Type", "multipart/form-data; boundary=" + boundary);
content.Add(new StringContent("param1 name"), "param1");
content.Add(new StringContent("param2 name"), "param2");
HttpClientHandler handler = new HttpClientHandler();
var cookieContainer = new CookieContainer();
handler.CookieContainer = cookieContainer;
HttpRequestMessage request = new HttpRequestMessage(HttpMethod.Post, url);
request.Headers.ExpectContinue = false;
request.Content = content;
try
{
var httpClient = new HttpClient(handler);
HttpResponseMessage response = await httpClient.SendAsync(request);
response.EnsureSuccessStatusCode();
servResp = await response.Content.ReadAsStringAsync();
}
catch (Exception ex)
{
Logger.Log("PostTest() - Exception : " + ex.ToString());
}
}
return servResp;
But there is still a 400 - Bad Request error:
Exception : System.Net.Http.HttpRequestException: 400 (Bad Request)
at System.Net.Http.HttpResponseMessage.EnsureSuccessStatusCode () [0x0002a] in <996a681f30a44cd685a4da54e11956e2>:0
=> Would you have any explanation? How could I fix this?
Edit : the audio file
I haven't specified the code of the audio file, I use a ByteArrayContent in both cases:
byte[] b = System.IO.File.ReadAllBytes(fullFilePath);
formData.Add(new ByteArrayContent(b, 0, b.Length), "audio_file", "audio.wav");
Conclusion:
I was able to resolve my problem by creating a console project that using the same service: this allows me to find and fix my error.
For an other issue, I followed the Fiddler instructions to parameter proxy on the device. However, it seems that the proxy doesn't work for apps but only for websites in the latest versions of Android...
Do you know an alternative to Fiddler that could work on Android devices?

How do I post www-form-urlencoded content-type data and accept json using RestSharp?

fiddler settings:
Url
http://localhost:8080/lzp/servRoot (post)
Headers
User-Agent: Fiddler
Host: localhost:8080
Content-Length: 86
Content-Type: application/x-www-form-urlencoded
RequestBody
className=com.lzp.service.UserInfoService&methodName=login&user_name=123&user_pwd=123
And response
HTTP/1.1 200 OK
Server: Apache-Coyote/1.1
Access-Control-Allow-Origin: *
Content-Type: text/json;charset=gbk
Transfer-Encoding: chunked
{"msg":false}
It returns {"msg":false} using fiddler.
Here is my code using RestSharp.
RestClient client = new RestClient("http://localhost:8080");
RestRequest request = new RestRequest("lzp/servRoot", Method.POST);
request.AddHeader("Content-Type", "application/x-www-form-urlencoded");
request.AddParameter("className", "com.lzp.service.UserInfoService");
request.AddParameter("methodName", "login");
request.AddParameter("user_name", "123");
request.AddParameter("user_pwd", "123");
var response = client.Execute<TestResultModel>(request);
var res = response.Data;
and the TestResultModel:
public class TestResultModel
{
public string msg { get; set; }
}
Why can't I get deserialize response data?

Formatting of multipart HTTP request body output with restsharp

I am creating a restsharp request in order to trigger a batch direct send push request off to Azure notification hub.
I am receiving a 400 Bad Request response, with the message; Could not find 'notifications' part in the multipart content supplied.
The request looks like such;
const string multipartContentType = "multipart/form-data; boundary=\"simple-boundary\"";
const string authSignature = "myvalidauthsignature";
const string url = "mynotificanhuburl";
const string message = "Some message";
var restClient = new RestClient
{
BaseUrl = new Uri(url),
Proxy = new WebProxy("127.0.0.1", 8888),
};
var request = new RestSharp.RestRequest(Method.POST)
{
RequestFormat = DataFormat.Json,
AlwaysMultipartFormData = true
};
request.AddHeader("Content-Type", multipartContentType);
request.AddHeader("Authorization", authSignature);
request.AddHeader("ServiceBusNotification-Format", "gcm");
request.AddParameter("notification", JsonConvert.SerializeObject(new { data = new { message } }), ParameterType.GetOrPost);
request.AddParameter("devices", JsonConvert.SerializeObject(new List<string> { "123", "456" }), ParameterType.GetOrPost);
var response = restClient.Execute(request);
I can see the raw request via Fiddler;
POST https://xxxxx.servicebus.windows.net/xxx/messages/$batch?direct&api-version=2015-04 HTTP/1.1
Authorization: [redacted]
ServiceBusNotification-Format: gcm
Accept: application/json, application/xml, text/json, text/x-json, text/javascript, text/xml
User-Agent: RestSharp/105.2.3.0
Content-Type: multipart/form-data; boundary=-----------------------------28947758029299
Host: [redacted]
Content-Length: 412
Accept-Encoding: gzip, deflate
Connection: Keep-Alive
-------------------------------28947758029299
Content-Disposition: form-data; name="notification"
{"data":{"message":"Some message"}}
-------------------------------28947758029299
Content-Disposition: form-data; name="devices"
["123","456"]
-------------------------------28947758029299--
Which looks about right. If I copy this into postman with the headers etc, I can see the same error response. HOWEVER in postman when I remove the quote marks around the parameter names, it works and returns a 201 Created response.
So this works....
Content-Disposition: form-data; name=notification
This doesn't
Content-Disposition: form-data; name="notification"
Which seems really peculiar. As we are using restsharp however I don't think I have any direct control over the raw output for the request body. I am wondering;
Is there a restsharp setting to manage these quote, perhaps a formatting setting
Why would the Azure endpoint reject a parameter name with quotes
It is possible that the issue is elsewhere and this is a red herring, but this does seem to be responsible.
Appreciate any help...
According our documentation, request should look like this:
POST https://{Namespace}.servicebus.windows.net/{Notification Hub}/messages/$batch?direct&api-version=2015-08 HTTP/1.1
Content-Type: multipart/mixed; boundary="simple-boundary"
Authorization: SharedAccessSignature sr=https%3a%2f%2f{Namespace}.servicebus.windows.net%2f{Notification Hub}%2fmessages%2f%24batch%3fdirect%26api-version%3d2015-08&sig={Signature}&skn=DefaultFullSharedAccessSignature
ServiceBusNotification-Format: gcm
Host: {Namespace}.servicebus.windows.net
Content-Length: 431
Expect: 100-continue
Connection: Keep-Alive
--simple-boundary
Content-Type: application/json
Content-Disposition: inline; name=notification
{"data":{"message":"Hello via Direct Batch Send!!!"}}
--simple-boundary
Content-Type: application/json
Content-Disposition: inline; name=devices
["Device Token1","Device Token2","Device Token3"]
--simple-boundary--
So, the name parameter's value is not quoted (name=devices). I've not found any RFC which would explicitly specify requirements regarding the situation. However, in examples inside of RFCs a values appear quoted. And because of that I'm going to fix the service to support both options. Fix should come with next deployment in a week or so.
I was plagued by this for a few days and was diligently searching for a solution with RestSharp and was unable to find one as it always default the content type to "multipart/form-data". I know the OP was looking for a way to do this with RestSharp but I don't believe there is currently.My solution comes from a few different posts over a few days so I apologize for not linking to them. Below is a sample Function to perform a multipart/related POST with json body and base64 pdf string as the file.
public static void PostBase64PdfHttpClient(string recordID, string docName, string pdfB64)
{
string url = $"baseURL";
HttpClient client = new HttpClient();
var myBoundary = "------------ThIs_Is_tHe_bouNdaRY_";
string auth = Convert.ToBase64String(Encoding.UTF8.GetBytes($"UN:PW"));
client.DefaultRequestHeaders.Add("Authorization", $"Basic {auth}");
HttpRequestMessage request = new HttpRequestMessage(HttpMethod.Post, $"{url}/api-endpoint");
request.Headers.Date = DateTime.UtcNow;
request.Headers.Add("Accept", "application/json; charset=utf-8");
MultipartContent mpContent = new MultipartContent("related", myBoundary);
mpContent.Headers.TryAddWithoutValidation("Content-Type", $"multipart/related; boundary={myBoundary}");
dynamic jObj = new Newtonsoft.Json.Linq.JObject(); jObj.ID = recordID; jObj.Name = docName;
var jsonSerializeSettings = new JsonSerializerSettings { NullValueHandling = NullValueHandling.Ignore };
var json = JsonConvert.SerializeObject(jObj, jsonSerializeSettings);
mpContent.Add(new StringContent(json, Encoding.UTF8, "application/json"));
mpContent.Add(new StringContent(pdfB64, Encoding.UTF8, "application/pdf"));
request.Content = mpContent;
HttpResponseMessage response = client.SendAsync(request).Result;
}

How to flash a spark core from C#

I am trying to flash a spark core from a C# application. I keep getting { error: Nothing to do? } response.
Below is my code
var url = string.Format("https://api.spark.io/v1/devices/{0}", sparkDeviceID);
using (var client = new HttpClient())
{
client.DefaultRequestHeaders.Authorization = new System.Net.Http.Headers.AuthenticationHeaderValue("Bearer", accesstoken);
using (var formData = new MultipartFormDataContent())
{
HttpContent fileContent = new ByteArrayContent(Encoding.ASCII.GetBytes(rom));
//client.SendAsync()
formData.Add(fileContent, "file", "file");
var response = client.PutAsync(url, formData).Result;
if (!response.IsSuccessStatusCode)
throw new Exception("An error occurred during rom flash!");
var responseStream = response.Content.ReadAsStreamAsync().Result;
using (var reader = new StreamReader(responseStream, true))
{
var result = reader.ReadToEnd();
}
}
return true;
}
The documentation reads:
The API request should be encoded as multipart/form-data with a file field populated.
I believe the problem is the endpoint doesn't see the file. Any idea on how to resolve this?
Finally got it working.
The issue was the way .NET generated the content-disposition header for the file form data.
I used fiddler to compare the output of a successful put request to the put request that my code was generating:
Successful PUT request generated using CURL:
PUT http://127.0.0.1:8888/ HTTP/1.1
User-Agent: curl/7.33.0
Host: 127.0.0.1:8888
Accept: */*
Content-Length: 2861
Expect: 100-continue
Content-Type: multipart/form-data; boundary=------------------------5efcf64a370f13c8
--------------------------5efcf64a370f13c8
Content-Disposition: form-data; name="file"; filename="ms.ino"
Content-Type: application/octet-stream
...
My PUT request (unsuccessful):
PUT https://api.spark.io/v1/devices/{deviceid} HTTP/1.1
Authorization: Bearer {access_token}
Content-Type: multipart/form-data; boundary="135f5425-9342-4ffa-a645-99c04834026f"
Host: api.spark.io
Content-Length: 2878
Expect: 100-continue
--135f5425-9342-4ffa-a645-99c04834026f
Content-Type: application/octet-stream
Content-Disposition: form-data; name=file; filename=file.ino; filename*=utf-8''file.ino
...
If you'll notice the difference in the Content-Type for the actual file being sent:
Successful: Content-Disposition: form-data; name="file"; filename="ms.ino"
Unsuccessful: Content-Disposition: form-data; name=file; filename=file.ino; filename*=utf-8''file.ino
Most specifically, the resolution was to add quotes around the name attribute.
Resolution:
formData.Add(fileContent, "\"file\"", "file.ino");

Categories

Resources