refit: Refit 6.0.x: Content-Length not set for PATCH requests

Hi

It seems like the following issue is related to a slightly old issue i.e. #885 and probably #372

Description

I am consuming a third-party REST API using .NET 5/C# 9.0 and Refit version 6.0.24. The third-party system doesn’t support chunked encoding. When making a PATCH call the Transfer-Encoding: chunked header is getting added, and the API returns with 400 status code.

The interface method looks like below:

[Patch("/states/{Id}"), Headers("Content-Type: application/json")]
Task<ApiResponse<State>> ChangeState([Body] StateControl stateControl, string id);

I have tried changing the [Body] attribute to all possible documented values without any success

- [Body]
- [Body(true)]
- [Body(BodySerializationMethod.Default, true)]
- [Body(BodySerializationMethod.Serialized)]
- [Body(BodySerializationMethod.Serialized, true)]

Following is the request and response captured by Fiddler

Request

PATCH http://thidparty.api.com:5000/states/0 HTTP/1.1
Host: thidparty.api.com:5000
X-Correlation-ID: 3edcc52b-aec1-4a8c-b725-c646fc269e95
traceparent: 00-4f869cf85a015f4fae43466700c2a395-5effe2f12308e444-00
Transfer-Encoding: chunked
Content-Type: application/json

1C
{"state":"new","force":true}
0

Response

HTTP/1.1 400 BAD REQUEST
server: gunicorn/19.4.5
date: Mon, 01 Mar 2021 23:29:51 GMT
content-type: application/json
content-length: 92
x-sentry-id: 3b2b0bad862a4eed9d62e8e76c96a7f0

{"message": "The browser (or proxy) sent a request that this server could not understand."}

Expected behavior The expected behavior should be how it was in the version prior to 6.0.x. i.e. Content-Length header should be set. It works fine in version 5.2.4, without changing the [Body] attribute. Following is the request and response captured by Fiddler when using Refit version 5.2.4

Request

PATCH http://thidparty.api.com:5000/states/0 HTTP/1.1
Host: thidparty.api.com:5000
X-Correlation-ID: 3edcc52b-aec1-4a8c-b725-c646fc269e95
traceparent: 00-1b3b2ddae1c35c4f9ae2b6c6e7c72093-ce1d3605b1a6b241-00
Content-Length: 28
Content-Type: application/json

{"state":"new","force":true}

Response

Please note that 408 is the expected status code for this request 😃

HTTP/1.1 408 REQUEST TIMEOUT
server: gunicorn/19.4.5
date: Mon, 01 Mar 2021 18:01:03 GMT
content-type: application/json
content-length: 35
x-sentry-id: 1b4b324faff9412e831aeff453efbeb2

{"message": "State didn't change"}

Environment

  • OS: Windows 10 Professional
  • Version: Refit 6.0.x
  • Working Version: Refit 5.2.4

Additional context I am using a workaround mentioned at #372 in a delegating handler before the request is sent like below and it works fine. However, it would be great if this issue can be fixed. I am happy to contribute if I can get some direction. It would probably be my first contribution ever 😃

protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
{
    if (request.Method == HttpMethod.Patch && request.Content != null)
    {
        var content = await request.Content.ReadAsStringAsync(cancellationToken);
        request.Content = new StringContent(content, Encoding.UTF8, "application/json");
    }

    return await base.SendAsync(request, cancellationToken);
}

About this issue

  • Original URL
  • State: closed
  • Created 3 years ago
  • Reactions: 1
  • Comments: 18 (6 by maintainers)

Most upvoted comments

This was helpful.

Just been struggling with Azure API Management cutting out the JSON content of my post body. Turns out that it was ignoring the content because the content-length header wasn’t being sent. Set buffered to true in the body attribute and it started working. [Body(BodySerializationMethod.Serialized, true)]

We noticed this as well with void return methods, which may be what you are seeing too. PR #1293 will fix that code path.

@lotz Thanks for the fix. I presume that it would work with the methods that return Task<<T>> as I mentioned in this bug. I’ll give it a go and will let you know.

I’m currently using the latest release (6.0.38), but seems that the issue is still there. I’m not getting the Content-Length header and the body is being chunked, causing my azure functions to not work 😦

Can anyone tell me if I’m doing something wrong? I’ve looked into what’s being sent with Wireshark and this is the request: image

This is how my client is configured:

public interface IApplicationApi
{
	[Post("/api/process-failed-contracts")]
	Task ProcessFailedContractFiles([Body(true)] ProcessFailedContractFilesRequest request,
		[Header("x-functions-key")] string functionKey = "test", CancellationToken cancellationToken = default);
}

I even tried to set Buffered to true when creating a RestService:

var api = RestService.For<IApplicationApi>("http://localhost:5001", new RefitSettings
{
	Buffered = true,
	ContentSerializer = new SystemTextJsonContentSerializer(new JsonSerializerOptions(JsonSerializerDefaults.Web))
});

await api.ProcessFailedContractFiles(new ProcessFailedContractFilesRequest{
	Prefetch = 10,
	ConsumerCount = 1
});

I created a PR to fix this bug.

Meanwhile, a more elegant workaround:

protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
{
    if (request.Content != null)
    {
        await request.Content.LoadIntoBufferAsync();
    }

    return await base.SendAsync(request, cancellationToken);
}