Skip to content

Commit b2031e0

Browse files
Response handling improvements (#1690)
* * Handle response content with custom encoding detection * Add ConfigureAwait(false) to async calls * One more async * Configure more awaits * Event more configure await * Simplify the Get with object parameters
1 parent 1556eed commit b2031e0

File tree

9 files changed

+136
-132
lines changed

9 files changed

+136
-132
lines changed

src/RestSharp/Authenticators/AuthenticatorBase.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,5 +22,5 @@ public abstract class AuthenticatorBase : IAuthenticator {
2222
protected abstract ValueTask<Parameter> GetAuthenticationParameter(string accessToken);
2323

2424
public async ValueTask Authenticate(RestClient client, RestRequest request)
25-
=> request.AddOrUpdateParameter(await GetAuthenticationParameter(Token));
25+
=> request.AddOrUpdateParameter(await GetAuthenticationParameter(Token).ConfigureAwait(false));
2626
}

src/RestSharp/Extensions/MiscExtensions.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -31,9 +31,9 @@ public static async Task<byte[]> ReadAsBytes(this Stream input, CancellationToke
3131

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

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
// Copyright © 2009-2020 John Sheehan, Andrew Young, Alexey Zimarev and RestSharp community
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.Text;
17+
18+
namespace RestSharp;
19+
20+
static class ResponseHandling {
21+
public static string GetResponseString(this HttpResponseMessage response, byte[] bytes) {
22+
var encodingString = response.Content.Headers.ContentEncoding.FirstOrDefault();
23+
var encoding = encodingString != null ? TryGetEncoding(encodingString) : Encoding.Default;
24+
return encoding.GetString(bytes);
25+
26+
Encoding TryGetEncoding(string es) {
27+
try {
28+
return Encoding.GetEncoding(es);
29+
}
30+
catch {
31+
return Encoding.Default;
32+
}
33+
}
34+
}
35+
36+
public static Task<Stream?> ReadResponse(this HttpResponseMessage response, CancellationToken cancellationToken) {
37+
#if NETSTANDARD
38+
return response.Content.ReadAsStreamAsync();
39+
# else
40+
return response.Content.ReadAsStreamAsync(cancellationToken);
41+
#endif
42+
}
43+
}

src/RestSharp/Response/RestResponse.cs

Lines changed: 13 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -14,8 +14,6 @@
1414

1515
using System.Diagnostics;
1616
using System.Net;
17-
using System.Net.Http.Headers;
18-
using System.Text;
1917
using RestSharp.Extensions;
2018

2119
// ReSharper disable SuggestBaseTypeForParameter
@@ -51,8 +49,7 @@ public static RestResponse<T> FromResponse(RestResponse response)
5149
Server = response.Server,
5250
StatusCode = response.StatusCode,
5351
StatusDescription = response.StatusDescription,
54-
Request = response.Request,
55-
ResponseMessage = response.ResponseMessage
52+
Request = response.Request
5653
};
5754
}
5855

@@ -67,40 +64,13 @@ internal static async Task<RestResponse> FromHttpResponse(
6764
CookieCollection cookieCollection,
6865
CancellationToken cancellationToken
6966
) {
70-
return request.AdvancedResponseWriter?.Invoke(httpResponse) ?? await GetDefaultResponse();
67+
return request.AdvancedResponseWriter?.Invoke(httpResponse) ?? await GetDefaultResponse().ConfigureAwait(false);
7168

7269
async Task<RestResponse> GetDefaultResponse() {
73-
byte[]? bytes;
74-
string? content;
75-
76-
if (request.ResponseWriter != null) {
77-
#if NETSTANDARD
78-
var stream = await httpResponse.Content.ReadAsStreamAsync();
79-
# else
80-
var stream = await httpResponse.Content.ReadAsStreamAsync(cancellationToken);
81-
#endif
82-
var converted = request.ResponseWriter(stream);
83-
84-
if (converted == null) {
85-
bytes = null;
86-
content = null;
87-
}
88-
else {
89-
bytes = await converted.ReadAsBytes(cancellationToken);
90-
var encodingString = httpResponse.Content.Headers.ContentEncoding.FirstOrDefault();
91-
var encoding = encodingString != null ? Encoding.GetEncoding(encodingString) : Encoding.UTF8;
92-
content = encoding.GetString(bytes);
93-
}
94-
}
95-
else {
96-
#if NETSTANDARD
97-
bytes = await httpResponse.Content.ReadAsByteArrayAsync();
98-
content = await httpResponse.Content.ReadAsStringAsync();
99-
# else
100-
bytes = await httpResponse.Content.ReadAsByteArrayAsync(cancellationToken);
101-
content = await httpResponse.Content.ReadAsStringAsync(cancellationToken);
102-
#endif
103-
}
70+
var readTask = request.ResponseWriter == null ? ReadResponse() : ReadAndConvertResponse();
71+
using var stream = await readTask.ConfigureAwait(false);
72+
var bytes = stream == null ? null : await stream.ReadAsBytes(cancellationToken).ConfigureAwait(false);
73+
var content = bytes == null ? null : httpResponse.GetResponseString(bytes);
10474

10575
return new RestResponse {
10676
Content = content,
@@ -116,11 +86,17 @@ async Task<RestResponse> GetDefaultResponse() {
11686
StatusDescription = httpResponse.ReasonPhrase,
11787
IsSuccessful = httpResponse.IsSuccessStatusCode,
11888
Request = request,
119-
ResponseMessage = httpResponse,
12089
Headers = httpResponse.Headers.GetHeaderParameters(),
12190
ContentHeaders = httpResponse.Content.Headers.GetHeaderParameters(),
12291
Cookies = cookieCollection
12392
};
93+
94+
Task<Stream?> ReadResponse() => httpResponse.ReadResponse(cancellationToken);
95+
96+
async Task<Stream?> ReadAndConvertResponse() {
97+
using var original = await ReadResponse().ConfigureAwait(false);
98+
return request.ResponseWriter!(original!);
99+
}
124100
}
125101
}
126102
}

src/RestSharp/Response/RestResponseBase.cs

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -34,8 +34,6 @@ public abstract class RestResponseBase {
3434
/// Mainly for debugging if ResponseStatus is not OK
3535
/// </remarks>
3636
public RestRequest? Request { get; set; }
37-
38-
public HttpResponseMessage? ResponseMessage { get; init; }
3937

4038
/// <summary>
4139
/// MIME content type of response

src/RestSharp/RestClient.Async.cs

Lines changed: 20 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -23,17 +23,18 @@ public partial class RestClient {
2323
/// <param name="request">Request to be executed</param>
2424
/// <param name="cancellationToken">Cancellation token</param>
2525
public async Task<RestResponse> ExecuteAsync(RestRequest request, CancellationToken cancellationToken = default) {
26-
var internalResponse = await ExecuteInternal(request, cancellationToken);
26+
var internalResponse = await ExecuteInternal(request, cancellationToken).ConfigureAwait(false);
2727

2828
var response = new RestResponse();
2929

3030
response = internalResponse.Exception == null
3131
? await RestResponse.FromHttpResponse(
32-
internalResponse.ResponseMessage!,
33-
request,
34-
CookieContainer.GetCookies(internalResponse.Url),
35-
cancellationToken
36-
)
32+
internalResponse.ResponseMessage!,
33+
request,
34+
CookieContainer.GetCookies(internalResponse.Url),
35+
cancellationToken
36+
)
37+
.ConfigureAwait(false)
3738
: ReturnErrorOrThrow(response, internalResponse.Exception, internalResponse.TimeoutToken);
3839

3940
response.Request = request;
@@ -47,7 +48,7 @@ async Task<InternalResponse> ExecuteInternal(RestRequest request, CancellationTo
4748
using var requestContent = new RequestContent(this, request);
4849

4950
if (Authenticator != null)
50-
await Authenticator.Authenticate(this, request);
51+
await Authenticator.Authenticate(this, request).ConfigureAwait(false);
5152

5253
var httpMethod = AsHttpMethod(request.Method);
5354
var url = BuildUri(request);
@@ -67,12 +68,12 @@ async Task<InternalResponse> ExecuteInternal(RestRequest request, CancellationTo
6768
message.AddHeaders(headers);
6869

6970
if (request.OnBeforeRequest != null)
70-
await request.OnBeforeRequest(message);
71+
await request.OnBeforeRequest(message).ConfigureAwait(false);
7172

72-
var responseMessage = await HttpClient.SendAsync(message, request.CompletionOption, ct);
73+
var responseMessage = await HttpClient.SendAsync(message, request.CompletionOption, ct).ConfigureAwait(false);
7374

7475
if (request.OnAfterRequest != null)
75-
await request.OnAfterRequest(responseMessage);
76+
await request.OnAfterRequest(responseMessage).ConfigureAwait(false);
7677

7778
return new InternalResponse(responseMessage, url, null, timeoutCts.Token);
7879
}
@@ -89,8 +90,9 @@ record InternalResponse(HttpResponseMessage? ResponseMessage, Uri Url, Exception
8990
/// <param name="request">Pre-configured request instance.</param>
9091
/// <param name="cancellationToken"></param>
9192
/// <returns>The downloaded stream.</returns>
93+
[PublicAPI]
9294
public async Task<Stream?> DownloadStreamAsync(RestRequest request, CancellationToken cancellationToken = default) {
93-
var response = await ExecuteInternal(request, cancellationToken);
95+
var response = await ExecuteInternal(request, cancellationToken).ConfigureAwait(false);
9496

9597
if (response.Exception != null) {
9698
return Options.ThrowOnAnyError ? throw response.Exception : null;
@@ -99,18 +101,11 @@ record InternalResponse(HttpResponseMessage? ResponseMessage, Uri Url, Exception
99101
if (response.ResponseMessage == null) return null;
100102

101103
if (request.ResponseWriter != null) {
102-
#if NETSTANDARD
103-
var stream = await response.ResponseMessage.Content.ReadAsStreamAsync();
104-
# else
105-
var stream = await response.ResponseMessage.Content.ReadAsStreamAsync(cancellationToken);
106-
#endif
107-
return request.ResponseWriter(stream);
104+
using var stream = await response.ResponseMessage.ReadResponse(cancellationToken).ConfigureAwait(false);
105+
return request.ResponseWriter(stream!);
108106
}
109-
#if NETSTANDARD
110-
return await response.ResponseMessage.Content.ReadAsStreamAsync();
111-
# else
112-
return await response.ResponseMessage.Content.ReadAsStreamAsync(cancellationToken);
113-
#endif
107+
108+
return await response.ResponseMessage.ReadResponse(cancellationToken).ConfigureAwait(false);
114109
}
115110

116111
/// <summary>
@@ -119,35 +114,10 @@ record InternalResponse(HttpResponseMessage? ResponseMessage, Uri Url, Exception
119114
/// <param name="request">Pre-configured request instance.</param>
120115
/// <param name="cancellationToken"></param>
121116
/// <returns>The downloaded file.</returns>
117+
[PublicAPI]
122118
public async Task<byte[]?> DownloadDataAsync(RestRequest request, CancellationToken cancellationToken = default) {
123-
var response = await ExecuteInternal(request, cancellationToken);
124-
125-
if (response.Exception != null) {
126-
return Options.ThrowOnAnyError ? throw response.Exception : null;
127-
}
128-
129-
if (response.ResponseMessage == null) return null;
130-
131-
byte[]? bytes;
132-
133-
if (request.ResponseWriter != null) {
134-
#if NETSTANDARD
135-
var stream = await response.ResponseMessage.Content.ReadAsStreamAsync();
136-
# else
137-
var stream = await response.ResponseMessage.Content.ReadAsStreamAsync(cancellationToken);
138-
#endif
139-
var converted = request.ResponseWriter(stream);
140-
bytes = converted == null ? null : await converted.ReadAsBytes(cancellationToken);
141-
}
142-
else {
143-
#if NETSTANDARD
144-
bytes = await response.ResponseMessage.Content.ReadAsByteArrayAsync();
145-
# else
146-
bytes = await response.ResponseMessage.Content.ReadAsByteArrayAsync(cancellationToken);
147-
#endif
148-
}
149-
150-
return bytes;
119+
using var stream = await DownloadStreamAsync(request, cancellationToken).ConfigureAwait(false);
120+
return stream == null ? null : await stream.ReadAsBytes(cancellationToken).ConfigureAwait(false);
151121
}
152122

153123
RestResponse ReturnErrorOrThrow(RestResponse response, Exception exception, CancellationToken timeoutToken) {

src/RestSharp/RestClientExtensions.Json.cs

Lines changed: 4 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -39,22 +39,10 @@ public static partial class RestClientExtensions {
3939
CancellationToken cancellationToken = default
4040
) {
4141
var props = parameters.GetProperties();
42-
var query = new List<Parameter>();
43-
44-
foreach (var (name, value) in props) {
45-
var param = $"{name}";
46-
47-
if (resource.Contains(param)) {
48-
resource = resource.Replace(param, value);
49-
}
50-
else {
51-
query.Add(new QueryParameter(name, value));
52-
}
53-
}
54-
5542
var request = new RestRequest(resource);
5643

57-
foreach (var parameter in query) {
44+
foreach (var (name, value) in props) {
45+
Parameter parameter = resource.Contains($"{name}") ? new UrlSegmentParameter(name, value!) : new QueryParameter(name, value);
5846
request.AddParameter(parameter);
5947
}
6048

@@ -99,7 +87,7 @@ public static async Task<HttpStatusCode> PostJsonAsync<TRequest>(
9987
CancellationToken cancellationToken = default
10088
) where TRequest : class {
10189
var restRequest = new RestRequest().AddJsonBody(request);
102-
var response = await client.PostAsync(restRequest, cancellationToken);
90+
var response = await client.PostAsync(restRequest, cancellationToken).ConfigureAwait(false);
10391
return response.StatusCode;
10492
}
10593

@@ -141,7 +129,7 @@ public static async Task<HttpStatusCode> PutJsonAsync<TRequest>(
141129
CancellationToken cancellationToken = default
142130
) where TRequest : class {
143131
var restRequest = new RestRequest().AddJsonBody(request);
144-
var response = await client.PutAsync(restRequest, cancellationToken);
132+
var response = await client.PutAsync(restRequest, cancellationToken).ConfigureAwait(false);
145133
return response.StatusCode;
146134
}
147135
}

0 commit comments

Comments
 (0)