Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[browser][http] Add support for AllowAutoRedirect. #41394

Merged
merged 11 commits into from
Sep 10, 2020
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions src/libraries/System.Net.Http/src/System.Net.Http.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -676,6 +676,8 @@
<Compile Include="System\Net\Http\BrowserHttpHandler\SystemProxyInfo.Browser.cs" />
<Compile Include="System\Net\Http\BrowserHttpHandler\SocketsHttpHandler.cs" />
<Compile Include="System\Net\Http\BrowserHttpHandler\BrowserHttpHandler.cs" />
<Compile Include="$(CommonPath)System\Net\Http\HttpHandlerDefaults.cs"
Link="Common\System\Net\Http\HttpHandlerDefaults.cs" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="$(LibrariesProjectRoot)System.Security.Principal.Windows\src\System.Security.Principal.Windows.csproj" />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,10 @@ internal sealed class BrowserHttpHandler : HttpMessageHandler

private static readonly HttpRequestOptionsKey<bool> EnableStreamingResponse = new HttpRequestOptionsKey<bool>("WebAssemblyEnableStreamingResponse");
private static readonly HttpRequestOptionsKey<IDictionary<string, object>> FetchOptions = new HttpRequestOptionsKey<IDictionary<string, object>>("WebAssemblyFetchOptions");
private bool _allowAutoRedirect = HttpHandlerDefaults.DefaultAutomaticRedirection;
private int _maxAutomaticRedirections = HttpHandlerDefaults.DefaultMaxAutomaticRedirections;
private bool _disposed;
private bool _handlerStarted;

/// <summary>
/// Gets whether the current Browser supports streaming responses
Expand All @@ -45,6 +49,23 @@ private static bool GetIsStreamingSupported()
return (bool)streamingSupported.Call();
}

private void CheckDisposed()
{
if (_disposed)
{
throw new ObjectDisposedException(nameof(BrowserHttpHandler));
}
}

private void CheckDisposedOrStarted()
{
CheckDisposed();
if (_handlerStarted)
kjpou1 marked this conversation as resolved.
Show resolved Hide resolved
{
throw new InvalidOperationException(SR.net_http_operation_started);
}
}

public bool UseCookies
{
get => throw new PlatformNotSupportedException();
Expand Down Expand Up @@ -95,14 +116,27 @@ public ICredentials? Credentials

public bool AllowAutoRedirect
{
get => throw new PlatformNotSupportedException();
set => throw new PlatformNotSupportedException();
get => _allowAutoRedirect;
set
{
CheckDisposedOrStarted();
_allowAutoRedirect = value;
}
}

public int MaxAutomaticRedirections
{
get => throw new PlatformNotSupportedException();
set => throw new PlatformNotSupportedException();
get => _maxAutomaticRedirections;
kjpou1 marked this conversation as resolved.
Show resolved Hide resolved
set
{
if (value < 1)
kjpou1 marked this conversation as resolved.
Show resolved Hide resolved
{
throw new ArgumentOutOfRangeException(nameof(value), value, SR.Format(SR.net_http_value_must_be_greater_than, 0));
}

CheckDisposedOrStarted();
_maxAutomaticRedirections = value;
}
}

public int MaxConnectionsPerServer
Expand All @@ -125,15 +159,27 @@ public SslClientAuthenticationOptions SslOptions

public bool SupportsAutomaticDecompression => false;
public bool SupportsProxy => false;
public bool SupportsRedirectConfiguration => false;
public bool SupportsRedirectConfiguration => true;

private Dictionary<string, object?>? _properties;
public IDictionary<string, object?> Properties => _properties ??= new Dictionary<string, object?>();

protected override void Dispose(bool disposing)
kjpou1 marked this conversation as resolved.
Show resolved Hide resolved
{
if (disposing && !_disposed)
{
_disposed = true;
_handlerStarted = false;
kjpou1 marked this conversation as resolved.
Show resolved Hide resolved
}

base.Dispose(disposing);
}

protected internal override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
{
try
{
_handlerStarted = true;
var requestObject = new JSObject();

if (request.Options.TryGetValue(FetchOptions, out IDictionary<string, object>? fetchOptions))
Expand All @@ -146,6 +192,24 @@ protected internal override async Task<HttpResponseMessage> SendAsync(HttpReques

requestObject.SetObjectProperty("method", request.Method.Method);

// Allowing or Disallowing redirects.
if (AllowAutoRedirect)
kjpou1 marked this conversation as resolved.
Show resolved Hide resolved
{
requestObject.SetObjectProperty("redirect", "follow");
}
else
{
// Here we will set redirect to `manual` so that we do not crash with an exception
//
// https://developer.mozilla.org/en-US/docs/Web/API/Response/type
//
// other issues from whatwg/fetch:
//
// https://github.com/whatwg/fetch/issues/763
// https://github.com/whatwg/fetch/issues/601
requestObject.SetObjectProperty("redirect", "manual");
kjpou1 marked this conversation as resolved.
Show resolved Hide resolved
}

// We need to check for body content
if (request.Content != null)
{
Expand Down Expand Up @@ -224,9 +288,20 @@ protected internal override async Task<HttpResponseMessage> SendAsync(HttpReques
JSObject t = (JSObject)await response.ConfigureAwait(continueOnCapturedContext: true);

var status = new WasmFetchResponse(t, abortController, abortCts, abortRegistration);

HttpResponseMessage httpResponse = new HttpResponseMessage((HttpStatusCode)status.Status);

// Here we will set the ReasonPhrase so that it can be evaluated later.
// We do not have a status code but this will signal some type of what happened
// after interrogating the status code for success or not i.e. IsSuccessStatusCode
//
// https://developer.mozilla.org/en-US/docs/Web/API/Response/type
// opaqueredirect: The fetch request was made with redirect: "manual".
// The Response's status is 0, headers are empty, body is null and trailer is empty.
if (!string.IsNullOrEmpty(status.ResponseType) && status.ResponseType == "opaqueredirect")
kjpou1 marked this conversation as resolved.
Show resolved Hide resolved
{
httpResponse.SetReasonPhraseWithoutValidation(status.ResponseType);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@karelz who could review this part? Should we try harder to map the result to status codes or expose that differently to the developers?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hello everyone. Is there any update on this?

/cc @marek-safar @karelz @ManickaP

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also there is a line in the PR comments about mapping a result and the approach for this PR:

Instead of faking a 3XX status code the implementation uses the ReasonPhrase to return the fetch response type opaqueredirect.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

CC @dotnet/ncl

Hrm this is an interesting one. It seems like there is not a perfect solution here -- fetch API seems poorly made. ¯\_(ツ)_/¯

Without setting a normal redirect status code, we break compat with any existing code handling redirects.

But, it seems wrong to fake a status code when we don't know what the actual status code was. If we were to fake a status code, a temporary redirect seems like the best choice -- permanent redirect could cause dangerous handling e.g. updating a URI in a database.

To add a third bad option, what do we think of this? It at least gives the user some flexibility here.

public class BrowserHttpHandler
{
    public const HttpStatusCode OpaqueRedirect = (HttpStatusCode)1000;

    // if false, return TemporaryRedirect. if true, return OpaqueRedirect.
    public bool ReturnOpaqueRedirect { get; set; } = false;
}

(note, using a status code like this may not be advisable as an on-by-default option, as you might have some code doing e.g. if(StatusCode >= 500))

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If I am understanding the above wouldn't this change the Public API as well?

}

bool streamingEnabled = false;
if (StreamingSupported)
{
Expand Down