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 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); }