/// <summary> /// Process incoming data from the WebSocket /// </summary> /// <param name="webSocket">Remote WebSocket</param> /// <param name="endpointConnection">Local UNIX socket connection</param> /// <param name="sessionId">Session ID</param> /// <param name="cancellationToken">Cancellation token</param> /// <returns>Asynchronous task</returns> private async Task ReadFromWebSocket(WebSocket webSocket, HttpEndpointConnection endpointConnection, int sessionId, CancellationToken cancellationToken) { int bufferSize = _configuration.GetValue("WebSocketBufferSize", 8192); byte[] rxBuffer = new byte[bufferSize]; do { // Read data from the WebSocket WebSocketReceiveResult result = await webSocket.ReceiveAsync(rxBuffer, cancellationToken); if (result.MessageType == WebSocketMessageType.Close) { // Remote end is closing this connection return; } if (result.MessageType == WebSocketMessageType.Binary) { // Terminate the connection if binary content is received await CloseConnection(webSocket, WebSocketCloseStatus.InvalidMessageType, "Only text commands are supported"); break; } // Forward it to the UNIX socket connection ReceivedHttpRequest receivedHttpRequest = new ReceivedHttpRequest { Body = Encoding.UTF8.GetString(rxBuffer, 0, result.Count), SessionId = sessionId }; await endpointConnection.SendHttpRequest(receivedHttpRequest, cancellationToken); }while (!cancellationToken.IsCancellationRequested); }
/// <summary> /// Read another message from the web socket and print it to the console /// </summary> /// <param name="connection">WebSocket connection</param> /// <param name="cancellationToken">Cancellation token</param> /// <returns>Asynchronous task</returns> private static async Task ReadFromWebSocket(HttpEndpointConnection connection, CancellationToken cancellationToken) { // Note that no content has been received when we get here for the first time. // In this case, it may take a while before/if data can be received from the client do { ReceivedHttpRequest websocketRequest = await connection.ReadRequest(cancellationToken); Console.WriteLine(websocketRequest.Body); }while (!cancellationToken.IsCancellationRequested); }
/// <summary> /// Handle an message /// </summary> /// <param name="context">Context unique for this handler instance</param> /// <param name="message">Message to process</param> public void HandleUpstream(IPipelineHandlerContext context, IPipelineMessage message) { if (message is Closed) { _bodyBytesLeft = 0; _headerParser.Reset(); } else if (message is Received) { var msg = (Received)message; // complete the body if (_bodyBytesLeft > 0) { var bytesToSend = Math.Min(_bodyBytesLeft, msg.BufferReader.RemainingLength); _bodyBytesLeft -= bytesToSend; context.SendUpstream(message); return; } _headerParser.Parse(msg.BufferReader); if (_headerCompleted) { var request = (IRequest)_message; var ourRequest = _message as HttpRequest; if (ourRequest != null) { ourRequest.RemoteEndPoint = msg.RemoteEndPoint as IPEndPoint; } request.AddHeader("RemoteAddress", msg.RemoteEndPoint.ToString()); var receivedHttpRequest = new ReceivedHttpRequest(request); _headerParser.Reset(); _headerCompleted = false; context.SendUpstream(receivedHttpRequest); if (msg.BufferReader.RemainingLength > 0) { context.SendUpstream(msg); } } return; } context.SendUpstream(message); }
/// <summary> /// Invokes the <see cref="IErrorFormatter.Format"/> and guards against any exceptions that it might throw. /// </summary> /// <param name="response">Response to send back</param> /// <param name="msg">Request pipeline message</param> /// <param name="exception">Caught exception</param> protected virtual void FormatException(IResponse response, ReceivedHttpRequest msg, HttpException exception) { var formatterContext = new ErrorFormatterContext(exception, msg.HttpRequest, response); try { _formatter.Format(formatterContext); } catch (Exception err) { _logger.Error(string.Format("Formatter '{0}' failed to process request.", _formatter.GetType().FullName), err); var formatter = new SimpleErrorFormatter(); formatter.Format(formatterContext); } }
public void Test() { var service = Substitute.For <IBodyDecoder>(); var context = Substitute.For <IPipelineHandlerContext>(); var request = new HttpRequest("GET", "/", "HTTP/1.1"); request.Body = new MemoryStream(Encoding.ASCII.GetBytes("Hello world!")); var msg = new ReceivedHttpRequest(request); var sut = new BodyDecoder(service, 65535, 65535); sut.HandleUpstream(context, msg); var received = new Received(new IPEndPoint(IPAddress.Loopback, 9231), Substitute.For <IBufferReader>()); sut.HandleUpstream(context, received); }
/// <summary> /// Handle an message /// </summary> /// <param name="context">Context unique for this handler instance</param> /// <param name="message">Message to process</param> public void HandleUpstream(IPipelineHandlerContext context, IPipelineMessage message) { if (message is Closed) { _bodyBytesLeft = 0; _parser.Reset(); } else if (message is Received) { var msg = (Received)message; // complete the body if (_bodyBytesLeft > 0) { _bodyBytesLeft -= msg.BufferSlice.Count; context.SendUpstream(message); return; } var httpMsg = _parser.Parse(msg.BufferSlice); if (httpMsg != null) { var recivedHttpMsg = new ReceivedHttpRequest((IRequest)httpMsg); _bodyBytesLeft = recivedHttpMsg.HttpRequest.ContentLength; _parser.Reset(); // send up the message to let someone else handle the body context.SendUpstream(recivedHttpMsg); if (msg.BufferSlice.RemainingLength > 0) { context.SendUpstream(msg); } } return; } context.SendUpstream(message); }
/// <summary> /// Handle an message /// </summary> /// <param name="context">Context unique for this handler instance</param> /// <param name="message">Message to process</param> public void HandleUpstream(IPipelineHandlerContext context, IPipelineMessage message) { if (message is Closed) { _bodyBytesLeft = 0; _parser.Reset(); } else if (message is Received) { var msg = (Received) message; // complete the body if (_bodyBytesLeft > 0) { _bodyBytesLeft -= msg.BufferSlice.Count; context.SendUpstream(message); return; } var httpMsg = _parser.Parse(msg.BufferSlice); if (httpMsg != null) { var recivedHttpMsg = new ReceivedHttpRequest((IRequest) httpMsg); _bodyBytesLeft = recivedHttpMsg.HttpRequest.ContentLength; _parser.Reset(); // send up the message to let someone else handle the body context.SendUpstream(recivedHttpMsg); if (msg.BufferSlice.RemainingLength > 0) context.SendUpstream(msg); } return; } context.SendUpstream(message); }
/// <summary> /// Process a RESTful HTTP request /// </summary> /// <param name="context">HTTP context</param> /// <param name="endpointConnection">Endpoint connection</param> /// <param name="sessionId">Session ID</param> /// <returns>Asynchronous task</returns> private async Task ProcessRestRequst(HttpContext context, HttpEndpointConnection endpointConnection, int sessionId) { // Deal with RESTful HTTP requests. Read the full HTTP request body first string body; using (StreamReader reader = new StreamReader(context.Request.Body)) { body = await reader.ReadToEndAsync(); } // Prepare the HTTP request notification ReceivedHttpRequest receivedHttpRequest = new ReceivedHttpRequest { Body = body, ContentType = context.Request.ContentType, SessionId = sessionId }; foreach (var item in context.Request.Headers) { receivedHttpRequest.Headers.Add(item.Key, item.Value.ToString()); } foreach (var item in context.Request.Query) { receivedHttpRequest.Queries.Add(item.Key, item.Value.ToString()); } // Send it to the third-party application and get a response type await endpointConnection.SendHttpRequest(receivedHttpRequest); SendHttpResponse httpResponse = await endpointConnection.GetHttpResponse(); // Send the response to the HTTP client context.Response.StatusCode = httpResponse.StatusCode; if (httpResponse.ResponseType == HttpResponseType.File) { context.Response.ContentType = "application/octet-stream"; using FileStream fs = new FileStream(httpResponse.Response, FileMode.Open, FileAccess.Read); await fs.CopyToAsync(context.Response.Body); } else { switch (httpResponse.ResponseType) { case HttpResponseType.StatusCode: context.Response.ContentType = null; break; case HttpResponseType.PlainText: context.Response.ContentType = "text/plain;charset=utf-8"; break; case HttpResponseType.JSON: context.Response.ContentType = "application/json"; break; } await context.Response.BodyWriter.WriteAsync(Encoding.UTF8.GetBytes(httpResponse.Response)); } }
/// <summary> /// Called when a new HTTP or WebSocket request has been received /// </summary> /// <param name="unixSocket">UNIX socket for HTTP endpoints</param> /// <param name="requestConnection">Connection from the UNIX socket representing the HTTP or WebSocket request</param> private static async void OnHttpRequestReceived(HttpEndpointUnixSocket unixSocket, HttpEndpointConnection requestConnection) { // Note that a call to ReadRequest can throw an exception in case DCS only created a test connection! // DCS may do that when an application attempts to register an existing endpoint twice if (_method == HttpEndpointType.WebSocket) { if (_websocketConnected) { await requestConnection.SendResponse(1000, "Demo application only supports one WebSocket connection"); return; } _websocketConnected = true; if (!_quiet) { Console.WriteLine("WebSocket connected, type 'close' to close this connection"); } try { using CancellationTokenSource cts = new CancellationTokenSource(); Task webSocketTask = ReadFromWebSocket(requestConnection, cts.Token); Task consoleTask = ReadFromConsole(requestConnection, cts.Token); await Task.WhenAny(webSocketTask, consoleTask); cts.Cancel(); } catch (Exception e) { if (!(e is OperationCanceledException) && !(e is SocketException)) { Console.WriteLine("Unexpected error:"); Console.WriteLine(e); } } finally { _websocketConnected = false; if (!_quiet) { Console.WriteLine("WebSocket disconnected"); } } } else { // 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 { // Replace query values in the arguments string args = _cmd; foreach (var kv in request.Queries) { args.Replace($"%{kv.Key}%", kv.Value); } // Prepare the process start info using Process process = new Process { StartInfo = new ProcessStartInfo { FileName = _cmd, Arguments = args, RedirectStandardOutput = true } }; // Start a process and wait for it to exit string output = ""; process.OutputDataReceived += (object sender, DataReceivedEventArgs e) => output += e.Data; process.ErrorDataReceived += (object sender, DataReceivedEventArgs e) => output += e.Data; if (process.Start()) { process.BeginOutputReadLine(); process.BeginErrorReadLine(); process.WaitForExit(); await requestConnection.SendResponse(200, output, HttpResponseType.PlainText); } else { await requestConnection.SendResponse(501, "Failed to start process", HttpResponseType.StatusCode); } } } }
/// <summary> /// Process a RESTful HTTP request /// </summary> /// <param name="context">HTTP context</param> /// <param name="endpoint">HTTP endpoint descriptor</param> /// <param name="endpointConnection">Endpoint connection</param> /// <param name="sessionId">Session ID</param> /// <returns>Asynchronous task</returns> private async Task ProcessRestRequst(HttpContext context, HttpEndpoint endpoint, HttpEndpointConnection endpointConnection, int sessionId) { string body; if (endpoint.IsUploadRequest) { // Write to a temporary file string filename = Path.GetTempFileName(); using (FileStream fileStream = new FileStream(filename, FileMode.Create, FileAccess.Write, FileShare.Read)) { await context.Request.Body.CopyToAsync(fileStream); } // Adjust the file permissions if possible endpointConnection.GetPeerCredentials(out _, out int uid, out int gid); if (uid != 0 && gid != 0) { LinuxApi.Commands.Chown(filename, uid, gid); } body = filename; } else { // Read the body content using StreamReader reader = new StreamReader(context.Request.Body); body = await reader.ReadToEndAsync(); } // Prepare the HTTP request notification ReceivedHttpRequest receivedHttpRequest = new ReceivedHttpRequest { Body = body, ContentType = context.Request.ContentType, SessionId = sessionId }; foreach (var item in context.Request.Headers) { receivedHttpRequest.Headers.Add(item.Key, item.Value.ToString()); } foreach (var item in context.Request.Query) { receivedHttpRequest.Queries.Add(item.Key, item.Value.ToString()); } // Send it to the third-party application and get a response type await endpointConnection.SendHttpRequest(receivedHttpRequest); SendHttpResponse httpResponse = await endpointConnection.GetHttpResponse(); // Send the response to the HTTP client context.Response.StatusCode = httpResponse.StatusCode; if (httpResponse.ResponseType == HttpResponseType.File) { context.Response.ContentType = "application/octet-stream"; using FileStream fs = new FileStream(httpResponse.Response, FileMode.Open, FileAccess.Read); await fs.CopyToAsync(context.Response.Body); } else { switch (httpResponse.ResponseType) { case HttpResponseType.StatusCode: context.Response.ContentType = null; break; case HttpResponseType.PlainText: context.Response.ContentType = "text/plain;charset=utf-8"; break; case HttpResponseType.JSON: context.Response.ContentType = "application/json"; break; } await context.Response.BodyWriter.WriteAsync(Encoding.UTF8.GetBytes(httpResponse.Response)); } }