public void HeaderRecvHandler(ref SelectControl selectControl, Socket clientSocket, Buf ignore) { builder.EnsureTotalCapacity(builder.contentLength + 128); UInt32 dataOffset; try { dataOffset = Http.ReadHttpHeaders(clientSocket, builder); } catch (Exception e) { if (WebServer.Logger != null) { WebServer.Logger.WriteLine("[{0}] Closed: {1}", clientLogString, e.Message); } selectControl.RemoveReceiveSocket(clientSocket); return; } // // Parse the request // try { UInt32 parseOffset = 0; Slice httpMethod; httpMethod.offset = parseOffset; httpMethod.limit = builder.bytes.IndexOfUInt32(parseOffset, dataOffset, (Byte)' '); if (httpMethod.limit == UInt32.MaxValue) { throw new FormatException("Invalid request: no space after HTTP method"); } parseOffset = (uint)httpMethod.limit + 1; Slice httpResource; httpResource.offset = parseOffset; httpResource.limit = builder.bytes.IndexOfUInt32(parseOffset, dataOffset, (Byte)' '); if (httpResource.limit == UInt32.MaxValue) { throw new FormatException("Invalid request: no space after HTTP resource"); } parseOffset = (uint)httpResource.limit + 1; this.method = httpMethod.Decode(builder.bytes); this.resource = httpResource.Decode(builder.bytes); headerContentLength = Http.GetContentLength(builder.bytes, 0, dataOffset); if (headerContentLength != UInt32.MaxValue) { throw new NotImplementedException(String.Format("Content-Length {0} is not implemented", headerContentLength)); } } catch (Exception e) { if (WebServer.Logger != null) { WebServer.Logger.WriteLine("[{0}] InvalidRequest: {1}", clientLogString, e.Message); } clientSocket.Shutdown(SocketShutdown.Both); clientSocket.Close(); selectControl.RemoveReceiveSocket(clientSocket); return; } if (WebServer.Logger != null) { WebServer.Logger.WriteLine("[{0}] {1} {2}", clientLogString, method, resource); } if (!method.Equals("GET")) { if (WebServer.Logger != null) { WebServer.Logger.WriteLine("[{0}] Unsupported HTTP Method: {1}", clientLogString, method); } clientSocket.Shutdown(SocketShutdown.Both); clientSocket.Close(); selectControl.RemoveReceiveSocket(clientSocket); return; } String filename = WebServer.HttpResourceToFile(resource); if (!File.Exists(filename)) { clientSocket.Send(NotFound404); clientSocket.Shutdown(SocketShutdown.Both); clientSocket.Close(); selectControl.RemoveReceiveSocket(clientSocket); return; } builder.Clear(); FileInfo fileInfo = new FileInfo(filename); Int64 fileLength = fileInfo.Length; builder.AppendAscii("HTTP/1.1 200 OK\r\nContent-Length: "); builder.AppendAscii(fileLength.ToString()); builder.AppendAscii("\r\n\r\n"); using (FileStream fileStream = new FileStream(filename, FileMode.Open, FileAccess.Read, FileShare.ReadWrite)) { UInt32 bufferLeft = (uint)builder.bytes.Length - builder.contentLength; if (fileLength <= bufferLeft) { fileStream.ReadFullSize(builder.bytes, (int)builder.contentLength, (int)fileLength); clientSocket.Send(builder.bytes, 0, (int)(builder.contentLength + fileLength), 0); } else { Int64 fileLeft = fileLength; fileStream.ReadFullSize(builder.bytes, (int)builder.contentLength, (int)bufferLeft); clientSocket.Send(builder.bytes); fileLeft -= bufferLeft; while (fileLeft > builder.bytes.Length) { fileStream.ReadFullSize(builder.bytes, 0, builder.bytes.Length); clientSocket.Send(builder.bytes); fileLeft -= builder.bytes.Length; } if (fileLeft > 0) { fileStream.ReadFullSize(builder.bytes, 0, (int)fileLeft); clientSocket.Send(builder.bytes, 0, (int)fileLeft, 0); } } clientSocket.Close(); selectControl.RemoveReceiveSocket(clientSocket); } }
// Returns offset into response content UInt32 ReadResponse(Boolean keepAlive, ByteBuilder builder) { UInt32 contentOffset; while (true) { Int32 bytesReceived = socket.Receive(builder.bytes, (int)builder.contentLength, (int)(builder.bytes.Length - builder.contentLength), 0); if (bytesReceived <= 0) { throw new FormatException(String.Format("Socket.Receive returned {0} while reading response. Response so far is '{1}'", bytesReceived, Encoding.UTF8.GetString(builder.bytes, 0, (int)builder.contentLength))); } UInt32 checkOffset = builder.contentLength; builder.contentLength += (UInt32)bytesReceived; // Search for the ending \r\n\r\n Byte[] checkBuffer = builder.bytes; if (checkOffset >= 3) { checkOffset -= 3; // Reverse 3 chars in case the last request had the partial end of double newline } for (; checkOffset + 3 < builder.contentLength; checkOffset++) { if (checkBuffer[checkOffset] == (Byte)'\r' && checkBuffer[checkOffset + 1] == (Byte)'\n' && checkBuffer[checkOffset + 2] == (Byte)'\r' && checkBuffer[checkOffset + 3] == (Byte)'\n') { contentOffset = checkOffset + 4; goto DONE_WITH_HEADERS; } } // Expand the receive buffer if necessary if (builder.bytes.Length - builder.contentLength < MinimumReceiveBuffer) { builder.EnsureTotalCapacity(builder.contentLength + MinimumExpandReceiveBuffer); } } DONE_WITH_HEADERS: // // Get Content Length // // TODO: verify this works UInt32 contentLengthFromHeader = Http.GetContentLength(builder.bytes, 0, builder.contentLength); // // TODO: Determine Message Length properly // 1. If the response cannot include a message body then it MUST NOT have any content (such // as 1xx, 204, 304, or any HEAD response) // 2. If a Transfer-Encoding header field is present with any other value besides "identity", then // the transfer-length is defined by use of the "chunked" transfer-coding unless the message // is terminated by closing the connection // 3. Use the Content-Length header // 4. Using Multipart/byteranges....??? // 5. Server closes the connection // // Read the rest of the response // if (contentLengthFromHeader == UInt32.MaxValue) { if (keepAlive) { // TODO: check the response code // The server did not include a Content-Length HTTP header // This is problematic if the socket connection is Keep-Alive // Note: I am currently assuming keepAlive from the client request...I should check the server header // for CloseConnection throw new FormatException("The server did not include a Content-Length HTTP header but the socket connection is Keep-Alive"); } else { while (true) { Int32 bytesReceived = socket.Receive(builder.bytes, (int)builder.contentLength, (int)(builder.bytes.Length - builder.contentLength), 0); if (bytesReceived <= 0) { throw new FormatException(String.Format("Socket.Receive returned {0} while reading response. Response so far is '{1}'", bytesReceived, Encoding.UTF8.GetString(builder.bytes, 0, (int)builder.contentLength))); } builder.contentLength += (UInt32)bytesReceived; // Expand the receive buffer if necessary if (builder.bytes.Length - builder.contentLength < MinimumReceiveBuffer) { builder.EnsureTotalCapacity(builder.contentLength + MinimumExpandReceiveBuffer); } } } } else if (contentLengthFromHeader > 0) { UInt32 contentSoFar = builder.contentLength - contentOffset; Int32 contentLeft = (Int32)(contentLengthFromHeader - contentSoFar); if (contentLeft > 0) { builder.EnsureTotalCapacity(builder.contentLength + (UInt32)contentLeft); do { Int32 bytesReceived = socket.Receive(builder.bytes, (int)builder.contentLength, (int)(builder.bytes.Length - builder.contentLength), 0); if (bytesReceived <= 0) { throw new FormatException(String.Format("Socket.Receive returned {0} while reading response. Response so far is '{1}'", bytesReceived, Encoding.UTF8.GetString(builder.bytes, 0, (int)builder.contentLength))); } builder.contentLength += (UInt32)bytesReceived; // Expand the receive buffer if necessary if (builder.bytes.Length - builder.contentLength < MinimumReceiveBuffer) { builder.EnsureTotalCapacity(builder.contentLength + MinimumExpandReceiveBuffer); } contentLeft -= bytesReceived; } while (contentLeft > 0); } } if (!keepAlive) { socket.ShutdownAndDispose(); socket = null; } return(contentOffset); }