Skip to content

Commit

Permalink
Refactor to use different internal parameter
Browse files Browse the repository at this point in the history
  • Loading branch information
Robert Johnson committed Jan 17, 2025
1 parent cee984a commit 53fb43b
Show file tree
Hide file tree
Showing 25 changed files with 182 additions and 53 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ public override async Task ExecuteResultAsync(ActionContext context)
}
catch (ObjectDisposedException ode)
{
throw new ServiceUnavailableException(Resources.NotAbleToCreateTheFinalResultsOfAnOperation, ode);
throw new ServiceUnavailableException(Api.Resources.NotAbleToCreateTheFinalResultsOfAnOperation, ode);
}

HttpResponse response = context.HttpContext.Response;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ public TooManyRequestsActionResult()
new OperationOutcomeIssue(
OperationOutcomeConstants.IssueSeverity.Error,
OperationOutcomeConstants.IssueType.Throttled,
Resources.TooManyConcurrentRequests),
Api.Resources.TooManyConcurrentRequests),
HttpStatusCode.TooManyRequests)
{
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
// -------------------------------------------------------------------------------------------------
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License (MIT). See LICENSE in the repo root for license information.
// -------------------------------------------------------------------------------------------------

using System;
using EnsureThat;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc.Filters;
using Microsoft.Health.Core.Features.Context;
using Microsoft.Health.Fhir.Api.Features.Headers;
using Microsoft.Health.Fhir.Core.Features.Context;
using Microsoft.Health.Fhir.Core.Registration;

namespace Microsoft.Health.Fhir.Api.Features.Filters
{
/// <summary>
/// Latency over efficiency filter.
/// Adds to FHIR Request Context a flag to optimize query latency over efficiency.
/// </summary>
[AttributeUsage(AttributeTargets.Class)]
public sealed class QueryCacheFilterAttribute : ActionFilterAttribute
{
private readonly RequestContextAccessor<IFhirRequestContext> _fhirRequestContextAccessor;
private readonly IFhirRuntimeConfiguration _runtimeConfiguration;

public QueryCacheFilterAttribute(RequestContextAccessor<IFhirRequestContext> fhirRequestContextAccessor, IFhirRuntimeConfiguration runtimeConfiguration)
{
EnsureArg.IsNotNull(fhirRequestContextAccessor, nameof(fhirRequestContextAccessor));
EnsureArg.IsNotNull(runtimeConfiguration, nameof(runtimeConfiguration));

_fhirRequestContextAccessor = fhirRequestContextAccessor;
_runtimeConfiguration = runtimeConfiguration;
}

public override void OnActionExecuting(ActionExecutingContext context)
{
EnsureArg.IsNotNull(context, nameof(context));

if (_runtimeConfiguration.IsQueryCacheSupported)
{
SetupConditionalRequestWithQueryCache(context.HttpContext, _fhirRequestContextAccessor.RequestContext);
}

base.OnActionExecuting(context);
}

private static void SetupConditionalRequestWithQueryCache(HttpContext context, IFhirRequestContext fhirRequestContext)
{
if (context?.Request?.Headers != null && fhirRequestContext != null)
{
string useQueryCache = context.GetQueryCache();

if (!string.IsNullOrEmpty(useQueryCache))
{
fhirRequestContext.DecorateRequestContextWithQueryCache(useQueryCache);
}
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ public override void OnActionExecuting(ActionExecutingContext context)
if (!context.HttpContext.Request.Headers.TryGetValue(KnownHeaders.Prefer, out var preferHeaderValue) ||
!string.Equals(preferHeaderValue[0], PreferHeaderExpectedValue, StringComparison.OrdinalIgnoreCase))
{
throw new RequestNotValidException(string.Format(Resources.UnsupportedHeaderValue, preferHeaderValue.FirstOrDefault(), KnownHeaders.Prefer));
throw new RequestNotValidException(string.Format(Api.Resources.UnsupportedHeaderValue, preferHeaderValue.FirstOrDefault(), KnownHeaders.Prefer));
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ public override void OnActionExecuting(ActionExecutingContext context)
acceptHeaderValue.Count != 1 ||
!string.Equals(acceptHeaderValue[0], KnownContentTypes.JsonContentType, StringComparison.OrdinalIgnoreCase))
{
throw new RequestNotValidException(string.Format(Resources.UnsupportedHeaderValue, acceptHeaderValue.FirstOrDefault(), HeaderNames.Accept));
throw new RequestNotValidException(string.Format(Api.Resources.UnsupportedHeaderValue, acceptHeaderValue.FirstOrDefault(), HeaderNames.Accept));
}

if (context.HttpContext.Request.Headers.TryGetValue(PreferHeaderName, out var preferHeaderValues))
Expand All @@ -79,7 +79,7 @@ public override void OnActionExecuting(ActionExecutingContext context)
|| (v.Length == 1 && !(requiredHeaderValueFound = string.Equals(v[0], PreferHeaderValueRequired, StringComparison.OrdinalIgnoreCase)))
|| (v.Length == 2 && (!string.Equals(v[0], PreferHeaderValueOptional, StringComparison.OrdinalIgnoreCase) || !Enum.TryParse<SearchParameterHandling>(v[1], true, out _))))
{
throw new RequestNotValidException(string.Format(Resources.UnsupportedHeaderValue, value, PreferHeaderName));
throw new RequestNotValidException(string.Format(Api.Resources.UnsupportedHeaderValue, value, PreferHeaderName));
}
}

Expand All @@ -102,14 +102,14 @@ public override void OnActionExecuting(ActionExecutingContext context)
continue;
}

throw new RequestNotValidException(string.Format(Resources.UnsupportedParameter, paramName));
throw new RequestNotValidException(string.Format(Api.Resources.UnsupportedParameter, paramName));
}

if (queryCollection?.Keys != null &&
queryCollection.Keys.Contains(KnownQueryParameterNames.TypeFilter) &&
!queryCollection.Keys.Contains(KnownQueryParameterNames.Type))
{
throw new RequestNotValidException(Resources.TypeFilterWithoutTypeIsUnsupported);
throw new RequestNotValidException(Api.Resources.TypeFilterWithoutTypeIsUnsupported);
}

if (queryCollection.TryGetValue(KnownQueryParameterNames.OutputFormat, out var outputFormats))
Expand All @@ -118,7 +118,7 @@ public override void OnActionExecuting(ActionExecutingContext context)
{
if (!(outputFormat == null || SupportedOutputFormats.Contains(outputFormat)))
{
throw new RequestNotValidException(string.Format(Resources.InvalidOutputFormat, outputFormat));
throw new RequestNotValidException(string.Format(Api.Resources.InvalidOutputFormat, outputFormat));
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -51,13 +51,13 @@ public override async Task OnActionExecutionAsync(ActionExecutingContext context
{
if (!await _parametersValidator.IsFormatSupportedAsync(headerValue[0]))
{
throw new UnsupportedMediaTypeException(string.Format(Resources.UnsupportedHeaderValue, headerValue.FirstOrDefault(), HeaderNames.ContentType));
throw new UnsupportedMediaTypeException(string.Format(Api.Resources.UnsupportedHeaderValue, headerValue.FirstOrDefault(), HeaderNames.ContentType));
}
}
else
{
// If no content type is supplied, then the server should respond with an unsupported media type exception.
throw new UnsupportedMediaTypeException(Resources.ContentTypeHeaderRequired);
throw new UnsupportedMediaTypeException(Api.Resources.ContentTypeHeaderRequired);
}
}
else if (httpContext.Request.Method.Equals(HttpMethod.Patch.Method, StringComparison.OrdinalIgnoreCase))
Expand All @@ -66,7 +66,7 @@ public override async Task OnActionExecutionAsync(ActionExecutingContext context
{
if (!await _parametersValidator.IsPatchFormatSupportedAsync(headerValue[0]))
{
throw new UnsupportedMediaTypeException(string.Format(Resources.UnsupportedHeaderValue, headerValue.FirstOrDefault(), HeaderNames.ContentType));
throw new UnsupportedMediaTypeException(string.Format(Api.Resources.UnsupportedHeaderValue, headerValue.FirstOrDefault(), HeaderNames.ContentType));
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ public override void OnActionExecuting(ActionExecutingContext context)
preferHeaderValue.Count != 1 ||
!string.Equals(preferHeaderValue[0], PreferHeaderExpectedValue, StringComparison.OrdinalIgnoreCase))
{
throw new RequestNotValidException(string.Format(Resources.UnsupportedHeaderValue, preferHeaderValue.FirstOrDefault(), PreferHeaderName));
throw new RequestNotValidException(string.Format(Api.Resources.UnsupportedHeaderValue, preferHeaderValue.FirstOrDefault(), PreferHeaderName));
}

if (string.Equals(context.HttpContext.Request.Method, "POST", StringComparison.OrdinalIgnoreCase))
Expand All @@ -44,7 +44,7 @@ public override void OnActionExecuting(ActionExecutingContext context)
contentTypeHeaderValue.Count != 1 ||
!contentTypeHeaderValue[0].Contains(ContentTypeHeaderExpectedValue, StringComparison.OrdinalIgnoreCase))
{
throw new RequestNotValidException(string.Format(Resources.UnsupportedHeaderValue, contentTypeHeaderValue.FirstOrDefault(), HeaderNames.ContentType));
throw new RequestNotValidException(string.Format(Api.Resources.UnsupportedHeaderValue, contentTypeHeaderValue.FirstOrDefault(), HeaderNames.ContentType));
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,12 +25,12 @@ public override void OnActionExecuting(ActionExecutingContext context)
context.ActionArguments?.TryGetValue("inputParams", out inputResource);
if (inputResource == null)
{
throw new RequestNotValidException(Resources.MissingInputParams);
throw new RequestNotValidException(Api.Resources.MissingInputParams);
}

if (inputResource is not Parameters)
{
throw new RequestNotValidException(string.Format(Resources.UnsupportedResourceType, inputResource.GetType().ToString()));
throw new RequestNotValidException(string.Format(Api.Resources.UnsupportedResourceType, inputResource.GetType().ToString()));

Check notice

Code scanning / CodeQL

Redundant ToString() call Note

Redundant call to 'ToString' on a String object.
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ public override void OnActionExecuting(ActionExecutingContext context)
if (context.HttpContext.Request.Headers.TryGetValue(PreferHeaderName, out var preferHeaderValue) &&
!string.Equals(preferHeaderValue[0], PreferHeaderExpectedValue, StringComparison.OrdinalIgnoreCase))
{
throw new RequestNotValidException(string.Format(Resources.UnsupportedHeaderValue, preferHeaderValue.FirstOrDefault(), PreferHeaderName));
throw new RequestNotValidException(string.Format(Api.Resources.UnsupportedHeaderValue, preferHeaderValue.FirstOrDefault(), PreferHeaderName));
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,21 @@ public static bool IsLatencyOverEfficiencyEnabled(this HttpContext outerHttpCont
return defaultValue;
}

/// <summary>
/// Retrieves from the HTTP header information on using query caching.
/// </summary>
/// <param name="outerHttpContext">HTTP context</param>
/// <returns>Query cache header value</returns>
public static string GetQueryCache(this HttpContext outerHttpContext)
{
if (outerHttpContext != null && outerHttpContext.Request.Headers.TryGetValue(KnownHeaders.QueryCacheEnabled, out StringValues headerValues))
{
return headerValues.FirstOrDefault();
}

return null;
}

/// <summary>
/// Retrieves from the HTTP header information about the conditional-query processing logic to be adopted.
/// </summary>
Expand Down Expand Up @@ -110,5 +125,15 @@ public static bool DecorateRequestContextWithOptimizedConcurrency(this IFhirRequ

return requestContext.Properties.TryAdd(KnownQueryParameterNames.OptimizeConcurrency, true);
}

public static bool DecorateRequestContextWithQueryCache(this IFhirRequestContext requestContext, string value)
{
if (requestContext == null)
{
return false;
}

return requestContext.Properties.TryAdd(KnownQueryParameterNames.QueryCaching, value);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ public sealed class InitialImportLockMiddleware

// hard-coding these to minimize resource consumption for locked message
private const string LockedContentType = "application/json; charset=utf-8";
private static readonly ReadOnlyMemory<byte> _lockedBody = CreateLockedBody(Resources.LockedForInitialImportMode);
private static readonly ReadOnlyMemory<byte> _lockedBody = CreateLockedBody(Api.Resources.LockedForInitialImportMode);

public InitialImportLockMiddleware(
RequestDelegate next,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ public async Task Invoke(HttpContext context)
{
context.Response.Clear();
context.Response.StatusCode = (int)HttpStatusCode.BadRequest;
await context.Response.WriteAsync(Resources.ContentTypeFormUrlEncodedExpected);
await context.Response.WriteAsync(Api.Resources.ContentTypeFormUrlEncodedExpected);
return;
}
}
Expand Down
4 changes: 2 additions & 2 deletions src/Microsoft.Health.Fhir.Api/Features/Routing/UrlResolver.cs
Original file line number Diff line number Diff line change
Expand Up @@ -260,7 +260,7 @@ public Uri ResolveOperationResultUrl(string operationName, string id)
routeName = RouteNames.GetBulkDeleteStatusById;
break;
default:
throw new OperationNotImplementedException(string.Format(Resources.OperationNotImplemented, operationName));
throw new OperationNotImplementedException(string.Format(Api.Resources.OperationNotImplemented, operationName));
}

var routeValues = new RouteValueDictionary()
Expand Down Expand Up @@ -317,7 +317,7 @@ public Uri ResolveOperationDefinitionUrl(string operationName)
routeName = RouteNames.SearchParameterStatusOperationDefinition;
break;
default:
throw new OperationNotImplementedException(string.Format(Resources.OperationNotImplemented, operationName));
throw new OperationNotImplementedException(string.Format(Api.Resources.OperationNotImplemented, operationName));
}

return GetRouteUri(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -152,14 +152,14 @@ public async Task Invoke(
{
if (authorizationConfiguration.ErrorOnMissingFhirUserClaim)
{
throw new BadHttpRequestException(string.Format(Resources.FhirUserClaimMustBeURL, fhirUser));
throw new BadHttpRequestException(string.Format(Api.Resources.FhirUserClaimMustBeURL, fhirUser));
}
}
catch (ArgumentNullException)
{
if (authorizationConfiguration.ErrorOnMissingFhirUserClaim)
{
throw new BadHttpRequestException(Resources.FhirUserClaimCannotBeNull);
throw new BadHttpRequestException(Api.Resources.FhirUserClaimCannotBeNull);
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ public sealed class ThrottlingMiddleware : IAsyncDisposable, IDisposable

// hard-coding these to minimize resource consumption when throttling
private const string ThrottledContentType = "application/json; charset=utf-8";
private static readonly ReadOnlyMemory<byte> _throttledBody = CreateThrottledBody(Resources.TooManyConcurrentRequests);
private static readonly ReadOnlyMemory<byte> _throttledBody = CreateThrottledBody(Api.Resources.TooManyConcurrentRequests);

private readonly RequestDelegate _next;
private readonly ILogger<ThrottlingMiddleware> _logger;
Expand Down Expand Up @@ -284,7 +284,7 @@ private async Task Return429(HttpContext context)
{
Interlocked.Increment(ref _currentPeriodRejectedCount);

_logger.LogWarning(Resources.TooManyConcurrentRequests + " Limit is {Limit}. Requests in flight {Requests}", _concurrentRequestLimit, _requestsInFlight);
_logger.LogWarning(Api.Resources.TooManyConcurrentRequests + " Limit is {Limit}. Requests in flight {Requests}", _concurrentRequestLimit, _requestsInFlight);

context.Response.StatusCode = StatusCodes.Status429TooManyRequests;

Expand Down
2 changes: 2 additions & 0 deletions src/Microsoft.Health.Fhir.Core/Features/KnownHeaders.cs
Original file line number Diff line number Diff line change
Expand Up @@ -34,5 +34,7 @@ public static class KnownHeaders

// #conditionalQueryParallelism - Header used to activate parallel conditional-query processing.
public const string ConditionalQueryProcessingLogic = "x-conditionalquery-processing-logic";

public const string QueryCacheEnabled = "x-ms-query-cache-enabled";
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -61,14 +61,18 @@ public static class KnownQueryParameterNames
public const string Container = "_container";

/// <summary>
/// This settings is currently set by:
/// x-ms-query-latency-over-efficiency - Gen1 and Gen2
/// x-conditionalquery-processing-logic - Gen1 only
/// In Gen1 it is used to hint that the request should run with a max parallel setting.
/// In Gen2 it is used to tell the system to optimize for latency over efficiency by running two queries in parallel (with and without query caching).
/// This setting is currently set by:
/// x-ms-query-latency-over-efficiency
/// x-conditionalquery-processing-logic
/// It is used to hint that the request should run with a max parallel setting.
/// </summary>
public const string OptimizeConcurrency = "_optimizeConcurrency";

/// <summary>
/// This setting is controlled by the x-ms-query-cache-enabled header. It controls whether to use the query cache or not.
/// </summary>
public const string QueryCaching = "_queryCaching";

/// <summary>
/// The anonymization configuration
/// </summary>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,5 +20,7 @@ public class AzureApiForFhirRuntimeConfiguration : IFhirRuntimeConfiguration
public bool IsTransactionSupported => false;

public bool IsLatencyOverEfficiencySupported => true;

public bool IsQueryCacheSupported => false;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@ public class AzureHealthDataServicesRuntimeConfiguration : IFhirRuntimeConfigura

public bool IsTransactionSupported => true;

public bool IsLatencyOverEfficiencySupported => true;
public bool IsLatencyOverEfficiencySupported => false;

public bool IsQueryCacheSupported => true;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -33,5 +33,10 @@ public interface IFhirRuntimeConfiguration
/// Supports the 'latency-over-efficiency' HTTP header.
/// </summary>
bool IsLatencyOverEfficiencySupported { get; }

/// <summary>
/// Supports the query cache HTTP header.
/// </summary>
bool IsQueryCacheSupported { get; }
}
}
Loading

0 comments on commit 53fb43b

Please sign in to comment.