diff --git a/src/CustomHttpEndpoint/Program.cs b/src/CustomHttpEndpoint/Program.cs index 2ec691bd7..516a4e17d 100644 --- a/src/CustomHttpEndpoint/Program.cs +++ b/src/CustomHttpEndpoint/Program.cs @@ -22,6 +22,11 @@ public static class Program /// private static string? _cmd = null; + /// + /// URI to relay the incoming request to + /// + private static string? _uri = null; + /// /// Command arguments for the binary to start /// @@ -67,6 +72,10 @@ public static async Task Main(string[] args) { _cmd = arg; } + else if (lastArg == "-u" || lastArg == "--uri") + { + _uri = arg; + } else if (lastArg == "-a" || lastArg == "--args") { _args = arg; @@ -84,6 +93,7 @@ public static async Task Main(string[] args) Console.WriteLine("-n, --namespace : Namespace to use (defaults to custom-http-endpoint)"); Console.WriteLine("-p, --path : HTTP query path (defaults to demo)"); Console.WriteLine("-e, --exec : Command to execute when an HTTP query is received, stdout and stderr are returned as the response body"); + Console.WriteLine("-u, --uri : Tell the web server to relay the incoming request to another server specified by the URI"); Console.WriteLine("-a, --args : Arguments for the executable command. Query values in % chars are replaced with query options (e.g. %myvalue%). Not applicable for WebSockets"); Console.WriteLine("-q, --quiet: Do not display info text"); Console.WriteLine("-h, --help: Displays this text"); @@ -238,39 +248,7 @@ private static async void OnHttpRequestReceived(HttpEndpointUnixSocket unixSocke // Read the HTTP response from the client ReceivedHttpRequest request = await requestConnection.ReadRequest(); - if (string.IsNullOrWhiteSpace(_cmd)) - { - // Write this event to the console if possible - if (!_quiet) - { - Console.WriteLine("Got new HTTP request from session {0}", request.SessionId); - } - - // Only print a demo response in case no process is supposed to be started - string response = $"This demo text has been returned from a third-party application.\n\nMethod: {_method}\nSession ID: {request.SessionId}"; - if (request.Headers.Count > 0) - { - response += "\n\nHeaders:"; - foreach (var kv in request.Headers) - { - response += $"\n{kv.Key} = {kv.Value}"; - } - } - if (request.Queries.Count > 0) - { - response += "\n\nQueries:"; - foreach (var kv in request.Queries) - { - response += $"\n{kv.Key} = {kv.Value}"; - } - } - if (!string.IsNullOrWhiteSpace(request.Body)) - { - response += "\n\nBody:\n" + request.Body; - } - await requestConnection.SendResponse(200, response, HttpResponseType.PlainText); - } - else + if (!string.IsNullOrWhiteSpace(_cmd)) { // Replace query values in the arguments string args = _cmd; @@ -306,6 +284,44 @@ private static async void OnHttpRequestReceived(HttpEndpointUnixSocket unixSocke await requestConnection.SendResponse(501, "Failed to start process", HttpResponseType.StatusCode); } } + else if (!string.IsNullOrWhiteSpace(_uri)) + { + // Tell DWS to relay this request to another URI + await requestConnection.SendResponse(200, _uri, HttpResponseType.URI); + } + else + { + // Write this event to the console if possible + if (!_quiet) + { + Console.WriteLine("Got new HTTP request from session {0}", request.SessionId); + } + + // Only print a demo response in case no process is supposed to be started + string response = $"This demo text has been returned from a third-party application.\n\nMethod: {_method}\nSession ID: {request.SessionId}"; + if (request.Headers.Count > 0) + { + response += "\n\nHeaders:"; + foreach (var kv in request.Headers) + { + response += $"\n{kv.Key} = {kv.Value}"; + } + } + if (request.Queries.Count > 0) + { + response += "\n\nQueries:"; + foreach (var kv in request.Queries) + { + response += $"\n{kv.Key} = {kv.Value}"; + } + } + if (!string.IsNullOrWhiteSpace(request.Body)) + { + response += "\n\nBody:\n" + request.Body; + } + await requestConnection.SendResponse(200, response, HttpResponseType.PlainText); + } + } } diff --git a/src/DuetAPI/Commands/HttpEndpoints/SendHttpResponse/HttpResponseType.cs b/src/DuetAPI/Commands/HttpEndpoints/SendHttpResponse/HttpResponseType.cs index a33b18573..b72139943 100644 --- a/src/DuetAPI/Commands/HttpEndpoints/SendHttpResponse/HttpResponseType.cs +++ b/src/DuetAPI/Commands/HttpEndpoints/SendHttpResponse/HttpResponseType.cs @@ -27,6 +27,11 @@ public enum HttpResponseType /// /// File content. Response must hold the absolute path to the file to return /// - File + File, + + /// + /// Send this request to another server (proxy). Response must hold the URI to send the request to + /// + URI } } diff --git a/src/DuetWebServer/Middleware/CustomEndpointMiddleware.cs b/src/DuetWebServer/Middleware/CustomEndpointMiddleware.cs index 1afcada29..e83541a69 100644 --- a/src/DuetWebServer/Middleware/CustomEndpointMiddleware.cs +++ b/src/DuetWebServer/Middleware/CustomEndpointMiddleware.cs @@ -6,7 +6,11 @@ using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Primitives; +using System; +using System.Collections.Generic; using System.IO; +using System.Linq; +using System.Net.Http; using System.Net.WebSockets; using System.Text; using System.Threading; @@ -302,6 +306,38 @@ private static async Task ProcessRestRequst(HttpContext context, HttpEndpoint en await using FileStream fs = new(httpResponse.Response, FileMode.Open, FileAccess.Read); await fs.CopyToAsync(context.Response.Body); } + else if (httpResponse.ResponseType == HttpResponseType.URI) + { + // Set up our own request + HttpRequestMessage requestMessage = new() + { + Method = new HttpMethod(context.Request.Method), + RequestUri = new Uri(httpResponse.Response) + }; + requestMessage.Headers.Host = requestMessage.RequestUri.Host; + foreach (var header in context.Request.Headers) + { + requestMessage.Content?.Headers.TryAddWithoutValidation(header.Key, header.Value.ToArray()); + } + requestMessage.Content = new StringContent(body); + + // Send it + using HttpClient client = new(); + using HttpResponseMessage responseMessage = await client.SendAsync(requestMessage, HttpCompletionOption.ResponseHeadersRead, context.RequestAborted); + + // Send the response data back to the client + context.Response.StatusCode = (int)responseMessage.StatusCode; + foreach (var header in responseMessage.Headers) + { + context.Response.Headers[header.Key] = header.Value.ToArray(); + } + foreach (var header in responseMessage.Content.Headers) + { + context.Response.Headers[header.Key] = header.Value.ToArray(); + } + context.Response.Headers.Remove("transfer-encoding"); + await responseMessage.Content.CopyToAsync(context.Response.Body); + } else { switch (httpResponse.ResponseType)