private void Close(UvTcpConnection connection, Exception error = null) { Console.WriteLine("[client] closing connection..."); connection.Input.CompleteReading(error); connection.Output.CompleteWriting(error); Console.WriteLine("[client] connection closed"); }
private void Close(UvTcpConnection connection, Exception error = null) { Console.WriteLine("[server] closing connection..."); connection.Output.Complete(error); connection.Input.Complete(error); Console.WriteLine("[server] connection closed"); }
internal async Task ConnectAsync(IPEndPoint endpoint) { thread = new UvThread(); client = new UvTcpClient(thread, endpoint); connection = await client.ConnectAsync(); ReadLoop(); // will hand over to libuv thread }
private async void OnConnection(UvTcpConnection connection) { WebSocketConnection socket = null; try { WriteStatus("Connected"); WriteStatus("Parsing http request..."); var request = await ParseHttpRequest(connection.Input); try { WriteStatus("Identifying protocol..."); socket = GetProtocol(connection, ref request); WriteStatus($"Protocol: {WebSocketProtocol.Name}"); WriteStatus("Authenticating..."); if (!await OnAuthenticateAsync(socket, ref request.Headers)) { throw new InvalidOperationException("Authentication refused"); } WriteStatus("Completing handshake..."); await WebSocketProtocol.CompleteHandshakeAsync(ref request, socket); } finally { request.Dispose(); // can't use "ref request" or "ref headers" otherwise } WriteStatus("Handshake complete hook..."); await OnHandshakeCompleteAsync(socket); connections.TryAdd(socket, socket); WriteStatus("Processing incoming frames..."); await socket.ProcessIncomingFramesAsync(this); WriteStatus("Exiting..."); socket.Close(); } catch (Exception ex) {// meh, bye bye broken connection try { socket?.Close(ex); } catch { } WriteStatus(ex.StackTrace); WriteStatus(ex.GetType().Name); WriteStatus(ex.Message); } finally { WebSocketConnection tmp; if (socket != null) { connections.TryRemove(socket, out tmp); } try { connection.Output.CompleteWriting(); } catch { } try { connection.Input.CompleteReading(); } catch { } } }
public void Close() { if (connection != null) { Close(connection); } connection = null; // client.Dispose(); // thread?.Dispose(); thread = null; }
private async void OnConnection(UvTcpConnection connection) { try { Console.WriteLine("[server] OnConnection entered"); lock (connections) { connections.Add(connection); } while (true) { ReadableBuffer request; Console.WriteLine("[server] awaiting input..."); try { request = await connection.Input.ReadAsync(); } finally { Console.WriteLine("[server] await completed"); } if (request.IsEmpty && connection.Input.Reading.IsCompleted) { break; } int len = request.Length; Console.WriteLine($"[server] echoing {len} bytes..."); var response = connection.Output.Alloc(); response.Append(ref request); await response.FlushAsync(); Console.WriteLine($"[server] echoed"); connection.Input.Advance(request.End); } Close(connection); } catch (Exception ex) { Program.WriteError(ex); } finally { lock (connections) { connections.Remove(connection); } Console.WriteLine("[server] OnConnection exited"); } }
private static async Task ProduceResponse(ConnectionState state, UvTcpConnection connection, HttpResponseMessage response) { // TODO: pipelining support! while (true) { var result = await connection.Input.ReadAsync(); var responseBuffer = result.Buffer; var consumed = responseBuffer.Start; var needMoreData = true; try { if (consumed == state.Consumed) { var oldBody = responseBuffer.Slice(0, state.PreviousContentLength); if (oldBody.Length != state.PreviousContentLength) { // Not enough data continue; } // The caller didn't read the body responseBuffer = responseBuffer.Slice(state.PreviousContentLength); consumed = responseBuffer.Start; state.Consumed = default(ReadCursor); } if (responseBuffer.IsEmpty && result.IsCompleted) { break; } ReadCursor delim; ReadableBuffer responseLine; if (!responseBuffer.TrySliceTo((byte)'\r', (byte)'\n', out responseLine, out delim)) { continue; } responseBuffer = responseBuffer.Slice(delim).Slice(2); ReadableBuffer httpVersion; if (!responseLine.TrySliceTo((byte)' ', out httpVersion, out delim)) { // Bad request throw new InvalidOperationException(); } consumed = responseBuffer.Start; responseLine = responseLine.Slice(delim).Slice(1); ReadableBuffer statusCode; if (!responseLine.TrySliceTo((byte)' ', out statusCode, out delim)) { // Bad request throw new InvalidOperationException(); } response.StatusCode = (HttpStatusCode)statusCode.GetUInt32(); responseLine = responseLine.Slice(delim).Slice(1); ReadableBuffer remaining; if (!responseLine.TrySliceTo((byte)' ', out remaining, out delim)) { // Bad request throw new InvalidOperationException(); } while (!responseBuffer.IsEmpty) { var ch = responseBuffer.Peek(); if (ch == -1) { break; } if (ch == '\r') { // Check for final CRLF. responseBuffer = responseBuffer.Slice(1); ch = responseBuffer.Peek(); responseBuffer = responseBuffer.Slice(1); if (ch == -1) { break; } else if (ch == '\n') { consumed = responseBuffer.Start; needMoreData = false; break; } // Headers don't end in CRLF line. throw new Exception(); } var headerName = default(ReadableBuffer); var headerValue = default(ReadableBuffer); // End of the header // \n ReadableBuffer headerPair; if (!responseBuffer.TrySliceTo((byte)'\n', out headerPair, out delim)) { break; } responseBuffer = responseBuffer.Slice(delim).Slice(1); // : if (!headerPair.TrySliceTo((byte)':', out headerName, out delim)) { throw new Exception(); } headerName = headerName.TrimStart(); headerPair = headerPair.Slice(headerName.End).Slice(1); // \r if (!headerPair.TrySliceTo((byte)'\r', out headerValue, out delim)) { // Bad request throw new Exception(); } headerValue = headerValue.TrimStart(); var hKey = headerName.GetAsciiString(); var hValue = headerValue.GetAsciiString(); if (!response.Content.Headers.TryAddWithoutValidation(hKey, hValue)) { response.Headers.TryAddWithoutValidation(hKey, hValue); } // Move the consumed consumed = responseBuffer.Start; } } catch (Exception ex) { // Close the connection connection.Output.Complete(ex); break; } finally { connection.Input.Advance(consumed); } if (needMoreData) { continue; } // Only handle content length for now var length = response.Content.Headers.ContentLength; if (!length.HasValue) { throw new NotSupportedException(); } checked { // BAD but it's a proof of concept ok? state.PreviousContentLength = (int)length.Value; ((PipelineHttpContent)response.Content).ContentLength = (int)length; state.Consumed = consumed; } break; } }
internal WebSocketConnection(UvTcpConnection connection) { this.connection = connection; }
private WebSocketConnection GetProtocol(UvTcpConnection connection, ref HttpRequest request) { var headers = request.Headers; string host = headers.GetAsciiString("Host"); if (string.IsNullOrEmpty(host)) { //4. The request MUST contain a |Host| header field whose value //contains /host/ plus optionally ":" followed by /port/ (when not //using the default port). throw new InvalidOperationException("host required"); } bool looksGoodEnough = false; // mozilla sends "keep-alive, Upgrade"; let's make it more forgiving var connectionParts = new HashSet <string>(StringComparer.OrdinalIgnoreCase); if (headers.ContainsKey("Connection")) { // so for mozilla, this will be the set {"keep-alive", "Upgrade"} var parts = headers.GetAsciiString("Connection").Split(Comma); foreach (var part in parts) { connectionParts.Add(part.Trim()); } } if (connectionParts.Contains("Upgrade") && IsCaseInsensitiveAsciiMatch(headers.GetRaw("Upgrade"), "websocket")) { //5. The request MUST contain an |Upgrade| header field whose value //MUST include the "websocket" keyword. //6. The request MUST contain a |Connection| header field whose value //MUST include the "Upgrade" token. looksGoodEnough = true; } if (!looksGoodEnough && AllowClientsMissingConnectionHeaders) { if ((headers.ContainsKey("Sec-WebSocket-Version") && headers.ContainsKey("Sec-WebSocket-Key")) || (headers.ContainsKey("Sec-WebSocket-Key1") && headers.ContainsKey("Sec-WebSocket-Key2"))) { looksGoodEnough = true; } } if (looksGoodEnough) { //9. The request MUST include a header field with the name //|Sec-WebSocket-Version|. The value of this header field MUST be if (!headers.ContainsKey("Sec-WebSocket-Version")) { throw new NotSupportedException(); } else { var version = headers.GetRaw("Sec-WebSocket-Version").GetUInt32(); switch (version) { case 4: case 5: case 6: case 7: case 8: // these are all early drafts case 13: // this is later drafts and RFC6455 break; // looks ok default: // should issues a 400 "upgrade required" and specify Sec-WebSocket-Version - see 4.4 throw new InvalidOperationException(string.Format("Sec-WebSocket-Version {0} is not supported", version)); } } } else { throw new InvalidOperationException("Request was not a web-socket upgrade request"); } //The "Request-URI" of the GET method [RFC2616] is used to identify the //endpoint of the WebSocket connection, both to allow multiple domains //to be served from one IP address and to allow multiple WebSocket //endpoints to be served by a single server. var socket = new WebSocketConnection(connection); socket.Host = host; // Some early drafts used the latter, so we'll allow it as a fallback // in particular, two drafts of version "8" used (separately) **both**, // so we can't rely on the version for this (hybi-10 vs hybi-11). // To make it even worse, hybi-00 used Origin, so it is all over the place! socket.Origin = headers.GetAsciiString("Origin") ?? headers.GetAsciiString("Sec-WebSocket-Origin"); socket.Protocol = headers.GetAsciiString("Sec-WebSocket-Protocol"); socket.RequestLine = request.Path.GetAsciiString(); return(socket); }