2

Summary

I am trying to implement a basic retry handler in Xamarin Android and Xamarin iOS which on receipt of a 401 response refreshes an access token and then retries the request with the new access token, however, both platforms result in errors when using the native Message handlers.

I am using a DelegatingHandler to bypass the restriction on HttpClient for the reuse of the HttpRequest message but the error is deeper than that.

The AndroidClientHandler class and the newer AndroidMessageHandler read the Content as a stream then dispose of it as its ReadAsStreamAsync inside a using context.

The iOS NSUrlSessionHandler does not rewind the stream resulting in the retry not containing the correct content.

Additionally, this comment on the dotnet runtime github suggests that reusing HttpRequestMessages is unsupported behaviour: Link

This error does not occur with the managed HttpClient option however, particularly on Android we need the AndroidClientHandler in order to get proper SSL and Https support, login throws exceptions without it.

I was able to hack around it by creating a CustomAndroidMessageHandler and removing the using statement on Android as the AndroidMessageHandler is extensible however this is not possible on iOS.

The question is whether I am using it correctly and if this is possible with the Xamarin Handlers.

Am I missing the trick here?

Expected behavior:

Successfully retries the request with the correct content.

Actual behaviour:

Android: Cannot access a closed stream

iOS: Empty request content, missing content causes request to fail.

Answers

Polly - 'Cannot access a closed Stream' The outcome of this quest seems to be the you CANNOT do this, however the Dotnet library clearly does exactly this with their Polly retry implementation here so that does not seem to be correct.
Is this purely a case of Xamarin not supporting this?

Code Snippets

Register platform specific HttpMessageHandler using MvvmCross IoC container

Mvx.IoCProvider.RegisterSingleton<HttpMessageHandler>(() => new AndroidMessageHandler());

Method to register HttpClient to Service Collection with Polly and AuthenticationDelegatingHandler

var builder = services.AddHttpClient<TClient, TImplementation>()
    .ConfigurePrimaryHttpMessageHandler(() => Mvx.IoCProvider.Resolve<HttpMessageHandler>());

if (requiresAuthorization)
{
    builder.AddHttpMessageHandler<AuthenticationDelegatingHandler>();
}

DelegatingHandler Send Async

    protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
    {
        request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", _sessionService.AccessToken);
        var response = await base.SendAsync(request, cancellationToken);

        if (response.StatusCode is not (HttpStatusCode.Unauthorized or HttpStatusCode.Forbidden))
        {
            return response;
        }

        var refreshToken = _sessionService.RefreshToken;
        var tokenResponse = await _authenticationClient.Refresh(refreshToken);

        if (!tokenResponse.IsError)
        {
            await _sessionService.UpdateTokens(tokenResponse.AccessToken, tokenResponse.RefreshToken);

            try
            {
                request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", tokenResponse.AccessToken);
                return await base.SendAsync(request, cancellationToken);
            }
            catch (Exception e)
            {
                _logger.LogError(e, "Post token refresh request retry failed");
                return response;
            }
        }

        await _sessionService.EndSessionAndLogout();

        return response;
    }

Custom Android Client Handler Hack

    public class CustomAndroidMessageHandler : AndroidMessageHandler
    {
        protected override async Task WriteRequestContentToOutput(
            HttpRequestMessage request,
            HttpURLConnection httpConnection,
            CancellationToken cancellationToken)
        {
            var stream = await request.Content.ReadAsStreamAsync().ConfigureAwait(false);
            await stream.CopyToAsync(httpConnection.OutputStream!, 4096, cancellationToken).ConfigureAwait(false);

            if (stream.CanSeek)
            {
                stream.Seek(0, SeekOrigin.Begin);
            }
        }
    }

Open Related Github Issues:

  1. Xamarin Android
  2. Xamarin iOS
Peter Csala
  • 17,736
  • 16
  • 35
  • 75
Duncan Tyrell
  • 150
  • 1
  • 1
  • 11
  • You can keep following up the Github Issues you refer to to see if there are any updates. – Jianwei Sun - MSFT May 17 '23 at 01:29
  • Please check [this SO thread](https://stackoverflow.com/questions/76058694/how-is-polly-able-to-resend-the-same-http-request/76061555#76061555). – Peter Csala May 23 '23 at 11:42
  • 1
    @PeterCsala I have read that and many posts like it, and it still does not answer my question as my question relates specifically to the Xamarin Message handlers. My error is obviously not related to the CheckRequestMessage method. I can only assume that there is no way to use the Xamarin message handlers with Polly at this point, which I find disturbing. – Duncan Tyrell May 24 '23 at 12:23

0 Answers0