POST/PUT request bodies silently dropped, causing requests to hang indefinitely #153

Closed
opened 2026-07-18 12:50:53 +07:00 by bagas · 0 comments
Owner

Any HTTP request with a body (POST, PUT, PATCH) sent through the tunnel hangs until the client times out.

Reproduce

  1. Run this server
import socket

HOST = "127.0.0.1"
PORT = 3000

s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
s.bind((HOST, PORT))
s.listen(5)

print(f"origin listening on {HOST}:{PORT}", flush=True)

def recv_until(sock, marker, limit=1024 * 1024):
    data = b""
    while marker not in data:
        chunk = sock.recv(4096)
        if not chunk:
            break
        data += chunk
        if len(data) > limit:
            raise RuntimeError("request too large")
    return data


while True:
    conn, addr = s.accept()
    print(f"\nconnection from {addr}", flush=True)
    conn.settimeout(10)

    try:
        data = recv_until(conn, b"\r\n\r\n")

        if not data:
            print("empty request", flush=True)
            conn.close()
            continue

        headers, _, body = data.partition(b"\r\n\r\n")
        lines = headers.split(b"\r\n")
        request_line = lines[0].decode(errors="replace")
        headers_map = {}
        for line in lines[1:]:
            if b":" in line:
                key, value = line.split(b":", 1)
                headers_map[key.lower()] = value.strip()
        content_length = int(
            headers_map.get(b"content-length", b"0")
        )

        print("=" * 60, flush=True)
        print("request:", request_line, flush=True)
        print(
            f"Content-Length={content_length} "
            f"| received initially={len(body)}",
            flush=True,
        )

        while len(body) < content_length:
            chunk = conn.recv(4096)
            if not chunk:
                print(
                    f"EOF before body complete "
                    f"{len(body)}/{content_length}",
                    flush=True,
                )
                break
            body += chunk
        if len(body) == content_length:
            print(
                f"FULL BODY RECEIVED "
                f"({len(body)} bytes): {body[:100]!r}",
                flush=True,
            )
        else:
            print(
                f"BODY INCOMPLETE "
                f"({len(body)}/{content_length})",
                flush=True,
            )

    except socket.timeout:
        print(
            f"TIMEOUT waiting for request/body",
            flush=True,
        )

    except Exception as e:
        print(
            "ERROR:",
            repr(e),
            flush=True,
        )

    finally:
        try:
            conn.sendall(
                b"HTTP/1.1 200 OK\r\n"
                b"Content-Length: 2\r\n"
                b"Connection: close\r\n"
                b"\r\n"
                b"ok"
            )
        except Exception as e:
            print(
                "reply failed:",
                repr(e),
                flush=True,
            )

        conn.close()
  1. Forward the server
  2. Do this curl
curl -sS --max-time 8 \
        -H "Host: <forwarded address>" \
        https://<forwarded address>/ \
        -d 'hello=world' \
        -w '\nHTTP %{http_code}\n'
Any HTTP request with a body (POST, PUT, PATCH) sent through the tunnel hangs until the client times out. ### Reproduce 1. Run this server ```python import socket HOST = "127.0.0.1" PORT = 3000 s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) s.bind((HOST, PORT)) s.listen(5) print(f"origin listening on {HOST}:{PORT}", flush=True) def recv_until(sock, marker, limit=1024 * 1024): data = b"" while marker not in data: chunk = sock.recv(4096) if not chunk: break data += chunk if len(data) > limit: raise RuntimeError("request too large") return data while True: conn, addr = s.accept() print(f"\nconnection from {addr}", flush=True) conn.settimeout(10) try: data = recv_until(conn, b"\r\n\r\n") if not data: print("empty request", flush=True) conn.close() continue headers, _, body = data.partition(b"\r\n\r\n") lines = headers.split(b"\r\n") request_line = lines[0].decode(errors="replace") headers_map = {} for line in lines[1:]: if b":" in line: key, value = line.split(b":", 1) headers_map[key.lower()] = value.strip() content_length = int( headers_map.get(b"content-length", b"0") ) print("=" * 60, flush=True) print("request:", request_line, flush=True) print( f"Content-Length={content_length} " f"| received initially={len(body)}", flush=True, ) while len(body) < content_length: chunk = conn.recv(4096) if not chunk: print( f"EOF before body complete " f"{len(body)}/{content_length}", flush=True, ) break body += chunk if len(body) == content_length: print( f"FULL BODY RECEIVED " f"({len(body)} bytes): {body[:100]!r}", flush=True, ) else: print( f"BODY INCOMPLETE " f"({len(body)}/{content_length})", flush=True, ) except socket.timeout: print( f"TIMEOUT waiting for request/body", flush=True, ) except Exception as e: print( "ERROR:", repr(e), flush=True, ) finally: try: conn.sendall( b"HTTP/1.1 200 OK\r\n" b"Content-Length: 2\r\n" b"Connection: close\r\n" b"\r\n" b"ok" ) except Exception as e: print( "reply failed:", repr(e), flush=True, ) conn.close() ``` 2. Forward the server 3. Do this curl ```bash curl -sS --max-time 8 \ -H "Host: <forwarded address>" \ https://<forwarded address>/ \ -d 'hello=world' \ -w '\nHTTP %{http_code}\n' ```
bagas closed this issue 2026-07-19 14:11:35 +07:00
Sign in to join this conversation.
No Label
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: bagas/tunnel-please#153