在IHttpFilter.SendRequestAsync的意外时间调用了一个方法

时间:2017-10-25 20:06:04

标签: c# web-services uwp async-await

我正在开发一个调用Web服务的UWP应用程序。为此,我使用来自Windows.Web.Http命名空间的HttpClient对象,并将IHttpFilter对象传递给它的构造函数。此过滤器负责身份验证过程。我根据此link建立了我的解决方案,并且身份验证逻辑基于this

我不知道我做错了什么,但我得到了这个例外:一个方法在意外时间被调用。 (来自HRESULT的异常:0x8000000E)

我测试的场景是令牌无效,尽管它被认为是有效的。在这种情况下(现在> TokenManager.Token.ExpiresOn)条件为假,然后添加授权头(带有无效令牌),然后发送请求,然后检查http响应代码和www-authenticate头并且如果需要,必须通过刷新令牌请求新的访问令牌以进行重试。当我在抛出异常时(第二次)到达此行时:response = await InnerFilter.SendRequestAsync(request).AsTask(cancellationToken,progress);

我不知道我做错了什么。我已经看到了另外一个问题,人们遇到了这个错误,通常是因为他们试图在不等待任务完成的情况下获得任务的结果,但我在使用await关键字所有异步方法。

public class AuthFilter : HttpFilter
{
    public override IAsyncOperationWithProgress<HttpResponseMessage, HttpProgress> SendRequestAsync(HttpRequestMessage request)
    {
        return AsyncInfo.Run<HttpResponseMessage, HttpProgress>(async (cancellationToken, progress) =>
        {
            var retry = true;
            if (TokenManager.TokenExists)
            {
                var now = DateTimeOffset.UtcNow.ToUnixTimeSeconds();
                if (now > TokenManager.Token.ExpiresOn)
                {
                    retry = false;
                    await RefreshTokenAsync();
                }
                request.Headers.Authorization = new HttpCredentialsHeaderValue(TokenManager.Token.TokenType, TokenManager.Token.AccessToken);
            }

            HttpResponseMessage response = await InnerFilter.SendRequestAsync(request).AsTask(cancellationToken, progress);

            cancellationToken.ThrowIfCancellationRequested();

            if (response.StatusCode == HttpStatusCode.Unauthorized)
            {
                var authHeader = response.Headers.WwwAuthenticate.SingleOrDefault(x => x.Scheme == "Bearer");
                if (authHeader != null)
                {
                    var challenge = ParseChallenge(authHeader.Parameters);
                    if (challenge.Error == "token_expired" && retry)
                    {
                        var success = await RefreshTokenAsync();
                        if (success)
                        {
                            request.Headers.Authorization = new HttpCredentialsHeaderValue(TokenManager.Token.TokenType, TokenManager.Token.AccessToken);
                            response = await InnerFilter.SendRequestAsync(request).AsTask(cancellationToken, progress);
                        }
                    }
                }
            }

            return response;
        });
    }

    private async Task<bool> RefreshTokenAsync()
    {
        using (var httpClient = new HttpClient())
        {
            httpClient.DefaultRequestHeaders.Accept.Add(new HttpMediaTypeWithQualityHeaderValue("application/json"));
            var content = new HttpStringContent(JsonConvert.SerializeObject(new { RefreshToken = TokenManager.Token.RefreshToken }), UnicodeEncoding.Utf8, "application/json");
            var response = await httpClient.PostAsync(SettingsService.Instance.WebApiUri.Append("api/login/refresh-token"), content);
            if (response.IsSuccessStatusCode)
                TokenManager.Token = JsonConvert.DeserializeObject<Token>(await response.Content.ReadAsStringAsync());
            return response.IsSuccessStatusCode;
        }
    }

    private (string Realm, string Error, string ErrorDescription) ParseChallenge(IEnumerable<HttpNameValueHeaderValue> input)
    {
        var realm = input.SingleOrDefault(x => x.Name == "realm")?.Value ?? string.Empty;
        var error = input.SingleOrDefault(x => x.Name == "error")?.Value ?? string.Empty;
        var errorDescription = input.SingleOrDefault(x => x.Name == "error_description")?.Value ?? string.Empty;
        return (realm, error, errorDescription);
    }

    public override void Dispose()
    {
        InnerFilter.Dispose();
        GC.SuppressFinalize(this);
    }
}

public abstract class HttpFilter : IHttpFilter
{
    public IHttpFilter InnerFilter { get; set; }
    public HttpFilter()
    {
        InnerFilter = new HttpBaseProtocolFilter();
    }
    public abstract IAsyncOperationWithProgress<HttpResponseMessage, HttpProgress> SendRequestAsync(HttpRequestMessage request);
    public abstract void Dispose();
}

修改

根据这些链接(linklink),我似乎无法两次发送相同的请求。但我需要重新发送相同的请求,但具有不同的身份验证标头。我怎样才能做到这一点?

1 个答案:

答案 0 :(得分:1)

好的,经过长时间的努力,我最终做到了这一点:

public override IAsyncOperationWithProgress<HttpResponseMessage, HttpProgress> SendRequestAsync(HttpRequestMessage request)
{
    return AsyncInfo.Run<HttpResponseMessage, HttpProgress>(async (cancellationToken, progress) =>
    {
        var retry = true;
        if (TokenManager.TokenExists)
        {
            var now = DateTimeOffset.UtcNow.ToUnixTimeSeconds();
            if (now > TokenManager.Token.ExpiresOn)
            {
                retry = false;
                await RefreshTokenAsync();
            }
            request.Headers.Authorization = new HttpCredentialsHeaderValue(TokenManager.Token.TokenType, TokenManager.Token.AccessToken);
        }

        HttpResponseMessage response = await InnerFilter.SendRequestAsync(request).AsTask(cancellationToken, progress);

        cancellationToken.ThrowIfCancellationRequested();

        if (response.StatusCode == HttpStatusCode.Unauthorized)
        {
            var authHeader = response.Headers.WwwAuthenticate.SingleOrDefault(x => x.Scheme == "Bearer");
            if (authHeader != null)
            {
                var challenge = ParseChallenge(authHeader.Parameters);
                if (challenge.Error == "token_expired" && retry)
                {
                    var secondRequest = request.Clone();
                    var success = await RefreshTokenAsync();
                    if (success)
                    {
                        secondRequest.Headers.Authorization = new HttpCredentialsHeaderValue(TokenManager.Token.TokenType, TokenManager.Token.AccessToken);
                        response = await InnerFilter.SendRequestAsync(secondRequest).AsTask(cancellationToken, progress);
                    }
                }
            }
        }

        return response;
    });
}

public static HttpRequestMessage Clone(this HttpRequestMessage request)
{
    var clone = new HttpRequestMessage(request.Method, request.RequestUri)
    {
        Content = request.Content
    };
    foreach (KeyValuePair<string, object> prop in request.Properties.ToList())
    {
        clone.Properties.Add(prop);
    }
    foreach (KeyValuePair<string, string> header in request.Headers.ToList())
    {
        clone.Headers.Add(header.Key, header.Value);
    }

    return clone;
}

因为我需要重新发送请求,所以我再次请求克隆第一个请求。