diff --git a/rproxy.c b/rproxy.c index 05c6583..d876c42 100644 --- a/rproxy.c +++ b/rproxy.c @@ -2094,39 +2094,32 @@ void handle_client_read(connection_t *conn) { static void handle_forwarding(connection_t *conn) { connection_t *pair = conn->pair; if (!pair || pair->fd == -1) { - // If this is a client with no pair, reset it to reading headers + // This is an orphaned connection. If it's a client, reset it to read new requests. + // Otherwise, it's a dead upstream connection that should be closed. if (conn->type == CONN_TYPE_CLIENT) { log_info("[ROUTING-ORPHAN] Client fd=%d lost upstream, resetting to READING_HEADERS", conn->fd); conn->state = CLIENT_STATE_READING_HEADERS; conn->pair = NULL; - // Clear old request data to avoid state confusion - memset(&conn->request, 0, sizeof(http_request_t)); - conn->request.keep_alive = 1; // Default for HTTP/1.1 - conn->request.content_length = -1; - // Try to read any pending requests + // Process any data that was received while it was orphaned. if (buffer_available_read(&conn->read_buf) > 0) { - log_info("[ROUTING-ORPHAN] Processing buffered data on orphaned client fd=%d", conn->fd); handle_client_read(conn); } - return; + } else { + close_connection(conn->fd); } - close_connection(conn->fd); return; } - // Read from this connection + // 1. Read data from the source connection (either client or upstream). int bytes_read = do_read(conn); - // Forward any data we have + // 2. Forward any data we just read to the paired connection's write buffer. size_t data_to_forward = buffer_available_read(&conn->read_buf); if (data_to_forward > 0) { - size_t available_write = buffer_available_write(&pair->write_buf); - if (available_write < data_to_forward) { - if (buffer_ensure_capacity(&pair->write_buf, pair->write_buf.tail + data_to_forward) < 0) { - log_error("Failed to grow write buffer"); - close_connection(conn->fd); - return; - } + if (buffer_ensure_capacity(&pair->write_buf, pair->write_buf.tail + data_to_forward) < 0) { + log_error("Failed to grow write buffer for forwarding"); + close_connection(conn->fd); // Close the pair of connections on memory error. + return; } memcpy(pair->write_buf.data + pair->write_buf.tail, @@ -2135,57 +2128,47 @@ static void handle_forwarding(connection_t *conn) { pair->write_buf.tail += data_to_forward; buffer_consume(&conn->read_buf, data_to_forward); - // Enable write events on the pair + // Make sure the event loop knows the pair is ready to be written to. modify_epoll(pair->fd, EPOLLIN | EPOLLOUT); } - // Handle connection closure - if (bytes_read == 0) { - // EOF received - log_info("[ROUTING-EOF] EOF on fd=%d (type=%d), keep-alive=%d, is_websocket=%d", - conn->fd, conn->type, conn->request.keep_alive, conn->request.is_websocket); - - // For client connections with keep-alive, don't close immediately - if (conn->type == CONN_TYPE_CLIENT && conn->request.keep_alive && !conn->request.is_websocket) { - // Close the upstream connection - if (pair) { - log_info("[ROUTING-KEEPALIVE] Closing upstream fd=%d, keeping client fd=%d alive", pair->fd, conn->fd); - close_connection(pair->fd); - conn->pair = NULL; - } - // Reset client to reading headers for next request - conn->state = CLIENT_STATE_READING_HEADERS; - conn->half_closed = 0; - conn->write_shutdown = 0; - // Clear request data - memset(&conn->request, 0, sizeof(http_request_t)); - conn->request.keep_alive = 1; // Default for HTTP/1.1 - conn->request.content_length = -1; - - // CRITICAL FIX: Process any buffered data (pipelined requests) - if (buffer_available_read(&conn->read_buf) > 0) { - log_info("[ROUTING-PIPELINE] Processing buffered pipelined request on fd=%d (%zu bytes)", - conn->fd, buffer_available_read(&conn->read_buf)); - handle_client_read(conn); - } + // 3. Handle connection state changes (EOF or errors). + if (bytes_read <= 0 && (errno != EAGAIN && errno != EWOULDBLOCK)) { + if (bytes_read < 0) { // A real read error occurred. + log_debug("Read error on fd=%d while forwarding. Closing connection.", conn->fd); + close_connection(conn->fd); // This will recursively close the pair. return; } + + // --- ROBUST FIX FOR KEEP-ALIVE RACE CONDITION --- + // At this point, bytes_read is 0, meaning one side has closed its connection (EOF). - // Regular half-close for non-keep-alive connections - if (!conn->half_closed) { - conn->half_closed = 1; - shutdown(conn->fd, SHUT_RD); - if (pair && !pair->write_shutdown) { - shutdown(pair->fd, SHUT_WR); - pair->write_shutdown = 1; - } - // If both sides are closed, close the connection - if (pair && pair->half_closed) { - close_connection(conn->fd); + log_info("[ROUTING-EOF] EOF on fd=%d (type=%s)", conn->fd, + (conn->type == CONN_TYPE_CLIENT) ? "CLIENT" : "UPSTREAM"); + + if (conn->type == CONN_TYPE_UPSTREAM) { + // **This is the critical fix.** The upstream server finished its response. + // The client connection must be immediately reset to handle the next keep-alive request. + if (pair->type == CONN_TYPE_CLIENT) { + log_info("[ROUTING-KEEPALIVE] Upstream fd=%d finished. Resetting client fd=%d.", conn->fd, pair->fd); + + // Reset client state to stop forwarding and prepare for new headers. + pair->state = CLIENT_STATE_READING_HEADERS; + pair->pair = NULL; // Decouple from this now-finished upstream connection. + + // Check for pipelined requests that may have already arrived. + if (buffer_available_read(&pair->read_buf) > 0) { + handle_client_read(pair); + } } + // The upstream connection's job is done. Close it, leaving the client open. + close_connection(conn->fd); + + } else if (conn->type == CONN_TYPE_CLIENT) { + // The client has closed its side of the connection. + // In this case, we perform a full teardown of both connections. + close_connection(conn->fd); } - } else if (bytes_read < 0 && errno != EAGAIN && errno != EWOULDBLOCK) { - close_connection(conn->fd); } }