diff --git a/rproxy.c b/rproxy.c index f8d85ed..05c6583 100644 --- a/rproxy.c +++ b/rproxy.c @@ -1983,129 +1983,111 @@ void handle_client_read(connection_t *conn) { buffer_t *buf = &conn->read_buf; - log_info("[ROUTING-DEBUG] handle_client_read fd=%d, state=%d, has_pair=%d, bytes_available=%zu, bytes_read=%d", - conn->fd, conn->state, conn->pair ? 1 : 0, buffer_available_read(buf), bytes_read); - - // CRITICAL FIX: Always reset to reading headers if we're not actively forwarding - // This ensures internal routes are always checked first + // This function can be re-entered for keep-alive connections. + // Ensure state is correct if a connection was forwarding but lost its pair. if (conn->state == CLIENT_STATE_FORWARDING && conn->pair == NULL) { - log_info("[ROUTING-FIX] Resetting orphaned forwarding state to READING_HEADERS for fd=%d", conn->fd); conn->state = CLIENT_STATE_READING_HEADERS; } - // Don't process new requests while actively forwarding - if (conn->state == CLIENT_STATE_FORWARDING && conn->pair != NULL) { - log_info("[ROUTING-DEBUG] Skipping request processing - actively forwarding for fd=%d", conn->fd); + // Do not attempt to parse new requests if the connection is already actively forwarding. + if (conn->state == CLIENT_STATE_FORWARDING) { return; } - - // Process all complete requests in the buffer (for pipelining) + + // Process all complete HTTP requests currently in the read buffer (handles pipelining). while (buffer_available_read(buf) > 0) { char *data_start = buf->data + buf->head; size_t data_len = buffer_available_read(buf); - // Look for end of headers + // --- MODIFICATION START: Read until \r\n\r\n before parsing --- + + // Step 1: Find the end-of-headers marker ("\r\n\r\n"). char *headers_end = memmem(data_start, data_len, "\r\n\r\n", 4); + + // If the marker is not found, the full headers have not been received yet. if (!headers_end) { if (data_len >= MAX_HEADER_SIZE) { - send_error_response(conn, 413, "Request Header Too Large", "Header too large"); + send_error_response(conn, 413, "Request Header Too Large", "Header is too large."); return; } - break; // Incomplete headers, wait for more data + // Wait for more data to arrive from the client. + log_debug("fd %d: Incomplete headers, waiting for more data.", conn->fd); + break; } - - size_t headers_len = (headers_end - data_start) + 4; - // Parse the request + // Step 2: The complete header block is now in the buffer. Proceed to parsing. + size_t headers_len = (headers_end - data_start) + 4; int parse_result = parse_http_request(data_start, headers_len, &conn->request); - if (parse_result < 0) { - break; // Incomplete, need more data - } else if (parse_result == 0) { - send_error_response(conn, 400, "Bad Request", "Malformed HTTP request"); + + if (parse_result == 0) { + send_error_response(conn, 400, "Bad Request", "Malformed HTTP request."); return; } + // This case should be rare, but indicates the parser needs more data than just the headers. + if (parse_result < 0) { + break; + } - log_info("[ROUTING-DEBUG] Parsed request fd=%d: %s %s, Host: %s, Content-Length: %ld", - conn->fd, conn->request.method, conn->request.uri, conn->request.host, - conn->request.content_length); - - // Calculate total request size including body + // Step 3: Check if the entire request body (if any) has been received. long long body_len = (conn->request.content_length > 0) ? conn->request.content_length : 0; size_t total_request_len = headers_len + body_len; if (data_len < total_request_len) { - break; // Incomplete body, wait for more data + // Body is not fully received yet, wait for more data. + log_debug("fd %d: Incomplete body, waiting for more data.", conn->fd); + break; } + + // --- MODIFICATION END: A full request is now ready for processing --- // Start timing the request struct timespec ts; clock_gettime(CLOCK_MONOTONIC, &ts); conn->request_start_time = ts.tv_sec + ts.tv_nsec / 1e9; - - // CRITICAL: Always check internal routes first, regardless of connection state - log_info("[ROUTING-CHECK] Checking if request is internal: method=%s, uri=%s", - conn->request.method, conn->request.uri); - - if (strcmp(conn->request.method, "GET") == 0) { + + // Check for internal routes like /dashboard or /api/stats + if (strcmp(conn->request.method, "GET") == 0 && + (strncmp(conn->request.uri, "/dashboard", 10) == 0 || strncmp(conn->request.uri, "/api/stats", 10) == 0)) { + + log_info("[ROUTING-INTERNAL] Serving internal route %s for fd=%d", conn->request.uri, conn->fd); + + // If there was a previous upstream connection, close it. + if (conn->pair) { + close_connection(conn->pair->fd); + conn->pair = NULL; + } + + conn->state = CLIENT_STATE_SERVING_INTERNAL; if (strncmp(conn->request.uri, "/dashboard", 10) == 0) { - log_info("[ROUTING-INTERNAL] *** DASHBOARD REQUEST DETECTED *** fd=%d", conn->fd); - - // Clean up any existing upstream connection - if (conn->pair) { - log_debug("[ROUTING] Closing existing upstream fd=%d before serving internal", conn->pair->fd); - close_connection(conn->pair->fd); - conn->pair = NULL; - } - - conn->state = CLIENT_STATE_SERVING_INTERNAL; serve_dashboard(conn); - buffer_consume(buf, total_request_len); - if (!conn->request.keep_alive) { - conn->state = CLIENT_STATE_CLOSING; - return; - } - conn->state = CLIENT_STATE_READING_HEADERS; - continue; - } - if (strncmp(conn->request.uri, "/api/stats", 10) == 0) { - log_info("[ROUTING-INTERNAL] *** API STATS REQUEST DETECTED *** fd=%d", conn->fd); - - // Clean up any existing upstream connection - if (conn->pair) { - log_debug("[ROUTING] Closing existing upstream fd=%d before serving internal", conn->pair->fd); - close_connection(conn->pair->fd); - conn->pair = NULL; - } - - conn->state = CLIENT_STATE_SERVING_INTERNAL; + } else { serve_stats_api(conn); - buffer_consume(buf, total_request_len); - if (!conn->request.keep_alive) { - conn->state = CLIENT_STATE_CLOSING; - return; - } - conn->state = CLIENT_STATE_READING_HEADERS; - continue; } + + buffer_consume(buf, total_request_len); // Consume the processed request + + if (!conn->request.keep_alive) { + conn->state = CLIENT_STATE_CLOSING; + return; // Exit function, connection will be closed on write complete. + } + conn->state = CLIENT_STATE_READING_HEADERS; // Ready for next keep-alive request. + continue; // Continue loop to process next pipelined request. } - - // This is a request to be forwarded to upstream - log_info("[ROUTING-FORWARD] >>> FORWARDING TO UPSTREAM <<< fd=%d: %s %s", - conn->fd, conn->request.method, conn->request.uri); + + // If not an internal route, forward the request to the upstream server. + log_info("[ROUTING-FORWARD] Forwarding request for fd=%d: %s %s", conn->fd, conn->request.method, conn->request.uri); conn->vhost_stats = monitor_get_or_create_vhost_stats(conn->request.host); monitor_record_request_start(conn->vhost_stats, conn->request.is_websocket); conn->state = CLIENT_STATE_FORWARDING; - - // Create upstream connection and forward the complete request connect_to_upstream(conn, data_start, total_request_len); - // Consume the forwarded request from buffer - buffer_consume(buf, total_request_len); + buffer_consume(buf, total_request_len); // Consume the forwarded request - // After forwarding, exit the loop - the connection is now in forwarding mode - return; + // After starting to forward, stop processing further pipelined requests from this client + // until the current forwarding is complete. The state is now CLIENT_STATE_FORWARDING. + return; } }