Skip to content

Commit

Permalink
7th release (#5)
Browse files Browse the repository at this point in the history
* Add DownloadAsync method to HttpRestClient

* Refactor DownloadAsync

---------

Co-authored-by: Kambiz Khojasteh <[email protected]>
  • Loading branch information
kampute and Khojasteh authored Apr 2, 2024
1 parent 5e0bc96 commit e20605f
Show file tree
Hide file tree
Showing 9 changed files with 165 additions and 65 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
<Title>Kampute.HttpClient.DataContract</Title>
<Description>This package is an extension package for Kampute.HttpClient, enhancing it to manage application/xml content types, using DataContractSerializer for serialization and deserialization of XML responses and payloads.</Description>
<Authors>Kambiz Khojasteh</Authors>
<Version>1.4.1</Version>
<Version>1.5.0</Version>
<Company>Kampute</Company>
<Copyright>Copyright (c) 2024 Kampute</Copyright>
<LangVersion>latest</LangVersion>
Expand Down
2 changes: 1 addition & 1 deletion src/Kampute.HttpClient.Json/Kampute.HttpClient.Json.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
<Title>Kampute.HttpClient.Json</Title>
<Description>This package is an extension package for Kampute.HttpClient, enhancing it to manage application/json content types, using System.Text.Json library for serialization and deserialization of JSON responses and payloads.</Description>
<Authors>Kambiz Khojasteh</Authors>
<Version>1.4.1</Version>
<Version>1.5.0</Version>
<Company>Kampute</Company>
<Copyright>Copyright (c) 2024 Kampute</Copyright>
<LangVersion>latest</LangVersion>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
<Title>Kampute.HttpClient.NewtonsoftJson</Title>
<Description>This package is an extension package for Kampute.HttpClient, enhancing it to manage application/json content types, using Newtonsoft.Json library for serialization and deserialization of JSON responses and payloads.</Description>
<Authors>Kambiz Khojasteh</Authors>
<Version>1.4.1</Version>
<Version>1.5.0</Version>
<Company>Kampute</Company>
<Copyright>Copyright (c) 2024 Kampute</Copyright>
<LangVersion>latest</LangVersion>
Expand Down
2 changes: 1 addition & 1 deletion src/Kampute.HttpClient.Xml/Kampute.HttpClient.Xml.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
<Title>Kampute.HttpClient.Xml</Title>
<Description>This package is an extension package for Kampute.HttpClient, enhancing it to manage application/xml content types, using XmlSerializer for serialization and deserialization of XML responses and payloads.</Description>
<Authors>Kambiz Khojasteh</Authors>
<Version>1.4.1</Version>
<Version>1.5.0</Version>
<Company>Kampute</Company>
<Copyright>Copyright (c) 2024 Kampute</Copyright>
<LangVersion>latest</LangVersion>
Expand Down
66 changes: 35 additions & 31 deletions src/Kampute.HttpClient/HttpRestClient.cs
Original file line number Diff line number Diff line change
Expand Up @@ -52,9 +52,10 @@ namespace Kampute.HttpClient
public class HttpRestClient : IDisposable, ICloneable
{
private static readonly MediaTypeWithQualityHeaderValue AnyMediaType = new("*/*", 0.1);
private static readonly SharedDisposable<HttpClient> SharedHttpClient = new(CreateDefaultHttpClient);
private static readonly SharedDisposable<HttpClient> SharedHttpClient = new(CreateClient);
private static readonly Lazy<HttpContentHeaders> EmptyContentHeaders = new(CreateEmptyContentHeaders);

private static HttpClient CreateDefaultHttpClient()
private static HttpClient CreateClient()
{
var messageHandler = new HttpClientHandler
{
Expand All @@ -69,6 +70,12 @@ private static HttpRequestHeaders CreateRequestHeaders()
return request.Headers;
}

private static HttpContentHeaders CreateEmptyContentHeaders()
{
using var content = new ByteArrayContent([]);
return content.Headers;
}

private readonly HttpClient _httpClient;
private readonly bool _disposeClient;

Expand Down Expand Up @@ -291,66 +298,63 @@ public void Dispose()
}

/// <summary>
/// Retrieves data from the specified URI and writes it to the provided stream asynchronously.
/// Sends an asynchronous HTTP request with the specified method, URI, and payload, returning the response content as a stream.
/// </summary>
/// <param name="stream">The stream to write the downloaded data to. It must be writable.</param>
/// <param name="method">The HTTP method to use for the request.</param>
/// <param name="uri">The URI to which the request is sent.</param>
/// <param name="payload">The HTTP request payload (optional).</param>
/// <param name="payload">The HTTP request payload content.</param>
/// <param name="streamProvider">A function that returns a <see cref="Stream"/> based on the HTTP content headers.</param>
/// <param name="cancellationToken">A token for canceling the request (optional).</param>
/// <returns>
/// A task that represents the asynchronous download operation. The task result contains the headers of the downloaded content.
/// If the response contains no content, null is returned.
/// A task that represents the asynchronous operation. The task result contains a <see cref="Stream"/> that represents the response content.
/// </returns>
/// <exception cref="ArgumentNullException">Thrown if <paramref name="stream"/>, <paramref name="method"/>, or <paramref name="uri"/> is <c>null</c>.</exception>
/// <exception cref="ArgumentNullException">Thrown if <paramref name="method"/>, <paramref name="uri"/>, or <paramref name="streamProvider"/> is <c>null</c>.</exception>
/// <exception cref="InvalidOperationException">Thrown if <paramref name="streamProvider"/> returns <c>null</c>.</exception>
/// <exception cref="HttpResponseException">Thrown if the response status code indicates a failure.</exception>
/// <exception cref="HttpRequestException">Thrown if the request fails due to an underlying issue such as network connectivity, DNS failure, server certificate validation, or timeout.</exception>
/// <exception cref="TaskCanceledException">Thrown if the operation is canceled via the cancellation token.</exception>
public async Task<HttpContentHeaders?> FetchToStreamAsync
(
Stream stream,
HttpMethod method,
string uri,
HttpContent? payload = null,
CancellationToken cancellationToken = default
)
public async Task<Stream> DownloadAsync(HttpMethod method, string uri, HttpContent? payload, Func<HttpContentHeaders, Stream> streamProvider, CancellationToken cancellationToken = default)
{
if (stream is null)
throw new ArgumentNullException(nameof(stream));
if (method is null)
throw new ArgumentNullException(nameof(method));
if (uri is null)
throw new ArgumentNullException(nameof(uri));
if (streamProvider is null)
throw new ArgumentNullException(nameof(streamProvider));

using var request = CreateHttpRequest(method, uri, null);
request.Content = payload;

using var response = await DispatchWithRetriesAsync(request, cancellationToken).ConfigureAwait(false);

if (response.Content is null)
return null;
return GetStream(EmptyContentHeaders.Value);

var stream = GetStream(response.Content.Headers);
await response.Content.CopyToAsync(stream).ConfigureAwait(false);
return response.Content.Headers;
return stream;

Stream GetStream(HttpContentHeaders contentHeaders)
{
return streamProvider(contentHeaders) ?? throw new InvalidOperationException("The stream provider must not return null.");
}
}

/// <summary>
/// Sends an asynchronous HTTP request with the specified method, URI, and payload, and returns the response body deserialized as the specified type.
/// Sends an asynchronous HTTP request with the specified method, URI, and payload, returning response body deserialized as the specified type.
/// </summary>
/// <typeparam name="T">The type of the response object.</typeparam>
/// <param name="method">The HTTP method to use for the request.</param>
/// <param name="uri">The URI to which the request is sent.</param>
/// <param name="payload">The HTTP request payload (optional).</param>
/// <param name="payload">The HTTP request payload content (optional).</param>
/// <param name="cancellationToken">A token for canceling the request (optional).</param>
/// <returns>
/// A task that represents the asynchronous operation, with a result of the specified type. If the response contains no content, the default value
/// for the type <typeparamref name="T"/> is returned.
/// </returns>
/// <returns>A task that represents the asynchronous operation, with a result of the specified type.</returns>
/// <exception cref="ArgumentNullException">Thrown if <paramref name="method"/> or <paramref name="uri"/> is <c>null</c>.</exception>
/// <exception cref="HttpResponseException">Thrown if the response status code indicates a failure.</exception>
/// <exception cref="HttpRequestException">Thrown if the request fails due to an underlying issue such as network connectivity, DNS failure, server certificate validation, or timeout.</exception>
/// <exception cref="HttpContentException">Thrown if the response body is empty or its media type is not supported.</exception>
/// <exception cref="TaskCanceledException">Thrown if the operation is canceled via the cancellation token.</exception>
public async Task<T?> SendAsync<T>(HttpMethod method, string uri, HttpContent? payload = null, CancellationToken cancellationToken = default)
public async Task<T?> SendAsync<T>(HttpMethod method, string uri, HttpContent? payload = default, CancellationToken cancellationToken = default)
{
if (method is null)
throw new ArgumentNullException(nameof(method));
Expand All @@ -369,14 +373,14 @@ public async Task<HttpContentHeaders?> FetchToStreamAsync
/// </summary>
/// <param name="method">The HTTP method to use for the request.</param>
/// <param name="uri">The URI to which the request is sent.</param>
/// <param name="payload">The HTTP request payload (optional).</param>
/// <param name="payload">The HTTP request payload content (optional).</param>
/// <param name="cancellationToken">A token for canceling the request (optional).</param>
/// <returns>A task that represents the asynchronous operation. The task result contains the headers of the response.</returns>
/// <exception cref="ArgumentNullException">Thrown if <paramref name="method"/> or <paramref name="uri"/> is <c>null</c>.</exception>
/// <exception cref="HttpResponseException">Thrown if the response status code indicates a failure.</exception>
/// <exception cref="HttpRequestException">Thrown if the request fails due to an underlying issue such as network connectivity, DNS failure, server certificate validation, or timeout.</exception>
/// <exception cref="TaskCanceledException">Thrown if the operation is canceled via the cancellation token.</exception>
public async Task<HttpResponseHeaders> SendAsync(HttpMethod method, string uri, HttpContent? payload = null, CancellationToken cancellationToken = default)
public async Task<HttpResponseHeaders> SendAsync(HttpMethod method, string uri, HttpContent? payload = default, CancellationToken cancellationToken = default)
{
if (method is null)
throw new ArgumentNullException(nameof(method));
Expand Down Expand Up @@ -578,7 +582,7 @@ protected async Task<HttpResponseException> ToExceptionAsync(HttpResponseMessage
throw new ArgumentNullException(nameof(response));

var responseObject = default(object);
if (ResponseErrorType is not null && response.Content is not null)
if (ResponseErrorType is not null && response.Content is not null && response.Content.Headers.ContentLength != 0)
{
try
{
Expand Down Expand Up @@ -620,7 +624,7 @@ protected async Task<HttpResponseException> ToExceptionAsync(HttpResponseMessage
if (objectType is null)
throw new ArgumentNullException(nameof(objectType));

if (response.Content is null)
if (response.Content is null || response.Content.Headers.ContentLength == 0)
throw Error("The response body is empty.");

if (response.Content.Headers.ContentType is null)
Expand Down
Loading

0 comments on commit e20605f

Please sign in to comment.