diff --git a/.gitignore b/.gitignore index 509cf80..853f8d9 100644 --- a/.gitignore +++ b/.gitignore @@ -2,4 +2,5 @@ *.bak *.bak2 *.db +*.txt rproxy diff --git a/rproxy.c b/rproxy.c index 352648c..f8d85ed 100644 --- a/rproxy.c +++ b/rproxy.c @@ -1976,12 +1976,29 @@ static int do_write(connection_t *conn) { void handle_client_read(connection_t *conn) { int bytes_read = do_read(conn); 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); return; } 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) while (buffer_available_read(buf) > 0) { char *data_start = buf->data + buf->head; @@ -2008,6 +2025,10 @@ void handle_client_read(connection_t *conn) { 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 long long body_len = (conn->request.content_length > 0) ? conn->request.content_length : 0; 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); 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 (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); @@ -2035,6 +2068,15 @@ void handle_client_read(connection_t *conn) { 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; serve_stats_api(conn); 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 + 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); 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) { connection_t *pair = conn->pair; 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); return; } @@ -2086,8 +2147,8 @@ static void handle_forwarding(connection_t *conn) { } } - memcpy(pair->write_buf.data + pair->write_buf.tail, - conn->read_buf.data + conn->read_buf.head, + memcpy(pair->write_buf.data + pair->write_buf.tail, + conn->read_buf.data + conn->read_buf.head, data_to_forward); pair->write_buf.tail += 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 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) { conn->half_closed = 1; shutdown(conn->fd, SHUT_RD); diff --git a/test_routing.sh b/test_routing.sh new file mode 100755 index 0000000..78cf0ba --- /dev/null +++ b/test_routing.sh @@ -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." \ No newline at end of file diff --git a/test_routing_comprehensive.sh b/test_routing_comprehensive.sh new file mode 100755 index 0000000..5a427c3 --- /dev/null +++ b/test_routing_comprehensive.sh @@ -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 \ No newline at end of file diff --git a/test_routing_fix.sh b/test_routing_fix.sh new file mode 100755 index 0000000..84b8a2c --- /dev/null +++ b/test_routing_fix.sh @@ -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 \ No newline at end of file diff --git a/test_upstream.py b/test_upstream.py new file mode 100644 index 0000000..c2b4b0e --- /dev/null +++ b/test_upstream.py @@ -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() diff --git a/verify_routing.sh b/verify_routing.sh new file mode 100755 index 0000000..eb8ce72 --- /dev/null +++ b/verify_routing.sh @@ -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 \ No newline at end of file