调用 Web API 有时会工作,但通常会出现有关关闭连接的错误。令牌验证是否会导致问题?

Call to an Web API works sometime, but usually get error about closed connection. Could the token validation be causing the issue?

更新 4 更加困惑 我以为我在做某事,但同样的错误不断出现。这个解决方案看起来很有希望,但不是 100%,但认为它可能有助于缩小错误范围。

步骤:

  1. 重新启动 API 和 Identity Server IIS 应用程序池
  2. 使用 QA 通过 SoapUI 获取访问令牌 URL
  3. 使用 SoapUI 在 QA 中点击 API
  4. 失败 - 不起作用(请参阅下面的堆栈跟踪)
  5. 使用 SoapUI 在本地点击 API(使用相同的 QA 身份服务器 URL)
  6. 本地APIURLreturns预期数据
  7. 使用 QA URL 使用 SoapUI 命中 API(我只是在 SoapUI 中更改端点)
  8. QA API URL returns 预期数据

我能够重复 4 次,然后在点击 QA 时仍然会出现错误。再次重新启动此过程后,我现在可以让我的 QA 环境正常工作,而无需在本地使用 API。任何想法发生了什么?似乎是设置 issue/proxy/certificate 问题,但不知道如何调试它。

这是我现在看到的错误: “/”应用程序中的服务器错误。 现有连接被远程主机强行关闭 说明:在执行当前 Web 请求期间发生未处理的异常。请查看堆栈跟踪以获取有关错误及其在代码中的来源的更多信息。 异常详细信息:System.Net.Sockets.SocketException:现有连接被远程主机强行关闭 来源错误: 在执行当前 Web 请求期间生成了未处理的异常。可以使用下面的异常堆栈跟踪来识别有关异常来源和位置的信息。 堆栈跟踪:

[SocketException (0x2746): An existing connection was forcibly closed by the remote host]
   System.Net.Sockets.Socket.EndReceive(IAsyncResult asyncResult) +8156963
   System.Net.Sockets.NetworkStream.EndRead(IAsyncResult asyncResult) +48

[IOException: Unable to read data from the transport connection: An existing connection was forcibly closed by the remote host.]
   System.Net.Security._SslStream.EndRead(IAsyncResult asyncResult) +8111720
   System.Net.TlsStream.EndRead(IAsyncResult asyncResult) +275
   System.Net.Connection.ReadCallback(IAsyncResult asyncResult) +45

[WebException: The underlying connection was closed: An unexpected error occurred on a receive.]
   System.Net.HttpWebRequest.EndGetResponse(IAsyncResult asyncResult) +764
   System.Net.Http.HttpClientHandler.GetResponseCallback(IAsyncResult ar) +78

[HttpRequestException: An error occurred while sending the request.]

[AggregateException: One or more errors occurred.]
   System.Threading.Tasks.Task`1.GetResultCore(Boolean waitCompletionNotification) +4451240
   Microsoft.IdentityModel.Protocols.<GetDocumentAsync>d__0.MoveNext() in c:\workspace\WilsonForDotNet45Release\src\Microsoft.IdentityModel.Protocol.Extensions\Configuration\HttpDocumentRetriever.cs:53

[IOException: Unable to get document from: https://securityeliqa.twcable.com/core/.well-known/openid-configuration]
   Microsoft.IdentityModel.Protocols.<GetDocumentAsync>d__0.MoveNext() in c:\workspace\WilsonForDotNet45Release\src\Microsoft.IdentityModel.Protocol.Extensions\Configuration\HttpDocumentRetriever.cs:59
   System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task) +13847892
   System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task) +61
   Microsoft.IdentityModel.Protocols.<GetAsync>d__0.MoveNext() in c:\workspace\WilsonForDotNet45Release\src\Microsoft.IdentityModel.Protocol.Extensions\Configuration\OpenIdConnectConfigurationRetriever.cs:81
   System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task) +13847892
   System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task) +61
   Microsoft.IdentityModel.Protocols.<GetConfigurationAsync>d__3.MoveNext() in c:\workspace\WilsonForDotNet45Release\src\Microsoft.IdentityModel.Protocol.Extensions\Configuration\ConfigurationManager.cs:0

[InvalidOperationException: IDX10803: Unable to create to obtain configuration from: 'https://securityeliqa.twcable.com/core/.well-known/openid-configuration'.]
   Microsoft.IdentityModel.Protocols.<GetConfigurationAsync>d__3.MoveNext() in c:\workspace\WilsonForDotNet45Release\src\Microsoft.IdentityModel.Protocol.Extensions\Configuration\ConfigurationManager.cs:212
   System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task) +13847892
   System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task) +61
   Microsoft.IdentityModel.Protocols.<GetConfigurationAsync>d__0.MoveNext() in c:\workspace\WilsonForDotNet45Release\src\Microsoft.IdentityModel.Protocol.Extensions\Configuration\ConfigurationManager.cs:0
   System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task) +13847892
   System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task) +61
   System.Runtime.CompilerServices.TaskAwaiter`1.GetResult() +31
   IdentityServer.WebApi.AccessTokenValidation.<<RetrieveMetadata>b__0>d__4.MoveNext() in e:\Source Code\GitHub\IdentityServer\IdentityServer.WebApi.AccessTokenValidation\Plumbing\DiscoveryDocumentIssuerSecurityTokenProvider.cs:123
   System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task) +13847892
   System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task) +61
   System.Runtime.CompilerServices.TaskAwaiter`1.GetResult() +31
   IdentityServer.WebApi.AccessTokenValidation.AsyncHelper.RunSync(Func`1 func) in e:\Source Code\GitHub\IdentityServer\IdentityServer.WebApi.AccessTokenValidation\Plumbing\AsyncHelper.cs:18
   IdentityServer.WebApi.AccessTokenValidation.DiscoveryDocumentIssuerSecurityTokenProvider.RetrieveMetadata() in e:\Source Code\GitHub\IdentityServer\IdentityServer.WebApi.AccessTokenValidation\Plumbing\DiscoveryDocumentIssuerSecurityTokenProvider.cs:141
   IdentityServer.WebApi.AccessTokenValidation.DiscoveryDocumentIssuerSecurityTokenProvider..ctor(String discoveryEndpoint, IdentityServerBearerTokenAuthenticationOptions options, ILoggerFactory loggerFactory) in e:\Source Code\GitHub\IdentityServer\IdentityServer.WebApi.AccessTokenValidation\Plumbing\DiscoveryDocumentIssuerSecurityTokenProvider.cs:43
   Owin.IdentityServerBearerTokenValidationAppBuilderExtensions.ConfigureLocalValidation(IdentityServerBearerTokenAuthenticationOptions options, ILoggerFactory loggerFactory) in e:\Source Code\GitHub\IdentityServer\IdentityServer.WebApi.AccessTokenValidation\IdentityServerBearerTokenValidationAppBuilderExtensions.cs:129
   Owin.IdentityServerBearerTokenValidationAppBuilderExtensions.UseIdentityServerBearerTokenAuthentication(IAppBuilder app, IdentityServerBearerTokenAuthenticationOptions options) in e:\Source Code\GitHub\IdentityServer\IdentityServer.WebApi.AccessTokenValidation\IdentityServerBearerTokenValidationAppBuilderExtensions.cs:39
   Company.WebApi.waAddressQualification.Startup.Configuration(IAppBuilder app) in e:\Source Code\GitHub\waDemo\Startup.cs:23

[TargetInvocationException: Exception has been thrown by the target of an invocation.]
   System.RuntimeMethodHandle.InvokeMethod(Object target, Object[] arguments, Signature sig, Boolean constructor) +0
   System.Reflection.RuntimeMethodInfo.UnsafeInvokeInternal(Object obj, Object[] parameters, Object[] arguments) +128
   System.Reflection.RuntimeMethodInfo.Invoke(Object obj, BindingFlags invokeAttr, Binder binder, Object[] parameters, CultureInfo culture) +146
   Owin.Loader.<>c__DisplayClass12.<MakeDelegate>b__b(IAppBuilder builder) +93
   Owin.Loader.<>c__DisplayClass1.<LoadImplementation>b__0(IAppBuilder builder) +209
   Microsoft.Owin.Host.SystemWeb.OwinAppContext.Initialize(Action`1 startup) +843
   Microsoft.Owin.Host.SystemWeb.OwinBuilder.Build(Action`1 startup) +51
   Microsoft.Owin.Host.SystemWeb.OwinHttpModule.InitializeBlueprint() +101
   System.Threading.LazyInitializer.EnsureInitializedCore(T& target, Boolean& initialized, Object& syncLock, Func`1 valueFactory) +137
   Microsoft.Owin.Host.SystemWeb.OwinHttpModule.Init(HttpApplication context) +172
   System.Web.HttpApplication.RegisterEventSubscriptionsWithIIS(IntPtr appContext, HttpContext context, MethodInfo[] handlers) +618
   System.Web.HttpApplication.InitSpecial(HttpApplicationState state, MethodInfo[] handlers, IntPtr appContext, HttpContext context) +172
   System.Web.HttpApplicationFactory.GetSpecialApplicationInstance(IntPtr appContext, HttpContext context) +402
   System.Web.Hosting.PipelineRuntime.InitializeApplication(IntPtr appContext) +343

[HttpException (0x80004005): Exception has been thrown by the target of an invocation.]
   System.Web.HttpRuntime.FirstRequestInit(HttpContext context) +579
   System.Web.HttpRuntime.EnsureFirstRequestInit(HttpContext context) +112
   System.Web.HttpRuntime.ProcessRequestNotificationPrivate(IIS7WorkerRequest wr, HttpContext context) +712

Version Information: Microsoft .NET Framework Version:4.0.30319; ASP.NET Version:4.6.1055.0

UPDATE 3 更加困惑

此时我相信是服务器取消了我的请求,但即便如此我也不能 100% 确定。以下是一些额外的数据点:

原始问题

我想知道我是否正确地调用了我的演示 API,或者 API 是否给我带来了问题。如果我同时发送三个请求,我至少会在我尝试处理的地址之一上遇到问题。

更新一号

在内部异常中,我收到以下消息,我能够捕获如下更完整的错误:"The underlying connection was closed: An unexpected error occurred on a receive. ---> System.IO.IOException: Unable to read data from the transport connection: An existing connection was forcibly closed by the remote host. ---> System.Net.Sockets.SocketException: An existing connection was forcibly closed by the remote host"

我在客户端和服务器端都使用 .NET 4.6.1 我没有使用代理。 我能够在大约 1/3 的时间内成功调用 API

我在调用服务时尝试了几个不同的选项,第一个只是将 DemoApiClient 包装在一个锁中,接下来是使用 Task 运行 和 async 和 await 关键字,最后使用 Task 运行 检索令牌以进行身份​​验证,或者只是调用它并在等待行中列出 ConfigureAwait(false) 。我不确定这是否重要,或者服务器在调用时是否只是失败。服务器并没有那么复杂,但我不想 post 此处使用该代码。它确实会验证令牌,然后处理一个地址,进行几次数据库调用并从数据库中找到 returns 数据。

更新 2 我一次测试三个地址,当我通过 SoapUI 将它们发送到服务器时,这三个地址都可以正常处理,但是对于使用下面代码的 .Net C# 客户端,三个请求中只有一个似乎可以发送到服务器。我期待看到两个堆栈跟踪和一个成功的请求,但 IIS 日志只显示一个请求进来。

我用来调用 API 的代码在这里:

using Demo.IdentityServer.IdentityModel.Client;
using Newtonsoft.Json;
using PartnerPortal.Model;
using System;
using System.Configuration;
using System.Diagnostics;
using System.Net.Http;
using System.Linq;
using System.Collections.Generic;
using System.Threading.Tasks;

namespace DemoPortal.Client
{
    public static class DemoApiClient
    {
        public readonly static string waDemoUrl = ConfigurationManager.AppSettings["waDemoURL"];

        public static async Task<DemoData> GetDemoData(RequestAddress requestAddress)
        {
            try
            {
                using (var httpClient = new HttpClient() { MaxResponseContentBufferSize = 10000000, Timeout = TimeSpan.FromMilliseconds(5000) })
                {
                    string reasonPhrase = "";
                    var demoUri = new Uri(string.Format(waDemoUrl + "api/DemoApp/GetSomeData?trackingId={0}&clientKey={1}&address={2}", requestAddress.TrackingId, requestAddress.ClientKey, requestAddress.Address));

                    var httpResult = new HttpResponseMessage();
                    var result = RequestAccessToken.RequestToken().Result;
                    var accessToken = await Task.Run(() => RequestAccessToken.RequestToken().Result);
                    //var accessToken = RequestAccessToken.RequestToken().Result;
                    //which of the above two options should I use?  Does it matter?
                    httpClient.SetBearerToken(accessToken);
                    HttpResponseMessage response = await httpClient.GetAsync(demoUri);
                    HttpContent httpContent = response.Content;
                    if (httpResult.IsSuccessStatusCode)
                    {
                        var content = await httpContent.ReadAsStringAsync();
                        DemoData demoData = null;
                        demoData = JsonConvert.DeserializeObject<DemoData>(content);
                        return demoData;
                    }
                    else
                    {
                        reasonPhrase = httpResult.ReasonPhrase;
                        if (reasonPhrase.ToUpper() == "UNAUTHORIZED")
                        {
                            throw new KeyNotFoundException("Not authorized");
                        }
                    }
                }
            }
            catch (Exception ex)
            {
                Debug.WriteLine("Message is:" + ex);
                throw (ex);
            }
            return null;
        }
    }
}

这是检索令牌的方式:

using Newtonsoft.Json;
using System;
using System.Collections.Generic;
using System.Configuration;
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Threading;
using System.Threading.Tasks;
using System.Web;
using Thinktecture.IdentityModel.Tokens.Http;

namespace Demo.IdentityServer.IdentityModel.Client
{
    public static class RequestAccessToken
    {
        public readonly static string securityUrl = ConfigurationManager.AppSettings["securityUrl"];
        public readonly static string clientSecret = ConfigurationManager.AppSettings["clientSecret"];
        public static async Task<string> RequestToken()
        {

            var url = new Uri(securityUrl);

            var fields = new Dictionary<string, string>
            {
                { OAuth2Constants.GrantType, OAuth2Constants.GrantTypes.ClientCredentials },
                { OAuth2Constants.Scope, "Read"}
            };

            using (var httpClient = new HttpClient())
            {
                var cancellationToken = new CancellationToken();

                httpClient.DefaultRequestHeaders.Authorization = new BasicAuthenticationHeaderValue("Demo", clientSecret);

                var ss = JsonConvert.SerializeObject(fields);
                var data = new FormUrlEncodedContent(fields);
                var s = data.ReadAsStringAsync();

                //var response = await httpClient.PostAsync(url, data, cancellationToken).ConfigureAwait(false);
                var response = await httpClient.PostAsync(url, data, cancellationToken);
                // Should I use ConfigureAwait(false) or call method with a Task(Run() ... ?

                if (response.StatusCode == HttpStatusCode.OK || response.StatusCode == HttpStatusCode.BadRequest)
                {
                    //var content = await response.Content.ReadAsStringAsync().ConfigureAwait(false);
                    var content = await response.Content.ReadAsStringAsync();

                    var token = new TokenResponse(content);

                    var accessToken = token.AccessToken;

                    return accessToken;
                }
                else
                {
                    return new TokenResponse(response.StatusCode, response.ReasonPhrase).AccessToken;
                }
            }
        }
    }
}   

找到解决方案!!! 整个问题源于错误配置的负载平衡器。 SNAT 池的东西。我不是网络专家,不管他做了什么,都解决了这个问题。负载均衡器之类的东西直接引用了盒子名称而不是 SNAT 池名称,或者 SNAT 池的名称错误。无论哪种方式,一旦更新,间歇性的成功就变成了总是成功。

我可以告诉您,您正面临大规模 SNAT 端口耗尽的问题。在 using 块中实例化 HttpClient 是有意义的,因为它实现了 IDisposable。但是这样做会导致HttpClient在处置后独占一个SNAT端口240秒。 See here for a great explanation 这个问题。