Skip to content
Open
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -3,58 +3,96 @@
// Licensed under the MIT License (MIT). See LICENSE in the repo root for license information.
// -------------------------------------------------------------------------------------------------

using System;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.Linq;
using System.Net;
using System.Threading.Tasks;
using System.Web;
using EnsureThat;
using Hl7.Fhir.Model;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Primitives;
using Microsoft.Health.Fhir.Api.Features.ActionResults;
using Microsoft.Health.Fhir.Core.Extensions;
using Microsoft.Health.Fhir.Core.Features.Routing;

namespace Microsoft.Health.Fhir.Api.Features.Routing
{
public class SearchPostReroutingMiddleware
{
private readonly RequestDelegate _next;
private readonly ILogger<SearchPostReroutingMiddleware> _logger;

public SearchPostReroutingMiddleware(RequestDelegate next)
public SearchPostReroutingMiddleware(RequestDelegate next, ILogger<SearchPostReroutingMiddleware> logger)
{
EnsureArg.IsNotNull(next);
_next = next;
_logger = logger;
}

public async Task Invoke(HttpContext context)
{
var request = context.Request;

if (request != null
&& request.Method == "POST"
&& request.Path.Value.EndsWith(KnownRoutes.Search, System.StringComparison.OrdinalIgnoreCase))
try
{
if (request.ContentType is null || request.HasFormContentType)
if (request != null
&& request.Method == "POST"
&& request.Path.Value.EndsWith(KnownRoutes.Search, System.StringComparison.OrdinalIgnoreCase))
{
if (request.HasFormContentType)
if (request.ContentType is null || request.HasFormContentType)
{
var mergedPairs = GetUniqueFormAndQueryStringKeyValues(HttpUtility.ParseQueryString(request.QueryString.ToString()), request.Form);
request.Query = mergedPairs;
_logger.LogInformation("Rerouting POST {Path} to GET with query parameters from form body.", request.Path);

if (request.HasFormContentType)
{
var mergedPairs = GetUniqueFormAndQueryStringKeyValues(HttpUtility.ParseQueryString(request.QueryString.ToString()), request.Form);
request.Query = mergedPairs;
}

request.ContentType = null;
request.Form = null;
request.Path = request.Path.Value.Substring(0, request.Path.Value.Length - KnownRoutes.Search.Length);
request.Method = "GET";
}
else
{
_logger.LogDebug("Rejecting POST {Path} with invalid Content-Type.", request.Path);

request.ContentType = null;
request.Form = null;
request.Path = request.Path.Value.Substring(0, request.Path.Value.Length - KnownRoutes.Search.Length);
request.Method = "GET";
}
else
{
context.Response.Clear();
context.Response.StatusCode = (int)HttpStatusCode.BadRequest;
await context.Response.WriteAsync(Api.Resources.ContentTypeFormUrlEncodedExpected);
return;
context.Response.Clear();
context.Response.StatusCode = (int)HttpStatusCode.BadRequest;

var operationOutcome = new OperationOutcome
{
Id = Guid.NewGuid().ToString(),
Issue = new List<OperationOutcome.IssueComponent>()
{
new OperationOutcome.IssueComponent()
{
Severity = OperationOutcome.IssueSeverity.Error,
Code = OperationOutcome.IssueType.Invalid,
Diagnostics = Api.Resources.ContentTypeFormUrlEncodedExpected,
},
},
Meta = new Meta()
{
LastUpdated = Clock.UtcNow,
},
};

await context.Response.WriteAsJsonAsync(operationOutcome);
return;
}
}
}
catch (Exception ex)
{
_logger.LogError(ex, "Error occurred while rerouting POST search to GET.");
throw;
}

await _next.Invoke(context);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,8 @@ public async Task Invoke(HttpContext context)

doesOperationOutcomeHaveError = true;

_logger.LogError(exception, "An unhandled exception occurred while processing the request");

await ExecuteResultAsync(context, result);
}
finally
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,6 @@
<Compile Include="$(MSBuildThisFileDirectory)Features\ActionConstraints\ConditionalConstraintAttribute.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Features\ActionResults\FhirResult.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Features\ActionResults\MemberMatchResult.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Features\ActionResults\OperationOutcomeResult.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Features\ActionResults\OperationSmartConfigurationResult.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Features\ActionResults\OperationVersionsResult.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Features\Filters\Metrics\ActionExecutedStatistics.cs" />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -422,19 +422,19 @@ public async Task<SqlConnection> GetConnection(ISqlConnectionBuilder sqlConnecti
{
SqlConnection conn;
var sw = Stopwatch.StartNew();
var logSB = new StringBuilder("Long running retrieve SQL connection");
var logSB = new StringBuilder("Long running retrieve SQL connection. ");
var isReadOnlyConnection = isReadOnly ? "read-only " : string.Empty;

if (!isReadOnly || !_coreFeatureConfiguration.SupportsSqlReplicas)
{
logSB.AppendLine("Not read only");
logSB.AppendLine("Not read only. ");
conn = await sqlConnectionBuilder.GetSqlConnectionAsync(false, applicationName);
}
else
{
logSB.AppendLine("Checking read only");
logSB.AppendLine("Checking read only. ");
var replicaTrafficRatio = GetReplicaTrafficRatio(sqlConnectionBuilder, logger);
logSB.AppendLine($"Got replica traffic ratio in {sw.Elapsed.TotalSeconds} seconds. Ratio is {replicaTrafficRatio}");
logSB.AppendLine($"Got replica traffic ratio in {sw.Elapsed.TotalSeconds} seconds. Ratio is {replicaTrafficRatio}. ");

if (replicaTrafficRatio < 0.5) // it does not make sense to use replica less than master at all
{
Expand Down
Loading