Skip to content

Commit

Permalink
Prerelease 107.2 (#1730)
Browse files Browse the repository at this point in the history
* #1725 set the timeout status correctly
* #1727 use the HttpClient base address if it is set
* Streaming API support #1672 #1674
  • Loading branch information
alexeyzimarev committed Feb 3, 2022
1 parent 077b8da commit cd4c434
Show file tree
Hide file tree
Showing 21 changed files with 355 additions and 213 deletions.
54 changes: 0 additions & 54 deletions .github/workflows/codacy-analysis.yml

This file was deleted.

54 changes: 0 additions & 54 deletions .github/workflows/codeql-analysis.yml

This file was deleted.

33 changes: 33 additions & 0 deletions RestSharp.sln
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "RestSharp.Tests.Serializers
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "RestSharp.Serializers.Xml", "src\RestSharp.Serializers.Xml\RestSharp.Serializers.Xml.csproj", "{4A35B1C5-520D-4267-BA70-2DCEAC0A5662}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "RestSharp.Tests.Legacy", "test\RestSharp.Tests.Legacy\RestSharp.Tests.Legacy.csproj", "{5A8A5BBE-28DA-4C89-B393-BE39A96E8DC0}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug.Appveyor|Any CPU = Debug.Appveyor|Any CPU
Expand Down Expand Up @@ -348,6 +350,36 @@ Global
{4A35B1C5-520D-4267-BA70-2DCEAC0A5662}.Release|x64.Build.0 = Release|Any CPU
{4A35B1C5-520D-4267-BA70-2DCEAC0A5662}.Release|x86.ActiveCfg = Release|Any CPU
{4A35B1C5-520D-4267-BA70-2DCEAC0A5662}.Release|x86.Build.0 = Release|Any CPU
{5A8A5BBE-28DA-4C89-B393-BE39A96E8DC0}.Debug.Appveyor|Any CPU.ActiveCfg = Debug|Any CPU
{5A8A5BBE-28DA-4C89-B393-BE39A96E8DC0}.Debug.Appveyor|Any CPU.Build.0 = Debug|Any CPU
{5A8A5BBE-28DA-4C89-B393-BE39A96E8DC0}.Debug.Appveyor|ARM.ActiveCfg = Debug|Any CPU
{5A8A5BBE-28DA-4C89-B393-BE39A96E8DC0}.Debug.Appveyor|ARM.Build.0 = Debug|Any CPU
{5A8A5BBE-28DA-4C89-B393-BE39A96E8DC0}.Debug.Appveyor|Mixed Platforms.ActiveCfg = Debug|Any CPU
{5A8A5BBE-28DA-4C89-B393-BE39A96E8DC0}.Debug.Appveyor|Mixed Platforms.Build.0 = Debug|Any CPU
{5A8A5BBE-28DA-4C89-B393-BE39A96E8DC0}.Debug.Appveyor|x64.ActiveCfg = Debug|Any CPU
{5A8A5BBE-28DA-4C89-B393-BE39A96E8DC0}.Debug.Appveyor|x64.Build.0 = Debug|Any CPU
{5A8A5BBE-28DA-4C89-B393-BE39A96E8DC0}.Debug.Appveyor|x86.ActiveCfg = Debug|Any CPU
{5A8A5BBE-28DA-4C89-B393-BE39A96E8DC0}.Debug.Appveyor|x86.Build.0 = Debug|Any CPU
{5A8A5BBE-28DA-4C89-B393-BE39A96E8DC0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{5A8A5BBE-28DA-4C89-B393-BE39A96E8DC0}.Debug|Any CPU.Build.0 = Debug|Any CPU
{5A8A5BBE-28DA-4C89-B393-BE39A96E8DC0}.Debug|ARM.ActiveCfg = Debug|Any CPU
{5A8A5BBE-28DA-4C89-B393-BE39A96E8DC0}.Debug|ARM.Build.0 = Debug|Any CPU
{5A8A5BBE-28DA-4C89-B393-BE39A96E8DC0}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU
{5A8A5BBE-28DA-4C89-B393-BE39A96E8DC0}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU
{5A8A5BBE-28DA-4C89-B393-BE39A96E8DC0}.Debug|x64.ActiveCfg = Debug|Any CPU
{5A8A5BBE-28DA-4C89-B393-BE39A96E8DC0}.Debug|x64.Build.0 = Debug|Any CPU
{5A8A5BBE-28DA-4C89-B393-BE39A96E8DC0}.Debug|x86.ActiveCfg = Debug|Any CPU
{5A8A5BBE-28DA-4C89-B393-BE39A96E8DC0}.Debug|x86.Build.0 = Debug|Any CPU
{5A8A5BBE-28DA-4C89-B393-BE39A96E8DC0}.Release|Any CPU.ActiveCfg = Release|Any CPU
{5A8A5BBE-28DA-4C89-B393-BE39A96E8DC0}.Release|Any CPU.Build.0 = Release|Any CPU
{5A8A5BBE-28DA-4C89-B393-BE39A96E8DC0}.Release|ARM.ActiveCfg = Release|Any CPU
{5A8A5BBE-28DA-4C89-B393-BE39A96E8DC0}.Release|ARM.Build.0 = Release|Any CPU
{5A8A5BBE-28DA-4C89-B393-BE39A96E8DC0}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU
{5A8A5BBE-28DA-4C89-B393-BE39A96E8DC0}.Release|Mixed Platforms.Build.0 = Release|Any CPU
{5A8A5BBE-28DA-4C89-B393-BE39A96E8DC0}.Release|x64.ActiveCfg = Release|Any CPU
{5A8A5BBE-28DA-4C89-B393-BE39A96E8DC0}.Release|x64.Build.0 = Release|Any CPU
{5A8A5BBE-28DA-4C89-B393-BE39A96E8DC0}.Release|x86.ActiveCfg = Release|Any CPU
{5A8A5BBE-28DA-4C89-B393-BE39A96E8DC0}.Release|x86.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
Expand All @@ -365,5 +397,6 @@ Global
{6D7D1D60-4473-4C52-800C-9B892C6640A5} = {9051DDA0-E563-45D5-9504-085EBAACF469}
{E6D94C12-9AD7-46E6-AB62-3676F25FDE51} = {9051DDA0-E563-45D5-9504-085EBAACF469}
{4A35B1C5-520D-4267-BA70-2DCEAC0A5662} = {8C7B43EB-2F93-483C-B433-E28F9386AD67}
{5A8A5BBE-28DA-4C89-B393-BE39A96E8DC0} = {9051DDA0-E563-45D5-9504-085EBAACF469}
EndGlobalSection
EndGlobal
28 changes: 28 additions & 0 deletions docs/usage.md
Original file line number Diff line number Diff line change
Expand Up @@ -360,6 +360,34 @@ var statusCode = client.PostJsonAsync("orders", request, cancellationToken);

The same two extensions also exist for `PUT` requests (`PutJsonAsync`);

### JSON streaming APIs

For HTTP API endpoints that stream the response data (like [Twitter search stream](https://developer.twitter.com/en/docs/twitter-api/tweets/filtered-stream/api-reference/get-tweets-search-stream)) you can use RestSharp with `StreamJsonAsync<T>`, which returns an `IAsyncEnumerable<T>`:

```csharp
public async IAsyncEnumerable<SearchResponse> SearchStream(
[EnumeratorCancellation] CancellationToken cancellationToken = default
) {
var response = _client.StreamJsonAsync<TwitterSingleObject<SearchResponse>>(
"tweets/search/stream", cancellationToken
);

await foreach (var item in response.WithCancellation(cancellationToken)) {
yield return item.Data;
}
}
```

The main limitation of this function is that it expects each JSON object to be returned as a single line. It is unable to parse the response by combining multiple lines into a JSON string.

### Downloading binary data

There are two functions that allow you to download binary data from the remote API.

First, there's `DownloadDataAsync`, which returns `Task<byte[]`. It will read the binary response to the end, and return the whole binary content as a byte array. It works well for downloading smaller files.

For larger responses, you can use `DownloadStreamAsync` that returns `Task<Stream>`. This function allows you to open a stream reader and asynchronously stream large responses to memory or disk.

## Blazor support

Inside a Blazor webassembly app, you can make requests to external API endpoints. Microsoft examples show how to do it with `HttpClient`, and it's also possible to use RestSharp for the same purpose.
Expand Down
1 change: 0 additions & 1 deletion src/RestSharp/.nvmrc

This file was deleted.

35 changes: 0 additions & 35 deletions src/RestSharp/Extensions/ResponseStatusExtensions.cs

This file was deleted.

22 changes: 0 additions & 22 deletions src/RestSharp/Request/InvalidRequestException.cs

This file was deleted.

2 changes: 1 addition & 1 deletion src/RestSharp/Request/RequestContent.cs
Original file line number Diff line number Diff line change
Expand Up @@ -164,7 +164,7 @@ class RequestContent : IDisposable {
var formContent = new FormUrlEncodedContent(
_request.Parameters
.Where(x => x.Type == ParameterType.GetOrPost)
.Select(x => new KeyValuePair<string, string>(x.Name!, x.Value!.ToString()!))
.Select(x => new KeyValuePair<string, string>(x.Name!, x.Value!.ToString()!))!
);
Content = formContent;
}
Expand Down
12 changes: 10 additions & 2 deletions src/RestSharp/Response/RestResponse.cs
Original file line number Diff line number Diff line change
Expand Up @@ -70,8 +70,12 @@ CancellationToken cancellationToken
return request.AdvancedResponseWriter?.Invoke(httpResponse) ?? await GetDefaultResponse().ConfigureAwait(false);

async Task<RestResponse> GetDefaultResponse() {
var readTask = request.ResponseWriter == null ? ReadResponse() : ReadAndConvertResponse();
using var stream = await readTask.ConfigureAwait(false);
var readTask = request.ResponseWriter == null ? ReadResponse() : ReadAndConvertResponse();
#if NETSTANDARD
using var stream = await readTask.ConfigureAwait(false);
#else
await using var stream = await readTask.ConfigureAwait(false);
#endif

var bytes = stream == null ? null : await stream.ReadAsBytes(cancellationToken).ConfigureAwait(false);
var content = bytes == null ? null : httpResponse.GetResponseString(bytes, encoding);
Expand Down Expand Up @@ -109,7 +113,11 @@ CancellationToken cancellationToken
Task<Stream?> ReadResponse() => httpResponse.ReadResponse(cancellationToken);

async Task<Stream?> ReadAndConvertResponse() {
#if NETSTANDARD
using var original = await ReadResponse().ConfigureAwait(false);
#else
await using var original = await ReadResponse().ConfigureAwait(false);
#endif
return request.ResponseWriter!(original!);
}
}
Expand Down
25 changes: 9 additions & 16 deletions src/RestSharp/RestClient.Async.cs
Original file line number Diff line number Diff line change
Expand Up @@ -37,11 +37,10 @@ public partial class RestClient {
)
.ConfigureAwait(false)
: AddError(response, internalResponse.Exception, internalResponse.TimeoutToken);


response.Request = request;
response.Request.IncreaseNumAttempts();

return Options.ThrowOnAnyError ? ThrowIfError(response) : response;
}

Expand Down Expand Up @@ -104,34 +103,28 @@ record InternalResponse(HttpResponseMessage? ResponseMessage, Uri Url, Exception
if (response.ResponseMessage == null) return null;

if (request.ResponseWriter != null) {
#if NETSTANDARD
using var stream = await response.ResponseMessage.ReadResponse(cancellationToken).ConfigureAwait(false);
#else
await using var stream = await response.ResponseMessage.ReadResponse(cancellationToken).ConfigureAwait(false);
#endif
return request.ResponseWriter(stream!);
}

return await response.ResponseMessage.ReadResponse(cancellationToken).ConfigureAwait(false);
}

/// <summary>
/// A specialized method to download files.
/// </summary>
/// <param name="request">Pre-configured request instance.</param>
/// <param name="cancellationToken"></param>
/// <returns>The downloaded file.</returns>
[PublicAPI]
public async Task<byte[]?> DownloadDataAsync(RestRequest request, CancellationToken cancellationToken = default) {
using var stream = await DownloadStreamAsync(request, cancellationToken).ConfigureAwait(false);
return stream == null ? null : await stream.ReadAsBytes(cancellationToken).ConfigureAwait(false);
}

static RestResponse AddError(RestResponse response, Exception exception, CancellationToken timeoutToken) {
response.ResponseStatus = exception is OperationCanceledException
? timeoutToken.IsCancellationRequested ? ResponseStatus.TimedOut : ResponseStatus.Aborted
? TimedOut() ? ResponseStatus.TimedOut : ResponseStatus.Aborted
: ResponseStatus.Error;

response.ErrorMessage = exception.Message;
response.ErrorException = exception;

return response;

bool TimedOut() => timeoutToken.IsCancellationRequested || exception.Message.Contains("HttpClient.Timeout");
}

internal static RestResponse ThrowIfError(RestResponse response) {
Expand All @@ -140,7 +133,7 @@ record InternalResponse(HttpResponseMessage? ResponseMessage, Uri Url, Exception

return response;
}

static HttpMethod AsHttpMethod(Method method)
=> method switch {
Method.Get => HttpMethod.Get,
Expand Down
3 changes: 3 additions & 0 deletions src/RestSharp/RestClient.cs
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,9 @@ public partial class RestClient : IDisposable {
Options = options ?? new RestClientOptions();
CookieContainer = new CookieContainer();
_disposeHttpClient = disposeHttpClient;
if (httpClient.BaseAddress != null && Options.BaseUrl == null) {
Options.BaseUrl = httpClient.BaseAddress;
}

ConfigureHttpClient(HttpClient);
}
Expand Down

0 comments on commit cd4c434

Please sign in to comment.