Skip to content

Multipart requests with .Net HttpClient failing #160

@rplati

Description

@rplati

We were able to get RestRserve working with .Net RestSharp client but multipart requests with the .Net core HttpClient are failing. A minimal example follows.

restrserve.r

library(RestRserve)

app = Application$new(content_type = "text/plain")
app$logger$set_log_level("trace")

app$add_post(
path = "/echo",
FUN = function(request, response) {
  part1 <- request$get_file("part1")
  part2 <- request$get_file("part2")
  response$set_body(part1)
}
)

backend = BackendRserve$new()
backend$start(app, http_port = 80)

This is started within a Docker container using the rexyai/restrserve:dev image.

Dockerfile

FROM rexyai/restrserve:dev
COPY / /
EXPOSE 80
ENTRYPOINT ["Rscript", "restrserve.r"]

The following C# code uses .Net core 3.1

Program.cs

using RestSharp;
using System;
using System.IO;
using System.Net.Http;
using System.Text;
using System.Threading.Tasks;

internal class Program
{
    private static async Task Main(string[] args)
    {
        var requestUrl = "http://127.0.0.1:6027/echo";

        try
        {
            Console.WriteLine("Submitting multipart with RestSharp");
            Console.WriteLine("RestSharpResult: " + await TestRestSharp(requestUrl));
        }
        catch (Exception e)
        {
            Console.WriteLine($"Failed to process request (RestSharp): {e}");
        }

        try
        {
            Console.WriteLine("Submitting multipart with HttpClient");
            Console.WriteLine("HttpClientResult: " + await TestHttpClient(requestUrl));
        }
        catch (Exception e)
        {
            Console.WriteLine($"Failed to process request (HttpClient): {e}");
        }

        Console.ReadLine();
    }

    private static async Task<string> TestHttpClient(string requestUrl)
    {
        using var client = new HttpClient { Timeout = TimeSpan.FromSeconds(20) };
        using var content = new MultipartFormDataContent
            {
                { new StreamContent(ToMemoryStream("This is the first part.")), "part1", "part1.csv" },
                { new StreamContent(ToMemoryStream("This is the second part.")), "part2", "part2.csv" },
            };

        Console.WriteLine($"Submit request: {requestUrl}");
        var response = await client.PostAsync(requestUrl, content);
        response.EnsureSuccessStatusCode();
        return await response.Content.ReadAsStringAsync();
    }

    private static async Task<string> TestRestSharp(string requestUrl)
    {
        var client = new RestClient(requestUrl);
        var streams = new Stream[] { ToMemoryStream("This is the first part."), ToMemoryStream("This is the second part.") };
        var clientRequest = new RestRequest(Method.POST);
        clientRequest.AddFile("part1", async writer => await streams[0].CopyToAsync(writer), "part1.csv", streams[0].Length);
        clientRequest.AddFile("part2", async writer => await streams[1].CopyToAsync(writer), "part2.csv", streams[1].Length);
        Console.WriteLine($"Submit request: {requestUrl}");
        var response = await client.ExecuteAsync(clientRequest);

        if (response.IsSuccessful == false)
            throw new InvalidOperationException($"{response.Content} {response.ErrorException}");

        return response.Content;
    }

    private static MemoryStream ToMemoryStream(string text) => new MemoryStream(Encoding.UTF8.GetBytes(text));
}

Requests submitted with the RestSharp client work fine and produce expected RestRserve log entries. Requests submitted with the HttpClient fail with the below error message and produce no RestRserve log entries.

Console output

Submitting multipart with RestSharp
Submit request: http://127.0.0.1:6027/echo
RestSharpResult: This is the first part.
Submitting multipart with HttpClient
Submit request: http://127.0.0.1:6027/echo
Failed to process request (HttpClient): System.Net.Http.HttpRequestException: Response status code does not indicate success: 500 (Evaluation error).
   at System.Net.Http.HttpResponseMessage.EnsureSuccessStatusCode()
   at Program.TestHttpClient(String requestUrl) in D:\temp\HttpClientMultipartBug\HttpClientMultipartBug\Program.cs:line 48
   at Program.Main(String[] args) in D:\temp\HttpClientMultipartBug\HttpClientMultipartBug\Program.cs:line 27

Something about the way the .Net core HttpClient forms the multipart request causes problems for RestRserve. The HttpClient request should be ok as it works with the corresponding API implemented with plumber.

We would prefer to use the .Net core HttpClient over RestSharp because RestSharp uses unreasonable amounts of memory when the multipart body is large.

Metadata

Metadata

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions