Skip to content

Commit e147e1c

Browse files
Add CacheControl (#2053)
Cleanup response writer code
1 parent edce8c9 commit e147e1c

File tree

12 files changed

+153
-101
lines changed

12 files changed

+153
-101
lines changed

src/RestSharp/Extensions/HttpResponseExtensions.cs

Lines changed: 49 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -13,15 +13,60 @@
1313
// limitations under the License.
1414
//
1515

16+
using System.Text;
17+
1618
namespace RestSharp.Extensions;
1719

18-
public static class HttpResponseExtensions {
19-
internal static Exception? MaybeException(this HttpResponseMessage httpResponse)
20+
static class HttpResponseExtensions {
21+
public static Exception? MaybeException(this HttpResponseMessage httpResponse)
2022
=> httpResponse.IsSuccessStatusCode
2123
? null
22-
#if NETSTANDARD || NETFRAMEWORK
24+
#if NET
25+
: new HttpRequestException($"Request failed with status code {httpResponse.StatusCode}", null, httpResponse.StatusCode);
26+
#else
2327
: new HttpRequestException($"Request failed with status code {httpResponse.StatusCode}");
28+
#endif
29+
30+
public static string GetResponseString(this HttpResponseMessage response, byte[] bytes, Encoding clientEncoding) {
31+
var encodingString = response.Content.Headers.ContentType?.CharSet;
32+
var encoding = encodingString != null ? TryGetEncoding(encodingString) : clientEncoding;
33+
34+
using var reader = new StreamReader(new MemoryStream(bytes), encoding);
35+
return reader.ReadToEnd();
36+
37+
Encoding TryGetEncoding(string es) {
38+
try {
39+
return Encoding.GetEncoding(es);
40+
}
41+
catch {
42+
return Encoding.Default;
43+
}
44+
}
45+
}
46+
47+
public static Task<Stream?> ReadResponseStream(
48+
this HttpResponseMessage httpResponse,
49+
Func<Stream, Stream?>? writer,
50+
CancellationToken cancellationToken = default
51+
) {
52+
var readTask = writer == null ? ReadResponse() : ReadAndConvertResponse();
53+
return readTask;
54+
55+
Task<Stream?> ReadResponse() {
56+
#if NET
57+
return httpResponse.Content.ReadAsStreamAsync(cancellationToken)!;
58+
# else
59+
return httpResponse.Content.ReadAsStreamAsync();
60+
#endif
61+
}
62+
63+
async Task<Stream?> ReadAndConvertResponse() {
64+
#if NET
65+
await using var original = await ReadResponse().ConfigureAwait(false);
2466
#else
25-
: new HttpRequestException($"Request failed with status code {httpResponse.StatusCode}", null, httpResponse.StatusCode);
67+
using var original = await ReadResponse().ConfigureAwait(false);
2668
#endif
69+
return writer!(original!);
70+
}
71+
}
2772
}

src/RestSharp/Extensions/StreamExtensions.cs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -30,10 +30,10 @@ public static async Task<byte[]> ReadAsBytes(this Stream input, CancellationToke
3030
using var ms = new MemoryStream();
3131

3232
int read;
33-
#if NETSTANDARD || NETFRAMEWORK
34-
while ((read = await input.ReadAsync(buffer, 0, buffer.Length, cancellationToken).ConfigureAwait(false)) > 0)
35-
#else
33+
#if NET
3634
while ((read = await input.ReadAsync(buffer, cancellationToken).ConfigureAwait(false)) > 0)
35+
#else
36+
while ((read = await input.ReadAsync(buffer, 0, buffer.Length, cancellationToken).ConfigureAwait(false)) > 0)
3737
#endif
3838
ms.Write(buffer, 0, read);
3939

src/RestSharp/Properties/IsExternalInit.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
#if NETSTANDARD || NETFRAMEWORK
1+
#if !NET
22
using System.ComponentModel;
33

44
// ReSharper disable once CheckNamespace

src/RestSharp/Request/RestRequest.cs

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
// limitations under the License.
1414

1515
using System.Net;
16+
using System.Net.Http.Headers;
1617
using RestSharp.Authenticators;
1718
using RestSharp.Extensions;
1819

@@ -25,8 +26,8 @@ namespace RestSharp;
2526
/// Container for data used to make requests
2627
/// </summary>
2728
public class RestRequest {
28-
readonly Func<HttpResponseMessage, RestRequest, RestResponse>? _advancedResponseHandler;
29-
readonly Func<Stream, Stream?>? _responseWriter;
29+
Func<HttpResponseMessage, RestRequest, RestResponse>? _advancedResponseHandler;
30+
Func<Stream, Stream?>? _responseWriter;
3031

3132
/// <summary>
3233
/// Default constructor
@@ -186,12 +187,17 @@ public RestRequest(Uri resource, Method method = Method.Get)
186187
/// </summary>
187188
public HttpCompletionOption CompletionOption { get; set; } = HttpCompletionOption.ResponseContentRead;
188189

190+
/// <summary>
191+
/// Cache policy to be used for requests using <seealso cref="CacheControlHeaderValue"/>
192+
/// </summary>
193+
public CacheControlHeaderValue? CachePolicy { get; set; }
194+
189195
/// <summary>
190196
/// Set this to write response to Stream rather than reading into memory.
191197
/// </summary>
192198
public Func<Stream, Stream?>? ResponseWriter {
193199
get => _responseWriter;
194-
init {
200+
set {
195201
if (AdvancedResponseWriter != null)
196202
throw new ArgumentException(
197203
"AdvancedResponseWriter is not null. Only one response writer can be used."
@@ -206,7 +212,7 @@ public RestRequest(Uri resource, Method method = Method.Get)
206212
/// </summary>
207213
public Func<HttpResponseMessage, RestRequest, RestResponse>? AdvancedResponseWriter {
208214
get => _advancedResponseHandler;
209-
init {
215+
set {
210216
if (ResponseWriter != null) throw new ArgumentException("ResponseWriter is not null. Only one response writer can be used.");
211217

212218
_advancedResponseHandler = value;

src/RestSharp/Response/ResponseHandling.cs

Lines changed: 0 additions & 45 deletions
This file was deleted.

src/RestSharp/Response/RestResponse.cs

Lines changed: 4 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -72,14 +72,13 @@ CancellationToken cancellationToken
7272
return request.AdvancedResponseWriter?.Invoke(httpResponse, request) ?? await GetDefaultResponse().ConfigureAwait(false);
7373

7474
async Task<RestResponse> GetDefaultResponse() {
75-
var readTask = request.ResponseWriter == null ? ReadResponse() : ReadAndConvertResponse();
76-
#if NETSTANDARD || NETFRAMEWORK
77-
using var stream = await readTask.ConfigureAwait(false);
75+
#if NET
76+
await using var stream = await httpResponse.ReadResponseStream(request.ResponseWriter, cancellationToken).ConfigureAwait(false);
7877
#else
79-
await using var stream = await readTask.ConfigureAwait(false);
78+
using var stream = await httpResponse.ReadResponseStream(request.ResponseWriter, cancellationToken).ConfigureAwait(false);
8079
#endif
8180

82-
var bytes = request.ResponseWriter != null || stream == null ? null : await stream.ReadAsBytes(cancellationToken).ConfigureAwait(false);
81+
var bytes = stream == null ? null : await stream.ReadAsBytes(cancellationToken).ConfigureAwait(false);
8382
var content = bytes == null ? null : httpResponse.GetResponseString(bytes, encoding);
8483

8584
return new RestResponse(request) {
@@ -101,17 +100,6 @@ async Task<RestResponse> GetDefaultResponse() {
101100
Cookies = cookieCollection,
102101
RootElement = request.RootElement
103102
};
104-
105-
Task<Stream?> ReadResponse() => httpResponse.ReadResponse(cancellationToken);
106-
107-
async Task<Stream?> ReadAndConvertResponse() {
108-
#if NETSTANDARD || NETFRAMEWORK
109-
using var original = await ReadResponse().ConfigureAwait(false);
110-
#else
111-
await using var original = await ReadResponse().ConfigureAwait(false);
112-
#endif
113-
return request.ResponseWriter!(original!);
114-
}
115103
}
116104
}
117105

src/RestSharp/RestClient.Async.cs

Lines changed: 3 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
// limitations under the License.
1414

1515
using System.Net;
16+
using System.Net.Http.Headers;
1617
using RestSharp.Extensions;
1718

1819
namespace RestSharp;
@@ -52,16 +53,7 @@ public async Task<RestResponse> ExecuteAsync(RestRequest request, CancellationTo
5253

5354
if (response.ResponseMessage == null) return null;
5455

55-
if (request.ResponseWriter != null) {
56-
#if NETSTANDARD || NETFRAMEWORK
57-
using var stream = await response.ResponseMessage.ReadResponse(cancellationToken).ConfigureAwait(false);
58-
#else
59-
await using var stream = await response.ResponseMessage.ReadResponse(cancellationToken).ConfigureAwait(false);
60-
#endif
61-
return request.ResponseWriter(stream!);
62-
}
63-
64-
return await response.ResponseMessage.ReadResponse(cancellationToken).ConfigureAwait(false);
56+
return await response.ResponseMessage.ReadResponseStream(request.ResponseWriter, cancellationToken).ConfigureAwait(false);
6557
}
6658

6759
static RestResponse GetErrorResponse(RestRequest request, Exception exception, CancellationToken timeoutToken) {
@@ -95,7 +87,7 @@ async Task<HttpResponse> ExecuteRequestAsync(RestRequest request, CancellationTo
9587
var url = this.BuildUri(request);
9688
var message = new HttpRequestMessage(httpMethod, url) { Content = requestContent.BuildContent() };
9789
message.Headers.Host = Options.BaseHost;
98-
message.Headers.CacheControl = Options.CachePolicy;
90+
message.Headers.CacheControl = request.CachePolicy ?? Options.CachePolicy;
9991

10092
using var timeoutCts = new CancellationTokenSource(request.Timeout > 0 ? request.Timeout : int.MaxValue);
10193
using var cts = CancellationTokenSource.CreateLinkedTokenSource(timeoutCts.Token, cancellationToken);

src/RestSharp/RestClient.Extensions.cs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -294,10 +294,10 @@ public static async Task<RestResponse> DeleteAsync(this IRestClient client, Rest
294294
/// <returns>The downloaded file.</returns>
295295
[PublicAPI]
296296
public static async Task<byte[]?> DownloadDataAsync(this IRestClient client, RestRequest request, CancellationToken cancellationToken = default) {
297-
#if NETSTANDARD || NETFRAMEWORK
298-
using var stream = await client.DownloadStreamAsync(request, cancellationToken).ConfigureAwait(false);
299-
#else
297+
#if NET
300298
await using var stream = await client.DownloadStreamAsync(request, cancellationToken).ConfigureAwait(false);
299+
#else
300+
using var stream = await client.DownloadStreamAsync(request, cancellationToken).ConfigureAwait(false);
301301
#endif
302302
return stream == null ? null : await stream.ReadAsBytes(cancellationToken).ConfigureAwait(false);
303303
}

test/RestSharp.Tests.Integrated/DownloadFileTests.cs

Lines changed: 17 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -34,13 +34,15 @@ void FileHandler(HttpListenerRequest request, HttpListenerResponse response) {
3434
public async Task AdvancedResponseWriter_without_ResponseWriter_reads_stream() {
3535
var tag = string.Empty;
3636

37-
var rr = new RestRequest("Assets/Koala.jpg") {
38-
AdvancedResponseWriter = (response, request) => {
39-
var buf = new byte[16];
40-
response.Content.ReadAsStream().Read(buf, 0, buf.Length);
41-
tag = Encoding.ASCII.GetString(buf, 6, 4);
42-
return new RestResponse(request);
43-
}
37+
// ReSharper disable once UseObjectOrCollectionInitializer
38+
var rr = new RestRequest("Assets/Koala.jpg");
39+
40+
rr.AdvancedResponseWriter = (response, request) => {
41+
var buf = new byte[16];
42+
// ReSharper disable once MustUseReturnValue
43+
response.Content.ReadAsStream().Read(buf, 0, buf.Length);
44+
tag = Encoding.ASCII.GetString(buf, 6, 4);
45+
return new RestResponse(request);
4446
};
4547

4648
await _client.ExecuteAsync(rr);
@@ -50,7 +52,7 @@ public async Task AdvancedResponseWriter_without_ResponseWriter_reads_stream() {
5052
[Fact]
5153
public async Task Handles_File_Download_Failure() {
5254
var request = new RestRequest("Assets/Koala1.jpg");
53-
var task = () => _client.DownloadDataAsync(request);
55+
var task = () => _client.DownloadDataAsync(request);
5456
await task.Should().ThrowAsync<HttpRequestException>().WithMessage("Request failed with status code NotFound");
5557
}
5658

@@ -67,13 +69,14 @@ public async Task Handles_Binary_File_Download() {
6769
public async Task Writes_Response_To_Stream() {
6870
var tempFile = Path.GetTempFileName();
6971

70-
var request = new RestRequest("Assets/Koala.jpg") {
71-
ResponseWriter = responseStream => {
72-
using var writer = File.OpenWrite(tempFile);
72+
// ReSharper disable once UseObjectOrCollectionInitializer
73+
var request = new RestRequest("Assets/Koala.jpg");
7374

74-
responseStream.CopyTo(writer);
75-
return null;
76-
}
75+
request.ResponseWriter = responseStream => {
76+
using var writer = File.OpenWrite(tempFile);
77+
78+
responseStream.CopyTo(writer);
79+
return null;
7780
};
7881
var response = await _client.DownloadDataAsync(request);
7982

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
// Copyright (c) .NET Foundation and Contributors
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
//
15+
16+
using System.Net;
17+
using RestSharp.Tests.Integrated.Server;
18+
19+
namespace RestSharp.Tests.Integrated;
20+
21+
[Collection(nameof(TestServerCollection))]
22+
public class RedirectTests {
23+
readonly RestClient _client;
24+
25+
public RedirectTests(TestServerFixture fixture, ITestOutputHelper output) {
26+
var options = new RestClientOptions(fixture.Server.Url) {
27+
FollowRedirects = true
28+
};
29+
_client = new RestClient(options);
30+
}
31+
32+
[Fact]
33+
public async Task Can_Perform_GET_Async_With_Redirect() {
34+
const string val = "Works!";
35+
36+
var request = new RestRequest("redirect");
37+
38+
var response = await _client.ExecuteAsync<Response>(request);
39+
response.StatusCode.Should().Be(HttpStatusCode.OK);
40+
response.Data!.Message.Should().Be(val);
41+
}
42+
43+
class Response {
44+
public string? Message { get; set; }
45+
}
46+
}

test/RestSharp.Tests.Integrated/Server/TestServer.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ public HttpServer(ITestOutputHelper? output = null) {
4040
// Cookies
4141
_app.MapGet("get-cookies", CookieHandlers.HandleCookies);
4242
_app.MapGet("set-cookies", CookieHandlers.HandleSetCookies);
43+
_app.MapGet("redirect", () => Results.Redirect("/success", false, true));
4344

4445
// PUT
4546
_app.MapPut(

test/RestSharp.Tests/OptionsTests.cs

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
namespace RestSharp.Tests;
2+
3+
public class OptionsTests {
4+
[Fact]
5+
public void Ensure_follow_redirect() {
6+
var value = false;
7+
var options = new RestClientOptions { FollowRedirects = true, ConfigureMessageHandler = Configure};
8+
var _ = new RestClient(options);
9+
value.Should().BeTrue();
10+
11+
HttpMessageHandler Configure(HttpMessageHandler handler) {
12+
value = ((handler as HttpClientHandler)!).AllowAutoRedirect;
13+
return handler;
14+
}
15+
}
16+
}

0 commit comments

Comments
 (0)