Example #1
0
 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");
 }
Example #2
0
 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");
 }
Example #3
0
        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
        }
Example #4
0
        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 { }
            }
        }
Example #5
0
 public void Close()
 {
     if (connection != null)
     {
         Close(connection);
     }
     connection = null;
     // client.Dispose(); //
     thread?.Dispose();
     thread = null;
 }
Example #6
0
        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");
            }
        }
Example #7
0
        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;
            }
        }
Example #8
0
 internal WebSocketConnection(UvTcpConnection connection)
 {
     this.connection = connection;
 }
Example #9
0
        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);
        }