static HttpRequest ParseHttpRequest(HttpRequest state) { var host = state.GetHeader("Host"); var contentType = state.GetHeaderWithDefault(HttpHeader.ContentType, null); var connection = state.GetHeaderWithDefault(HttpHeader.Connection, null); var isKeepAlive = false; var url = new Uri(string.Format("{0}://{1}{2}", state.IsSecureConnection ? "https" : "http", host, state.Path)); if (connection != null) { isKeepAlive = connection.IndexOf("keep-alive", StringComparison.OrdinalIgnoreCase) >= 0; } state.Host = host; state.Url = url; state.ContentType = contentType; state.IsKeepAlive = isKeepAlive; return state; }
//TODO: split to TryParseHttpRequest(out hasBody) + TryParseHttpRequestBody (IF we want to read ahead) public static bool TryParseHttpRequest(byte[] buffer, ref int start, int end, HttpRequest state, out HttpRequest parsedRequest) { while (true) { switch (state.State) { case State.ReadRequestLine: Method method; string path, query, version; if (TryParseRequestLine(buffer, ref start, end, out method, out path, out query, out version)) { state.Method = method; state.Path = path; state.Query = query; state.State = State.ReadHeaders; continue; } else { parsedRequest = null; return false; } case State.ReadHeaders: if (TryParseHeaders(buffer, ref start, end, state.SetHeader)) { switch (state.Method) { case Method.GET: case Method.HEAD: case Method.DELETE: case Method.OPTIONS: state.ContentLength = state.GetHeaderWithDefault<int>(HttpHeader.ContentLength, 0); if (state.ContentLength > 0) //TODO: or 'Transfer-Encoding: chunked' { throw new ParseRequestException("Request body not accepted for [GET|HEAD|DELETE|OPTIONS]", HttpStatusCode.BadRequest); } state.Body = EmptyRequestStream.Instance; parsedRequest = ParseHttpRequest(state); return true; case Method.POST: case Method.PUT: // 1) Fully-readahead path state.ContentLength = state.GetHeaderWithDefault<int>(HttpHeader.ContentLength, -1); if (state.ContentLength < 0) { throw new ParseRequestException("Content-Length required", HttpStatusCode.LengthRequired); } state.Body = new MemoryRequestStream(state.ContentLength); //TODO: guard length + support 'Transfer-Encoding: chunked' state.State = State.ReadBodyToEnd; // 2) TODO: pass (wrapped) networkstream through to client for reading continue; default: throw new ParseRequestException(state.Method + " not supported", HttpStatusCode.MethodNotAllowed); //TODO } } else { parsedRequest = null; return false; } case State.ReadBodyToEnd: var requestBody = (MemoryRequestStream)state.Body; var bytesAvailable = end - start; // may span multiple requests (eg. pipelined) var bytesOutstanding = state.ContentLength - (int)requestBody.Length; var bytesToWrite = Math.Min(bytesAvailable, bytesOutstanding); // only consume enough for the current request requestBody.WriteRequestBody(buffer, start, bytesToWrite); start += bytesToWrite; if (requestBody.Length == state.ContentLength) { requestBody.ResetPosition(); parsedRequest = ParseHttpRequest(state); state.State = State.Completed; return true; } else { parsedRequest = null; return false; } } } }