private async Task <HttpRequest> ReadRequestHead(Socket partner, Stopwatch receiveTimer, DateTime receivedAt, HttpRequestReaderStream reader, StreamWriter writer, List <HttpHeader> headers)
        {
            var questing = new StringSegment(await reader.ReadLineAsync());

            if (questing.Empty)
            {
                return(null); // no request -> no response, close connection
            }
            // If it is not the first request, we set the DateTime for this request here
            // right after receving the first line, which is presumably in the first packet.
            if (receivedAt == DateTime.MinValue)
            {
                receiveTimer = Stopwatch.StartNew();
                receivedAt   = DateTime.Now;
            }

            headers.Clear();
            while (true)
            {
                var header = await reader.ReadLineAsync();

                if (String.IsNullOrEmpty(header))
                {
                    break;
                }

                var colon = header.IndexOf(':');
                var name  = header.Substring(0, colon);
                var value = header.Substring(colon + 2); // skip colon + space

                headers.Add(new HttpHeader {
                    Name = name.ToLowerInvariant(), Value = value
                });
            }

            var space1 = questing.IndexOf(' ');
            var space2 = questing.IndexOf(' ', space1 + 1); // if space1 is -1 this is fine as well

            if (!((space1 > 0) && (space2 > 0)))
            {
                return(await WriteBadRequest(questing, writer, "Invalid request line"));
            }
            var method  = questing.Substring(0, space1);
            var path    = questing.Substring(space1 + 1, space2 - space1 - 1);
            var version = questing.Substring(space2 + 1);

            if (!(version == "HTTP/1.1" && path[0] == '/'))
            {
                return(await WriteBadRequest(questing, writer, "Invalid protocol or path"));
            }
            path = path.Substring(1);

            HttpMethod prettyMethod;
            bool       hasBody = false;

            if (method == "GET")
            {
                prettyMethod = HttpMethod.Get;
            }
            else if (method == "POST")
            {
                prettyMethod = HttpMethod.Post;
                hasBody      = true;
            }
            else if (method == "PUT")
            {
                prettyMethod = HttpMethod.Put;
                hasBody      = true;
            }
            else if (method == "DELETE")
            {
                prettyMethod = HttpMethod.Delete;
            }
            else
            {
                return(await WriteBadRequest(questing, writer, "Invalid or unsupported method"));
            }

            var request = new HttpRequest(prettyMethod, path, headers, Encoding.UTF8, receivedAt, receiveTimer, partner.RemoteEndPoint as IPEndPoint, this);

            if (hasBody)
            {
                int bodyLength;
                if (!int.TryParse(request.GetHeader("content-length"), out bodyLength))
                {
                    return(await WriteBadRequest(questing, writer, "Request has body but no content-length given!", HttpStatus.LengthRequired));
                }

                if (bodyLength > MaxRequestBodySize)
                {
                    return(await WriteBadRequest(questing, writer, "Request body too large!", HttpStatus.EntityTooLarge));
                }

                // read body into byte array (not sure about this tho)
                var body     = new byte[bodyLength];
                int bodyRead = 0;
                while (bodyRead < body.Length)
                {
                    int read = await reader.ReadAsync(body, bodyRead, body.Length - bodyRead);

                    if (read == 0)
                    {
                        return(await WriteBadRequest(questing, writer,
                                                     $"Invalid request: content length is {bodyLength} bytes, but stream closed after {bodyRead} bytes"));
                    }
                    bodyRead += read;
                }

                request.Body = body;
            }
            return(request);
        }
Beispiel #2
0
        private async void HandleClient(Socket partner)
        {
            try
            {
                Stream readStream;
                using (var writeStream = MakeSaneNetworkStream(partner, out readStream))
                    using (var reader = new HttpRequestReaderStream(readStream))
                        using (var writer = new StreamWriter(writeStream, new UTF8Encoding(false), 4096)
                        {
                            NewLine = "\r\n", AutoFlush = false
                        })
                        {
                            var headers = new List <HttpHeader>();
                            while (true)
                            {
                                var questing = new StringSegment(await reader.ReadLineAsync());
                                if (questing.Empty)
                                {
                                    break;
                                }

                                headers.Clear();
                                while (true)
                                {
                                    var header = await reader.ReadLineAsync();

                                    if (String.IsNullOrEmpty(header))
                                    {
                                        break;
                                    }

                                    var colon = header.IndexOf(':');
                                    var name  = header.Substring(0, colon);
                                    var value = header.Substring(colon + 2); // skip colon + space

                                    headers.Add(new HttpHeader {
                                        Name = name.ToLowerInvariant(), Value = value
                                    });
                                }

                                var space1 = questing.IndexOf(' ');
                                var space2 = questing.IndexOf(' ', space1 + 1); // if space1 is -1 this is fine as well

                                if ((space1 > 0) && (space2 > 0))
                                {
                                    var method  = questing.Substring(0, space1);
                                    var path    = questing.Substring(space1 + 1, space2 - space1 - 1);
                                    var version = questing.Substring(space2 + 1);
                                    if (version == "HTTP/1.1" && path[0] == '/')
                                    {
                                        path = path.Substring(1);

                                        HttpMethod?prettyMethod = null;
                                        bool       hasBody      = false;
                                        if (method == "GET")
                                        {
                                            prettyMethod = HttpMethod.Get;
                                        }
                                        else if (method == "POST")
                                        {
                                            prettyMethod = HttpMethod.Post;
                                            hasBody      = true;
                                        }

                                        if (prettyMethod.HasValue)
                                        {
                                            var request = new HttpRequest(prettyMethod.Value, path, headers, Encoding.UTF8, this);

                                            bool isWebSocket = false;
                                            if (hasBody)
                                            {
                                                int bodyLength;
                                                if (!int.TryParse(request.GetHeader("content-length"), out bodyLength))
                                                {
                                                    throw new InvalidOperationException("Request has body but no content-length given!");
                                                }

                                                // read body into byte array (not sure about this tho)
                                                var body = new byte[bodyLength];
                                                int read = 1337;
                                                for (int i = 0; (i < body.Length) && (read != 0); i += read)
                                                {
                                                    read = await reader.ReadAsync(body, i, body.Length - i);
                                                }
                                                request.Body = body;
                                            }
                                            else if (prettyMethod == HttpMethod.Get)
                                            {
                                                var connection = request.GetHeader("connection")?.ToLowerInvariant();
                                                isWebSocket = connection == "upgrade" || connection == "websocket";
                                            }

                                            var response = await RoutingManager.DispatchRequest(request);

                                            if (response.WebSocketHandler != null)
                                            {
                                                if (!isWebSocket)
                                                {
                                                    throw new InvalidOperationException("WebSocket provided but not requested.");
                                                }

                                                await HandleWebSocket(partner, readStream, writer, response.WebSocketHandler, request);

                                                return;
                                            }

                                            await writer.WriteLineAsync(StatusStrings[(int)response.Status]);

                                            await writer.WriteLineAsync(ServerHeader);

                                            if (response.ContentType != ContentType.Custom)
                                            {
                                                await writer.WriteAsync("Content-Type: ");

                                                await writer.WriteLineAsync(ContentTypeStrings[(int)response.ContentType]);
                                            }

                                            if (response.ExtraHeaders != null)
                                            {
                                                foreach (var header in response.ExtraHeaders)
                                                {
                                                    await writer.WriteAsync(header.Name);

                                                    await writer.WriteAsync(": ");

                                                    await writer.WriteLineAsync(header.Value);
                                                }
                                            }

                                            await writer.WriteAsync("Content-Length: ");

                                            await writer.WriteLineAsync(response.Content.Count.ToString());

                                            await writer.WriteLineAsync();

                                            // This flushes the BufferedStream as well which is NOT what we want.
                                            // Solving this would require us to either reimplement StreamWriter or
                                            // to wrap the BufferedStream in another Stream (because it's sealed).
                                            // Worth it? I don't know.
                                            await writer.FlushAsync();

                                            await writeStream.WriteAsync(response.Content.Array, response.Content.Offset, response.Content.Count);

                                            await writeStream.FlushAsync();

                                            // All is well - we can loop (keepalive).
                                            continue;
                                        }
                                    }
                                }

                                // If we reach this, something is weird/wrong.
                                // "Bye, have a great day!"
                                await writer.WriteLineAsync(StatusStrings[(int)HttpStatus.BadRequest]);

                                await writer.WriteLineAsync(ServerHeader);

                                await writer.WriteLineAsync("Connection: close");

                                await writer.WriteLineAsync();

                                await writer.FlushAsync();

                                return;
                            }
                        }
            }
            catch (Exception e)
            {
                UnexpectedException?.Invoke(e);
            }
        }
        private async void HandleClient(Socket partner)
        {
            var receiveTimer = Stopwatch.StartNew();
            var receivedAt   = DateTime.Now;

            try
            {
                Stream readStream;
                using (var writeStream = MakeSaneNetworkStream(partner, out readStream))
                    using (var reader = new HttpRequestReaderStream(readStream))
                        using (var writer = new StreamWriter(writeStream, new UTF8Encoding(false), 4096)
                        {
                            NewLine = "\r\n", AutoFlush = false
                        })
                        {
                            var headers = new List <HttpHeader>();
                            while (true)
                            {
                                var request = await ReadRequestHead(partner, receiveTimer, receivedAt, reader, writer, headers);

                                if (request == null)
                                {
                                    return;
                                }

                                receiveTimer = null;
                                receivedAt   = DateTime.MinValue;

                                var response = await RoutingManager.DispatchRequest(request);

                                using (response.ContentStream)
                                {
                                    try
                                    {
                                        response.ResolveJsonContent(this);
                                    }
                                    catch (Exception e)
                                    {
                                        response = HttpResponse.String("Error serializing JSON: " + e, HttpStatus.InternalServerError);
                                    }

                                    if ((response.WebSocketHandler != null) && request.IsWebSocket)
                                    {
                                        await HandleWebSocket(partner, readStream, writer, response.WebSocketHandler, request);

                                        return;
                                    }

                                    await WriteResponseHead(writer, response);

                                    if (response.ContentStream != null)
                                    {
                                        await WriteChunkedResponse(writeStream, writer, response.ContentStream);
                                    }
                                    else
                                    {
                                        await WriteSimpleResponse(writeStream, writer, response);
                                    }
                                }

                                // All is well - we can loop (keepalive).
                            }
                        }
            }
            catch (Exception e)
            {
                UnexpectedException?.Invoke(e);
            }
        }