From e56cb4333d001e58c173cfe4292497d80f5fdd1b Mon Sep 17 00:00:00 2001 From: BenjamenMeyer Date: Wed, 11 Feb 2015 14:25:16 +0000 Subject: [PATCH 1/8] Enhancement: Reverse Proxy - Adding Reverse Proxy support so EOM can be used with non-python HTTP Stacks --- eom/proxy.py | 154 ++++++++++++++++++ examples/reverse_proxy_app.py | 6 + test-requirements.txt | 4 + tests/test_proxy.py | 289 ++++++++++++++++++++++++++++++++++ 4 files changed, 453 insertions(+) create mode 100644 eom/proxy.py create mode 100644 examples/reverse_proxy_app.py create mode 100644 tests/test_proxy.py diff --git a/eom/proxy.py b/eom/proxy.py new file mode 100644 index 0000000..0f3add0 --- /dev/null +++ b/eom/proxy.py @@ -0,0 +1,154 @@ +# Copyright (c) 2014 Rackspace, Inc. +# +# Licensed 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 logging +import re +import uuid + +from oslo.config import cfg +import requests + + +LOG = logging.getLogger(__name__) +CONF = cfg.CONF + +PROXY_GROUP_NAME = 'eom:proxy' +PROXY_OPTIONS = [ + cfg.StrOpt('upstream'), + cfg.IntOpt('timeout') +] + +CONF.register_opts(PROXY_OPTIONS, group=PROXY_GROUP_NAME) + + +class ReverseProxyRequest(object): + + def __init__(self, environ): + self.environ = environ + self.environ_controlled = { + k.upper(): v + for k, v in environ.items() + } + self.headers = {} + + self.method = self.environ_controlled['REQUEST_METHOD'] + self.path_virtual = self.environ_controlled['SCRIPT_NAME'] + self.path = self.environ_controlled['PATH_INFO'] + self.query_string = self.environ_controlled['QUERY_STRING'] + self.server = { + 'host': self.environ_controlled['HTTP_HOST'] + if 'HTTP_HOST' in self.environ_controlled else None, + 'name': self.environ_controlled['SERVER_NAME'], + 'port': self.environ_controlled['SERVER_PORT'] + } + self.body = None + self.wsgi = { + 'version': environ['wsgi.version'], + 'scheme': environ['wsgi.url_scheme'], + 'input': environ['wsgi.input'], + 'errors': environ['wsgi.errors'], + 'multithread': environ['wsgi.multithread'], + 'multiprocess': environ['wsgi.multiprocess'], + 'run_once': environ['wsgi.run_once'] + } + self.transaction_id = uuid.uuid4() + self.rebuild_headers() + + def rebuild_headers(self): + self.headers = {} + + for k, v in self.environ_controlled.items(): + + kp = k + kp.replace('_', '-') + + if k.startswith('HTTP_'): + self.headers[kp] = v + + elif k.startswith('CONTENT_'): + self.headers[kp] = v + + self.headers['X-Reverse-Proxy-Transaction-Id'] = str( + self.transaction_id) + + def url(self, upstream): + return '{0}{1}?{2}'.format(upstream, + self.path, + self.query_string) + + def body(self): + return self.wsgi['input'] + + +class ReverseProxy(object): + """WSGI Reverse Proxy Application + + """ + STREAM_BLOCK_SIZE = 8 * 1024 # 8 Kilobytes + + def __init__(self): + config_group = CONF[PROXY_GROUP_NAME] + + self.config = { + 'upstream': config_group['upstream'], + 'timeout': config_group['timeout'] + } + + if self.config['upstream'] in (None, ''): + LOG.error('upstream not valid') + LOG.warn('Configuration Error - upstream = {0}' + .format(self.config['upstream'])) + + url_regex = re.compile('(http|https|ftp)?(://)?[\w\d\.\-_]+:?(\d+)?/*') + if self.config['upstream']: + if not url_regex.match(self.config['upstream']): + LOG.error('Invalid URL - {0:}'.format(self.config['upstream'])) + + @staticmethod + def make_response_text(response): + return '{0} {1}'.format(response.status_code, + response.reason) + + def handler(self, environ, start_response): + + if self.config['upstream'] in (None, ''): + start_response('500 Internal Server Error', + [('Content-Type', 'plain/text')]) + return [b'Please contact the administrator.'] + + req = ReverseProxyRequest(environ) + + target_url = req.url(self.config['upstream']) + + response = requests.request(req.method, + target_url, + headers=req.headers, + data=req.body, + timeout=self.config['timeout'], + allow_redirects=False, + stream=True) + start_response(ReverseProxy.make_response_text(response), + [(k, v) for k, v in response.headers.items()]) + if 'wsgi.file_wrapper' in environ: + if environ['wsgi.file_wrapper']: + file_wrapper = environ['wsgi.file_wrapper'] + return file_wrapper(response.raw, + ReverseProxy.STREAM_BLOCK_SIZE) + + return iter(lambda: response.raw.read(ReverseProxy.STREAM_BLOCK_SIZE), + b'') + + def __call__(self, environ, start_response): + return self.handler(environ, start_response) diff --git a/examples/reverse_proxy_app.py b/examples/reverse_proxy_app.py new file mode 100644 index 0000000..6e4207c --- /dev/null +++ b/examples/reverse_proxy_app.py @@ -0,0 +1,6 @@ +from eom import proxy +from oslo.config import cfg + +conf = cfg.CONF +conf(project='eom', args=[]) +app = proxy.ReverseProxy() diff --git a/test-requirements.txt b/test-requirements.txt index 756877e..1718719 100644 --- a/test-requirements.txt +++ b/test-requirements.txt @@ -17,7 +17,11 @@ hacking # Utils fakeredis>=0.5.1 +fixtures +httpretty +-e git+https://github.com/BenjamenMeyer/stackInABox.git@master#egg=stackinabox_master requests +# stackinabox # uwsgi-module testing uwsgi diff --git a/tests/test_proxy.py b/tests/test_proxy.py new file mode 100644 index 0000000..45267fe --- /dev/null +++ b/tests/test_proxy.py @@ -0,0 +1,289 @@ +# Copyright (c) 2013 Rackspace, Inc. +# +# Licensed 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 io +import logging + +import ddt +import fixtures +import httpretty +from oslo_config import fixture as fixture_config +import stackinabox.httpretty +from stackinabox.services.service import StackInABoxService +from stackinabox.stack import StackInABox + +from eom import proxy +from tests import util + + +LOG = logging.getLogger(__name__) + + +class ProxyTestingService(StackInABoxService): + + DELETE_RESPONSE = "Where'd it go?" + GET_RESPONSE = "You've been gotten" + OPTIONS_ALLOW_HEADERS = ','.join(StackInABoxService.METHODS) + OPTIONS_RESPONSE = "Enjoy your options while they last" + PATCH_RESPONSE = "Glad to be of your medical service" + POST_RESPONSE = "Riding horses is fun." + PUT_RESPONSE = "You're presciption is ready." + + def __init__(self): + super(ProxyTestingService, self).__init__('proxy') + self.register(StackInABoxService.GET, '/hello', + ProxyTestingService.root_handler) + self.register('DELETE', '/d', + ProxyTestingService.delete_handler) + self.register(StackInABoxService.GET, '/g', + ProxyTestingService.get_handler) + self.register(StackInABoxService.HEAD, '/h', + ProxyTestingService.head_handler) + self.register(StackInABoxService.OPTIONS, '/o', + ProxyTestingService.options_handler) + self.register(StackInABoxService.PATCH, '/p1', + ProxyTestingService.patch_handler) + self.register(StackInABoxService.POST, '/p2', + ProxyTestingService.post_handler) + self.register(StackInABoxService.PUT, '/p3', + ProxyTestingService.put_handler) + + def root_handler(self, request, uri, headers): + return (200, headers, 'Hello') + + def delete_handler(self, request, uri, headers): + return (200, headers, ProxyTestingService.DELETE_RESPONSE) + + def get_handler(self, request, uri, headers): + return (200, headers, ProxyTestingService.GET_RESPONSE) + + def head_handler(self, request, uri, headers): + return (201, headers, '') + + def options_handler(self, request, uri, headers): + headers['allow'] = ProxyTestingService.OPTIONS_ALLOW_HEADERS + return (200, headers, ProxyTestingService.OPTIONS_RESPONSE) + + def patch_handler(self, request, uri, headers): + return (200, headers, ProxyTestingService.PATCH_RESPONSE) + + def post_handler(self, request, uri, headers): + return (200, headers, ProxyTestingService.POST_RESPONSE) + + def put_handler(self, request, uri, headers): + return (200, headers, ProxyTestingService.PUT_RESPONSE) + + +class FakeProxyResponse(object): + + def __init__(self): + self.status_code = None + self.reason = None + self.result = None + self.headers = None + self.__body = None + + def __call__(self, result, headers): + self.result = result + status_code, self.reason = result.split(' ', maxsplit=1) + self.status_code = int(status_code) + self.headers = headers + + def app_call(self, app, environ): + self.__body = app(environ, self) + if self.__body is not None: + LOG.debug('Response body has a body') + else: + LOG.debug('Response has no body') + + @property + def body(self): + return bytes().join(self.__body) + + def iter_content(self, chunk_size=None): + yield self.__body + + +@ddt.ddt +@httpretty.activate +class TestProxy(util.TestCase, fixtures.TestWithFixtures): + + def setUp(self): + super(TestProxy, self).setUp() + + StackInABox.register_service(ProxyTestingService()) + + self.response = FakeProxyResponse() + self.CONF = self.useFixture(fixture_config.Config()).conf + self.__wsgi = { + 'version': 'test.proxy', + 'url_scheme': None, + 'input': io.BytesIO(), + 'errors': io.BytesIO(), + 'multithread': False, + 'multiprocess': False, + 'run_once': False + } + + def tearDown(self): + super(TestProxy, self).tearDown() + StackInABox.reset_services() + + def make_environ(self, scheme, method, path, + headers, body=None, query_string=''): + environ = { + 'wsgi.{0}'.format(k): v + for k, v in self.__wsgi.items() + } + environ['wsgi.url_scheme'] = scheme + + environ.update({ + k: v + for k, v in headers.items() + }) + + environ['request_method'] = method + environ['script_name'] = path + environ['path_info'] = path + environ['query_string'] = query_string + environ['http_host'] = 'proxy.test' + environ['server_name'] = 'proxy.test' + environ['server_port'] = 9999 + + if body is not None: + environ['wsgi.input'].write(body) + + return environ + + def test_proxy_init_valid(self): + stackinabox.httpretty.httpretty_registration('localhost') + service_url = 'http://localhost/proxy/' + service_timeoutms = 30000 + + self.CONF.set_override('upstream', + service_url, + group=proxy.PROXY_GROUP_NAME) + self.CONF.set_override('timeout', + service_timeoutms, + group=proxy.PROXY_GROUP_NAME) + + my_proxy = proxy.ReverseProxy() + + self.assertEqual(my_proxy.config['upstream'], + service_url) + self.assertEqual(my_proxy.config['timeout'], + service_timeoutms) + + environ = self.make_environ(scheme=u'http', + method=u'GET', + path=u'hello', + headers={}, + body=None, + query_string=u'') + + self.response.app_call(my_proxy, environ) + + controlled_headers = { + k.upper(): v + for k, v in self.response.headers + } + self.assertNotIn('X-Reverse-Proxy-Transaction-Id'.upper(), + controlled_headers) + + self.assertEqual(self.response.status_code, + 200) + self.assertEqual(self.response.body, + b'Hello') + + def test_proxy_init_invalid(self): + stackinabox.httpretty.httpretty_registration('localhost') + service_url = None + service_timeoutms = 30000 + + self.CONF.set_override('upstream', + service_url, + group=proxy.PROXY_GROUP_NAME) + self.CONF.set_override('timeout', + service_timeoutms, + group=proxy.PROXY_GROUP_NAME) + + my_proxy = proxy.ReverseProxy() + + self.assertEqual(my_proxy.config['upstream'], + service_url) + self.assertEqual(my_proxy.config['timeout'], + service_timeoutms) + + environ = self.make_environ(scheme=u'http', + method=u'GET', + path=u'/', + headers={}, + body=None, + query_string=u'') + + self.response.app_call(my_proxy, environ) + + self.assertEqual(self.response.status_code, + 500) + self.assertEqual(self.response.reason, + 'Internal Server Error') + + @ddt.data((200, 'd', 'DELETE', + ProxyTestingService.DELETE_RESPONSE.encode()), + (200, 'g', 'GET', + ProxyTestingService.GET_RESPONSE.encode()), + (201, 'h', 'HEAD', + b''), + (200, 'o', 'OPTIONS', + ProxyTestingService.OPTIONS_RESPONSE.encode()), + (200, 'p1', 'PATCH', + ProxyTestingService.PATCH_RESPONSE.encode()), + (200, 'p2', 'POST', + ProxyTestingService.POST_RESPONSE.encode()), + (200, 'p3', 'PUT', + ProxyTestingService.PUT_RESPONSE.encode())) + @ddt.unpack + def test_proxy_test_http_verb(self, code, path, verb, response_body): + stackinabox.httpretty.httpretty_registration('localhost') + service_url = 'http://localhost/proxy/' + service_timeoutms = 30000 + + self.CONF.set_override('upstream', + service_url, + group=proxy.PROXY_GROUP_NAME) + self.CONF.set_override('timeout', + service_timeoutms, + group=proxy.PROXY_GROUP_NAME) + + my_proxy = proxy.ReverseProxy() + + self.assertEqual(my_proxy.config['upstream'], + service_url) + self.assertEqual(my_proxy.config['timeout'], + service_timeoutms) + + environ = self.make_environ(scheme=u'http', + method=verb, + path=path, + headers={}, + body=None, + query_string=u'') + + self.response.app_call(my_proxy, environ) + + self.assertEqual(self.response.status_code, + code) + self.assertEqual(self.response.body, + response_body) From 53e0d276d7e1597efba0aaa6fe54190500e6041b Mon Sep 17 00:00:00 2001 From: BenjamenMeyer Date: Wed, 11 Feb 2015 15:12:46 +0000 Subject: [PATCH 2/8] Update stackinabox - stackinabox has a new release that fixes the two bugs we had - DELETE != PUT - pip install now works correctly --- test-requirements.txt | 3 +-- tests/test_proxy.py | 2 +- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/test-requirements.txt b/test-requirements.txt index 1718719..40683fe 100644 --- a/test-requirements.txt +++ b/test-requirements.txt @@ -19,9 +19,8 @@ hacking fakeredis>=0.5.1 fixtures httpretty --e git+https://github.com/BenjamenMeyer/stackInABox.git@master#egg=stackinabox_master requests -# stackinabox +stackinabox # uwsgi-module testing uwsgi diff --git a/tests/test_proxy.py b/tests/test_proxy.py index 45267fe..3ab192c 100644 --- a/tests/test_proxy.py +++ b/tests/test_proxy.py @@ -45,7 +45,7 @@ def __init__(self): super(ProxyTestingService, self).__init__('proxy') self.register(StackInABoxService.GET, '/hello', ProxyTestingService.root_handler) - self.register('DELETE', '/d', + self.register(StackInABoxService.DELETE, '/d', ProxyTestingService.delete_handler) self.register(StackInABoxService.GET, '/g', ProxyTestingService.get_handler) From 4f361f35f574a3f94e0ade57587a36400e2f6ad8 Mon Sep 17 00:00:00 2001 From: BenjamenMeyer Date: Wed, 11 Feb 2015 21:04:00 +0000 Subject: [PATCH 3/8] pep8 compliance and test output limitting - Compliance for pep8 - Limiting the display output for py27, pypy so the columns continue to look nice --- test-requirements.txt | 2 +- tests/test_proxy.py | 26 ++++++++++++++------------ 2 files changed, 15 insertions(+), 13 deletions(-) diff --git a/test-requirements.txt b/test-requirements.txt index 40683fe..f6c7d2c 100644 --- a/test-requirements.txt +++ b/test-requirements.txt @@ -20,7 +20,7 @@ fakeredis>=0.5.1 fixtures httpretty requests -stackinabox +stackinabox>=0.3 # uwsgi-module testing uwsgi diff --git a/tests/test_proxy.py b/tests/test_proxy.py index 3ab192c..ceae93b 100644 --- a/tests/test_proxy.py +++ b/tests/test_proxy.py @@ -20,9 +20,10 @@ import fixtures import httpretty from oslo_config import fixture as fixture_config -import stackinabox.httpretty +import six from stackinabox.services.service import StackInABoxService from stackinabox.stack import StackInABox +import stackinabox.util_httpretty from eom import proxy from tests import util @@ -33,13 +34,13 @@ class ProxyTestingService(StackInABoxService): - DELETE_RESPONSE = "Where'd it go?" - GET_RESPONSE = "You've been gotten" + DELETE_RESPONSE = "DELETED" + GET_RESPONSE = "GET" OPTIONS_ALLOW_HEADERS = ','.join(StackInABoxService.METHODS) - OPTIONS_RESPONSE = "Enjoy your options while they last" - PATCH_RESPONSE = "Glad to be of your medical service" - POST_RESPONSE = "Riding horses is fun." - PUT_RESPONSE = "You're presciption is ready." + OPTIONS_RESPONSE = "OPTIONS" + PATCH_RESPONSE = "PATCH" + POST_RESPONSE = "POST" + PUT_RESPONSE = "PUT" def __init__(self): super(ProxyTestingService, self).__init__('proxy') @@ -97,7 +98,8 @@ def __init__(self): def __call__(self, result, headers): self.result = result - status_code, self.reason = result.split(' ', maxsplit=1) + status_code, self.reason = result.split(' ', maxsplit=1)\ + if six.PY3 else result.split(' ', 1) self.status_code = int(status_code) self.headers = headers @@ -168,7 +170,7 @@ def make_environ(self, scheme, method, path, return environ def test_proxy_init_valid(self): - stackinabox.httpretty.httpretty_registration('localhost') + stackinabox.util_httpretty.httpretty_registration('localhost') service_url = 'http://localhost/proxy/' service_timeoutms = 30000 @@ -200,7 +202,7 @@ def test_proxy_init_valid(self): for k, v in self.response.headers } self.assertNotIn('X-Reverse-Proxy-Transaction-Id'.upper(), - controlled_headers) + controlled_headers) self.assertEqual(self.response.status_code, 200) @@ -208,7 +210,7 @@ def test_proxy_init_valid(self): b'Hello') def test_proxy_init_invalid(self): - stackinabox.httpretty.httpretty_registration('localhost') + stackinabox.util_httpretty.httpretty_registration('localhost') service_url = None service_timeoutms = 30000 @@ -256,7 +258,7 @@ def test_proxy_init_invalid(self): ProxyTestingService.PUT_RESPONSE.encode())) @ddt.unpack def test_proxy_test_http_verb(self, code, path, verb, response_body): - stackinabox.httpretty.httpretty_registration('localhost') + stackinabox.util_httpretty.httpretty_registration('localhost') service_url = 'http://localhost/proxy/' service_timeoutms = 30000 From 0ec3fc3baed9073a572021fcf960fde2b6da6c72 Mon Sep 17 00:00:00 2001 From: BenjamenMeyer Date: Wed, 11 Feb 2015 21:12:40 +0000 Subject: [PATCH 4/8] Enhancement: Added README documentation for EOM Proxy --- README.rst | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/README.rst b/README.rst index 7d98474..9c0e5ef 100644 --- a/README.rst +++ b/README.rst @@ -173,6 +173,30 @@ Metrics TBD +===== +Proxy +===== + +EOM Proxy provides a Reverse Proxy WSGI App so that EOM can be used with +non-Python-based REST API stacks (f.e C++, Java, PHP, .NET). + +EOM Proxy is easy to use and configure as shown in the following examples: + +.. code-block:: ini + + [eom:proxy] + upstream = http://localhost:8080 + timeout = 30000 + +.. code-block:: python + + from eom.proxy import ReverseProxy + from oslo.config import cfg + + conf = cfg.CONF + conf(project='eom', args=[]) + app = proxy.ReverseProxy() + ==== RBAC ==== From 9ffc899a1c2c02944089d65ee8aed8973ad75536 Mon Sep 17 00:00:00 2001 From: BenjamenMeyer Date: Thu, 12 Feb 2015 21:01:46 +0000 Subject: [PATCH 5/8] Bug Fix - Converted to using the iterable returned by Requests instead of implementing our own for the return from the WSGI app - Bug Fix: ReversProxyRequest.body wasn't a property and had a conflict between a method and a member variable --- eom/proxy.py | 19 +++++++------------ 1 file changed, 7 insertions(+), 12 deletions(-) diff --git a/eom/proxy.py b/eom/proxy.py index 0f3add0..3df2ca0 100644 --- a/eom/proxy.py +++ b/eom/proxy.py @@ -53,7 +53,6 @@ def __init__(self, environ): 'name': self.environ_controlled['SERVER_NAME'], 'port': self.environ_controlled['SERVER_PORT'] } - self.body = None self.wsgi = { 'version': environ['wsgi.version'], 'scheme': environ['wsgi.url_scheme'], @@ -63,6 +62,7 @@ def __init__(self, environ): 'multiprocess': environ['wsgi.multiprocess'], 'run_once': environ['wsgi.run_once'] } + self.transaction_id = uuid.uuid4() self.rebuild_headers() @@ -71,11 +71,10 @@ def rebuild_headers(self): for k, v in self.environ_controlled.items(): - kp = k - kp.replace('_', '-') + kp = k.lower().replace('_', '-') if k.startswith('HTTP_'): - self.headers[kp] = v + self.headers[kp[5:]] = v elif k.startswith('CONTENT_'): self.headers[kp] = v @@ -88,6 +87,7 @@ def url(self, upstream): self.path, self.query_string) + @property def body(self): return self.wsgi['input'] @@ -139,16 +139,11 @@ def handler(self, environ, start_response): timeout=self.config['timeout'], allow_redirects=False, stream=True) + start_response(ReverseProxy.make_response_text(response), [(k, v) for k, v in response.headers.items()]) - if 'wsgi.file_wrapper' in environ: - if environ['wsgi.file_wrapper']: - file_wrapper = environ['wsgi.file_wrapper'] - return file_wrapper(response.raw, - ReverseProxy.STREAM_BLOCK_SIZE) - - return iter(lambda: response.raw.read(ReverseProxy.STREAM_BLOCK_SIZE), - b'') + + return response.iter_content(decode_unicode=False) def __call__(self, environ, start_response): return self.handler(environ, start_response) From 015769479d16751e1f1acebbe5e053c48cc55484 Mon Sep 17 00:00:00 2001 From: BenjamenMeyer Date: Fri, 13 Feb 2015 16:29:45 +0000 Subject: [PATCH 6/8] Bug Fix: Removing FTP from URL check EOM Proxy doesn't support FTP proxying, just HTTP(S). Original thought was it might as that is normal proxy behavior but it's simply not necessary --- eom/proxy.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/eom/proxy.py b/eom/proxy.py index 0f3add0..ec62c1e 100644 --- a/eom/proxy.py +++ b/eom/proxy.py @@ -111,7 +111,7 @@ def __init__(self): LOG.warn('Configuration Error - upstream = {0}' .format(self.config['upstream'])) - url_regex = re.compile('(http|https|ftp)?(://)?[\w\d\.\-_]+:?(\d+)?/*') + url_regex = re.compile('(http|https)?(://)?[\w\d\.\-_]+:?(\d+)?/*') if self.config['upstream']: if not url_regex.match(self.config['upstream']): LOG.error('Invalid URL - {0:}'.format(self.config['upstream'])) From f8cf9f2e0456d251ec415ba3c46bf5dd9d8db19f Mon Sep 17 00:00:00 2001 From: BenjamenMeyer Date: Fri, 13 Feb 2015 18:27:11 +0000 Subject: [PATCH 7/8] Clarity - Converted some calls to ones that were more straight forward and clear, and probably more performant since they use native Python functionality insteado redo'ing that same functionality (even though it's also in pure python) --- eom/proxy.py | 2 +- tests/test_proxy.py | 5 +---- 2 files changed, 2 insertions(+), 5 deletions(-) diff --git a/eom/proxy.py b/eom/proxy.py index 6775e54..8715b7a 100644 --- a/eom/proxy.py +++ b/eom/proxy.py @@ -141,7 +141,7 @@ def handler(self, environ, start_response): stream=True) start_response(ReverseProxy.make_response_text(response), - [(k, v) for k, v in response.headers.items()]) + list(response.headers.items())) return response.iter_content(decode_unicode=False) diff --git a/tests/test_proxy.py b/tests/test_proxy.py index ceae93b..d716e33 100644 --- a/tests/test_proxy.py +++ b/tests/test_proxy.py @@ -151,10 +151,7 @@ def make_environ(self, scheme, method, path, } environ['wsgi.url_scheme'] = scheme - environ.update({ - k: v - for k, v in headers.items() - }) + environ.update(headers) environ['request_method'] = method environ['script_name'] = path From 1b9c41aab2b017675e6cfc6804ebe44b0cfcb468 Mon Sep 17 00:00:00 2001 From: BenjamenMeyer Date: Thu, 6 Aug 2015 21:26:10 +0000 Subject: [PATCH 8/8] Enhancement: Updated against master - updates per changes to master - no more global CONF - stackinabox is already in, but at least keep the pinning - pep8 compliance, namely in license text --- eom/proxy.py | 10 +++++----- test-requirements.txt | 3 +-- tests/test_proxy.py | 2 +- 3 files changed, 7 insertions(+), 8 deletions(-) diff --git a/eom/proxy.py b/eom/proxy.py index 8715b7a..5ed3078 100644 --- a/eom/proxy.py +++ b/eom/proxy.py @@ -1,4 +1,4 @@ -# Copyright (c) 2014 Rackspace, Inc. +# Copyright (c) 2013 Rackspace, Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -10,9 +10,9 @@ # 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 logging import re import uuid @@ -22,7 +22,7 @@ LOG = logging.getLogger(__name__) -CONF = cfg.CONF +_CONF = cfg.CONF PROXY_GROUP_NAME = 'eom:proxy' PROXY_OPTIONS = [ @@ -30,7 +30,7 @@ cfg.IntOpt('timeout') ] -CONF.register_opts(PROXY_OPTIONS, group=PROXY_GROUP_NAME) +_CONF.register_opts(PROXY_OPTIONS, group=PROXY_GROUP_NAME) class ReverseProxyRequest(object): @@ -99,7 +99,7 @@ class ReverseProxy(object): STREAM_BLOCK_SIZE = 8 * 1024 # 8 Kilobytes def __init__(self): - config_group = CONF[PROXY_GROUP_NAME] + config_group = _CONF[PROXY_GROUP_NAME] self.config = { 'upstream': config_group['upstream'], diff --git a/test-requirements.txt b/test-requirements.txt index af0b269..f1a4ea9 100644 --- a/test-requirements.txt +++ b/test-requirements.txt @@ -7,7 +7,7 @@ testtools mock # HTTP interface testing -stackinabox +stackinabox>=0.3 requests-mock # Test runner @@ -24,7 +24,6 @@ fakeredis>=0.5.1 fixtures httpretty requests -stackinabox>=0.3 # uwsgi-module testing uwsgi diff --git a/tests/test_proxy.py b/tests/test_proxy.py index d716e33..ea59668 100644 --- a/tests/test_proxy.py +++ b/tests/test_proxy.py @@ -10,9 +10,9 @@ # 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 io import logging