Skip to content

Commit 07d437f

Browse files
committed
Merge branch 'develop' into stable
2 parents 1685e7b + 9bc0f93 commit 07d437f

File tree

9 files changed

+107
-40
lines changed

9 files changed

+107
-40
lines changed

Client/Client.csproj

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
<RootNamespace>Pathoschild.Http.Client</RootNamespace>
66
<PackageId>Pathoschild.Http.FluentClient</PackageId>
77
<Title>FluentHttpClient</Title>
8-
<Version>4.3.0</Version>
8+
<Version>4.4.0</Version>
99
<Authors>Pathoschild</Authors>
1010
<Description>A modern async HTTP client for REST APIs. Its fluent interface lets you send an HTTP request and parse the response in one go.</Description>
1111
<PackageLicenseExpression>MIT</PackageLicenseExpression>
@@ -27,11 +27,11 @@
2727
</PropertyGroup>
2828

2929
<ItemGroup Condition=" '$(TargetFramework)' != 'netstandard1.3' ">
30-
<PackageReference Include="Microsoft.AspNet.WebApi.Client" Version="5.2.9" />
30+
<PackageReference Include="Microsoft.AspNet.WebApi.Client" Version="6.0.0" />
3131
</ItemGroup>
3232

3333
<ItemGroup Condition=" '$(TargetFramework)' == 'netstandard1.3' ">
34-
<PackageReference Include="Newtonsoft.Json" Version="13.0.2" />
34+
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
3535
<PackageReference Include="System.Net.Http" Version="4.3.4" />
3636
<PackageReference Include="WinInsider.System.Net.Http.Formatting" Version="1.0.14" />
3737
</ItemGroup>

Client/FluentClient.cs

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -176,7 +176,11 @@ protected virtual async Task<HttpResponseMessage> SendImplAsync(IRequest request
176176

177177
// dispatch request
178178
return await this.BaseClient
179-
.SendAsync(requestMessage, request.CancellationToken)
179+
.SendAsync(
180+
request: requestMessage,
181+
completionOption: request.Options.CompleteWhen ?? HttpCompletionOption.ResponseContentRead,
182+
cancellationToken: request.CancellationToken
183+
)
180184
.ConfigureAwait(false);
181185
}
182186

Client/FluentClientExtensions.cs

Lines changed: 12 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -189,14 +189,16 @@ public static IClient SetRequestCoordinator(this IClient client, IEnumerable<IRe
189189

190190
/// <summary>Set default options for all requests.</summary>
191191
/// <param name="client">The client.</param>
192-
/// <param name="ignoreHttpErrors">Whether HTTP error responses like HTTP 404 should be ignored; else raised as exceptions (or <c>null</c> to leave the option unchanged).</param>
193-
/// <param name="ignoreNullArguments">Whether to ignore null arguments when the request is dispatched (or <c>null</c> to leave the option unchanged).</param>
194-
public static IClient SetOptions(this IClient client, bool? ignoreHttpErrors = null, bool? ignoreNullArguments = null)
192+
/// <param name="ignoreHttpErrors">Whether HTTP error responses like HTTP 404 should be ignored (<c>true</c>) or raised as exceptions (<c>false</c>). Default <c>false</c>.</param>
193+
/// <param name="ignoreNullArguments">Whether null arguments in the request body and URL query string should be ignored (<c>true</c>) or sent as-is (<c>false</c>). Default <c>true</c>.</param>
194+
/// <param name="completeWhen">When we should stop waiting for the response. For example, setting this to <see cref="HttpCompletionOption.ResponseHeadersRead"/> will let you handle the response as soon as the headers are received, before the full response body has been fetched. This only affects getting the <see cref="IResponse"/>; reading the response body (e.g. using a method like <see cref="IResponse.As{T}"/>) will still wait for the request body to be fetched as usual.</param>
195+
public static IClient SetOptions(this IClient client, bool? ignoreHttpErrors = null, bool? ignoreNullArguments = null, HttpCompletionOption? completeWhen = null)
195196
{
196197
return client.SetOptions(new FluentClientOptions
197198
{
198199
IgnoreHttpErrors = ignoreHttpErrors,
199-
IgnoreNullArguments = ignoreNullArguments
200+
IgnoreNullArguments = ignoreNullArguments,
201+
CompleteWhen = completeWhen
200202
});
201203
}
202204

@@ -272,14 +274,16 @@ public static IRequest WithRequestCoordinator(this IRequest request, IEnumerable
272274

273275
/// <summary>Set options for this request.</summary>
274276
/// <param name="request">The request.</param>
275-
/// <param name="ignoreHttpErrors">Whether HTTP error responses like HTTP 404 should be ignored; else raised as exceptions (or <c>null</c> to leave the option unchanged).</param>
276-
/// <param name="ignoreNullArguments">Whether to ignore null arguments when the request is dispatched (or <c>null</c> to leave the option unchanged).</param>
277-
public static IRequest WithOptions(this IRequest request, bool? ignoreHttpErrors = null, bool? ignoreNullArguments = null)
277+
/// <param name="ignoreHttpErrors">Whether HTTP error responses like HTTP 404 should be ignored (<c>true</c>) or raised as exceptions (<c>false</c>). Default <c>false</c>.</param>
278+
/// <param name="ignoreNullArguments">Whether null arguments in the request body and URL query string should be ignored (<c>true</c>) or sent as-is (<c>false</c>). Default <c>true</c>.</param>
279+
/// <param name="completeWhen">When we should stop waiting for the response. For example, setting this to <see cref="HttpCompletionOption.ResponseHeadersRead"/> will let you handle the response as soon as the headers are received, before the full response body has been fetched. This only affects getting the <see cref="IResponse"/>; reading the response body (e.g. using a method like <see cref="IResponse.As{T}"/>) will still wait for the request body to be fetched as usual.</param>
280+
public static IRequest WithOptions(this IRequest request, bool? ignoreHttpErrors = null, bool? ignoreNullArguments = null, HttpCompletionOption? completeWhen = null)
278281
{
279282
return request.WithOptions(new RequestOptions
280283
{
281284
IgnoreHttpErrors = ignoreHttpErrors,
282-
IgnoreNullArguments = ignoreNullArguments
285+
IgnoreNullArguments = ignoreNullArguments,
286+
CompleteWhen = completeWhen
283287
});
284288
}
285289

Client/FluentClientOptions.cs

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
using System.Net.Http;
2+
13
namespace Pathoschild.Http.Client
24
{
35
/// <summary>Options for the fluent client.</summary>
@@ -6,12 +8,18 @@ public class FluentClientOptions
68
/*********
79
** Accessors
810
*********/
9-
/// <summary>Whether to ignore null arguments when the request is dispatched. Default true if not specified.</summary>
11+
/// <summary>Whether null arguments in the request body and URL query string should be ignored (<c>true</c>) or sent as-is (<c>false</c>). Default <c>true</c>.</summary>
1012
public bool? IgnoreNullArguments { get; set; }
1113

12-
/// <summary>Whether HTTP error responses (e.g. HTTP 404) should be ignored (else raised as exceptions). Default false if not specified.</summary>
14+
/// <summary>Whether HTTP error responses like HTTP 404 should be ignored (<c>true</c>) or raised as exceptions (<c>false</c>). Default <c>false</c>.</summary>
1315
public bool? IgnoreHttpErrors { get; set; }
1416

17+
/// <summary>
18+
/// <para>When we should stop waiting for the response. For example, setting this to <see cref="HttpCompletionOption.ResponseHeadersRead"/> will let you handle the response as soon as the headers are received, before the full response body has been fetched. This only affects getting the <see cref="IResponse"/>; reading the response body (e.g. using a method like <see cref="IResponse.As{T}"/>) will still wait for the request body to be fetched as usual.</para>
19+
/// <para>Default <see cref="HttpCompletionOption.ResponseContentRead"/> if not specified.</para>
20+
/// </summary>
21+
public HttpCompletionOption? CompleteWhen { get; set; }
22+
1523

1624
/*********
1725
** Public methods
@@ -22,7 +30,8 @@ internal RequestOptions ToRequestOptions()
2230
return new RequestOptions
2331
{
2432
IgnoreHttpErrors = this.IgnoreHttpErrors,
25-
IgnoreNullArguments = this.IgnoreNullArguments
33+
IgnoreNullArguments = this.IgnoreNullArguments,
34+
CompleteWhen = this.CompleteWhen,
2635
};
2736
}
2837

@@ -32,6 +41,7 @@ internal void MergeFrom(FluentClientOptions? options)
3241
{
3342
this.IgnoreNullArguments = options?.IgnoreNullArguments ?? this.IgnoreNullArguments;
3443
this.IgnoreHttpErrors = options?.IgnoreHttpErrors ?? this.IgnoreHttpErrors;
44+
this.CompleteWhen = options?.CompleteWhen ?? this.CompleteWhen;
3545
}
3646
}
3747
}

Client/RequestOptions.cs

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
using System.Net.Http;
2+
13
namespace Pathoschild.Http.Client
24
{
35
/// <summary>Options for a request.</summary>
@@ -6,12 +8,17 @@ public class RequestOptions
68
/*********
79
** Accessors
810
*********/
9-
/// <summary>Whether to ignore null arguments when the request is dispatched. Default true if not specified.</summary>
11+
/// <summary>Whether null arguments in the request body and URL query string should be ignored (<c>true</c>) or sent as-is (<c>false</c>). Default <c>true</c>.</summary>
1012
public bool? IgnoreNullArguments { get; set; }
1113

12-
/// <summary>Whether HTTP error responses (e.g. HTTP 404) should be ignored (else raised as exceptions). Default false if not specified.</summary>
14+
/// <summary>Whether HTTP error responses like HTTP 404 should be ignored (<c>true</c>) or raised as exceptions (<c>false</c>). Default <c>false</c>.</summary>
1315
public bool? IgnoreHttpErrors { get; set; }
1416

17+
/// <summary>
18+
/// <para>When we should stop waiting for the response. For example, setting this to <see cref="HttpCompletionOption.ResponseHeadersRead"/> will let you handle the response as soon as the headers are received, before the full response body has been fetched. This only affects getting the <see cref="IResponse"/>; reading the response body (e.g. using a method like <see cref="IResponse.As{T}"/>) will still wait for the request body to be fetched as usual.</para>
19+
/// <para>Default <see cref="HttpCompletionOption.ResponseContentRead"/> if not specified.</para>
20+
/// </summary>
21+
public HttpCompletionOption? CompleteWhen { get; set; }
1522

1623
/*********
1724
** Public methods
@@ -22,6 +29,7 @@ internal void MergeFrom(RequestOptions? options)
2229
{
2330
this.IgnoreNullArguments = options?.IgnoreNullArguments ?? this.IgnoreNullArguments;
2431
this.IgnoreHttpErrors = options?.IgnoreHttpErrors ?? this.IgnoreHttpErrors;
32+
this.CompleteWhen = options?.CompleteWhen ?? this.CompleteWhen;
2533
}
2634
}
2735
}

README.md

Lines changed: 30 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -21,9 +21,10 @@ methods are available at each step.
2121
* [Body](#body)
2222
* [Headers](#headers)
2323
* [Read the response](#read-the-response)
24+
* [Read the response info](#read-the-response-info)
2425
* [Handle errors](#handle-errors)
2526
* [Advanced features](#advanced-features)
26-
* [Response metadata](#response-metadata)
27+
* [Request options](#request-options)
2728
* [Simple retry policy](#simple-retry-policy)
2829
* [Cancellation tokens](#cancellation-tokens)
2930
* [Custom requests](#custom-requests)
@@ -172,6 +173,15 @@ If you don't need the content, you can just await the request:
172173
await client.PostAsync("items", new Item(…));
173174
```
174175

176+
### Read the response info
177+
You can also read the HTTP response info before parsing the body:
178+
179+
```c#
180+
IResponse response = await client.GetAsync("items");
181+
if (response.IsSuccessStatusCode || response.Status == HttpStatusCode.Found)
182+
return response.AsArray<Item>();
183+
```
184+
175185
### Handle errors
176186
By default the client will throw `ApiException` if the server returns an error code:
177187
```c#
@@ -204,16 +214,29 @@ If you don't want that, you can...
204214
* [use your own error filter](#custom-filters).
205215

206216
## Advanced features
207-
### Response metadata
208-
The previous examples parse the response directly, but sometimes you want to peek at the HTTP
209-
metadata:
217+
### Request options
218+
You can customize the request/response flow using a few built-in options.
210219

220+
You can set an option for one request:
211221
```c#
212-
IResponse response = await client.GetAsync("messages/latest");
213-
if (response.IsSuccessStatusCode || response.Status == HttpStatusCode.Found)
214-
return response.AsArray<T>();
222+
IResponse response = await client
223+
.GetAsync("items")
224+
.WithOptions(ignoreHttpErrors: true);
225+
```
226+
227+
Or for all requests:
228+
```c#
229+
client.SetOptions(ignoreHttpErrors: true);
215230
```
216231

232+
The available options are:
233+
234+
option | default | effect
235+
--------------------- | ------- | ------
236+
`ignoreHttpErrors` | `false` | Whether HTTP error responses like HTTP 404 should be ignored (`true`) or raised as exceptions (`false`).
237+
`ignoreNullArguments` | `true` | Whether null arguments in the request body and URL query string should be ignored (`true`) or sent as-is (`false`).
238+
`completeWhen` | `ResponseContentRead` | When we should stop waiting for the response. For example, setting this to `ResponseHeadersRead` will let you handle the response as soon as the headers are received, before the full response body has been fetched. This only affects getting the `IResponse`; reading the response body (e.g. using a method like `IResponse.As<T>()`) will still wait for the request body to be fetched as usual.
239+
217240
### Simple retry policy
218241
The client won't retry failed requests by default, but that's easy to configure:
219242
```c#

RELEASE-NOTES.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,10 @@
11
# Release notes
2+
## 4.4.0
3+
Released 24 May 2024.
4+
5+
* Added support for response streaming using the [`completeWhen` request option](README.md#request-options) (thanks to Jericho!).
6+
* Updated dependencies to fix a vulnerability warning.
7+
28
## 4.3.0
39
Released 14 March 2023.
410

Tests/Integration/IntegrationTests.cs

Lines changed: 3 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -24,17 +24,12 @@ public class IntegrationTests
2424
Base = "https://en.wikipedia.org/wiki/Main_Page",
2525
Language = "en",
2626
MainPage = "Main Page",
27-
MaxUploadSize = 4294967296,
27+
MaxUploadSize = 5368709120,
2828
ScriptPath = "/w",
2929
Server = "//en.wikipedia.org",
3030
SiteName = "Wikipedia",
3131
Time = DateTime.UtcNow,
32-
VariantArticlePath =
33-
#if NET452
34-
"False",
35-
#else
36-
"false",
37-
#endif
32+
VariantArticlePath = "false",
3833
WikiID = "enwiki"
3934
};
4035

@@ -45,7 +40,7 @@ public class IntegrationTests
4540
Base = "https://zh.wikipedia.org/wiki/Wikipedia:%E9%A6%96%E9%A1%B5",
4641
Language = "zh",
4742
MainPage = "Wikipedia:\u9996\u9875",
48-
MaxUploadSize = 4294967296,
43+
MaxUploadSize = 5368709120,
4944
ScriptPath = "/w",
5045
Server = "//zh.wikipedia.org",
5146
SiteName = "Wikipedia",

Tests/Tests.csproj

Lines changed: 25 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -23,19 +23,36 @@
2323
</ItemGroup>
2424

2525
<ItemGroup>
26-
<!-- main versions -->
27-
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.4.1" Condition="'$(NetCore2OrNewer)' == 'true'" />
26+
<!--
27+
main versions
28+
29+
We deliberately use old versions of some packages to support our target frameworks:
30+
31+
package | max | reason
32+
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ | ~~~~~~ | ~~~~~~~~~~~~~~~~~~~~~~~~~~~
33+
Microsoft.AspNetCore.WebUtilities | 2.2.0 | next only supports .NET 8+.
34+
NUnit | 3.14.0 | next only supports .NET 6+.
35+
-->
2836
<PackageReference Include="Microsoft.AspNetCore.WebUtilities" Version="2.2.0" Condition="'$(NetCore2OrNewer)' == 'true'" />
29-
<PackageReference Include="Moq" Version="4.18.4" Condition="'$(NetCore2OrNewer)' == 'true'" />
30-
<PackageReference Include="NUnit" Version="3.13.3" Condition="'$(NetCore2OrNewer)' == 'true'" />
31-
<PackageReference Include="NUnit3TestAdapter" Version="4.3.1" PrivateAssets="all" IncludeAssets="runtime; build; native; contentfiles; analyzers; buildtransitive" />
32-
<PackageReference Include="RichardSzalay.MockHttp" Version="6.0.0" />
37+
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.9.0" />
38+
<PackageReference Include="Moq" Version="4.20.70" Condition="'$(NetCore2OrNewer)' == 'true'" />
39+
<PackageReference Include="NUnit" Version="3.14.0" Condition="'$(NetCore2OrNewer)' == 'true'" />
40+
<PackageReference Include="NUnit3TestAdapter" Version="4.5.0" PrivateAssets="all" IncludeAssets="runtime; build; native; contentfiles; analyzers; buildtransitive" />
41+
<PackageReference Include="RichardSzalay.MockHttp" Version="7.0.0" />
42+
43+
<!--
44+
legacy versions for older platforms
45+
46+
We deliberately use old versions of some packages to support our target frameworks:
3347
34-
<!-- legacy versions for older platforms -->
48+
package | max | reason
49+
~~~~~~~ | ~~~~~~ | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
50+
Moq | 4.10.1 | next is .NET Standard 2.0+ only.
51+
NUnit | 3.12.0 | next is .NET Standard 2.0+ only.
52+
-->
3553
<PackageReference Include="Microsoft.AspNet.WebUtilities" Version="1.0.0-rc1-final" Condition="'$(NetCore2OrNewer)' == 'false'" />
3654
<PackageReference Include="Moq" Version="4.10.1" Condition="'$(NetCore2OrNewer)' == 'false'" />
3755
<PackageReference Include="NUnit" Version="3.12.0" Condition="'$(NetCore2OrNewer)' == 'false'" />
38-
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.3.2" Condition="'$(NetCore2OrNewer)' == 'false'" />
3956
</ItemGroup>
4057

4158
<ItemGroup>

0 commit comments

Comments
 (0)