diff --git a/examples/example.json b/examples/example.json index f43847d..7bfe6b3 100644 --- a/examples/example.json +++ b/examples/example.json @@ -94,6 +94,33 @@ "delay": 15, // seconds "response": "so long", "code": 503 + }, + + //proxy some requests to upstream + { + "path_regexp": "^/proxy/(.*)", + "response_proxy": "http://example.com/path/$1" + }, + + //proxy request with some delay + { + "path_regexp": "^/slow_proxy/(.*)/(.*)$", + "response_proxy": "http://example.com/otherpath/$1/$2", + "delay": 10 //seconds + }, + + //proxy with some response headers replaced to config defined + { + "path_regexp": "^/fixed_proxy/(.*)/(.*)$", + "response_proxy": "http://example.com/otherpath/$1/$2", + "headers": { + "Server": "MyServer/0.1" + } + }, + { + "path_regexp": "^/fixed_proxy2/(.*)$", + "response_proxy": "http://example.com/otherpath/$1", + "headers_file": "page1.headers" } ] } diff --git a/zaglushka.py b/zaglushka.py index 6265a80..b64d32f 100755 --- a/zaglushka.py +++ b/zaglushka.py @@ -6,6 +6,7 @@ import json import httplib import time +from copy import deepcopy from os import path from collections import namedtuple @@ -14,6 +15,7 @@ from tornado.options import define, options from tornado.httpserver import HTTPServer from tornado.httputil import HTTPHeaders +from tornado.httpclient import AsyncHTTPClient, HTTPRequest logger = logging.getLogger('zaglushka') @@ -67,14 +69,7 @@ def __init__(self, raw_config, config_full_path): rules.append(Rule(matcher, responder)) else: logger.warn('Unable to build matcher from url spec #{}, skipping'.format(num)) - default_response = static_response( - body='', - headers_func=build_static_headers_func({ - 'X-Zaglushka-Default-Response': 'true', - }), - code=httplib.NOT_FOUND - ) - rules.append(Rule(always_match, default_response)) + rules.append(Rule(always_match, default_response())) self.rules = rules self.stubs_base_path = stubs_base_path @@ -183,6 +178,16 @@ def _body_func(handler, ready_cb): **stub_kwargs) +def default_response(): + return static_response( + body='', + headers_func=build_static_headers_func({ + 'X-Zaglushka-Default-Response': 'true', + }), + code=httplib.NOT_FOUND + ) + + def filebased_response(full_path, headers_func, warn_func=None, **stub_kwargs): def _body_func(handler, ready_cb): @@ -200,6 +205,51 @@ def _body_func(handler, ready_cb): **stub_kwargs) +def proxied_response(url_pattern, proxy_url, headers_func, warn_func=None, **stub_kwargs): + + try: + url_regexp = re.compile(url_pattern) + except re.error as e: + if warn_func is not None: + warn_func('Unable to compile url pattern "{}": {}'.format(url_pattern, e)) + return default_response() + + def _body_func(handler, ready_cb): + match = url_regexp.search(handler.request.uri) + if match is None: + handler.set_header('X-Zaglushka-Failed-Response', 'true') + return ready_cb() + url = proxy_url + for i, group in enumerate(match.groups()): + url = url.replace('${}'.format(i), group) + http_client = handler.application.settings['http_client'] + request = HTTPRequest(url, method=handler.request.method, headers=handler.request.headers, + body=handler.request.body, follow_redirects=False) + + def _on_proxied_request_ready(response): + # TODO: delay ? what about - нужно не сложить а смержить + # TODO: 599 response ? + if response.code == 599: # special tornado status code + pass + headers_before = deepcopy(handler.headers) + handler.write(response.body) + for header, value in response.headers.iteritems(): + handler.add_header(header, value) + # replace with headers from config if any + for header, _ in headers_before.get_all(): + handler.clear_header(header) + for value in headers_before.get_list(header): + handler.add_header(header, value) + handler.set_status(response.code) + ready_cb() + + http_client.fetch(request, callback=_on_proxied_request_ready) + + return ResponseStub(headers_func=headers_func, + body_func=_body_func, + **stub_kwargs) + + def choose_headers_func(spec, base_stubs_path): paths = set() if 'headers' in spec: @@ -386,10 +436,12 @@ def compute_etag(self): def build_app(zaglushka_config, debug=False): + http_client = AsyncHTTPClient() return Application( handlers=[(r'.*', StubHandler)], debug=debug, - zaglushka_config=zaglushka_config + zaglushka_config=zaglushka_config, + http_client=http_client )