nfv/nova-api-proxy/nova-api-proxy/nova_api_proxy/apps/proxy.py

115 lines
4.0 KiB
Python

#
# 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: <keypair. redacted>")
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]