public async Task AllowedHeaders_Copied(string names, int expected) { var httpContext = new DefaultHttpContext(); var proxyResponse = new HttpResponseMessage(); proxyResponse.Headers.TryAddWithoutValidation("header1", "value1"); proxyResponse.Headers.TryAddWithoutValidation("header2", "value2"); proxyResponse.Headers.TryAddWithoutValidation("header3", "value3"); proxyResponse.Headers.TryAddWithoutValidation("header4", "value4"); proxyResponse.Headers.TryAddWithoutValidation("header5", "value5"); var allowed = names.Split(';'); var transform = new ResponseHeadersAllowedTransform(allowed); var transformContext = new ResponseTransformContext() { HttpContext = httpContext, ProxyResponse = proxyResponse, HeadersCopied = false, }; await transform.ApplyAsync(transformContext); Assert.True(transformContext.HeadersCopied); Assert.Equal(expected, httpContext.Response.Headers.Count()); foreach (var header in httpContext.Response.Headers) { Assert.Contains(header.Key, allowed, StringComparer.OrdinalIgnoreCase); } }
/// <summary> /// Removes and returns the current header value by first checking the HttpResponse /// and falling back to the value from HttpResponseMessage or HttpContent only if /// <see cref="ResponseTransformContext.HeadersCopied"/> is not set. /// This ordering allows multiple transforms to mutate the same header. /// </summary> /// <param name="context">The transform context.</param> /// <param name="headerName">The name of the header to take.</param> /// <returns>The response header value, or StringValues.Empty if none.</returns> public static StringValues TakeHeader(ResponseTransformContext context, string headerName) { if (context is null) { throw new ArgumentNullException(nameof(context)); } if (string.IsNullOrEmpty(headerName)) { throw new ArgumentException($"'{nameof(headerName)}' cannot be null or empty.", nameof(headerName)); } if (context.HttpContext.Response.Headers.TryGetValue(headerName, out var existingValues)) { context.HttpContext.Response.Headers.Remove(headerName); } else if (context.ProxyResponse is { } proxyResponse&& !context.HeadersCopied) { if (!RequestUtilities.TryGetValues(proxyResponse.Headers, headerName, out existingValues)) { RequestUtilities.TryGetValues(proxyResponse.Content.Headers, headerName, out existingValues); } } return(existingValues); }
public override async ValueTask <bool> TransformResponseAsync(HttpContext httpContext, HttpResponseMessage?proxyResponse) { if (ShouldCopyResponseHeaders.GetValueOrDefault(true)) { await base.TransformResponseAsync(httpContext, proxyResponse); } if (ResponseTransforms.Count == 0) { return(true); } var transformContext = new ResponseTransformContext() { HttpContext = httpContext, ProxyResponse = proxyResponse, HeadersCopied = ShouldCopyResponseHeaders.GetValueOrDefault(true), }; foreach (var responseTransform in ResponseTransforms) { await responseTransform.ApplyAsync(transformContext); } return(!transformContext.SuppressResponseBody); }
/// <summary> /// Removes and returns the current header value by first checking the HttpResponse /// and falling back to the value from HttpResponseMessage or HttpContent only if /// <see cref="ResponseTransformContext.HeadersCopied"/> is not set. /// This ordering allows multiple transforms to mutate the same header. /// </summary> /// <param name="context">The transform context.</param> /// <param name="headerName">The name of the header to take.</param> /// <returns>The response header value, or StringValues.Empty if none.</returns> public static StringValues TakeHeader(ResponseTransformContext context, string headerName) { if (context is null) { throw new System.ArgumentNullException(nameof(context)); } if (string.IsNullOrEmpty(headerName)) { throw new System.ArgumentException($"'{nameof(headerName)}' cannot be null or empty.", nameof(headerName)); } var existingValues = StringValues.Empty; if (context.HttpContext.Response.Headers.TryGetValue(headerName, out var responseValues)) { context.HttpContext.Response.Headers.Remove(headerName); existingValues = responseValues; } else if (context.ProxyResponse != null && !context.HeadersCopied && (context.ProxyResponse.Headers.TryGetValues(headerName, out var values) || context.ProxyResponse.Content.Headers.TryGetValues(headerName, out values))) { existingValues = (string[])values; } return(existingValues); }
public async Task ApplyAsync_InvokeAffinitizeRequest() { var cluster = GetCluster(); var destination = cluster.DestinationManager.Items[0]; var provider = new Mock <ISessionAffinityProvider>(MockBehavior.Strict); provider.Setup(p => p.AffinitizeRequest(It.IsAny <HttpContext>(), It.IsNotNull <SessionAffinityOptions>(), It.IsAny <DestinationInfo>())); var transform = new AffinitizeTransform(provider.Object); var context = new DefaultHttpContext(); context.Features.Set <IReverseProxyFeature>(new ReverseProxyFeature() { ClusterSnapshot = cluster.Config, ProxiedDestination = destination, }); var transformContext = new ResponseTransformContext() { HttpContext = context, }; await transform.ApplyAsync(transformContext); provider.Verify(); }
public async Task ApplyAsync_InvokeAffinitizeRequest() { var cluster = GetCluster(); var destination = cluster.Destinations.Values.First(); var provider = new Mock <ISessionAffinityPolicy>(MockBehavior.Strict); provider.Setup(p => p.AffinitizeResponse(It.IsAny <HttpContext>(), It.IsAny <ClusterState>(), It.IsNotNull <SessionAffinityConfig>(), It.IsAny <DestinationState>())); var transform = new AffinitizeTransform(provider.Object); var context = new DefaultHttpContext(); context.Features.Set <IReverseProxyFeature>(new ReverseProxyFeature() { Cluster = cluster.Model, Route = new RouteModel(new RouteConfig(), cluster, HttpTransformer.Default), ProxiedDestination = destination, }); var transformContext = new ResponseTransformContext() { HttpContext = context, }; await transform.ApplyAsync(transformContext); provider.Verify(); }
public override ValueTask ApplyAsync(ResponseTransformContext context) { var proxyFeature = context.HttpContext.GetReverseProxyFeature(); var options = proxyFeature.Cluster.Config.SessionAffinity; // The transform should only be added to routes that have affinity enabled. // However, the cluster can be re-assigned dynamically. if (options is null || !options.Enabled.GetValueOrDefault()) { return(default);
/// <inheritdoc/> public override ValueTask ApplyAsync(ResponseTransformContext context) { if (context is null) { throw new ArgumentNullException(nameof(context)); } if (context.ProxyResponse == null) { return(default);
public override ValueTask ApplyAsync(ResponseTransformContext context) { var proxyFeature = context.HttpContext.GetRequiredProxyFeature(); var options = proxyFeature.ClusterSnapshot.Options.SessionAffinity; // The transform should only be added to routes that have affinity enabled. Debug.Assert(options?.Enabled ?? true, "Session affinity is not enabled"); var selectedDestination = proxyFeature.ProxiedDestination; _sessionAffinityProvider.AffinitizeRequest(context.HttpContext, options, selectedDestination); return(default);
public override ValueTask ApplyAsync(ResponseTransformContext context) { var proxyFeature = context.HttpContext.GetReverseProxyFeature(); var options = proxyFeature.Cluster.Config.SessionAffinity; // The transform should only be added to routes that have affinity enabled. Debug.Assert(options?.Enabled ?? true, "Session affinity is not enabled"); var selectedDestination = proxyFeature.ProxiedDestination !; _sessionAffinityPolicy.AffinitizeResponse(context.HttpContext, proxyFeature.Route.Cluster !, options !, selectedDestination); return(default);
// Assumes the response status code has been set on the HttpContext already. /// <inheritdoc/> public override ValueTask ApplyAsync(ResponseTransformContext context) { if (context is null) { throw new ArgumentNullException(nameof(context)); } if (Condition == ResponseCondition.Always || Success(context) == (Condition == ResponseCondition.Success)) { context.HttpContext.Response.Headers.Remove(HeaderName); } return(default);
public async Task ProxyResponseNull_DoNothing() { var httpContext = new DefaultHttpContext(); httpContext.Response.StatusCode = 502; var transform = new ResponseHeadersAllowedTransform(new[] { "header1" }); var transformContext = new ResponseTransformContext() { HttpContext = httpContext, ProxyResponse = null, HeadersCopied = false, }; await transform.ApplyAsync(transformContext); }
public async Task AddResponseHeader_Success(string startValue, int status, string value, bool append, ResponseCondition condition, string expected, bool responseNull) { var httpContext = new DefaultHttpContext(); httpContext.Response.Headers["name"] = startValue.Split(";", System.StringSplitOptions.RemoveEmptyEntries); httpContext.Response.StatusCode = status; var transformContext = new ResponseTransformContext() { HttpContext = httpContext, ProxyResponse = responseNull ? null : new HttpResponseMessage(), HeadersCopied = true, }; var transform = new ResponseHeaderValueTransform("name", value, append, condition); await transform.ApplyAsync(transformContext); Assert.Equal(expected.Split(";", System.StringSplitOptions.RemoveEmptyEntries), httpContext.Response.Headers["name"]); }
// Assumes the response status code has been set on the HttpContext already. /// <inheritdoc/> public override ValueTask ApplyAsync(ResponseTransformContext context) { if (context is null) { throw new ArgumentNullException(nameof(context)); } if (Condition == ResponseCondition.Always || Success(context) == (Condition == ResponseCondition.Success)) { var existingHeader = TakeHeader(context, HeaderName); if (Append) { var value = StringValues.Concat(existingHeader, Value); SetHeader(context, HeaderName, value); } else { SetHeader(context, HeaderName, Value); } } return(default);
public async Task ContentHeaders_CopiedIfAllowed(string names, int expected) { var httpContext = new DefaultHttpContext(); var proxyResponse = new HttpResponseMessage(); proxyResponse.Content = new StringContent(""); proxyResponse.Content.Headers.TryAddWithoutValidation("header0", "value0"); proxyResponse.Content.Headers.TryAddWithoutValidation(HeaderNames.Allow, "value1"); proxyResponse.Content.Headers.TryAddWithoutValidation(HeaderNames.ContentDisposition, "value2"); proxyResponse.Content.Headers.TryAddWithoutValidation(HeaderNames.ContentEncoding, "value3"); proxyResponse.Content.Headers.TryAddWithoutValidation(HeaderNames.ContentLanguage, "value4"); proxyResponse.Content.Headers.TryAddWithoutValidation(HeaderNames.ContentLocation, "value5"); proxyResponse.Content.Headers.TryAddWithoutValidation(HeaderNames.ContentMD5, "value6"); proxyResponse.Content.Headers.TryAddWithoutValidation(HeaderNames.ContentRange, "value7"); proxyResponse.Content.Headers.TryAddWithoutValidation(HeaderNames.ContentType, "value8"); proxyResponse.Content.Headers.TryAddWithoutValidation(HeaderNames.Expires, "value9"); proxyResponse.Content.Headers.TryAddWithoutValidation(HeaderNames.LastModified, "value10"); proxyResponse.Content.Headers.TryAddWithoutValidation(HeaderNames.ContentLength, "0"); var allowed = names.Split(';'); var transform = new ResponseHeadersAllowedTransform(allowed); var transformContext = new ResponseTransformContext() { HttpContext = httpContext, ProxyResponse = proxyResponse, HeadersCopied = false, }; await transform.ApplyAsync(transformContext); Assert.True(transformContext.HeadersCopied); Assert.Equal(expected, httpContext.Response.Headers.Count()); foreach (var header in httpContext.Response.Headers) { Assert.Contains(header.Key, allowed, StringComparer.OrdinalIgnoreCase); } }
internal static bool Success(ResponseTransformContext context) { // TODO: How complex should this get? Compare with http://nginx.org/en/docs/http/ngx_http_headers_module.html#add_header return(context.HttpContext.Response.StatusCode < 400); }
/// <summary> /// Sets the given header on the HttpResponse. /// </summary> public static void SetHeader(ResponseTransformContext context, string headerName, StringValues values) { context.HttpContext.Response.Headers[headerName] = values; }
/// <inheritdoc/> public override ValueTask ApplyAsync(ResponseTransformContext context) { return(_func(context)); }
/// <summary> /// Transforms the given response. The status and headers will have (optionally) already been /// copied to the <see cref="HttpResponse"/> and any changes should be made there. /// </summary> public abstract ValueTask ApplyAsync(ResponseTransformContext context);