我有一个ASP.NET Core中间件,该中间件调用另一个HTTP服务以检查用户是否有权进行请求。目前,它取决于提供的名为X-Parameter-Id
的自定义标头。
using System;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Configuration;
namespace ParameterAuthorization.Middleware
{
public class ParameterAuthorizationMiddleware
{
private readonly RequestDelegate _next;
private readonly IParameterAuthorizationService _parameterAuthorizationService;
public ParameterAuthorizationMiddleware(RequestDelegate next, IParameterAuthorizationService parameterAuthorizationService)
{
_next = next ?? throw new ArgumentNullException(nameof(next));
_parameterAuthorizationService = parameterAuthorizationService ?? throw new ArgumentNullException(nameof(parameterAuthorizationService));
}
public async Task InvokeAsync(HttpContext httpContext, IConfiguration configuration)
{
if (httpContext is null)
{
throw new ArgumentNullException(nameof(httpContext));
}
if (parameterRequestContext is null)
{
throw new ArgumentNullException(nameof(parameterRequestContext));
}
if (!(httpContext.Request.Headers.ContainsKey("X-Parameter-Id") && httpContext.Request.Headers.ContainsKey("Authorization")))
{
await ForbiddenResponseAsync(httpContext);
}
var parameterIdHeader = httpContext.Request.Headers["X-Parameter-Id"].ToString();
if (!int.TryParse(parameterIdHeader, out var parameterId) || parameterId < 1)
{
await ForbiddenResponseAsync(httpContext);
}
var authorizationHeader = httpContext.Request.Headers["Authorization"].ToString();
var parameterResponse = await _parameterAuthorizationService.AuthorizeUserParameterAsync(parameterId, authorizationHeader);
if (string.IsNullOrWhiteSpace(parameterResponse))
{
await ForbiddenResponseAsync(httpContext);
}
await _next.Invoke(httpContext);
}
private static async Task ForbiddenResponseAsync(HttpContext httpContext)
{
httpContext.Response.StatusCode = StatusCodes.Status403Forbidden;
await httpContext.Response.WriteAsync("Forbidden");
return;
}
}
}
这就是HTTP调用的实现:
using System;
using System.IO;
using System.Net.Http;
using System.Threading.Tasks;
using Newtonsoft.Json;
namespace ParameterAuthorization.Middleware.Http
{
public class ParameterAuthorizationService : IParameterAuthorizationService
{
private readonly HttpClient _httpClient;
private readonly JsonSerializer _jsonSerializer;
public ParameterAuthorizationService(HttpClient httpClient, JsonSerializer jsonSerializer)
{
_httpClient = httpClient ?? throw new ArgumentNullException(nameof(httpClient));
_jsonSerializer = jsonSerializer ?? throw new ArgumentNullException(nameof(jsonSerializer));
}
public async Task<string> AuthorizeUserParameterAsync(int parameterId, string authorizationHeader)
{
var request = CreateRequest(parameterId, authorizationHeader);
var result = await _httpClient.SendAsync(request, HttpCompletionOption.ResponseHeadersRead);
if (!result.IsSuccessStatusCode)
{
return string.Empty;
}
using (var responseStream = await result.Content.ReadAsStreamAsync())
using (var streamReader = new StreamReader(responseStream))
using (var jsonTextReader = new JsonTextReader(streamReader))
{
return _jsonSerializer.Deserialize<ParameterResponse>(jsonTextReader).StringImInterestedIn;
}
}
private static HttpRequestMessage CreateRequest(int parameterId, string authorizationHead1er)
{
var parameterUri = new Uri($"parameters/{parameterId}", UriKind.Relative);
var message = new HttpRequestMessage(HttpMethod.Get, parameterUri);
message.Headers.Add("Authorization", authorizationHead1er);
return message;
}
}
}
这是DI的样板代码,名为HttpClient
sc.TryAddSingleton<JsonSerializer>();
sc.AddHttpClient<IParameterAuthorizationService, ParameterAuthorizationService>(client =>
{
client.BaseAddress = authorizationServiceUri;
client.DefaultRequestHeaders.Add("Accept", "application/json");
});
authorizationServiceUri
是我通过自定义扩展方法提供的。
问题在于,我对此服务的呼叫将随机花费7、10,甚至20秒才能使用此服务,然后它很快就会变慢。我称它为Postman的确切ParameterAuthorizationService
,持续时间不到50毫秒。
我要附加Application Insights的屏幕快照,显示事件的整个序列。
这两个服务均作为Azure App Services部署在同一App Service计划内的同一订阅下。
代码工作得很好,但是我已经不知道可能导致这些性能异常的原因了。
我还检查了Azure App服务中的TCP连接,它都是绿色的。
某些HTTP调用会真的很慢的原因可能是什么?
答案 0 :(得分:1)
如果您写入httpContent,则应使管道短路。参见Aspnet Core Middleware documentation:
不要再呼叫。将响应发送到客户端后调用。
使用类似这样的内容:
if (string.IsNullOrWhiteSpace(parameterResponse))
{
await ForbiddenResponseAsync(httpContext);
}
else
{
await _next.Invoke(httpContext);
}
还考虑使用从aspnet核心AuthenticationHandler类继承的中间件来处理身份验证,以利用所有aspnet核心的Authentification / Authorization功能。为简单起见,这是BasicAuthentification handler的实现示例。
您的ParameterAuthorizationService看起来不错。我认为这不是您请求速度慢的原因。可以肯定的是,您可以track the entire service call通过测量花费的时间并将其发布到appinsights中:
public class ParameterAuthorizationService : IParameterAuthorizationService
{
//...
private readonly TelemetryClient _telemetryClient;
public ParameterAuthorizationService(HttpClient httpClient, JsonSerializer jsonSerializer)
{
//...
_telemetryClient = new TelemetryClient();
}
public async Task<string> AuthorizeUserParameterAsync(int parameterId, string authorizationHeader)
{
var startTime = DateTime.UtcNow;
var timer = Stopwatch.StartNew();
var isSuccess = true;
try
{
return await AuthorizeUserParameterImpl(parameterId, authorizationHeader);
}
catch (Exception ex)
{
timer.Stop();
isSuccess = false;
_telemetryClient.TrackException(ex);
_telemetryClient.TrackDependency("Http", nameof(ParameterAuthorizationService), nameof(AuthorizeUserParameterAsync),
startTime, timer.Elapsed, isSuccess);
throw;
}
finally
{
if (timer.IsRunning)
timer.Stop();
if (isSuccess)
_telemetryClient.TrackDependency(
"Http", nameof(ParameterAuthorizationService), nameof(AuthorizeUserParameterAsync),
startTime, timer.Elapsed, isSuccess);
}
}
private async Task<string> AuthorizeUserParameterImpl(int parameterId, string authorizationHeader)
{
//Your original code
}
}
答案 1 :(得分:0)
我相信我可能已经找到了您的麻烦所在,摘自Microsoft docs:
尽管
HttpClient
实现了IDisposable
接口,但是 为重复使用而设计。关闭的HttpClient
实例将套接字保持打开状态 短时间的TIME_WAIT状态。如果一个代码路径 创建并处理HttpClient
个对象,该应用程序 可能会耗尽可用的插槽。HttpClientFactory
的引入 ASP.NET Core 2.1作为此问题的解决方案。处理池 HTTP连接以优化性能和可靠性。建议:
- 请勿直接创建和处理HttpClient实例。
- 请使用HttpClientFactory检索HttpClient实例。有关更多信息,请参见使用HttpClientFactory实现弹性HTTP
请求。
我可以看到您在代码中使用HttpClient
,这可能暗示您应该将性能解决方案放在哪里。
您在问题中标记了asp.net core 2.2,因此,我建议您按照建议在代码中使用HttpClientFactory而不是HttpClient
。