Search code examples
c#restrestsharp

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


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


Solution

  • Problem is System.Net.Http.MultipartFormDataContent(string boundary)

    In restsharp version 106.15.0, it is using http request via RestSharp.Http. But since version 107 restsharp uses System.Net.Http. This makes a difference.

    When calling RestSharp.ExecuteAsync, it internally calls RestSharp.RequestContent.AddFiles.
    Here, System.Net.Http.MultipartFormDataContent is created and Boundary is passed as a parameter.

    RequestContent.cs
    void AddFiles() 
    {
            if (!_request.HasFiles() && !_request.AlwaysMultipartFormData) return;
    
            var mpContent = new MultipartFormDataContent(GetOrSetFormBoundary());
    
            // ...
    }
    

    As a result, the boundary is stored in MultipartFormDataContent.Headers.ContentType.Parameters, and this is where the difference occurs.

    In 106.15.0 , the boundary string is not enclosed in double quotes.

    "---------be62f9a5-f149-4782-a129-ad7bdae33924"

    However, in version 107 and later, the boundary string is enclosed in double quotation marks.

    ---------be62f9a5-f149-4782-a129-ad7bdae33924

    That made it seem like my server couldn't recognize the boundary string and the file wasn't being delivered.



    To remove double quotes I had to modify the RestSharp Library myself.

    RequestContent.cs
    void AddFiles() 
    {
            if (!_request.HasFiles() && !_request.AlwaysMultipartFormData) return;
    
            var mpContent = new MultipartFormDataContent(GetOrSetFormBoundary());
    
            var boundary = mpContent.Headers.ContentType.Parameters.First(o => o.Name == "boundary"); ;
            boundary.Value = boundary.Value.Replace("\"", String.Empty);
            
            // ...
    }
    
    string GetContentTypeHeader(string contentType)
                => Content is MultipartFormDataContent
                    ? $"{contentType}; boundary=\"{GetOrSetFormBoundary()}\""
                    : contentType;
    
    To this
    RestRequest.cs

    Add new property

    public bool RemoveBoundaryQuotes { get; set; }
    
    RequestContent.cs

    Process new property

    void AddFiles() {
        if (!_request.HasFiles() && !_request.AlwaysMultipartFormData) return;
    
        var mpContent = new MultipartFormDataContent(GetOrSetFormBoundary());
    
        if (_request.RemoveBoundaryQuotes == true) {
            RemoveBoundaryQuotes(mpContent);
        }
    
        // ...
    
    }
    
    private static void RemoveBoundaryQuotes(MultipartFormDataContent mpContent) {
        if (mpContent.Headers.ContentType != null) {
    
            var boundary = mpContent.Headers.ContentType.Parameters.First(o => o.Name == "boundary");
            if (boundary != null) {
    
                if (string.IsNullOrEmpty(boundary.Value) == false) {
                    string boundaryText = boundary.Value;
    
                    if (boundaryText.StartsWith("\"") && boundaryText.EndsWith("\"")) {
    
                        boundaryText = boundaryText.Remove(0, 1);
                        boundaryText = boundaryText.Remove(boundaryText.Length - 1, 1);
    
                        boundary.Value = boundaryText;
                    }
    
                }
            }
        }
    }
    
    string GetContentTypeHeader(string contentType) {
        if(Content is MultipartFormDataContent) {
            return _request.RemoveBoundaryQuotes is true ?
                $"{contentType}; boundary={GetOrSetFormBoundary()}" :
                $"{contentType}; boundary=\"{GetOrSetFormBoundary()}\"";
        }
        else {
            return contentType;
        }
        
    }