def test_http2_relay_traffic(self): """Tests if HTTP2 traffic can correctly be forwarded (including url-encoded characters).""" # Create a simple HTTP echo server class MyListener(ProxyListener): def forward_request(self, method, path, data, headers): return {"method": method, "path": path, "data": data} listener = MyListener() port_http_server = get_free_tcp_port() http_server = start_proxy_server(port_http_server, update_listener=listener, use_ssl=True) # Create a relay proxy which forwards request to the HTTP echo server port_relay_proxy = get_free_tcp_port() forward_url = f"https://localhost:{port_http_server}" relay_proxy = start_proxy_server(port_relay_proxy, forward_url=forward_url, use_ssl=True) # Contact the relay proxy query = "%2B=%3B%2C%2F%3F%3A%40%26%3D%2B%24%21%2A%27%28%29%23" path = f"/foo/bar%3B%2C%2F%3F%3A%40%26%3D%2B%24%21%2A%27%28%29%23baz?{query}" url = f"https://localhost:{port_relay_proxy}{path}" response = requests.post(url, verify=False) # Expect the response from the HTTP echo server expected = { "method": "POST", "path": path, "data": "", } assert json.loads(to_str(response.content)) == expected http_server.stop() relay_proxy.stop()
def test_ssl_proxy_server(): class MyListener(ProxyListener): def forward_request(self, *args, **kwargs): invocations.append((args, kwargs)) return 200 invocations = [] # start SSL proxy listener = MyListener() port = get_free_tcp_port() server = start_proxy_server(port, update_listener=listener, use_ssl=True) wait_for_port_open(port) # start SSL proxy proxy_port = get_free_tcp_port() proxy = start_ssl_proxy(proxy_port, port, asynchronous=True) wait_for_port_open(proxy_port) # invoke SSL proxy server url = f"https://{LOCALHOST_HOSTNAME}:{proxy_port}" num_requests = 3 for i in range(num_requests): response = requests.get(url, verify=False) assert response.status_code == 200 # assert backend server has been invoked assert len(invocations) == num_requests # clean up proxy.stop() server.stop()
def start_server(port, asynchronous=False): if is_port_open(port): LOG.debug("API Multiserver appears to be already running.") return class ConfigListener(ProxyListener): def forward_request(self, method, path, data, **kwargs): response = Response() response.status_code = 200 response._content = "{}" try: if path == API_PATH_SERVERS: if method == "POST": start_api_server_locally(json.loads(to_str(data))) elif method == "GET": response._content = json.dumps(json_safe(API_SERVERS)) except Exception as e: LOG.error("Unable to process request: %s" % e) response.status_code = 500 response._content = str(e) return response proxy = start_proxy_server(port, update_listener=ConfigListener()) if asynchronous: return proxy proxy.join()
def run_parallel_download(): file_length = 10000000 class DownloadListener(ProxyListener): def forward_request(self, method, path, data, headers): sleep_time = int(path.replace("/", "")) time.sleep(sleep_time) response = Response() response.status_code = 200 response._content = ("%s" % sleep_time) * file_length return response test_port = 12124 tmp_file_pattern = "/tmp/test.%s" proxy = start_proxy_server(test_port, update_listener=DownloadListener()) def do_download(param): tmp_file = tmp_file_pattern % param TMP_FILES.append(tmp_file) download("http://localhost:%s/%s" % (test_port, param), tmp_file) values = [1, 2, 3] parallelize(do_download, values) proxy.stop() for val in values: tmp_file = tmp_file_pattern % val assert len(load_file(tmp_file)) == file_length
def _do_start_ssl_proxy_with_listener(port: int, target: PortOrUrl, requests_kwargs: Dict[str, Any] = None): target = f"http://localhost:{target}" if isinstance(target, int) else target base_url = f"{'https://' if '://' not in target else ''}{target.rstrip('/')}" requests_kwargs = requests_kwargs or {} # define forwarding listener class Listener(ProxyListener): def forward_request(self, method, path, data, headers): # send request to target url = f"{base_url}{path}" response = requests.request(method=method, url=url, data=data, headers=headers, verify=False, **requests_kwargs) # fix encoding of response, based on Accept-Encoding header if "gzip" in headers.get(HEADER_ACCEPT_ENCODING, "").lower(): response._content = gzip.compress(to_bytes(response._content)) response.headers["Content-Length"] = str(len( response._content)) response.headers["Content-Encoding"] = "gzip" return response proxy_thread = start_proxy_server(port, update_listener=Listener(), use_ssl=True) return proxy_thread
def do_start_edge(bind_address, port, use_ssl, asynchronous=False): from localstack.http.adapters import RouterListener from localstack.services.internal import LocalstackResourceHandler start_dns_server(asynchronous=True) listeners = [ LocalstackResourceHandler(), # handle internal resources first RouterListener(ROUTER), # then custom routes PROXY_LISTENER_EDGE, # then call the edge proxy listener ] # get port and start Edge print("Starting edge router (http%s port %s)..." % ("s" if use_ssl else "", port)) # use use_ssl=True here because our proxy allows both, HTTP and HTTPS traffic proxy = start_proxy_server( port, bind_address=bind_address, use_ssl=True, update_listener=listeners, check_port=False, ) if not asynchronous: proxy.join() return proxy
def _do_start_ssl_proxy_with_client_auth(port: int, target: str, client_cert_key: Tuple[str, str]): base_url = f"{'https://' if '://' not in target else ''}{target.rstrip('/')}" # prepare cert files (TODO: check whether/how we can pass cert strings to requests.request(..) directly) cert_file = client_cert_key[0] if not os.path.exists(cert_file): cert_file = new_tmp_file() save_file(cert_file, client_cert_key[0]) key_file = client_cert_key[1] if not os.path.exists(key_file): key_file = new_tmp_file() save_file(key_file, client_cert_key[1]) cert_params = (cert_file, key_file) # define forwarding listener class Listener(ProxyListener): def forward_request(self, method, path, data, headers): url = f"{base_url}{path}" result = requests.request(method=method, url=url, data=data, headers=headers, cert=cert_params, verify=False) return result proxy_thread = start_proxy_server(port, update_listener=Listener(), use_ssl=True) return proxy_thread
def start_proxy(port, backend_url=None, update_listener=None, quiet=False, params={}, use_ssl=None): use_ssl = config.USE_SSL if use_ssl is None else use_ssl proxy_thread = start_proxy_server( port=port, forward_url=backend_url, use_ssl=use_ssl, update_listener=update_listener, quiet=quiet, params=params, ) return proxy_thread
def test_basic_https_invocation(self): class MyListener(ProxyListener): def forward_request(self, method, path, data, headers): return {"method": method, "path": path, "data": data} port = get_free_tcp_port() url = f"https://localhost:{port}/foo/bar" listener = MyListener() proxy = start_proxy_server(port, update_listener=listener, use_ssl=True) response = requests.post(url, verify=False) expected = {"method": "POST", "path": "/foo/bar", "data": ""} assert json.loads(to_str(response.content)) == expected proxy.stop()
def do_start_edge(port, use_ssl, asynchronous=False): try: # start local DNS server, if present from localstack_ext.services import dns_server dns_server.start_servers() except Exception: pass # get port and start Edge print('Starting edge router (http%s port %s)...' % ('s' if use_ssl else '', port)) # use use=True here because our proxy allows both, HTTP and HTTPS traffic proxy = start_proxy_server(port, use_ssl=True, update_listener=ProxyListenerEdge()) if not asynchronous: proxy.join() return proxy
def do_start_edge(bind_address, port, use_ssl, asynchronous=False): start_dns_server(asynchronous=True) # get port and start Edge print("Starting edge router (http%s port %s)..." % ("s" if use_ssl else "", port)) # use use=True here because our proxy allows both, HTTP and HTTPS traffic proxy = start_proxy_server( port, bind_address=bind_address, use_ssl=True, update_listener=PROXY_LISTENER_EDGE, ) if not asynchronous: proxy.join() return proxy
def test_ssl_proxy_server(self): class MyListener(ProxyListener): def forward_request(self, *args, **kwargs): invocations.append((args, kwargs)) return {"foo": "bar"} invocations = [] # start SSL proxy listener = MyListener() port = get_free_tcp_port() server = start_proxy_server(port, update_listener=listener, use_ssl=True) wait_for_port_open(port) # start SSL proxy proxy_port = get_free_tcp_port() proxy = start_ssl_proxy(proxy_port, port, asynchronous=True, fix_encoding=True) wait_for_port_open(proxy_port) # invoke SSL proxy server url = f"https://{LOCALHOST_HOSTNAME}:{proxy_port}" num_requests = 3 for i in range(num_requests): response = requests.get(url, verify=False) assert response.status_code == 200 # assert backend server has been invoked assert len(invocations) == num_requests # invoke SSL proxy server with gzip response for encoding in ["gzip", "gzip, deflate"]: headers = {HEADER_ACCEPT_ENCODING: encoding} response = requests.get(url, headers=headers, verify=False, stream=True) result = response.raw.read() assert to_str(gzip.decompress(result)) == json.dumps( {"foo": "bar"}) # clean up proxy.stop() server.stop()
def proxy_server(proxy_listener, host="127.0.0.1", port=None) -> str: """ Create a temporary proxy server on a random port (or the specified port) with the given proxy listener for the duration of the context manager. """ from localstack.services.generic_proxy import start_proxy_server host = host port = port or get_free_tcp_port() thread = start_proxy_server(port, bind_address=host, update_listener=proxy_listener) url = f"http://{host}:{port}" assert poll_condition( lambda: is_port_open(port), timeout=5 ), f"server on port {port} did not start" yield url thread.stop()
def start_proxy( port: int, backend_url: str = None, update_listener=None, quiet: bool = False, use_ssl: bool = None, ): use_ssl = config.USE_SSL if use_ssl is None else use_ssl proxy_thread = start_proxy_server( port=port, forward_url=backend_url, use_ssl=use_ssl, update_listener=update_listener, quiet=quiet, check_port=False, ) return proxy_thread
def test_http2_traffic(self): port = get_free_tcp_port() class MyListener(ProxyListener): def forward_request(self, method, path, data, headers): return { 'method': method, 'path': path, 'data': data } url = 'https://localhost:%s/foo/bar' % port listener = MyListener() proxy = start_proxy_server(port, update_listener=listener, use_ssl=True) time.sleep(1) response = requests.post(url, verify=False) self.assertEqual({'method': 'POST', 'path': '/foo/bar', 'data': ''}, json.loads(to_str(response.content))) proxy.stop()
def test_http2_traffic(self): port = get_free_tcp_port() class MyListener(ProxyListener): def forward_request(self, method, path, data, headers): return {"method": method, "path": path, "data": data} url = "https://localhost:%s/foo/bar" % port listener = MyListener() proxy = start_proxy_server(port, update_listener=listener, use_ssl=True) time.sleep(1) response = requests.post(url, verify=False) expected = {"method": "POST", "path": "/foo/bar", "data": ""} assert json.loads(to_str(response.content)) == expected proxy.stop()
def test_download_with_timeout(): class DownloadListener(ProxyListener): def forward_request(self, method, path, data, headers): if path == "/sleep": time.sleep(2) return {} port = get_free_tcp_port() proxy = start_proxy_server(port, update_listener=DownloadListener()) tmp_file = new_tmp_file() download(f"http://localhost:{port}/", tmp_file) assert load_file(tmp_file) == "{}" with pytest.raises(TimeoutError): download(f"http://localhost:{port}/sleep", tmp_file, timeout=1) # clean up proxy.stop() rm_rf(tmp_file)
def do_start_edge(bind_address, port, use_ssl, asynchronous=False): from localstack.services.internal import LocalstackResourceHandler # add internal routes as default listener ProxyListener.DEFAULT_LISTENERS.append(LocalstackResourceHandler()) start_dns_server(asynchronous=True) # get port and start Edge print("Starting edge router (http%s port %s)..." % ("s" if use_ssl else "", port)) # use use=True here because our proxy allows both, HTTP and HTTPS traffic proxy = start_proxy_server( port, bind_address=bind_address, use_ssl=True, update_listener=PROXY_LISTENER_EDGE, ) if not asynchronous: proxy.join() return proxy
def test_forward_raw_path(self, monkeypatch): class MyListener(ProxyListener): def forward_request(self, method, path, data, headers): _path = get_full_raw_path(quart_request) return {"method": method, "path": _path} # start listener and configure EDGE_FORWARD_URL port = get_free_tcp_port() forward_url = f"http://localhost:{port}" listener = MyListener() proxy = start_proxy_server(port, update_listener=listener, use_ssl=True) monkeypatch.setattr(config, "EDGE_FORWARD_URL", forward_url) # run test request, assert that raw request path is forwarded test_arn = "arn:aws:test:resource/test" raw_path = f"/test/{urllib.parse.quote(test_arn)}/bar?q1=foo&q2=bar" url = f"{config.get_edge_url()}{raw_path}" response = requests.get(url) expected = {"method": "GET", "path": raw_path} assert json.loads(to_str(response.content)) == expected proxy.stop()
def test_static_route(self): class MyListener(ProxyListener): def forward_request(self, method, path, *args, **kwargs): return {"method": method, "path": path} # start proxy server listener = MyListener() port = get_free_tcp_port() server = start_proxy_server(port, update_listener=listener) wait_for_port_open(port) # request a /static/... path from the server and assert result url = f"http://{LOCALHOST_HOSTNAME}:{port}/static/index.html" response = requests.get(url, verify=False) assert response.ok assert json.loads(to_str(response.content)) == { "method": "GET", "path": "/static/index.html", } # clean up server.stop()