Skip to content

Commit

Permalink
Fix HTTP request message being marked as completed too early (#63)
Browse files Browse the repository at this point in the history
  • Loading branch information
Tyrrrz authored Oct 27, 2023
1 parent 41b2f46 commit 0165800
Show file tree
Hide file tree
Showing 5 changed files with 63 additions and 6 deletions.
2 changes: 2 additions & 0 deletions examples/Passwordless.AspNetIdentity.Example/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
example.db
example.db-*
4 changes: 2 additions & 2 deletions examples/Passwordless.AspNetIdentity.Example/appsettings.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,8 @@
},
"AllowedHosts": "*",
"Passwordless": {
"ApiKey": "YOUR_API_SECRET",
"ApiSecret": "YOUR_API_KEY",
"ApiKey": "YOUR_API_KEY",
"ApiSecret": "YOUR_API_SECRET",
"Register": {
"Discoverable": true
}
Expand Down
47 changes: 47 additions & 0 deletions src/Passwordless/Helpers/Extensions/HttpExtensions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
using System.Net;

namespace Passwordless.Helpers.Extensions;

internal static class HttpExtensions
{
private class NonDisposableHttpContent : HttpContent
{
private readonly HttpContent _content;

public NonDisposableHttpContent(HttpContent content) => _content = content;

protected override async Task SerializeToStreamAsync(
Stream stream,
TransportContext? context
) => await _content.CopyToAsync(stream);

protected override bool TryComputeLength(out long length)
{
length = default;
return false;
}
}

public static HttpRequestMessage Clone(this HttpRequestMessage request)
{
var clonedRequest = new HttpRequestMessage(request.Method, request.RequestUri)
{
Version = request.Version,
// Don't dispose the original request's content
Content = request.Content is not null
? new NonDisposableHttpContent(request.Content)
: null
};

foreach (var header in request.Headers)
clonedRequest.Headers.TryAddWithoutValidation(header.Key, header.Value);

if (request.Content is not null && clonedRequest.Content is not null)
{
foreach (var header in request.Content.Headers)
clonedRequest.Content.Headers.TryAddWithoutValidation(header.Key, header.Value);
}

return clonedRequest;
}
}
13 changes: 11 additions & 2 deletions src/Passwordless/PasswordlessHttpHandler.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
using System.Net.Http.Json;
using Passwordless.Helpers;
using Passwordless.Helpers.Extensions;

namespace Passwordless;

Expand All @@ -16,10 +17,18 @@ public PasswordlessHttpHandler(HttpClient http, bool disposeClient = false)
}

protected override async Task<HttpResponseMessage> SendAsync(
HttpRequestMessage request,
HttpRequestMessage providedRequest,
CancellationToken cancellationToken)
{
var response = await _http.SendAsync(request, HttpCompletionOption.ResponseHeadersRead, cancellationToken);
// Clone the request to reset its completion status, which is required
// because we're crossing the boundary between two HTTP clients.
using var request = providedRequest.Clone();

var response = await _http.SendAsync(
request,
HttpCompletionOption.ResponseHeadersRead,
cancellationToken
);

// On failed requests, check if responded with ProblemDetails and provide a nicer error if so
if (!request.ShouldSkipErrorHandling() &&
Expand Down
3 changes: 1 addition & 2 deletions src/Passwordless/PasswordlessHttpRequestExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,7 @@ internal static bool ShouldSkipErrorHandling(this HttpRequestMessage request)
return request.Options.TryGetValue(SkipErrorHandlingOption, out var doNotErrorHandle) && doNotErrorHandle;
#elif NET462 || NETSTANDARD2_0
return request.Properties.TryGetValue(SkipErrorHandlingOption, out var shouldSkipOptionObject)
&& shouldSkipOptionObject is bool shouldSkipOption
&& shouldSkipOption;
&& shouldSkipOptionObject is true;
#endif
}
}

0 comments on commit 0165800

Please sign in to comment.