You are viewing a plain text version of this content. The canonical link for it is here.
Posted to dev@qpid.apache.org by GitBox <gi...@apache.org> on 2021/04/07 19:34:46 UTC

[GitHub] [qpid-dispatch] kgiusti commented on a change in pull request #1097: DISPATCH-2029: Added a base class and moved some of the http1 adaptor…

kgiusti commented on a change in pull request #1097:
URL: https://github.com/apache/qpid-dispatch/pull/1097#discussion_r608990009



##########
File path: tests/system_tests_http1_base.py
##########
@@ -0,0 +1,1187 @@
+#
+# 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 socket
+import uuid
+from threading import Thread
+
+from time import sleep
+try:
+    from http.server import HTTPServer, BaseHTTPRequestHandler
+    from http.client import HTTPConnection
+    from http.client import HTTPException
+except ImportError:
+    from BaseHTTPServer import HTTPServer, BaseHTTPRequestHandler
+    from httplib import HTTPConnection, HTTPException
+
+from system_test import TestCase, TIMEOUT, Logger, Qdrouterd
+
+
+class RequestHandler(BaseHTTPRequestHandler):
+    """
+    Dispatches requests received by the HTTPServer based on the method
+    """
+    protocol_version = 'HTTP/1.1'
+
+    def _execute_request(self, tests):
+        for req, resp, val in tests:
+            if req.target == self.path:
+                xhdrs = None
+                if "test-echo" in self.headers:
+                    xhdrs = {"test-echo":
+                             self.headers["test-echo"]}
+
+                self._consume_body()
+                if not isinstance(resp, list):
+                    resp = [resp]
+                for r in resp:
+                    r.send_response(self, extra_headers=xhdrs)
+                self.server.request_count += 1
+                return
+        self.send_error(404, "Not Found")
+
+    def do_GET(self):
+        self._execute_request(self.server.system_tests["GET"])
+
+    def do_HEAD(self):
+        self._execute_request(self.server.system_tests["HEAD"])
+
+    def do_POST(self):
+        if self.path == "/SHUTDOWN":
+            self.send_response(200, "OK")
+            self.send_header("Content-Length", "13")
+            self.end_headers()
+            self.wfile.write(b'Server Closed')
+            self.wfile.flush()
+            self.close_connection = True
+            self.server.server_killed = True
+            return
+        self._execute_request(self.server.system_tests["POST"])
+
+    def do_PUT(self):
+        self._execute_request(self.server.system_tests["PUT"])
+
+    # these overrides just quiet the test output
+    # comment them out to help debug:
+    def log_request(self, code=None, size=None):
+        pass
+
+    def log_message(self, format=None, *args):
+        pass
+
+    def _consume_body(self):
+        """
+        Read the entire body off the rfile.  This must be done to allow
+        multiple requests on the same socket
+        """
+        if self.command == 'HEAD':
+            return b''
+
+        for key, value in self.headers.items():
+            if key.lower() == 'content-length':
+                return self.rfile.read(int(value))
+
+            if key.lower() == 'transfer-encoding'  \
+               and 'chunked' in value.lower():
+                body = b''
+                while True:
+                    header = self.rfile.readline().strip().split(b';')[0]
+                    hlen = int(header, base=16)
+                    if hlen > 0:
+                        data = self.rfile.read(hlen + 2)  # 2 = \r\n
+                        body += data[:-2]
+                    else:
+                        self.rfile.readline()  # discard last \r\n
+                        break
+                return body
+        return self.rfile.read()
+
+
+class RequestHandler10(RequestHandler):
+    """
+    RequestHandler that forces the server to use HTTP version 1.0 semantics
+    """
+    protocol_version = 'HTTP/1.0'
+
+
+class MyHTTPServer(HTTPServer):
+    """
+    Adds a switch to the HTTPServer to allow it to exit gracefully
+    """
+
+    def __init__(self, addr, handler_cls, testcases):
+        self.system_tests = testcases
+        self.request_count = 0
+        HTTPServer.__init__(self, addr, handler_cls)
+
+    def server_close(self):
+        try:
+            # force immediate close of listening socket
+            self.socket.shutdown(socket.SHUT_RDWR)
+        except Exception:
+            pass
+        HTTPServer.server_close(self)
+
+
+class ThreadedTestClient(object):
+    """
+    An HTTP client running in a separate thread
+    """
+
+    def __init__(self, tests, port, repeat=1):
+        self._id = uuid.uuid4().hex
+        self._conn_addr = ("127.0.0.1:%s" % port)
+        self._tests = tests
+        self._repeat = repeat
+        self._logger = Logger(title="TestClient: %s" % self._id,
+                              print_to_console=False)
+        self._thread = Thread(target=self._run)
+        self._thread.daemon = True
+        self.error = None
+        self.count = 0
+        self._thread.start()
+
+    def _run(self):
+        self._logger.log("TestClient connecting on %s" % self._conn_addr)
+        client = HTTPConnection(self._conn_addr, timeout=TIMEOUT)
+        self._logger.log("TestClient connected")
+        for loop in range(self._repeat):
+            self._logger.log("TestClient start request %d" % loop)
+            for op, tests in self._tests.items():
+                for req, _, val in tests:
+                    self._logger.log("TestClient sending %s %s request" % (op, req.target))
+                    req.send_request(client,
+                                     {"test-echo": "%s-%s-%s-%s" % (self._id,
+                                                                    loop,
+                                                                    op,
+                                                                    req.target)})
+                    self._logger.log("TestClient getting %s response" % op)
+                    try:
+                        rsp = client.getresponse()
+                    except HTTPException as exc:
+                        self._logger.log("TestClient response failed: %s" % exc)
+                        self.error = str(exc)
+                        return
+                    self._logger.log("TestClient response %s received" % op)
+                    if val:
+                        try:
+                            body = val.check_response(rsp)
+                        except Exception as exc:
+                            self._logger.log("TestClient response invalid: %s"
+                                             % str(exc))
+                            self.error = "client failed: %s" % str(exc)
+                            return
+
+                        if req.method == "BODY" and body != b'':
+                            self._logger.log("TestClient response invalid: %s"
+                                             % "body present!")
+                            self.error = "error: body present!"
+                            return
+                    self.count += 1
+                    self._logger.log("TestClient request %s %s completed!" %
+                                     (op, req.target))
+        client.close()
+        self._logger.log("TestClient to %s closed" % self._conn_addr)
+
+    def wait(self, timeout=TIMEOUT):
+        self._thread.join(timeout=TIMEOUT)
+        self._logger.log("TestClient %s shut down" % self._conn_addr)
+        sleep(0.5)  # fudge factor allow socket close to complete
+
+    def dump_log(self):
+        self._logger.dump()
+
+
+class TestServer(object):
+    """
+    A HTTPServer running in a separate thread
+    """
+
+    def __init__(self, server_port, client_port, tests, handler_cls=None):
+        self._logger = Logger(title="TestServer", print_to_console=False)
+        self._client_port = client_port
+        self._server_addr = ("", server_port)
+        self._server = MyHTTPServer(self._server_addr,
+                                    handler_cls or RequestHandler,
+                                    tests)
+        self._server.allow_reuse_address = True
+        self._thread = Thread(target=self._run)
+        self._thread.daemon = True
+        self._thread.start()
+
+    def _run(self):
+        self._logger.log("TestServer listening on %s:%s" % self._server_addr)
+        try:
+            self._server.server_killed = False
+            while not self._server.server_killed:
+                self._server.handle_request()
+        except Exception as exc:
+            self._logger.log("TestServer %s crash: %s" %
+                             (self._server_addr, exc))
+            raise
+        self._logger.log("TestServer %s:%s closed" % self._server_addr)
+
+    def wait(self, timeout=TIMEOUT):
+        self._logger.log("TestServer %s:%s shutting down" % self._server_addr)
+        self.request_count = 0
+        if self._thread.is_alive():
+            client = HTTPConnection("127.0.0.1:%s" % self._client_port,
+                                    timeout=TIMEOUT)
+            client.putrequest("POST", "/SHUTDOWN")
+            client.putheader("Content-Length", "0")
+            client.endheaders()
+            # 13 == len('Server Closed')
+            client.getresponse().read(13)
+            client.close()
+            self._thread.join(timeout=TIMEOUT)
+        if self._server:
+            self._server.server_close()
+            self.request_count = self._server.request_count
+            del self._server
+        sleep(0.5)  # fudge factor allow socket close to complete
+
+
+def http1_ping(sport, cport):
+    """
+    Test the HTTP path by doing a simple GET request
+    """
+    TEST = {
+        "GET": [
+            (RequestMsg("GET", "/GET/ping",
+                        headers={"Content-Length": 0}),
+             ResponseMsg(200, reason="OK",
+                         headers={"Content-Length": 4,
+                                  "Content-Type": "text/plain;charset=utf-8"},
+                         body=b'pong'),
+             ResponseValidator(expect_body=b'pong'))
+        ]
+    }
+
+    server = TestServer(server_port=sport,
+                        client_port=cport,
+                        tests=TEST)
+    client = ThreadedTestClient(tests=TEST, port=cport)
+    client.wait()
+    server.wait()
+    return (client.count, client.error)
+
+
+class ResponseMsg(object):
+    """
+    A 'hardcoded' HTTP response message.  This class writes its response
+    message when called by the HTTPServer via the BaseHTTPRequestHandler
+    """
+
+    def __init__(self, status, version=None, reason=None,
+                 headers=None, body=None, error=False):
+        self.status = status
+        self.version = version or "HTTP/1.1"
+        self.reason = reason
+        self.headers = headers or {}
+        self.body = body
+        self.error = error
+
+    def send_response(self, handler, extra_headers=None):
+        extra_headers = extra_headers or {}
+        if self.error:
+            handler.send_error(self.status,
+                               message=self.reason)
+            return
+
+        handler.send_response(self.status, self.reason)
+        for key, value in self.headers.items():
+            handler.send_header(key, value)
+        for key, value in extra_headers.items():
+            handler.send_header(key, value)
+        handler.end_headers()
+
+        if self.body:
+            handler.wfile.write(self.body)
+            handler.wfile.flush()
+
+
+class RequestMsg(object):
+    """
+    A 'hardcoded' HTTP request message.  This class writes its request
+    message to the HTTPConnection.
+    """
+
+    def __init__(self, method, target, headers=None, body=None):
+        self.method = method
+        self.target = target
+        self.headers = headers or {}
+        self.body = body
+
+    def send_request(self, conn, extra_headers=None):
+        extra_headers = extra_headers or {}
+        conn.putrequest(self.method, self.target)
+        for key, value in self.headers.items():
+            conn.putheader(key, value)
+        for key, value in extra_headers.items():
+            conn.putheader(key, value)
+        conn.endheaders()
+        if self.body:
+            conn.send(self.body)
+
+
+class ResponseValidator(object):
+    """
+    Validate a response as received by the HTTP client
+    """
+
+    def __init__(self, status=200, expect_headers=None, expect_body=None):
+        if expect_headers is None:
+            expect_headers = {}
+        self.status = status
+        self.expect_headers = expect_headers
+        self.expect_body = expect_body
+
+    def check_response(self, rsp):
+        if self.status and rsp.status != self.status:
+            raise Exception("Bad response code, expected %s got %s"
+                            % (self.status, rsp.status))
+        for key, value in self.expect_headers.items():
+            if rsp.getheader(key) != value:
+                raise Exception("Missing/bad header (%s), expected %s got %s"
+                                % (key, value, rsp.getheader(key)))
+
+        body = rsp.read()
+        if (self.expect_body and self.expect_body != body):
+            raise Exception("Bad response body expected %s got %s"
+                            % (self.expect_body, body))
+        return body
+
+
+class CommonHttp1AdaptorEdge2EdgeTest():

Review comment:
       add object as parent class




-- 
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.

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



---------------------------------------------------------------------
To unsubscribe, e-mail: dev-unsubscribe@qpid.apache.org
For additional commands, e-mail: dev-help@qpid.apache.org