Example #1
0
        public override async ValueTask TransformRequestAsync(HttpContext httpContext, HttpRequestMessage proxyRequest, string destinationPrefix)
        {
            if (ShouldCopyRequestHeaders.GetValueOrDefault(true))
            {
                await base.TransformRequestAsync(httpContext, proxyRequest, destinationPrefix);
            }

            if (RequestTransforms.Count == 0)
            {
                return;
            }

            var transformContext = new RequestTransformContext()
            {
                DestinationPrefix = destinationPrefix,
                HttpContext       = httpContext,
                ProxyRequest      = proxyRequest,
                Path          = httpContext.Request.Path,
                Query         = new QueryTransformContext(httpContext.Request),
                HeadersCopied = ShouldCopyRequestHeaders.GetValueOrDefault(true),
            };

            foreach (var requestTransform in RequestTransforms)
            {
                await requestTransform.ApplyAsync(transformContext);
            }

            // Allow a transform to directly set a custom RequestUri.
            proxyRequest.RequestUri ??= RequestUtilities.MakeDestinationAddress(
                transformContext.DestinationPrefix, transformContext.Path, transformContext.Query.QueryString);
        }
Example #2
0
    private async ValueTask <(HttpRequestMessage, StreamCopyHttpContent?)> CreateRequestMessageAsync(HttpContext context, string destinationPrefix,
                                                                                                     HttpTransformer transformer, ForwarderRequestConfig?requestConfig, bool isStreamingRequest, ActivityCancellationTokenSource activityToken)
    {
        // "http://a".Length = 8
        if (destinationPrefix == null || destinationPrefix.Length < 8)
        {
            throw new ArgumentException("Invalid destination prefix.", nameof(destinationPrefix));
        }

        var destinationRequest = new HttpRequestMessage();

        destinationRequest.Method = RequestUtilities.GetHttpMethod(context.Request.Method);

        var upgradeFeature   = context.Features.Get <IHttpUpgradeFeature>();
        var upgradeHeader    = context.Request.Headers[HeaderNames.Upgrade].ToString();
        var isUpgradeRequest = (upgradeFeature?.IsUpgradableRequest ?? false)
                               // Mitigate https://github.com/microsoft/reverse-proxy/issues/255, IIS considers all requests upgradeable.
                               && (string.Equals("WebSocket", upgradeHeader, StringComparison.OrdinalIgnoreCase)
                               // https://github.com/microsoft/reverse-proxy/issues/467 for kubernetes APIs
                                   || upgradeHeader.StartsWith("SPDY/", StringComparison.OrdinalIgnoreCase));

        // Default to HTTP/1.1 for proxying upgradeable requests. This is already the default as of .NET Core 3.1
        // Otherwise request what's set in proxyOptions (e.g. default HTTP/2) and let HttpClient negotiate the protocol
        // based on VersionPolicy (for .NET 5 and higher). For example, downgrading to HTTP/1.1 if it cannot establish HTTP/2 with the target.
        // This is done without extra round-trips thanks to ALPN. We can detect a downgrade after calling HttpClient.SendAsync
        // (see Step 3 below). TBD how this will change when HTTP/3 is supported.
        destinationRequest.Version = isUpgradeRequest ? ProtocolHelper.Http11Version : (requestConfig?.Version ?? DefaultVersion);
#if NET
        destinationRequest.VersionPolicy = isUpgradeRequest ? HttpVersionPolicy.RequestVersionOrLower : (requestConfig?.VersionPolicy ?? DefaultVersionPolicy);
#endif

        // :: Step 2: Setup copy of request body (background) Client --► Proxy --► Destination
        // Note that we must do this before step (3) because step (3) may also add headers to the HttpContent that we set up here.
        var requestContent = SetupRequestBodyCopy(context.Request, isStreamingRequest, activityToken);
        destinationRequest.Content = requestContent;

        // :: Step 3: Copy request headers Client --► Proxy --► Destination
        await transformer.TransformRequestAsync(context, destinationRequest, destinationPrefix);

        if (isUpgradeRequest)
        {
            RestoreUpgradeHeaders(context, destinationRequest);
        }

        // Allow someone to custom build the request uri, otherwise provide a default for them.
        var request = context.Request;
        destinationRequest.RequestUri ??= RequestUtilities.MakeDestinationAddress(destinationPrefix, request.Path, request.QueryString);

        Log.Proxying(_logger, destinationRequest, isStreamingRequest);

        if (requestConfig?.AllowResponseBuffering != true)
        {
            context.Features.Get <IHttpResponseBodyFeature>()?.DisableBuffering();
        }

        // TODO: What if they replace the HttpContent object? That would mess with our tracking and error handling.
        return(destinationRequest, requestContent);
    }
Example #3
0
            /// <summary>
            /// A callback that is invoked prior to sending the proxied request. All HttpRequestMessage
            /// fields are initialized except RequestUri, which will be initialized after the
            /// callback if no value is provided. The string parameter represents the destination
            /// URI prefix that should be used when constructing the RequestUri. The headers
            /// are copied by the base implementation, excluding some protocol headers like HTTP/2
            /// pseudo headers (":authority").
            /// </summary>
            /// <param name="httpContext">The incoming request.</param>
            /// <param name="proxyRequest">The outgoing proxy request.</param>
            /// <param name="destinationPrefix">The uri prefix for the selected destination server which can be used to create
            /// the RequestUri.</param>
            public override async ValueTask TransformRequestAsync(HttpContext httpContext, HttpRequestMessage proxyRequest, string destinationPrefix)
            {
                // Copy all request headers
                await base.TransformRequestAsync(httpContext, proxyRequest, destinationPrefix);

                // Customize the query string:
                var queryContext = new QueryTransformContext(httpContext.Request);

                queryContext.Collection.Remove("param1");
                queryContext.Collection["area"] = "xx2";

                // Assign the custom uri. Be careful about extra slashes when concatenating here. RequestUtilities.MakeDestinationAddress is a safe default.
                proxyRequest.RequestUri = RequestUtilities.MakeDestinationAddress("https://example.com", httpContext.Request.Path, queryContext.QueryString);

                // Suppress the original request header, use the one from the destination Uri.
                proxyRequest.Headers.Host = null;
            }
Example #4
0
        // These intentionally do not call base because the logic here overlaps with the default header copy logic.
        public override Task TransformRequestAsync(HttpContext context, HttpRequestMessage proxyRequest, string destinationPrefix)
        {
            var transformContext = new RequestParametersTransformContext()
            {
                DestinationPrefix = destinationPrefix,
                HttpContext       = context,
                ProxyRequest      = proxyRequest,
                Path  = context.Request.Path,
                Query = new QueryTransformContext(context.Request),
            };

            foreach (var requestTransform in RequestTransforms)
            {
                requestTransform.Apply(transformContext);
            }

            // Allow a transform to directly set a custom RequestUri.
            proxyRequest.RequestUri ??= RequestUtilities.MakeDestinationAddress(
                transformContext.DestinationPrefix, transformContext.Path, transformContext.Query.QueryString);

            CopyRequestHeaders(context, proxyRequest);

            return(Task.CompletedTask);
        }
    public void MakeDestinationAddress(string destinationPrefix, string path, string query, string expected)
    {
        var uri = RequestUtilities.MakeDestinationAddress(destinationPrefix, new PathString(path), new QueryString(query));

        Assert.Equal(expected, uri.AbsoluteUri);
    }
Example #6
0
        /// <summary>
        /// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
        /// </summary>
        public void Configure(IApplicationBuilder app, IHttpForwarder forwarder)
        {
            // Configure our own HttpMessageInvoker for outbound calls for proxy operations
            var httpClient = new HttpMessageInvoker(new SocketsHttpHandler()
            {
                UseProxy               = false,
                AllowAutoRedirect      = false,
                AutomaticDecompression = DecompressionMethods.None,
                UseCookies             = false
            });

            // Setup our own request transform class
            var transformer    = new CustomTransformer(); // or HttpTransformer.Default;
            var requestOptions = new ForwarderRequestConfig {
                ActivityTimeout = TimeSpan.FromSeconds(100)
            };

            app.UseRouting();
            app.UseEndpoints(endpoints =>
            {
                endpoints.Map("/test/{**catch-all}", async httpContext =>
                {
                    var error = await forwarder.SendAsync(httpContext, "https://example.com", httpClient, requestOptions,
                                                          static (context, proxyRequest) =>
                    {
                        // Customize the query string:
                        var queryContext = new QueryTransformContext(context.Request);
                        queryContext.Collection.Remove("param1");
                        queryContext.Collection["area"] = "xx2";

                        // Assign the custom uri. Be careful about extra slashes when concatenating here. RequestUtilities.MakeDestinationAddress is a safe default.
                        proxyRequest.RequestUri = RequestUtilities.MakeDestinationAddress("https://example.com", context.Request.Path, queryContext.QueryString);

                        // Suppress the original request header, use the one from the destination Uri.
                        proxyRequest.Headers.Host = null;

                        return(default);
                    });

                    // Check if the proxy operation was successful
                    if (error != ForwarderError.None)
                    {
                        var errorFeature = httpContext.Features.Get <IForwarderErrorFeature>();
                        var exception    = errorFeature.Exception;
                    }
                });


                // When using IHttpForwarder for direct forwarding you are responsible for routing, destination discovery, load balancing, affinity, etc..
                // For an alternate example that includes those features see BasicYarpSample.
                endpoints.Map("/{**catch-all}", async httpContext =>
                {
                    var error = await forwarder.SendAsync(httpContext, "https://example.com", httpClient, requestOptions, transformer);
                    // Check if the proxy operation was successful
                    if (error != ForwarderError.None)
                    {
                        var errorFeature = httpContext.Features.Get <IForwarderErrorFeature>();
                        var exception    = errorFeature.Exception;
                    }
                });
            });