Reputation: 13
I'm trying to create a middleware using .net 6 CORE, but I can't figure out how to set the content-length header to prevent the request being returned with the "Transfer-Encoding: chunked" header. The middleware catches the request then modifies the request before sending it on to another endpoint using HttpClient. The response from the HttpClient is then manipulated and the context response is updated with the manipulated response. I can't seem to get the end response from my middleware to have a content-length header and as a result it sets the Transfer-Encoding header to chunked.
Desired Headers
HTTP/1.1 302 Found
Cache-Control: private
Content-Type: text/html; charset=utf-8
Location: /geocoder/rest/services
Server: Microsoft-IIS/10.0
X-AspNet-Version: 4.0.30319
X-Powered-By: ASP.NET
Date: Thu, 07 Apr 2022 14:49:37 GMT
Content-Length: 138
Current Headers
HTTP/1.1 302 Found
Content-Type: text/html; charset=utf-8
Date: Thu, 07 Apr 2022 14:49:37 GMT
Server: Microsoft-IIS/10.0
Cache-Control: private
Location: /geocoder/rest/services
Transfer-Encoding: chunked
X-AspNet-Version: 4.0.30319
X-Powered-By: ASP.NET
In the ProcessResponseContent is where I'm manipulateting the HttpClient response and writing it back out to the current context response.
public class ReverseProxyMiddleware2
{
private static readonly HttpClient _httpClient = new HttpClient(new HttpClientHandler { AllowAutoRedirect = false });
private readonly RequestDelegate _nextMiddleware;
private readonly InterceptionConfig _configurationSettings;
private static readonly HashSet<string> _headersToSkipGoingDownstream = new HashSet<string>(StringComparer.OrdinalIgnoreCase)
{
"Transfer-Encoding",
};
public ReverseProxyMiddleware2(RequestDelegate nextMiddleware, IOptions<InterceptionConfig> optionsSettings)
{
_nextMiddleware = nextMiddleware;
_configurationSettings = optionsSettings.Value;
}
public async Task Invoke(HttpContext context)
{
var targetUri = BuildTargetUri(context.Request);
if (targetUri != null)
{
//Build URL to redirect request to another enpoint
var targetRequestMessage = CreateTargetMessage(context, targetUri);
//Use HttpClient to send request to another endpoint
using (var responseMessage = await _httpClient.SendAsync(targetRequestMessage, HttpCompletionOption.ResponseHeadersRead, context.RequestAborted))
{
context.Response.StatusCode = (int)responseMessage.StatusCode;
//Copy over headers from the HttpClient response to the current context response
CopyFromTargetResponseHeaders(context, responseMessage);
//Update response content is where the problem starts
await ProcessResponseContent(context, responseMessage);
}
return;
}
await _nextMiddleware(context);
}
private async Task ProcessResponseContent(HttpContext context, HttpResponseMessage responseMessage)
{
var content = await responseMessage.Content.ReadAsByteArrayAsync();
if (IsContentOfType(responseMessage, "text/html") ||
IsContentOfType(responseMessage, "text/javascript"))
{
context.Response.Headers.Remove("Content-Length");
//Update content
var newContent = Encoding.UTF8.GetString(content).ReplaceAdaptorUrls(context, _configurationSettings, NetworkFlowEnum.ArcGISToProxyToClient);
//******trying to set content-length header to avoid Transfer-Encoding: chunked
UTF8Encoding encoding = new UTF8Encoding();
byte[] bytes = encoding.GetBytes(newContent);
context.Response.Headers["Content-Length"] = bytes.Length.ToString();
context.Response.OnStarting(() =>
{
//this didn't seem to work either
context.Response.Headers.ContentLength = bytes.Length;
return Task.CompletedTask;
});
//******end trying to set content-length header to avoid Transfer-Encoding: chunked
await context.Response.WriteAsync(newContent, Encoding.UTF8);
}
else
{
await context.Response.Body.WriteAsync(content);
}
}
private bool IsContentOfType(HttpResponseMessage responseMessage, string type)
{
var result = false;
if (responseMessage.Content?.Headers?.ContentType != null)
{
result = responseMessage.Content.Headers.ContentType.MediaType == type;
}
return result;
}
private HttpRequestMessage CreateTargetMessage(HttpContext context, Uri targetUri)
{
var requestMessage = new HttpRequestMessage();
CopyFromOriginalRequestContentAndHeaders(context, requestMessage);
requestMessage.RequestUri = targetUri;
requestMessage.Headers.Host = targetUri.Host;
requestMessage.Method = GetMethod(context.Request.Method);
return requestMessage;
}
private void CopyFromOriginalRequestContentAndHeaders(HttpContext context, HttpRequestMessage requestMessage)
{
var requestMethod = context.Request.Method;
if (!HttpMethods.IsGet(requestMethod) &&
!HttpMethods.IsHead(requestMethod) &&
!HttpMethods.IsDelete(requestMethod) &&
!HttpMethods.IsTrace(requestMethod))
{
var streamContent = new StreamContent(context.Request.Body);
requestMessage.Content = streamContent;
}
foreach (var header in context.Request.Headers)
{
var newValues = header.Value.ToArray().Select(value => value.ReplaceAdaptorUrls(context, _configurationSettings, NetworkFlowEnum.ClientToProxyToArcGIS)).ToArray();
requestMessage.Headers.Add(header.Key, newValues);
}
}
private void CopyFromTargetResponseHeaders(HttpContext context, HttpResponseMessage responseMessage)
{
foreach (var header in responseMessage.Headers)
{
if (_headersToSkipGoingDownstream.Contains(header.Key))
{
continue;
}
var newValues = header.Value.ToArray().Select(value => value.ReplaceAdaptorUrls(context, _configurationSettings, NetworkFlowEnum.ArcGISToProxyToClient)).ToArray();
context.Response.Headers.Add(header.Key, newValues);
}
foreach (var header in responseMessage.Content.Headers)
{
if (_headersToSkipGoingDownstream.Contains(header.Key))
{
continue;
}
var newValues = header.Value.ToArray().Select(value => value.ReplaceAdaptorUrls(context, _configurationSettings, NetworkFlowEnum.ArcGISToProxyToClient)).ToArray();
context.Response.Headers.Add(header.Key, newValues);
}
}
private static HttpMethod GetMethod(string method)
{
if (HttpMethods.IsDelete(method)) return HttpMethod.Delete;
if (HttpMethods.IsGet(method)) return HttpMethod.Get;
if (HttpMethods.IsHead(method)) return HttpMethod.Head;
if (HttpMethods.IsOptions(method)) return HttpMethod.Options;
if (HttpMethods.IsPost(method)) return HttpMethod.Post;
if (HttpMethods.IsPut(method)) return HttpMethod.Put;
if (HttpMethods.IsTrace(method)) return HttpMethod.Trace;
return new HttpMethod(method);
}
private Uri BuildTargetUri(HttpRequest request)
{
string targetUri = null;
//this is more hacking for portal login, seems the url it completely different
if (request.Path.Value.EndsWith("login/config/dojo.js"))
targetUri = $"{_configurationSettings.GISServer.HostURL}/{_configurationSettings.GISServer.ServerAdaptor?.AdaptorName}/login/login/config/dojo.js";
else if (request.Path.Value.EndsWith("jsapi/dojo/dojo.js"))
targetUri = $"{_configurationSettings.GISServer.HostURL}/{_configurationSettings.GISServer.ServerAdaptor?.AdaptorName}/login/jsapi/dojo/dojo.js";
else if (request.Path.Value.EndsWith("login/main.js"))
targetUri = $"{_configurationSettings.GISServer.HostURL}/{_configurationSettings.GISServer.ServerAdaptor?.AdaptorName}/login/login/main.js";
else if (request.Path.Value.EndsWith("login/nls/main_en-us.js"))
targetUri = $"{_configurationSettings.GISServer.HostURL}/{_configurationSettings.GISServer.ServerAdaptor?.AdaptorName}/login/login/nls/main_en-us.js";
else if (request.Path.StartsWithSegments("/geocoder", out var remainingPath))
{
targetUri = $"{_configurationSettings.GISServer.HostURL}/{_configurationSettings.GISServer.ServerAdaptor?.AdaptorName}{remainingPath}";
}
else if (request.Path.StartsWithSegments($"/{_configurationSettings.GISServer.PortalAdaptor?.ProxyAdaptorName}", out var remainingPortalPath))
{
targetUri = $"{_configurationSettings.GISServer.HostURL}/{_configurationSettings.GISServer.PortalAdaptor?.AdaptorName}{remainingPortalPath}";
}
else if (request.Path.StartsWithSegments("/rest", out var remainingRestPath))
{
targetUri = $"{_configurationSettings.GISServer.HostURL}/{_configurationSettings.GISServer.ServerAdaptor?.AdaptorName}/rest{remainingRestPath}";
}
if (targetUri != null && request.QueryString.HasValue)
{
return new Uri(QueryHelpers.AddQueryString(targetUri, request.Query.ReplaceAdaptorUrls(request.HttpContext, _configurationSettings, NetworkFlowEnum.ClientToProxyToArcGIS)));
}
else if (targetUri != null)
return new Uri(targetUri);
return null;
}
}
Upvotes: 1
Views: 5822
Reputation: 13
I believe the browser linking that Visual Studio 2022 was doing caused the problem. Once I disabled it I was able to set the content-length as expected.
Disable browser link in Visual Studio 2022
Upvotes: 0