bneradt commented on code in PR #13402:
URL: https://github.com/apache/trafficserver/pull/13402#discussion_r3617198459


##########
tests/gold_tests/pipeline/request_framing_client.py:
##########
@@ -0,0 +1,119 @@
+#!/usr/bin/env python3
+"""Send a request over a raw socket and print the responses received.
+
+Two request shapes are supported: a body-less POST (Content-Length: 0) followed
+by a second pipelined request on the same connection, and a single request that
+carries conflicting Content-Length header fields.
+"""
+
+#  Licensed to the Apache Software Foundation (ASF) under one
+#  or more contributor license agreements.  See the NOTICE file
+#  distributed with this work for additional information
+#  regarding copyright ownership.  The ASF licenses this file
+#  to you under the Apache License, Version 2.0 (the
+#  "License"); you may not use this file except in compliance
+#  with the License.  You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+#  Unless required by applicable law or agreed to in writing, software
+#  distributed under the License is distributed on an "AS IS" BASIS,
+#  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+#  See the License for the specific language governing permissions and
+#  limitations under the License.
+
+import argparse
+import socket
+import sys
+
+
+def parse_args() -> argparse.Namespace:
+    """Parse the command line arguments."""
+    parser = argparse.ArgumentParser(description=__doc__)
+    parser.add_argument("proxy_address", help="Address of the proxy to connect 
to.")
+    parser.add_argument("proxy_port", type=int, help="The port of the proxy to 
connect to.")
+    parser.add_argument("host", help="The Host header field value to use.")
+    parser.add_argument(
+        "mode",
+        nargs='?',
+        default='pipeline',
+        choices=['pipeline', 'conflicting_cl'],
+        help="Which request to send: a body-less POST followed by a pipelined "
+        "request, or a request with conflicting Content-Length headers.")
+    return parser.parse_args()
+
+
+def build_request(mode: str, host: str) -> bytes:
+    """Build the raw request bytes for the given mode.
+
+    :param mode: 'pipeline' for a body-less POST followed by a pipelined GET, 
or
+        'conflicting_cl' for a request carrying conflicting Content-Length
+        header fields.
+    :param host: The Host header field value.
+    :returns: The raw request bytes.
+    """
+    if mode == 'conflicting_cl':
+        # Two different Content-Length values are an ambiguous framing that a
+        # careful proxy must reject (RFC 9112 section 6.3) rather than forward,
+        # since a downstream server might frame the body differently.
+        return (
+            f'POST / HTTP/1.1\r\n'
+            f'Host: {host}\r\n'
+            f'Content-Length: 0\r\n'
+            f'Content-Length: 38\r\n'
+            f'Connection: keep-alive\r\n'
+            f'\r\n'
+            f'GET /second HTTP/1.1\r\n'
+            f'Host: {host}\r\n'
+            f'X-Marker: second-request\r\n'
+            f'\r\n').encode()
+
+    return (
+        f'POST / HTTP/1.1\r\n'
+        f'Host: {host}\r\n'
+        f'Content-Length: 0\r\n'
+        f'Connection: keep-alive\r\n'
+        f'\r\n'
+        f'GET /second HTTP/1.1\r\n'
+        f'Host: {host}\r\n'
+        f'X-Marker: second-request\r\n'
+        f'\r\n').encode()
+
+
+def main() -> int:
+    """Send the request and print the received responses."""
+    args = parse_args()
+
+    request = build_request(args.mode, args.host)
+
+    with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as sock:
+        sock.connect((args.proxy_address, args.proxy_port))
+        print(f'Connected to {args.proxy_address}:{args.proxy_port}')
+        print(f'Sending request ({len(request)} bytes):')
+        print(request)
+        sock.sendall(request)
+
+        sock.settimeout(5.0)
+        responses = b""
+        try:
+            while True:
+                data = sock.recv(4096)
+                if not data:
+                    break
+                responses += data
+                # We expect at most two responses; stop once we have both 
status
+                # lines and the connection goes quiet.
+                if responses.count(b'HTTP/1.1') >= 2 and 
responses.endswith(b'\n'):
+                    break
+        except socket.timeout:
+            print('Read timed out.')
+

Review Comment:
   Good point — the client now sets the expected response count per mode (1 for 
conflicting_cl, 2 for pipeline) and switches to a short quiet-timeout once 
reached, so it stops promptly while still catching an unexpected extra 
response. Read timed out. now only prints when the expected responses genuinely 
don't arrive.
   
   



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]

Reply via email to