# # Content of this file includes code from paste/proxy.py file in package # "paste": https://bitbucket.org/ianb/paste/ # # The following license is presented by paste/proxy.py: # # (c) 2005 Ian Bicking and contributors; written for Paste (http://pythonpaste.org) # Licensed under the MIT license: http://www.opensource.org/licenses/mit-license.php # # Copyright (c) 2015-2018 Wind River Systems, Inc. # # SPDX-License-Identifier: Apache-2.0 # from paste.proxy import parse_headers from paste.proxy import TransparentProxy from six.moves import http_client as httplib import urllib from oslo_log import log as logging from nova_api_proxy.common import histogram from nova_api_proxy.common.service import Application from nova_api_proxy.common.timestamp import get_monotonic_timestamp_in_ms LOG = logging.getLogger(__name__) class Proxy(Application): """ A proxy that sends the request just as it was given, including respecting HTTP_HOST, wsgi.url_scheme, etc. """ def __init__(self): self.proxy_app = TransparentProxy() def __call__(self, environ, start_response): LOG.debug("Proxy the request to the remote host: (%s)", environ[ 'HTTP_HOST']) start_ms = get_monotonic_timestamp_in_ms() result = self.proxy_app(environ, start_response) now_ms = get_monotonic_timestamp_in_ms() elapsed_secs = (now_ms - start_ms) / 1000 histogram.add_histogram_data("%s" % environ['HTTP_HOST'], elapsed_secs) if environ.get('REQUEST_METHOD') == 'POST': if 'os-keypairs' in environ.get('PATH_INFO', ''): LOG.info("POST response body: ") else: LOG.info("POST response body: %s" % result) return result class DebugProxy(Application): def __call__(self, environ, start_response): LOG.debug("Proxy the request to the remote host: (%s)", environ[ 'HTTP_HOST']) scheme = environ['wsgi.url_scheme'] conn_scheme = scheme if conn_scheme == 'http': conn_class = httplib.HTTPConnection elif conn_scheme == 'https': conn_class = httplib.HTTPSConnection else: raise ValueError( "Unknown scheme %r" % scheme) if 'HTTP_HOST' not in environ: raise ValueError( "WSGI environ must contain an HTTP_HOST key") host = environ['HTTP_HOST'] conn_host = host conn = conn_class(conn_host) headers = {} for key, value in environ.items(): if key.startswith('HTTP_'): key = key[5:].lower().replace('_', '-') headers[key] = value headers['host'] = host if 'REMOTE_ADDR' in environ and 'HTTP_X_FORWARDED_FOR' not in environ: headers['x-forwarded-for'] = environ['REMOTE_ADDR'] if environ.get('CONTENT_TYPE'): headers['content-type'] = environ['CONTENT_TYPE'] if environ.get('CONTENT_LENGTH'): length = int(environ['CONTENT_LENGTH']) body = environ['wsgi.input'].read(length) if length == -1: environ['CONTENT_LENGTH'] = str(len(body)) else: body = '' path = (environ.get('SCRIPT_NAME', '') + environ.get('PATH_INFO', '')) path = urllib.quote(path) if 'QUERY_STRING' in environ: path += '?' + environ['QUERY_STRING'] LOG.debug("REQ header: (%s)" % headers) LOG.debug("REQ body: (%s)" % body) conn.request(environ['REQUEST_METHOD'], path, body, headers) res = conn.getresponse() LOG.debug("RESP header: (%s)" % res.msg) headers_out = parse_headers(res.msg) status = '%s %s' % (res.status, res.reason) start_response(status, headers_out) length = res.getheader('content-length') if length is not None: body = res.read(int(length)) else: body = res.read() LOG.debug("RESP body: (%s)" % body) conn.close() return [body]