|  | # | 
|  | # 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 CommonHttp1Edge2EdgeTest(object): | 
|  | def test_01_concurrent_requests(self): | 
|  | """ | 
|  | Test multiple concurrent clients sending streaming messages | 
|  | """ | 
|  |  | 
|  | REQ_CT = 3  # 3 requests per TEST_* | 
|  | TESTS_11 = { | 
|  | "PUT": [ | 
|  | (RequestMsg("PUT", "/PUT/test_01_concurrent_requests_11", | 
|  | headers={ | 
|  | "Transfer-encoding": "chunked", | 
|  | "Content-Type": "text/plain;charset=utf-8" | 
|  | }, | 
|  | # ~384K to trigger Q2 | 
|  | body=b'20000\r\n' + b'1' * 0x20000 + b'\r\n' | 
|  | + b'20000\r\n' + b'2' * 0x20000 + b'\r\n' | 
|  | + b'20000\r\n' + b'3' * 0x20000 + b'\r\n' | 
|  | + b'13\r\nEND OF TRANSMISSION\r\n' | 
|  | + b'0\r\n\r\n'), | 
|  | ResponseMsg(201, reason="Created", | 
|  | headers={"Test-Header": "/PUT/test_01_concurrent_requests_11", | 
|  | "Content-Length": "0"}), | 
|  | ResponseValidator(status=201) | 
|  | )], | 
|  |  | 
|  | "GET": [ | 
|  | (RequestMsg("GET", "/GET/test_01_concurrent_requests_11_small", | 
|  | headers={"Content-Length": "000"}), | 
|  | ResponseMsg(200, reason="OK", | 
|  | headers={ | 
|  | "Content-Length": "19", | 
|  | "Content-Type": "text/plain;charset=utf-8", | 
|  | "Test-Header": "/GET/test_01_concurrent_requests_11_small" | 
|  | }, | 
|  | body=b'END OF TRANSMISSION'), | 
|  | ResponseValidator(status=200)), | 
|  |  | 
|  | (RequestMsg("GET", "/GET/test_01_concurrent_requests_11", | 
|  | headers={"Content-Length": "000"}), | 
|  | ResponseMsg(200, reason="OK", | 
|  | headers={ | 
|  | "transfer-Encoding": "chunked", | 
|  | "Content-Type": "text/plain;charset=utf-8", | 
|  | "Test-Header": "/GET/test_01_concurrent_requests_11" | 
|  | }, | 
|  | # ~384K to trigger Q2 | 
|  | body=b'20000\r\n' + b'1' * 0x20000 + b'\r\n' | 
|  | + b'20000\r\n' + b'2' * 0x20000 + b'\r\n' | 
|  | + b'20000\r\n' + b'3' * 0x20000 + b'\r\n' | 
|  | + b'13\r\nEND OF TRANSMISSION\r\n' | 
|  | + b'0\r\n\r\n'), | 
|  | ResponseValidator(status=200) | 
|  | )], | 
|  | } | 
|  |  | 
|  | TESTS_10 = { | 
|  | "POST": [ | 
|  | (RequestMsg("POST", "/POST/test_01_concurrent_requests_10", | 
|  | headers={"Content-Type": "text/plain;charset=utf-8", | 
|  | "Content-Length": "393216"}, | 
|  | body=b'P' * 393197 | 
|  | + b'END OF TRANSMISSION'), | 
|  | ResponseMsg(201, reason="Created", | 
|  | headers={"Test-Header": "/POST/test_01_concurrent_requests_10", | 
|  | "Content-Length": "0"}), | 
|  | ResponseValidator(status=201) | 
|  | )], | 
|  |  | 
|  | "GET": [ | 
|  | (RequestMsg("GET", "/GET/test_01_concurrent_requests_10_small", | 
|  | headers={"Content-Length": "000"}), | 
|  | ResponseMsg(200, reason="OK", | 
|  | # no content-length, server must close conn when done | 
|  | headers={"Test-Header": "/GET/test_01_concurrent_requests_10_small", | 
|  | "Content-Type": "text/plain;charset=utf-8"}, | 
|  | body=b'END OF TRANSMISSION'), | 
|  | ResponseValidator(status=200)), | 
|  |  | 
|  | (RequestMsg("GET", "/GET/test_01_concurrent_requests_10", | 
|  | headers={"Content-Length": "000"}), | 
|  | ResponseMsg(200, reason="OK", | 
|  | headers={"Test-Header": "/GET/test_01_concurrent_requests_10", | 
|  | "Content-Length": "393215", | 
|  | "Content-Type": "text/plain;charset=utf-8"}, | 
|  | body=b'G' * 393196 | 
|  | + b'END OF TRANSMISSION'), | 
|  | ResponseValidator(status=200) | 
|  | )], | 
|  | } | 
|  | server11 = TestServer(server_port=self.http_server11_port, | 
|  | client_port=self.http_listener11_port, | 
|  | tests=TESTS_11) | 
|  | server10 = TestServer(server_port=self.http_server10_port, | 
|  | client_port=self.http_listener10_port, | 
|  | tests=TESTS_10, | 
|  | handler_cls=RequestHandler10) | 
|  |  | 
|  | self.EA2.wait_connectors() | 
|  |  | 
|  | repeat_ct = 10 | 
|  | client_ct = 4  # per version | 
|  | clients = [] | 
|  | for _ in range(client_ct): | 
|  | clients.append(ThreadedTestClient(TESTS_11, | 
|  | self.http_listener11_port, | 
|  | repeat=repeat_ct)) | 
|  | clients.append(ThreadedTestClient(TESTS_10, | 
|  | self.http_listener10_port, | 
|  | repeat=repeat_ct)) | 
|  | for client in clients: | 
|  | client.wait() | 
|  | try: | 
|  | self.assertIsNone(client.error) | 
|  | self.assertEqual(repeat_ct * REQ_CT, client.count) | 
|  | except Exception: | 
|  | client.dump_log() | 
|  | raise | 
|  |  | 
|  | server11.wait() | 
|  | self.assertEqual(client_ct * repeat_ct * REQ_CT, | 
|  | server11.request_count) | 
|  | server10.wait() | 
|  | self.assertEqual(client_ct * repeat_ct * REQ_CT, | 
|  | server10.request_count) | 
|  |  | 
|  | def test_02_credit_replenish(self): | 
|  | """ | 
|  | Verify credit is replenished by sending > the default credit window | 
|  | requests across the routers.  The default credit window is 250 | 
|  | """ | 
|  |  | 
|  | TESTS = { | 
|  | "GET": [ | 
|  | (RequestMsg("GET", "/GET/test_02_credit_replenish", | 
|  | headers={"Content-Length": "000"}), | 
|  | ResponseMsg(200, reason="OK", | 
|  | headers={"Content-Length": "24", | 
|  | "Content-Type": "text/plain;charset=utf-8"}, | 
|  | body=b'test_02_credit_replenish'), | 
|  | ResponseValidator(status=200), | 
|  | ), | 
|  | ] | 
|  | } | 
|  | server = TestServer(server_port=self.http_server11_port, | 
|  | client_port=self.http_listener11_port, | 
|  | tests=TESTS) | 
|  | self.EA2.wait_connectors() | 
|  |  | 
|  | client = ThreadedTestClient(TESTS, | 
|  | self.http_listener11_port, | 
|  | repeat=300) | 
|  | client.wait() | 
|  | self.assertIsNone(client.error) | 
|  | self.assertEqual(300, client.count) | 
|  | server.wait() | 
|  |  | 
|  | def test_03_server_reconnect(self): | 
|  | """ | 
|  | Verify server reconnect logic. | 
|  | """ | 
|  | TESTS = { | 
|  | "GET": [ | 
|  | (RequestMsg("GET", "/GET/test_03_server_reconnect", | 
|  | headers={"Content-Length": "000"}), | 
|  | ResponseMsg(200, reason="OK", | 
|  | headers={"Content-Length": "24", | 
|  | "Content-Type": "text/plain;charset=utf-8"}, | 
|  | body=b'test_03_server_reconnect'), | 
|  | ResponseValidator(status=200), | 
|  | ), | 
|  | ] | 
|  | } | 
|  |  | 
|  | # bring up the server and send some requests. This will cause the | 
|  | # router to grant credit for clients | 
|  | server = TestServer(server_port=self.http_server11_port, | 
|  | client_port=self.http_listener11_port, | 
|  | tests=TESTS) | 
|  | self.EA2.wait_connectors() | 
|  |  | 
|  | client = ThreadedTestClient(TESTS, | 
|  | self.http_listener11_port, | 
|  | repeat=2) | 
|  | client.wait() | 
|  | self.assertIsNone(client.error) | 
|  | self.assertEqual(2, client.count) | 
|  |  | 
|  | # simulate server loss.  Fire up a client which should be granted | 
|  | # credit since the adaptor does not immediately teardown the server | 
|  | # links.  This will cause the adaptor to run qdr_connection_process | 
|  | # without a raw connection available to wake the I/O thread.. | 
|  | server.wait() | 
|  | client = ThreadedTestClient(TESTS, | 
|  | self.http_listener11_port, | 
|  | repeat=2) | 
|  | # the adaptor will detach the links to the server if the connection | 
|  | # cannot be reestablished after 2.5 seconds.  Restart the server before | 
|  | # that occurrs to prevent client messages from being released with 503 | 
|  | # status. | 
|  | server = TestServer(server_port=self.http_server11_port, | 
|  | client_port=self.http_listener11_port, | 
|  | tests=TESTS) | 
|  | client.wait() | 
|  | self.assertIsNone(client.error) | 
|  | self.assertEqual(2, client.count) | 
|  | server.wait() | 
|  |  | 
|  | def test_04_server_pining_for_the_fjords(self): | 
|  | """ | 
|  | Test permanent loss of server | 
|  | """ | 
|  | TESTS = { | 
|  | "GET": [ | 
|  | (RequestMsg("GET", "/GET/test_04_fjord_pining", | 
|  | headers={"Content-Length": "000"}), | 
|  | ResponseMsg(200, reason="OK", | 
|  | headers={"Content-Length": "20", | 
|  | "Content-Type": "text/plain;charset=utf-8"}, | 
|  | body=b'test_04_fjord_pining'), | 
|  | ResponseValidator(status=200), | 
|  | ), | 
|  | ] | 
|  | } | 
|  |  | 
|  | # bring up the server and send some requests. This will cause the | 
|  | # router to grant credit for clients | 
|  | server = TestServer(server_port=self.http_server11_port, | 
|  | client_port=self.http_listener11_port, | 
|  | tests=TESTS) | 
|  | self.EA2.wait_connectors() | 
|  |  | 
|  | client = ThreadedTestClient(TESTS, self.http_listener11_port) | 
|  | client.wait() | 
|  | self.assertIsNone(client.error) | 
|  | self.assertEqual(1, client.count) | 
|  |  | 
|  | TESTS_FAIL = { | 
|  | "GET": [ | 
|  | (RequestMsg("GET", "/GET/test_04_fjord_pining", | 
|  | headers={"Content-Length": "000"}), | 
|  | ResponseMsg(200, reason="OK", | 
|  | headers={"Content-Length": "20", | 
|  | "Content-Type": "text/plain;charset=utf-8"}, | 
|  | body=b'test_04_fjord_pining'), | 
|  | ResponseValidator(status=503), | 
|  | ), | 
|  | ] | 
|  | } | 
|  |  | 
|  | # Kill the server then issue client requests. These requests will be | 
|  | # held on the server's outgoing links until they expire (2.5 seconds). | 
|  | # At that point the client will receive a 503 response. | 
|  | server.wait() | 
|  | client = ThreadedTestClient(TESTS_FAIL, self.http_listener11_port) | 
|  | client.wait() | 
|  | self.assertIsNone(client.error) | 
|  | self.assertEqual(1, client.count) | 
|  |  | 
|  | # ensure links recover once the server re-appears | 
|  | server = TestServer(server_port=self.http_server11_port, | 
|  | client_port=self.http_listener11_port, | 
|  | tests=TESTS) | 
|  | self.EA2.wait_connectors() | 
|  |  | 
|  | client = ThreadedTestClient(TESTS, self.http_listener11_port) | 
|  | client.wait() | 
|  | self.assertIsNone(client.error) | 
|  | self.assertEqual(1, client.count) | 
|  | server.wait() | 
|  |  | 
|  | def test_05_large_streaming_msg(self): | 
|  | """ | 
|  | Verify large streaming message transfer | 
|  | """ | 
|  | TESTS_11 = { | 
|  | "PUT": [ | 
|  | (RequestMsg("PUT", "/PUT/streaming_test_11", | 
|  | headers={ | 
|  | "Transfer-encoding": "chunked", | 
|  | "Content-Type": "text/plain;charset=utf-8" | 
|  | }, | 
|  | # 4 chunks each ~= 600K | 
|  | body=b'927C1\r\n' + b'0' * 0x927C0 + b'X\r\n' | 
|  | + b'927C0\r\n' + b'1' * 0x927C0 + b'\r\n' | 
|  | + b'927C1\r\n' + b'2' * 0x927C0 + b'X\r\n' | 
|  | + b'927C0\r\n' + b'3' * 0x927C0 + b'\r\n' | 
|  | + b'0\r\n\r\n'), | 
|  |  | 
|  | ResponseMsg(201, reason="Created", | 
|  | headers={"Response-Header": "data", | 
|  | "Content-Length": "0"}), | 
|  | ResponseValidator(status=201)) | 
|  | ], | 
|  |  | 
|  | "GET": [ | 
|  | (RequestMsg("GET", "/GET/streaming_test_11", | 
|  | headers={"Content-Length": "000"}), | 
|  | ResponseMsg(200, reason="OK", | 
|  | headers={ | 
|  | "transfer-Encoding": "chunked", | 
|  | "Content-Type": "text/plain;charset=utf-8" | 
|  | }, | 
|  | # two 1.2MB chunk | 
|  | body=b'124f80\r\n' + b'4' * 0x124F80 + b'\r\n' | 
|  | + b'124f80\r\n' + b'5' * 0x124F80 + b'\r\n' | 
|  | + b'0\r\n\r\n'), | 
|  | ResponseValidator(status=200)) | 
|  | ], | 
|  | } | 
|  |  | 
|  | TESTS_10 = { | 
|  | "POST": [ | 
|  | (RequestMsg("POST", "/POST/streaming_test_10", | 
|  | headers={"Header-1": "H" * 2048, | 
|  | "Content-Length": "2097155", | 
|  | "Content-Type": "text/plain;charset=utf-8"}, | 
|  | body=b'P' * 2097155), | 
|  | ResponseMsg(201, reason="Created", | 
|  | headers={"Response-Header": "data", | 
|  | "Content-Length": "0"}), | 
|  | ResponseValidator(status=201)) | 
|  | ], | 
|  |  | 
|  | "GET": [ | 
|  | (RequestMsg("GET", "/GET/streaming_test_10", | 
|  | headers={"Content-Length": "000"}), | 
|  | ResponseMsg(200, reason="OK", | 
|  | headers={"Content-Length": "1999999", | 
|  | "Content-Type": "text/plain;charset=utf-8"}, | 
|  | body=b'G' * 1999999), | 
|  | ResponseValidator(status=200)) | 
|  | ], | 
|  | } | 
|  | server11 = TestServer(server_port=self.http_server11_port, | 
|  | client_port=self.http_listener11_port, | 
|  | tests=TESTS_11) | 
|  | server10 = TestServer(server_port=self.http_server10_port, | 
|  | client_port=self.http_listener10_port, | 
|  | tests=TESTS_10, | 
|  | handler_cls=RequestHandler10) | 
|  |  | 
|  | self.EA2.wait_connectors() | 
|  |  | 
|  | client11 = ThreadedTestClient(TESTS_11, | 
|  | self.http_listener11_port, | 
|  | repeat=2) | 
|  | client11.wait() | 
|  | self.assertIsNone(client11.error) | 
|  | self.assertEqual(4, client11.count) | 
|  |  | 
|  | client10 = ThreadedTestClient(TESTS_10, | 
|  | self.http_listener10_port, | 
|  | repeat=2) | 
|  | client10.wait() | 
|  | self.assertIsNone(client10.error) | 
|  | self.assertEqual(4, client10.count) | 
|  |  | 
|  | server11.wait() | 
|  | server10.wait() | 
|  |  | 
|  |  | 
|  | class CommonHttp1OneRouterTest(object): | 
|  | TESTS_11 = { | 
|  | # | 
|  | # GET | 
|  | # | 
|  | "GET": [ | 
|  | (RequestMsg("GET", "/GET/error", | 
|  | headers={"Content-Length": 0}), | 
|  | ResponseMsg(400, reason="Bad breath", error=True), | 
|  | ResponseValidator(status=400)), | 
|  |  | 
|  | (RequestMsg("GET", "/GET/no_content", | 
|  | headers={"Content-Length": 0}), | 
|  | ResponseMsg(204, reason="No Content"), | 
|  | ResponseValidator(status=204)), | 
|  |  | 
|  | (RequestMsg("GET", "/GET/content_len", | 
|  | headers={"Content-Length": "00"}), | 
|  | ResponseMsg(200, reason="OK", | 
|  | headers={"Content-Length": 1, | 
|  | "Content-Type": "text/plain;charset=utf-8"}, | 
|  | body=b'?'), | 
|  | ResponseValidator(expect_headers={'Content-Length': '1'}, | 
|  | expect_body=b'?')), | 
|  |  | 
|  | (RequestMsg("GET", "/GET/content_len_511", | 
|  | headers={"Content-Length": 0}), | 
|  | ResponseMsg(200, reason="OK", | 
|  | headers={"Content-Length": 511, | 
|  | "Content-Type": "text/plain;charset=utf-8"}, | 
|  | body=b'X' * 511), | 
|  | ResponseValidator(expect_headers={'Content-Length': '511'}, | 
|  | expect_body=b'X' * 511)), | 
|  |  | 
|  | (RequestMsg("GET", "/GET/content_len_4096", | 
|  | headers={"Content-Length": 0}), | 
|  | ResponseMsg(200, reason="OK", | 
|  | headers={"Content-Length": 4096, | 
|  | "Content-Type": "text/plain;charset=utf-8"}, | 
|  | body=b'X' * 4096), | 
|  | ResponseValidator(expect_headers={'Content-Length': '4096'}, | 
|  | expect_body=b'X' * 4096)), | 
|  |  | 
|  | (RequestMsg("GET", "/GET/chunked", | 
|  | headers={"Content-Length": 0}), | 
|  | ResponseMsg(200, reason="OK", | 
|  | headers={"transfer-encoding": "chunked", | 
|  | "Content-Type": "text/plain;charset=utf-8"}, | 
|  | # note: the chunk length does not count the trailing CRLF | 
|  | body=b'16\r\n' | 
|  | + b'Mary had a little pug \r\n' | 
|  | + b'1b\r\n' | 
|  | + b'Its name was "Skupper-Jack"\r\n' | 
|  | + b'0\r\n' | 
|  | + b'Optional: Trailer\r\n' | 
|  | + b'Optional: Trailer\r\n' | 
|  | + b'\r\n'), | 
|  | ResponseValidator(expect_headers={'transfer-encoding': 'chunked'}, | 
|  | expect_body=b'Mary had a little pug Its name was "Skupper-Jack"')), | 
|  |  | 
|  | (RequestMsg("GET", "/GET/chunked_large", | 
|  | headers={"Content-Length": 0}), | 
|  | ResponseMsg(200, reason="OK", | 
|  | headers={"transfer-encoding": "chunked", | 
|  | "Content-Type": "text/plain;charset=utf-8"}, | 
|  | # note: the chunk length does not count the trailing CRLF | 
|  | body=b'1\r\n' | 
|  | + b'?\r\n' | 
|  | + b'800\r\n' | 
|  | + b'X' * 0x800 + b'\r\n' | 
|  | + b'13\r\n' | 
|  | + b'Y' * 0x13  + b'\r\n' | 
|  | + b'0\r\n' | 
|  | + b'Optional: Trailer\r\n' | 
|  | + b'Optional: Trailer\r\n' | 
|  | + b'\r\n'), | 
|  | ResponseValidator(expect_headers={'transfer-encoding': 'chunked'}, | 
|  | expect_body=b'?' + b'X' * 0x800 + b'Y' * 0x13)), | 
|  |  | 
|  | (RequestMsg("GET", "/GET/info_content_len", | 
|  | headers={"Content-Length": 0}), | 
|  | [ResponseMsg(100, reason="Continue", | 
|  | headers={"Blab": 1, "Blob": "?"}), | 
|  | ResponseMsg(200, reason="OK", | 
|  | headers={"Content-Length": 1, | 
|  | "Content-Type": "text/plain;charset=utf-8"}, | 
|  | body=b'?')], | 
|  | ResponseValidator(expect_headers={'Content-Type': "text/plain;charset=utf-8"}, | 
|  | expect_body=b'?')), | 
|  |  | 
|  | # (RequestMsg("GET", "/GET/no_length", | 
|  | #             headers={"Content-Length": "0"}), | 
|  | #  ResponseMsg(200, reason="OK", | 
|  | #              headers={"Content-Type": "text/plain;charset=utf-8", | 
|  | #                       "connection": "close" | 
|  | #              }, | 
|  | #              body=b'Hi! ' * 1024 + b'X'), | 
|  | #  ResponseValidator(expect_body=b'Hi! ' * 1024 + b'X')), | 
|  | ], | 
|  | # | 
|  | # HEAD | 
|  | # | 
|  | "HEAD": [ | 
|  | (RequestMsg("HEAD", "/HEAD/test_01", | 
|  | headers={"Content-Length": "0"}), | 
|  | ResponseMsg(200, headers={"App-Header-1": "Value 01", | 
|  | "Content-Length": "10", | 
|  | "App-Header-2": "Value 02"}, | 
|  | body=None), | 
|  | ResponseValidator(expect_headers={"App-Header-1": "Value 01", | 
|  | "Content-Length": "10", | 
|  | "App-Header-2": "Value 02"}) | 
|  | ), | 
|  | (RequestMsg("HEAD", "/HEAD/test_02", | 
|  | headers={"Content-Length": "0"}), | 
|  | ResponseMsg(200, headers={"App-Header-1": "Value 01", | 
|  | "Transfer-Encoding": "chunked", | 
|  | "App-Header-2": "Value 02"}), | 
|  | ResponseValidator(expect_headers={"App-Header-1": "Value 01", | 
|  | "Transfer-Encoding": "chunked", | 
|  | "App-Header-2": "Value 02"})), | 
|  |  | 
|  | (RequestMsg("HEAD", "/HEAD/test_03", | 
|  | headers={"Content-Length": "0"}), | 
|  | ResponseMsg(200, headers={"App-Header-3": "Value 03"}), | 
|  | ResponseValidator(expect_headers={"App-Header-3": "Value 03"})), | 
|  | ], | 
|  | # | 
|  | # POST | 
|  | # | 
|  | "POST": [ | 
|  | (RequestMsg("POST", "/POST/test_01", | 
|  | headers={"App-Header-1": "Value 01", | 
|  | "Content-Length": "19", | 
|  | "Content-Type": "application/x-www-form-urlencoded"}, | 
|  | body=b'one=1&two=2&three=3'), | 
|  | ResponseMsg(200, reason="OK", | 
|  | headers={"Response-Header": "whatever", | 
|  | "Transfer-Encoding": "chunked"}, | 
|  | body=b'8\r\n' | 
|  | + b'12345678\r\n' | 
|  | + b'f\r\n' | 
|  | + b'abcdefghijklmno\r\n' | 
|  | + b'000\r\n' | 
|  | + b'\r\n'), | 
|  | ResponseValidator(expect_body=b'12345678abcdefghijklmno') | 
|  | ), | 
|  | (RequestMsg("POST", "/POST/test_02", | 
|  | headers={"App-Header-1": "Value 01", | 
|  | "Transfer-Encoding": "chunked"}, | 
|  | body=b'01\r\n' | 
|  | + b'!\r\n' | 
|  | + b'0\r\n\r\n'), | 
|  | ResponseMsg(200, reason="OK", | 
|  | headers={"Response-Header": "whatever", | 
|  | "Content-Length": "9"}, | 
|  | body=b'Hi There!'), | 
|  | ResponseValidator(expect_body=b'Hi There!') | 
|  | ), | 
|  | ], | 
|  | # | 
|  | # PUT | 
|  | # | 
|  | "PUT": [ | 
|  | (RequestMsg("PUT", "/PUT/test_01", | 
|  | headers={"Put-Header-1": "Value 01", | 
|  | "Transfer-Encoding": "chunked", | 
|  | "Content-Type": "text/plain;charset=utf-8"}, | 
|  | body=b'80\r\n' | 
|  | + b'$' * 0x80 + b'\r\n' | 
|  | + b'0\r\n\r\n'), | 
|  | ResponseMsg(201, reason="Created", | 
|  | headers={"Response-Header": "whatever", | 
|  | "Content-length": "3"}, | 
|  | body=b'ABC'), | 
|  | ResponseValidator(status=201, expect_body=b'ABC') | 
|  | ), | 
|  |  | 
|  | (RequestMsg("PUT", "/PUT/test_02", | 
|  | headers={"Put-Header-1": "Value 01", | 
|  | "Content-length": "0", | 
|  | "Content-Type": "text/plain;charset=utf-8"}), | 
|  | ResponseMsg(201, reason="Created", | 
|  | headers={"Response-Header": "whatever", | 
|  | "Transfer-Encoding": "chunked"}, | 
|  | body=b'1\r\n$\r\n0\r\n\r\n'), | 
|  | ResponseValidator(status=201, expect_body=b'$') | 
|  | ), | 
|  | ] | 
|  | } | 
|  |  | 
|  | # HTTP/1.0 compliant test cases (no chunked, response length unspecified) | 
|  | TESTS_10 = { | 
|  | # | 
|  | # GET | 
|  | # | 
|  | "GET": [ | 
|  | (RequestMsg("GET", "/GET/error", | 
|  | headers={"Content-Length": 0}), | 
|  | ResponseMsg(400, reason="Bad breath", error=True), | 
|  | ResponseValidator(status=400)), | 
|  |  | 
|  | (RequestMsg("GET", "/GET/no_content", | 
|  | headers={"Content-Length": 0}), | 
|  | ResponseMsg(204, reason="No Content"), | 
|  | ResponseValidator(status=204)), | 
|  |  | 
|  | (RequestMsg("GET", "/GET/content_len_511", | 
|  | headers={"Content-Length": 0}), | 
|  | ResponseMsg(200, reason="OK", | 
|  | headers={"Content-Length": 511, | 
|  | "Content-Type": "text/plain;charset=utf-8"}, | 
|  | body=b'X' * 511), | 
|  | ResponseValidator(expect_headers={'Content-Length': '511'}, | 
|  | expect_body=b'X' * 511)), | 
|  |  | 
|  | (RequestMsg("GET", "/GET/content_len_4096", | 
|  | headers={"Content-Length": 0}), | 
|  | ResponseMsg(200, reason="OK", | 
|  | headers={"Content-Type": "text/plain;charset=utf-8"}, | 
|  | body=b'X' * 4096), | 
|  | ResponseValidator(expect_headers={"Content-Type": "text/plain;charset=utf-8"}, | 
|  | expect_body=b'X' * 4096)), | 
|  |  | 
|  | (RequestMsg("GET", "/GET/info_content_len", | 
|  | headers={"Content-Length": 0}), | 
|  | ResponseMsg(200, reason="OK", | 
|  | headers={"Content-Type": "text/plain;charset=utf-8"}, | 
|  | body=b'?'), | 
|  | ResponseValidator(expect_headers={'Content-Type': "text/plain;charset=utf-8"}, | 
|  | expect_body=b'?')), | 
|  |  | 
|  | # test support for "folded headers" | 
|  |  | 
|  | (RequestMsg("GET", "/GET/folded_header_01", | 
|  | headers={"Content-Length": 0}), | 
|  | ResponseMsg(200, reason="OK", | 
|  | headers={"Content-Type": "text/plain;charset=utf-8", | 
|  | "Content-Length": 1, | 
|  | "folded-header": "One\r\n \r\n\tTwo"}, | 
|  | body=b'X'), | 
|  | ResponseValidator(expect_headers={"Content-Type": | 
|  | "text/plain;charset=utf-8", | 
|  | "folded-header": | 
|  | "One     \tTwo"}, | 
|  | expect_body=b'X')), | 
|  |  | 
|  | (RequestMsg("GET", "/GET/folded_header_02", | 
|  | headers={"Content-Length": 0}), | 
|  | ResponseMsg(200, reason="OK", | 
|  | headers={"Content-Type": "text/plain;charset=utf-8", | 
|  | "Content-Length": 1, | 
|  | "folded-header": "\r\n \r\n\tTwo", | 
|  | "another-header": "three"}, | 
|  | body=b'X'), | 
|  | ResponseValidator(expect_headers={"Content-Type": | 
|  | "text/plain;charset=utf-8", | 
|  | # trim leading and | 
|  | # trailing ws: | 
|  | "folded-header": | 
|  | "Two", | 
|  | "another-header": | 
|  | "three"}, | 
|  | expect_body=b'X')), | 
|  | ], | 
|  | # | 
|  | # HEAD | 
|  | # | 
|  | "HEAD": [ | 
|  | (RequestMsg("HEAD", "/HEAD/test_01", | 
|  | headers={"Content-Length": "0"}), | 
|  | ResponseMsg(200, headers={"App-Header-1": "Value 01", | 
|  | "Content-Length": "10", | 
|  | "App-Header-2": "Value 02"}, | 
|  | body=None), | 
|  | ResponseValidator(expect_headers={"App-Header-1": "Value 01", | 
|  | "Content-Length": "10", | 
|  | "App-Header-2": "Value 02"}) | 
|  | ), | 
|  |  | 
|  | (RequestMsg("HEAD", "/HEAD/test_03", | 
|  | headers={"Content-Length": "0"}), | 
|  | ResponseMsg(200, headers={"App-Header-3": "Value 03"}), | 
|  | ResponseValidator(expect_headers={"App-Header-3": "Value 03"})), | 
|  | ], | 
|  | # | 
|  | # POST | 
|  | # | 
|  | "POST": [ | 
|  | (RequestMsg("POST", "/POST/test_01", | 
|  | headers={"App-Header-1": "Value 01", | 
|  | "Content-Length": "19", | 
|  | "Content-Type": "application/x-www-form-urlencoded"}, | 
|  | body=b'one=1&two=2&three=3'), | 
|  | ResponseMsg(200, reason="OK", | 
|  | headers={"Response-Header": "whatever"}, | 
|  | body=b'12345678abcdefghijklmno'), | 
|  | ResponseValidator(expect_body=b'12345678abcdefghijklmno') | 
|  | ), | 
|  | (RequestMsg("POST", "/POST/test_02", | 
|  | headers={"App-Header-1": "Value 01", | 
|  | "Content-Length": "5"}, | 
|  | body=b'01234'), | 
|  | ResponseMsg(200, reason="OK", | 
|  | headers={"Response-Header": "whatever", | 
|  | "Content-Length": "9"}, | 
|  | body=b'Hi There!'), | 
|  | ResponseValidator(expect_body=b'Hi There!') | 
|  | ), | 
|  | ], | 
|  | # | 
|  | # PUT | 
|  | # | 
|  | "PUT": [ | 
|  | (RequestMsg("PUT", "/PUT/test_01", | 
|  | headers={"Put-Header-1": "Value 01", | 
|  | "Content-Length": "513", | 
|  | "Content-Type": "text/plain;charset=utf-8"}, | 
|  | body=b'$' * 513), | 
|  | ResponseMsg(201, reason="Created", | 
|  | headers={"Response-Header": "whatever", | 
|  | "Content-length": "3"}, | 
|  | body=b'ABC'), | 
|  | ResponseValidator(status=201, expect_body=b'ABC') | 
|  | ), | 
|  |  | 
|  | (RequestMsg("PUT", "/PUT/test_02", | 
|  | headers={"Put-Header-1": "Value 01", | 
|  | "Content-length": "0", | 
|  | "Content-Type": "text/plain;charset=utf-8"}), | 
|  | ResponseMsg(201, reason="Created", | 
|  | headers={"Response-Header": "whatever"}, | 
|  | body=b'No Content Length'), | 
|  | ResponseValidator(status=201, expect_body=b'No Content Length') | 
|  | ), | 
|  | ] | 
|  | } | 
|  |  | 
|  | def _do_request(self, client, tests): | 
|  | for req, _, val in tests: | 
|  | req.send_request(client) | 
|  | rsp = client.getresponse() | 
|  | try: | 
|  | body = val.check_response(rsp) | 
|  | except Exception as exc: | 
|  | self.fail("request failed:  %s" % str(exc)) | 
|  |  | 
|  | if req.method == "BODY": | 
|  | self.assertEqual(b'', body) | 
|  |  | 
|  | def test_001_get(self): | 
|  | client = HTTPConnection("127.0.0.1:%s" % self.http_listener11_port, | 
|  | timeout=TIMEOUT) | 
|  | self._do_request(client, self.TESTS_11["GET"]) | 
|  | client.close() | 
|  |  | 
|  | def test_002_head(self): | 
|  | client = HTTPConnection("127.0.0.1:%s" % self.http_listener11_port, | 
|  | timeout=TIMEOUT) | 
|  | self._do_request(client, self.TESTS_11["HEAD"]) | 
|  | client.close() | 
|  |  | 
|  | def test_003_post(self): | 
|  | client = HTTPConnection("127.0.0.1:%s" % self.http_listener11_port, | 
|  | timeout=TIMEOUT) | 
|  | self._do_request(client, self.TESTS_11["POST"]) | 
|  | client.close() | 
|  |  | 
|  | def test_004_put(self): | 
|  | client = HTTPConnection("127.0.0.1:%s" % self.http_listener11_port, | 
|  | timeout=TIMEOUT) | 
|  | self._do_request(client, self.TESTS_11["PUT"]) | 
|  | client.close() | 
|  |  | 
|  | def test_006_head_10(self): | 
|  | client = HTTPConnection("127.0.0.1:%s" % self.http_listener10_port, | 
|  | timeout=TIMEOUT) | 
|  | self._do_request(client, self.TESTS_10["HEAD"]) | 
|  | client.close() | 
|  |  | 
|  | def test_007_post_10(self): | 
|  | client = HTTPConnection("127.0.0.1:%s" % self.http_listener10_port, | 
|  | timeout=TIMEOUT) | 
|  | self._do_request(client, self.TESTS_10["POST"]) | 
|  | client.close() | 
|  |  | 
|  | def test_008_put_10(self): | 
|  | client = HTTPConnection("127.0.0.1:%s" % self.http_listener10_port, | 
|  | timeout=TIMEOUT) | 
|  | self._do_request(client, self.TESTS_10["PUT"]) | 
|  | client.close() | 
|  |  | 
|  |  | 
|  | class Http1OneRouterTestBase(TestCase): | 
|  | # HTTP/1.1 compliant test cases | 
|  |  | 
|  | @classmethod | 
|  | def router(cls, name, mode, extra): | 
|  | config = [ | 
|  | ('router', {'mode': mode, | 
|  | 'id': name, | 
|  | 'allowUnsettledMulticast': 'yes'}), | 
|  | ('listener', {'role': 'normal', | 
|  | 'port': cls.tester.get_port()}), | 
|  | ('address', {'prefix': 'closest', 'distribution': 'closest'}), | 
|  | ('address', | 
|  | {'prefix': 'multicast', 'distribution': 'multicast'}), | 
|  | ] | 
|  |  | 
|  | if extra: | 
|  | config.extend(extra) | 
|  | config = Qdrouterd.Config(config) | 
|  | cls.routers.append(cls.tester.qdrouterd(name, config, wait=True)) | 
|  | return cls.routers[-1] | 
|  |  | 
|  | @classmethod | 
|  | def setUpClass(cls): | 
|  | """Start a router""" | 
|  | super(Http1OneRouterTestBase, cls).setUpClass() | 
|  |  | 
|  | cls.http_server11_port = cls.tester.get_port() | 
|  | cls.http_server10_port = cls.tester.get_port() | 
|  | cls.http_listener11_port = cls.tester.get_port() | 
|  | cls.http_listener10_port = cls.tester.get_port() | 
|  |  | 
|  |  | 
|  | class Http1Edge2EdgeTestBase(TestCase): | 
|  | @classmethod | 
|  | def router(cls, name, mode, extra): | 
|  | config = [ | 
|  | ('router', {'mode': mode, | 
|  | 'id': name, | 
|  | 'allowUnsettledMulticast': 'yes'}), | 
|  | ('listener', {'role': 'normal', | 
|  | 'port': cls.tester.get_port()}), | 
|  | ('address', {'prefix': 'closest', 'distribution': 'closest'}), | 
|  | ('address', {'prefix': 'multicast', 'distribution': 'multicast'}), | 
|  | ] | 
|  |  | 
|  | if extra: | 
|  | config.extend(extra) | 
|  | config = Qdrouterd.Config(config) | 
|  | cls.routers.append(cls.tester.qdrouterd(name, config, wait=True)) | 
|  | return cls.routers[-1] | 
|  |  | 
|  | @classmethod | 
|  | def setUpClass(cls): | 
|  | """Start a router""" | 
|  | super(Http1Edge2EdgeTestBase, cls).setUpClass() | 
|  | cls.routers = [] | 
|  | cls.INTA_edge1_port   = cls.tester.get_port() | 
|  | cls.INTA_edge2_port   = cls.tester.get_port() | 
|  | cls.http_server11_port = cls.tester.get_port() | 
|  | cls.http_listener11_port = cls.tester.get_port() | 
|  | cls.http_server10_port = cls.tester.get_port() | 
|  | cls.http_listener10_port = cls.tester.get_port() |