Update, bug fixed.
This commit is contained in:
parent
d60dea1c44
commit
59d075d8e0
1
.gitignore
vendored
1
.gitignore
vendored
@ -2,4 +2,5 @@
|
|||||||
*.bak
|
*.bak
|
||||||
*.bak2
|
*.bak2
|
||||||
*.db
|
*.db
|
||||||
|
*.txt
|
||||||
rproxy
|
rproxy
|
||||||
|
|||||||
99
rproxy.c
99
rproxy.c
@ -1976,12 +1976,29 @@ static int do_write(connection_t *conn) {
|
|||||||
void handle_client_read(connection_t *conn) {
|
void handle_client_read(connection_t *conn) {
|
||||||
int bytes_read = do_read(conn);
|
int bytes_read = do_read(conn);
|
||||||
if (bytes_read < 0 && errno != EAGAIN && errno != EWOULDBLOCK) {
|
if (bytes_read < 0 && errno != EAGAIN && errno != EWOULDBLOCK) {
|
||||||
|
log_debug("[ROUTING] Closing connection fd=%d due to read error", conn->fd);
|
||||||
close_connection(conn->fd);
|
close_connection(conn->fd);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
buffer_t *buf = &conn->read_buf;
|
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
|
||||||
|
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);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// Process all complete requests in the buffer (for pipelining)
|
// Process all complete requests in the buffer (for pipelining)
|
||||||
while (buffer_available_read(buf) > 0) {
|
while (buffer_available_read(buf) > 0) {
|
||||||
char *data_start = buf->data + buf->head;
|
char *data_start = buf->data + buf->head;
|
||||||
@ -2008,6 +2025,10 @@ void handle_client_read(connection_t *conn) {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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
|
// Calculate total request size including body
|
||||||
long long body_len = (conn->request.content_length > 0) ? conn->request.content_length : 0;
|
long long body_len = (conn->request.content_length > 0) ? conn->request.content_length : 0;
|
||||||
size_t total_request_len = headers_len + body_len;
|
size_t total_request_len = headers_len + body_len;
|
||||||
@ -2021,9 +2042,21 @@ void handle_client_read(connection_t *conn) {
|
|||||||
clock_gettime(CLOCK_MONOTONIC, &ts);
|
clock_gettime(CLOCK_MONOTONIC, &ts);
|
||||||
conn->request_start_time = ts.tv_sec + ts.tv_nsec / 1e9;
|
conn->request_start_time = ts.tv_sec + ts.tv_nsec / 1e9;
|
||||||
|
|
||||||
// Handle internal routes
|
// 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) {
|
if (strcmp(conn->request.method, "GET") == 0) {
|
||||||
if (strncmp(conn->request.uri, "/dashboard", 10) == 0) {
|
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;
|
conn->state = CLIENT_STATE_SERVING_INTERNAL;
|
||||||
serve_dashboard(conn);
|
serve_dashboard(conn);
|
||||||
buffer_consume(buf, total_request_len);
|
buffer_consume(buf, total_request_len);
|
||||||
@ -2035,6 +2068,15 @@ void handle_client_read(connection_t *conn) {
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
if (strncmp(conn->request.uri, "/api/stats", 10) == 0) {
|
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;
|
conn->state = CLIENT_STATE_SERVING_INTERNAL;
|
||||||
serve_stats_api(conn);
|
serve_stats_api(conn);
|
||||||
buffer_consume(buf, total_request_len);
|
buffer_consume(buf, total_request_len);
|
||||||
@ -2048,6 +2090,9 @@ void handle_client_read(connection_t *conn) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// This is a request to be forwarded to upstream
|
// 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);
|
||||||
|
|
||||||
conn->vhost_stats = monitor_get_or_create_vhost_stats(conn->request.host);
|
conn->vhost_stats = monitor_get_or_create_vhost_stats(conn->request.host);
|
||||||
monitor_record_request_start(conn->vhost_stats, conn->request.is_websocket);
|
monitor_record_request_start(conn->vhost_stats, conn->request.is_websocket);
|
||||||
|
|
||||||
@ -2067,6 +2112,22 @@ void handle_client_read(connection_t *conn) {
|
|||||||
static void handle_forwarding(connection_t *conn) {
|
static void handle_forwarding(connection_t *conn) {
|
||||||
connection_t *pair = conn->pair;
|
connection_t *pair = conn->pair;
|
||||||
if (!pair || pair->fd == -1) {
|
if (!pair || pair->fd == -1) {
|
||||||
|
// If this is a client with no pair, reset it to reading headers
|
||||||
|
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
|
||||||
|
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;
|
||||||
|
}
|
||||||
close_connection(conn->fd);
|
close_connection(conn->fd);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -2086,8 +2147,8 @@ static void handle_forwarding(connection_t *conn) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
memcpy(pair->write_buf.data + pair->write_buf.tail,
|
memcpy(pair->write_buf.data + pair->write_buf.tail,
|
||||||
conn->read_buf.data + conn->read_buf.head,
|
conn->read_buf.data + conn->read_buf.head,
|
||||||
data_to_forward);
|
data_to_forward);
|
||||||
pair->write_buf.tail += data_to_forward;
|
pair->write_buf.tail += data_to_forward;
|
||||||
buffer_consume(&conn->read_buf, data_to_forward);
|
buffer_consume(&conn->read_buf, data_to_forward);
|
||||||
@ -2098,7 +2159,37 @@ static void handle_forwarding(connection_t *conn) {
|
|||||||
|
|
||||||
// Handle connection closure
|
// Handle connection closure
|
||||||
if (bytes_read == 0) {
|
if (bytes_read == 0) {
|
||||||
// EOF received - perform half-close
|
// 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);
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Regular half-close for non-keep-alive connections
|
||||||
if (!conn->half_closed) {
|
if (!conn->half_closed) {
|
||||||
conn->half_closed = 1;
|
conn->half_closed = 1;
|
||||||
shutdown(conn->fd, SHUT_RD);
|
shutdown(conn->fd, SHUT_RD);
|
||||||
|
|||||||
207
test_routing.sh
Executable file
207
test_routing.sh
Executable file
@ -0,0 +1,207 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
# Test script for verifying proxy routing behavior
|
||||||
|
# Ensures /dashboard and /api/stats are always handled internally
|
||||||
|
|
||||||
|
PORT=8080
|
||||||
|
TEST_UPSTREAM_PORT=3000
|
||||||
|
|
||||||
|
echo "=== Proxy Routing Test Suite ==="
|
||||||
|
echo "================================"
|
||||||
|
|
||||||
|
# Start a simple upstream server that logs all requests
|
||||||
|
echo "Starting test upstream server on port $TEST_UPSTREAM_PORT..."
|
||||||
|
cat > test_upstream.py << 'EOF'
|
||||||
|
from http.server import HTTPServer, BaseHTTPRequestHandler
|
||||||
|
import json
|
||||||
|
import datetime
|
||||||
|
|
||||||
|
class TestHandler(BaseHTTPRequestHandler):
|
||||||
|
def do_GET(self):
|
||||||
|
print(f"[{datetime.datetime.now()}] UPSTREAM RECEIVED: {self.command} {self.path}")
|
||||||
|
if self.path == "/dashboard" or self.path == "/api/stats":
|
||||||
|
print("ERROR: Internal route leaked to upstream!")
|
||||||
|
self.send_response(200)
|
||||||
|
self.send_header('Content-Type', 'application/json')
|
||||||
|
self.end_headers()
|
||||||
|
response = {"upstream": "response", "path": self.path}
|
||||||
|
self.wfile.write(json.dumps(response).encode())
|
||||||
|
|
||||||
|
def do_POST(self):
|
||||||
|
self.do_GET()
|
||||||
|
|
||||||
|
def log_message(self, format, *args):
|
||||||
|
return # Suppress default logging
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
server = HTTPServer(('localhost', 3000), TestHandler)
|
||||||
|
print("Test upstream server running on port 3000")
|
||||||
|
server.serve_forever()
|
||||||
|
EOF
|
||||||
|
|
||||||
|
python3 test_upstream.py &
|
||||||
|
UPSTREAM_PID=$!
|
||||||
|
sleep 2
|
||||||
|
|
||||||
|
# Start the proxy with debug mode
|
||||||
|
echo "Starting proxy server..."
|
||||||
|
DEBUG=1 ./rproxy proxy_config.json &
|
||||||
|
PROXY_PID=$!
|
||||||
|
sleep 3
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
echo "=== Test 1: Direct dashboard access ==="
|
||||||
|
echo "Request: GET /dashboard"
|
||||||
|
response=$(curl -s -o /dev/null -w "%{http_code}" http://localhost:$PORT/dashboard)
|
||||||
|
if [ "$response" = "200" ]; then
|
||||||
|
echo "✓ Dashboard served correctly (HTTP $response)"
|
||||||
|
else
|
||||||
|
echo "✗ Dashboard failed (HTTP $response)"
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
echo "=== Test 2: Direct API stats access ==="
|
||||||
|
echo "Request: GET /api/stats"
|
||||||
|
response=$(curl -s http://localhost:$PORT/api/stats | jq -r '.current' 2>/dev/null)
|
||||||
|
if [ ! -z "$response" ]; then
|
||||||
|
echo "✓ API stats served correctly"
|
||||||
|
else
|
||||||
|
echo "✗ API stats failed"
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
echo "=== Test 3: Multiple requests on same connection (keep-alive) ==="
|
||||||
|
echo "Testing pipeline: /api/stats -> /test -> /dashboard -> /another"
|
||||||
|
cat > test_requests.txt << 'EOF'
|
||||||
|
GET /api/stats HTTP/1.1
|
||||||
|
Host: localhost
|
||||||
|
Connection: keep-alive
|
||||||
|
|
||||||
|
GET /test HTTP/1.1
|
||||||
|
Host: localhost
|
||||||
|
Connection: keep-alive
|
||||||
|
|
||||||
|
GET /dashboard HTTP/1.1
|
||||||
|
Host: localhost
|
||||||
|
Connection: keep-alive
|
||||||
|
|
||||||
|
GET /another HTTP/1.1
|
||||||
|
Host: localhost
|
||||||
|
Connection: close
|
||||||
|
|
||||||
|
EOF
|
||||||
|
|
||||||
|
# Send pipelined requests
|
||||||
|
(cat test_requests.txt; sleep 1) | nc localhost $PORT > test_response.txt 2>&1
|
||||||
|
|
||||||
|
# Check if internal routes were handled correctly
|
||||||
|
if grep -q "cpu_percent" test_response.txt && grep -q "Reverse Proxy Monitor" test_response.txt; then
|
||||||
|
echo "✓ Internal routes handled correctly in pipeline"
|
||||||
|
else
|
||||||
|
echo "✗ Internal routes not handled correctly in pipeline"
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
echo "=== Test 4: Concurrent connections ==="
|
||||||
|
echo "Sending 10 concurrent requests to different endpoints..."
|
||||||
|
|
||||||
|
# Background requests
|
||||||
|
for i in {1..3}; do
|
||||||
|
curl -s http://localhost:$PORT/dashboard > /dev/null &
|
||||||
|
done
|
||||||
|
for i in {1..3}; do
|
||||||
|
curl -s http://localhost:$PORT/api/stats > /dev/null &
|
||||||
|
done
|
||||||
|
for i in {1..4}; do
|
||||||
|
curl -s http://localhost:$PORT/test$i > /dev/null &
|
||||||
|
done
|
||||||
|
|
||||||
|
wait
|
||||||
|
|
||||||
|
echo "✓ Concurrent requests completed"
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
echo "=== Test 5: Rapid sequential requests ==="
|
||||||
|
echo "Sending rapid requests to verify state management..."
|
||||||
|
|
||||||
|
success=0
|
||||||
|
failed=0
|
||||||
|
|
||||||
|
for i in {1..20}; do
|
||||||
|
# Alternate between internal and forwarded routes
|
||||||
|
if [ $((i % 3)) -eq 0 ]; then
|
||||||
|
response=$(curl -s -o /dev/null -w "%{http_code}" http://localhost:$PORT/dashboard)
|
||||||
|
if [ "$response" = "200" ]; then
|
||||||
|
((success++))
|
||||||
|
else
|
||||||
|
((failed++))
|
||||||
|
echo " ✗ Dashboard request $i failed (HTTP $response)"
|
||||||
|
fi
|
||||||
|
elif [ $((i % 3)) -eq 1 ]; then
|
||||||
|
response=$(curl -s -o /dev/null -w "%{http_code}" http://localhost:$PORT/api/stats)
|
||||||
|
if [ "$response" = "200" ]; then
|
||||||
|
((success++))
|
||||||
|
else
|
||||||
|
((failed++))
|
||||||
|
echo " ✗ API stats request $i failed (HTTP $response)"
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
response=$(curl -s -o /dev/null -w "%{http_code}" http://localhost:$PORT/test$i)
|
||||||
|
if [ "$response" = "200" ]; then
|
||||||
|
((success++))
|
||||||
|
else
|
||||||
|
((failed++))
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
echo "Results: $success successful, $failed failed"
|
||||||
|
if [ $failed -eq 0 ]; then
|
||||||
|
echo "✓ All rapid requests handled correctly"
|
||||||
|
else
|
||||||
|
echo "✗ Some requests failed"
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
echo "=== Test 6: WebSocket upgrade after regular request ==="
|
||||||
|
echo "Testing state reset after WebSocket..."
|
||||||
|
|
||||||
|
# First a normal request
|
||||||
|
curl -s http://localhost:$PORT/api/stats > /dev/null
|
||||||
|
echo "✓ Normal request sent"
|
||||||
|
|
||||||
|
# Then attempt WebSocket (will fail but tests state handling)
|
||||||
|
curl -s -H "Upgrade: websocket" -H "Connection: upgrade" http://localhost:$PORT/ws 2>/dev/null
|
||||||
|
echo "✓ WebSocket attempt sent"
|
||||||
|
|
||||||
|
# Then another normal request to internal route
|
||||||
|
response=$(curl -s -o /dev/null -w "%{http_code}" http://localhost:$PORT/dashboard)
|
||||||
|
if [ "$response" = "200" ]; then
|
||||||
|
echo "✓ Dashboard works after WebSocket attempt"
|
||||||
|
else
|
||||||
|
echo "✗ Dashboard failed after WebSocket attempt (HTTP $response)"
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
echo "=== Checking upstream server logs ==="
|
||||||
|
echo "Verifying no internal routes leaked to upstream..."
|
||||||
|
sleep 1
|
||||||
|
|
||||||
|
# The upstream server prints errors if it receives internal routes
|
||||||
|
# Check proxy debug logs for routing decisions
|
||||||
|
echo ""
|
||||||
|
echo "Recent routing decisions from proxy logs:"
|
||||||
|
echo "(Last 10 routing log entries)"
|
||||||
|
echo "----------------------------------------"
|
||||||
|
|
||||||
|
# Kill the servers
|
||||||
|
kill $PROXY_PID 2>/dev/null
|
||||||
|
kill $UPSTREAM_PID 2>/dev/null
|
||||||
|
|
||||||
|
# Clean up
|
||||||
|
rm -f test_upstream.py test_requests.txt test_response.txt
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
echo "=== Test Complete ==="
|
||||||
|
echo "If you see any 'ERROR: Internal route leaked to upstream!' messages above,"
|
||||||
|
echo "the routing issue is NOT fixed. Otherwise, the fix is working correctly."
|
||||||
185
test_routing_comprehensive.sh
Executable file
185
test_routing_comprehensive.sh
Executable file
@ -0,0 +1,185 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
# Comprehensive test script for routing reliability
|
||||||
|
|
||||||
|
set -e
|
||||||
|
|
||||||
|
echo "=== Comprehensive Routing Test ==="
|
||||||
|
echo "This test verifies that /dashboard and /api/stats are ALWAYS handled internally"
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
# Kill any existing proxy
|
||||||
|
pkill -f rproxy || true
|
||||||
|
sleep 1
|
||||||
|
|
||||||
|
# Start the proxy with debug output
|
||||||
|
echo "Starting proxy with debug logging..."
|
||||||
|
DEBUG=1 ./rproxy > proxy.log 2>&1 &
|
||||||
|
PROXY_PID=$!
|
||||||
|
sleep 2
|
||||||
|
|
||||||
|
# Check if proxy is running
|
||||||
|
if ! ps -p $PROXY_PID > /dev/null; then
|
||||||
|
echo "ERROR: Proxy failed to start"
|
||||||
|
cat proxy.log
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "Proxy started with PID: $PROXY_PID"
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
# Function to test a URL and check response
|
||||||
|
test_url() {
|
||||||
|
local url=$1
|
||||||
|
local expected_pattern=$2
|
||||||
|
local test_name=$3
|
||||||
|
|
||||||
|
echo "Testing: $test_name"
|
||||||
|
response=$(curl -s -w "\nHTTP_CODE:%{http_code}" "$url" 2>/dev/null || true)
|
||||||
|
http_code=$(echo "$response" | grep "HTTP_CODE:" | cut -d: -f2)
|
||||||
|
body=$(echo "$response" | grep -v "HTTP_CODE:")
|
||||||
|
|
||||||
|
if [ "$http_code" = "200" ]; then
|
||||||
|
if echo "$body" | grep -q "$expected_pattern"; then
|
||||||
|
echo " ✓ Success: Got expected response"
|
||||||
|
return 0
|
||||||
|
else
|
||||||
|
echo " ✗ FAIL: Got 200 but wrong content"
|
||||||
|
echo " Expected pattern: $expected_pattern"
|
||||||
|
echo " Got: ${body:0:100}..."
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
echo " ✗ FAIL: Got HTTP $http_code instead of 200"
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
# Run tests
|
||||||
|
echo "=== Test 1: Basic Dashboard Access ==="
|
||||||
|
test_url "http://localhost:8888/dashboard" "Reverse Proxy Monitor" "Dashboard"
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
echo "=== Test 2: Basic API Stats Access ==="
|
||||||
|
test_url "http://localhost:8888/api/stats" "cpu_percent" "API Stats"
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
echo "=== Test 3: Multiple Sequential Dashboard Requests ==="
|
||||||
|
for i in {1..5}; do
|
||||||
|
test_url "http://localhost:8888/dashboard" "Reverse Proxy Monitor" "Dashboard request $i"
|
||||||
|
done
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
echo "=== Test 4: Multiple Sequential API Requests ==="
|
||||||
|
for i in {1..5}; do
|
||||||
|
test_url "http://localhost:8888/api/stats" "cpu_percent" "API request $i"
|
||||||
|
done
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
echo "=== Test 5: Mixed Dashboard and API Requests ==="
|
||||||
|
for i in {1..3}; do
|
||||||
|
test_url "http://localhost:8888/dashboard" "Reverse Proxy Monitor" "Dashboard request $i"
|
||||||
|
test_url "http://localhost:8888/api/stats" "cpu_percent" "API request $i"
|
||||||
|
done
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
echo "=== Test 6: Concurrent Dashboard Requests ==="
|
||||||
|
echo "Sending 10 concurrent dashboard requests..."
|
||||||
|
for i in {1..10}; do
|
||||||
|
(
|
||||||
|
response=$(curl -s -o /dev/null -w "%{http_code}" "http://localhost:8888/dashboard" 2>/dev/null)
|
||||||
|
if [ "$response" = "200" ]; then
|
||||||
|
echo " Request $i: ✓ 200 OK"
|
||||||
|
else
|
||||||
|
echo " Request $i: ✗ Got $response"
|
||||||
|
fi
|
||||||
|
) &
|
||||||
|
done
|
||||||
|
wait
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
echo "=== Test 7: Concurrent API Requests ==="
|
||||||
|
echo "Sending 10 concurrent API requests..."
|
||||||
|
for i in {1..10}; do
|
||||||
|
(
|
||||||
|
response=$(curl -s -o /dev/null -w "%{http_code}" "http://localhost:8888/api/stats" 2>/dev/null)
|
||||||
|
if [ "$response" = "200" ]; then
|
||||||
|
echo " Request $i: ✓ 200 OK"
|
||||||
|
else
|
||||||
|
echo " Request $i: ✗ Got $response"
|
||||||
|
fi
|
||||||
|
) &
|
||||||
|
done
|
||||||
|
wait
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
echo "=== Test 8: Pipelined Requests (Keep-Alive) ==="
|
||||||
|
echo "Testing pipelined requests with keep-alive..."
|
||||||
|
(echo -ne "GET /dashboard HTTP/1.1\r\nHost: localhost\r\n\r\nGET /api/stats HTTP/1.1\r\nHost: localhost\r\n\r\n" | nc localhost 8888 | grep -E "(Reverse Proxy Dashboard|vhost_stats)" | wc -l) > pipeline_result.txt 2>&1
|
||||||
|
pipeline_count=$(cat pipeline_result.txt)
|
||||||
|
if [ "$pipeline_count" = "2" ]; then
|
||||||
|
echo " ✓ Both pipelined requests were handled correctly"
|
||||||
|
else
|
||||||
|
echo " ✗ FAIL: Only $pipeline_count/2 pipelined requests handled correctly"
|
||||||
|
fi
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
echo "=== Test 9: Rapid Fire Mixed Requests ==="
|
||||||
|
echo "Sending 50 rapid requests mixing dashboard, API, and proxy requests..."
|
||||||
|
success=0
|
||||||
|
fail=0
|
||||||
|
for i in {1..50}; do
|
||||||
|
case $((i % 3)) in
|
||||||
|
0) url="http://localhost:8888/dashboard" ;;
|
||||||
|
1) url="http://localhost:8888/api/stats" ;;
|
||||||
|
2) url="http://localhost:8888/" ;; # This should be proxied
|
||||||
|
esac
|
||||||
|
|
||||||
|
response=$(curl -s -o /dev/null -w "%{http_code}" "$url" 2>/dev/null || echo "000")
|
||||||
|
if [ "$response" = "200" ] || [ "$response" = "502" ]; then
|
||||||
|
((success++))
|
||||||
|
else
|
||||||
|
((fail++))
|
||||||
|
echo " Request $i to $url failed with code: $response"
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
echo " Results: $success successful, $fail failed"
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
echo "=== Test 10: Check Routing Log Patterns ==="
|
||||||
|
echo "Analyzing proxy log for routing decisions..."
|
||||||
|
dashboard_internal=$(grep -c "ROUTING-INTERNAL.*DASHBOARD" proxy.log || true)
|
||||||
|
api_internal=$(grep -c "ROUTING-INTERNAL.*API STATS" proxy.log || true)
|
||||||
|
dashboard_forward=$(grep -c "ROUTING-FORWARD.*dashboard" proxy.log || true)
|
||||||
|
api_forward=$(grep -c "ROUTING-FORWARD.*api/stats" proxy.log || true)
|
||||||
|
|
||||||
|
echo " Dashboard handled internally: $dashboard_internal times"
|
||||||
|
echo " API stats handled internally: $api_internal times"
|
||||||
|
echo " Dashboard incorrectly forwarded: $dashboard_forward times"
|
||||||
|
echo " API stats incorrectly forwarded: $api_forward times"
|
||||||
|
|
||||||
|
if [ "$dashboard_forward" -gt 0 ] || [ "$api_forward" -gt 0 ]; then
|
||||||
|
echo " ✗ FAIL: Internal routes were incorrectly forwarded!"
|
||||||
|
echo ""
|
||||||
|
echo "=== Showing incorrect forwarding logs ==="
|
||||||
|
grep "ROUTING-FORWARD.*dashboard\|ROUTING-FORWARD.*api/stats" proxy.log | head -10 || true
|
||||||
|
else
|
||||||
|
echo " ✓ Success: All internal routes handled correctly"
|
||||||
|
fi
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
echo "=== Cleanup ==="
|
||||||
|
kill $PROXY_PID 2>/dev/null || true
|
||||||
|
wait $PROXY_PID 2>/dev/null || true
|
||||||
|
echo "Proxy stopped"
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
echo "=== Test Summary ==="
|
||||||
|
if [ "$dashboard_forward" -gt 0 ] || [ "$api_forward" -gt 0 ]; then
|
||||||
|
echo "❌ TESTS FAILED: Internal routes were incorrectly forwarded"
|
||||||
|
echo " Check proxy.log for details"
|
||||||
|
exit 1
|
||||||
|
else
|
||||||
|
echo "✅ ALL TESTS PASSED: Internal routes are reliably handled"
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
115
test_routing_fix.sh
Executable file
115
test_routing_fix.sh
Executable file
@ -0,0 +1,115 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
# Focused test for routing fix verification
|
||||||
|
|
||||||
|
set -e
|
||||||
|
|
||||||
|
echo "=== Routing Fix Verification Test ==="
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
# Kill any existing proxy
|
||||||
|
pkill -f rproxy || true
|
||||||
|
sleep 1
|
||||||
|
|
||||||
|
# Start proxy with debug logging
|
||||||
|
echo "Starting proxy with debug logging..."
|
||||||
|
DEBUG=1 ./rproxy > proxy_test.log 2>&1 &
|
||||||
|
PROXY_PID=$!
|
||||||
|
sleep 2
|
||||||
|
|
||||||
|
# Check if proxy is running
|
||||||
|
if ! ps -p $PROXY_PID > /dev/null; then
|
||||||
|
echo "ERROR: Proxy failed to start"
|
||||||
|
cat proxy_test.log
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "Proxy started with PID: $PROXY_PID"
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
# Test function
|
||||||
|
test_endpoint() {
|
||||||
|
local endpoint=$1
|
||||||
|
local name=$2
|
||||||
|
|
||||||
|
echo "Testing $name..."
|
||||||
|
response=$(curl -s -o /dev/null -w "%{http_code}" "http://localhost:8888$endpoint" 2>/dev/null)
|
||||||
|
if [ "$response" = "200" ]; then
|
||||||
|
echo " ✓ Got 200 OK"
|
||||||
|
return 0
|
||||||
|
else
|
||||||
|
echo " ✗ Got HTTP $response"
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
# Run 20 rapid tests alternating between dashboard and API
|
||||||
|
echo "=== Rapid Alternating Tests (20 requests) ==="
|
||||||
|
success=0
|
||||||
|
fail=0
|
||||||
|
|
||||||
|
for i in {1..10}; do
|
||||||
|
# Test dashboard
|
||||||
|
if test_endpoint "/dashboard" "Dashboard #$i"; then
|
||||||
|
((success++))
|
||||||
|
else
|
||||||
|
((fail++))
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Test API stats
|
||||||
|
if test_endpoint "/api/stats" "API Stats #$i"; then
|
||||||
|
((success++))
|
||||||
|
else
|
||||||
|
((fail++))
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
echo "Results: $success/20 successful"
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
# Test pipelined requests
|
||||||
|
echo "=== Testing Pipelined Requests ==="
|
||||||
|
echo "Sending 2 pipelined requests..."
|
||||||
|
response=$(echo -ne "GET /dashboard HTTP/1.1\r\nHost: localhost\r\nConnection: keep-alive\r\n\r\nGET /api/stats HTTP/1.1\r\nHost: localhost\r\nConnection: close\r\n\r\n" | nc localhost 8888 2>/dev/null | grep -c "HTTP/1.1 200 OK" || echo "0")
|
||||||
|
|
||||||
|
if [ "$response" = "2" ]; then
|
||||||
|
echo " ✓ Both pipelined requests handled correctly"
|
||||||
|
else
|
||||||
|
echo " ✗ Only $response/2 pipelined requests handled"
|
||||||
|
fi
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
# Check logs for incorrect forwarding
|
||||||
|
echo "=== Checking Logs for Routing Issues ==="
|
||||||
|
dashboard_internal=$(grep -c "ROUTING-INTERNAL.*DASHBOARD" proxy_test.log || echo "0")
|
||||||
|
api_internal=$(grep -c "ROUTING-INTERNAL.*API STATS" proxy_test.log || echo "0")
|
||||||
|
dashboard_forward=$(grep -c "ROUTING-FORWARD.*dashboard" proxy_test.log || echo "0")
|
||||||
|
api_forward=$(grep -c "ROUTING-FORWARD.*api/stats" proxy_test.log || echo "0")
|
||||||
|
|
||||||
|
echo "Dashboard handled internally: $dashboard_internal times"
|
||||||
|
echo "API stats handled internally: $api_internal times"
|
||||||
|
echo "Dashboard forwarded (should be 0): $dashboard_forward times"
|
||||||
|
echo "API stats forwarded (should be 0): $api_forward times"
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
# Final verdict
|
||||||
|
if [ "$dashboard_forward" -eq 0 ] && [ "$api_forward" -eq 0 ] && [ "$fail" -eq 0 ]; then
|
||||||
|
echo "✅ SUCCESS: All internal routes handled correctly!"
|
||||||
|
result=0
|
||||||
|
else
|
||||||
|
echo "❌ FAILURE: Some requests were misrouted"
|
||||||
|
echo ""
|
||||||
|
echo "Sample of routing logs:"
|
||||||
|
grep "ROUTING-" proxy_test.log | tail -20
|
||||||
|
result=1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Cleanup
|
||||||
|
echo ""
|
||||||
|
echo "Cleaning up..."
|
||||||
|
kill $PROXY_PID 2>/dev/null || true
|
||||||
|
wait $PROXY_PID 2>/dev/null || true
|
||||||
|
echo "Done."
|
||||||
|
|
||||||
|
exit $result
|
||||||
25
test_upstream.py
Normal file
25
test_upstream.py
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
from http.server import HTTPServer, BaseHTTPRequestHandler
|
||||||
|
import json
|
||||||
|
import datetime
|
||||||
|
|
||||||
|
class TestHandler(BaseHTTPRequestHandler):
|
||||||
|
def do_GET(self):
|
||||||
|
print(f"[{datetime.datetime.now()}] UPSTREAM RECEIVED: {self.command} {self.path}")
|
||||||
|
if self.path == "/dashboard" or self.path == "/api/stats":
|
||||||
|
print("ERROR: Internal route leaked to upstream!")
|
||||||
|
self.send_response(200)
|
||||||
|
self.send_header('Content-Type', 'application/json')
|
||||||
|
self.end_headers()
|
||||||
|
response = {"upstream": "response", "path": self.path}
|
||||||
|
self.wfile.write(json.dumps(response).encode())
|
||||||
|
|
||||||
|
def do_POST(self):
|
||||||
|
self.do_GET()
|
||||||
|
|
||||||
|
def log_message(self, format, *args):
|
||||||
|
return # Suppress default logging
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
server = HTTPServer(('localhost', 3000), TestHandler)
|
||||||
|
print("Test upstream server running on port 3000")
|
||||||
|
server.serve_forever()
|
||||||
86
verify_routing.sh
Executable file
86
verify_routing.sh
Executable file
@ -0,0 +1,86 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
# Simple routing verification test
|
||||||
|
|
||||||
|
echo "=== Routing Verification Test ==="
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
# Kill any existing proxy
|
||||||
|
pkill -f rproxy 2>/dev/null
|
||||||
|
sleep 1
|
||||||
|
|
||||||
|
# Start proxy
|
||||||
|
echo "Starting proxy..."
|
||||||
|
DEBUG=1 ./rproxy > routing_verify.log 2>&1 &
|
||||||
|
PROXY_PID=$!
|
||||||
|
sleep 2
|
||||||
|
|
||||||
|
if ! ps -p $PROXY_PID > /dev/null; then
|
||||||
|
echo "ERROR: Proxy failed to start"
|
||||||
|
cat routing_verify.log
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "Proxy started (PID: $PROXY_PID)"
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
# Test internal routes
|
||||||
|
echo "Testing internal route handling:"
|
||||||
|
|
||||||
|
# Test dashboard 5 times
|
||||||
|
echo -n " Dashboard: "
|
||||||
|
for i in {1..5}; do
|
||||||
|
response=$(curl -s -o /dev/null -w "%{http_code}" http://localhost:8888/dashboard 2>/dev/null)
|
||||||
|
if [ "$response" = "200" ]; then
|
||||||
|
echo -n "✓"
|
||||||
|
else
|
||||||
|
echo -n "✗($response)"
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
# Test API stats 5 times
|
||||||
|
echo -n " API Stats: "
|
||||||
|
for i in {1..5}; do
|
||||||
|
response=$(curl -s -o /dev/null -w "%{http_code}" http://localhost:8888/api/stats 2>/dev/null)
|
||||||
|
if [ "$response" = "200" ]; then
|
||||||
|
echo -n "✓"
|
||||||
|
else
|
||||||
|
echo -n "✗($response)"
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
echo ""
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
# Check routing decisions in log
|
||||||
|
echo "Analyzing routing decisions:"
|
||||||
|
dashboard_internal=$(grep "ROUTING-INTERNAL.*DASHBOARD" routing_verify.log 2>/dev/null | wc -l)
|
||||||
|
api_internal=$(grep "ROUTING-INTERNAL.*API STATS" routing_verify.log 2>/dev/null | wc -l)
|
||||||
|
dashboard_forward=$(grep "ROUTING-FORWARD.*dashboard" routing_verify.log 2>/dev/null | wc -l)
|
||||||
|
api_forward=$(grep "ROUTING-FORWARD.*api/stats" routing_verify.log 2>/dev/null | wc -l)
|
||||||
|
|
||||||
|
echo " ✓ Dashboard handled internally: $dashboard_internal times"
|
||||||
|
echo " ✓ API stats handled internally: $api_internal times"
|
||||||
|
|
||||||
|
if [ "$dashboard_forward" -gt 0 ]; then
|
||||||
|
echo " ✗ Dashboard incorrectly forwarded: $dashboard_forward times"
|
||||||
|
fi
|
||||||
|
if [ "$api_forward" -gt 0 ]; then
|
||||||
|
echo " ✗ API stats incorrectly forwarded: $api_forward times"
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
# Cleanup
|
||||||
|
kill $PROXY_PID 2>/dev/null
|
||||||
|
wait $PROXY_PID 2>/dev/null
|
||||||
|
|
||||||
|
# Verdict
|
||||||
|
if [ "$dashboard_forward" -eq 0 ] && [ "$api_forward" -eq 0 ] && [ "$dashboard_internal" -gt 0 ] && [ "$api_internal" -gt 0 ]; then
|
||||||
|
echo "✅ SUCCESS: All internal routes are properly handled!"
|
||||||
|
echo " /dashboard and /api/stats are ALWAYS served internally, never forwarded."
|
||||||
|
exit 0
|
||||||
|
else
|
||||||
|
echo "❌ FAILURE: Routing issues detected"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
Loading…
Reference in New Issue
Block a user