Closed Jericho closed 2 years ago
Thanks for the PR! It seems a bit specialized to be its own feature though. I'd suggest using a request filter instead, which is designed to support custom validation like that. For example:
public class MaxRequestSizeFilter : IHttpFilter
{
private readonly ulong MaxSize;
public MaxRequestSizeFilter(ulong maxSize)
{
this.MaxSize = maxSize;
}
public void OnRequest(IRequest request)
{
// ensure the size of the request does not exceed the max size
string headers = request.Message.Headers.ToString();
byte[] content = request.Message.Content.ReadAsByteArrayAsync().Result;
ulong requestSizeInBytes = (ulong)((headers.Length * sizeof(char)) + content.Length);
if (requestSizeInBytes > this.MaxSize)
{
throw new InvalidOperationException($"The request size ({requestSizeInBytes} bytes) exceeds the maximum size of {this.MaxSize} bytes.");
}
}
public void OnResponse(IResponse response, bool httpErrorAsException) { }
}
IClient client = new FluentClient("https://example.org");
client.Filters.Add(new MaxRequestSizeFilter(1234));
var response = await fluentClient
.PostAsync("endpoint")
.WithBody(new StringContent("Hello World!"))
.AsResponse();
If you need to configure it per-request, we could add support for setting filters per-request too.
You're right: a filter is a much better idea. I should have thought of that!
As far as configuring it per-request, here's what I came up with:
// Prepare the request
var request = fluentClient
.PostAsync("endpoint")
.WithBody(new StringContent("Hello World!"))
.WithCancellationToken(cancellationToken);
// Replace the current filter (if any) with a new filter with the desired max size
request.Filters.Remove<MaxRequestSizeFilter>();
request.Filters.Add(new MaxRequestSizeFilter(1234));
// Send the request
var response = await request
.AsResponse()
.ConfigureAwait(false);
Thanks for helping me think through this scenario and for suggesting a solution that is simpler and better than what I had come up with. Feel free to close this issue and the related PR.
By the way, Filters.Remove
followed by Filters.Add
works fine but it's not "fluent". Maybe adding a "WithFilter" method to IRequest
and Request
would be more elegant. Something as simple as this:
public IRequest WithFilter<T>(T filter) where T : IHttpFilter
{
this.Filters.Remove<T>();
this.Filters.Add(filter);
return this;
}
This would allow me to change my code to:
var response = await fluentClient
.PostAsync("endpoint")
.WithBody(new StringContent("Hello World!"))
.WithFilter(new MaxRequestSizeFilter(1234))
.AsResponse();
Yep, that's what I was thinking. I wouldn't remove previous filters automatically though, since that wouldn't always be what you want. For example:
await client
.WithFilter(new HandleErrorCode("ItemNotFound", HttpStatusCode.NotFound))
.WithFilter(new HandleErrorCode("WhoAreYou", HttpStatusCode.Unauthorized))
We could add something like .WithoutFilter<T>()
, but you might not need it since the WithFilter
would only apply for the request you're configuring now.
In my scenario, I always want the global filter to be replaced but it did occur to me that it might not be the case for every scenario. I propose improving my suggestion like so:
public IRequest WithFilter<TFilter>(TFilter filter, bool removeExisting = true) where TFilter : IHttpFilter
{
if (removeExisting) this.Filters.Remove<TFilter>();
this.Filters.Add(filter);
return this;
}
Done in #112 (thanks for the PR!). The new fluent equivalent is:
var response = await client
.PostAsync("endpoint")
.WithBody(new StringContent("Hello World!"))
.WithCancellationToken(cancellationToken)
.WithoutFilter<MaxRequestSizeFilter>() // can be skipped if you didn't add one on the client level
.WithFilter(new MaxRequestSizeFilter(1234))
.AsResponse()
.ConfigureAwait(false);
I interface with a 3rd party API and I know that my requests will be rejected if they exceed a given maximum size. For example, I can attach multiple files when I POST to an endpoint but my request must not exceed 10MB. If I am not careful, I could add multiple files to my request which would grow in size to be much more than the allowed 10MB. I would needlessly upload all this content to the API and receive a "Your request exceeds the maximum allowable size" exception.
It would be very convenient if FluentHttpClient was able to check the size of a request and refuse to dispatch it if exceeds a given max size. The default, of course, would be not to enforce any max size in order to match the current behavior. What I have in mind is something like this:
The size would be calculated by adding up the number of bytes in the request headers with the number of bytes in the body.
The goal is to avoid uploading potentially large content to an API endpoint when we know that it will be rejected.
Will submit PR with my proposed implementation.