swift/swift/common/middleware/fake_footers.py

198 lines
7.1 KiB
Python

# Copyright (c) 2010-2012 OpenStack Foundation
#
# 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.
"""
This middleware is not intended to ever be merged on master branch.
This middleware mimics behaviour that we expect to eventually integrate into
the proxy app/object controller once feature/ec has merged to master.
Specifically, we need to be able to add trailing headers (aka footers) to PUT
requests, after the request body has been read by a downstream app, and have
these footers sent on to the object server.
As a workaround, while we wait for feature/ec to merge to master,this
middleware provides a callback mechanism for other (upstream) middlewares to
provide footers. FakeFooters will store these footers in memcache (since we
have no way currently to forward them on to the object server) using a key
based on the request path.
Then, when handling any response, FakeFooters will look in memcache for any
footers stored under the current request path, and append those footers to the
response headers.
Middleware wishing to send footers to FakeFooters should add a reference to a
callback function in the request environ under key 'swift.update.footers'.
Fake Footers will call this function with a single argument - a dict - after it
completes reading the request body. The middleware callback should add any
footers to this dict before returning.
To use FakeFooters you will need to add it to the proxy pipeline in
proxy-server.conf and also add a filter section::
pipeline = catch_errors proxy-logging cache tempauth fake_footers \
proxy-logging proxy-server
[filter:fake_footers]
use = egg:swift#fake_footers
An example of a middleware taking advantage of FakeFooters is given below::
from swift.common.utils import get_logger
from swift.common.wsgi import WSGIContext
FAKE_KEY = 'X-Object-Meta-FakeFooter'
class TestFakeFootersContext(WSGIContext):
def __init__(self, app, logger):
super(TestFakeFootersContext, self).__init__(app)
self.logger = logger
def footers_callback(self, footers):
footers.update(
{FAKE_KEY: self.env.get('swift.trans_id', 'bogus_tran_id')})
def handle_request(self, env, start_response):
self.env = env
env['swift.update.footers'] = self.footers_callback
resp = self._app_call(env)
for item in self._response_headers:
if item[0].lower() == FAKE_KEY.lower():
self.logger.info('TestFakeFooters resp found %s=%s' % item)
break
else:
self.logger.info('TestFakeFooters resp MISSING test footer')
start_response(self._response_status, self._response_headers,
self._response_exc_info)
return resp
class TestFakeFootersMiddleware(object):
def __init__(self, app, conf):
self.app = app
self.logger = get_logger(conf, log_route='fake-footers')
def __call__(self, env, start_response):
context = TestFakeFootersContext(self.app, self.logger)
return context.handle_request(env, start_response)
def filter_factory(global_conf, **local_conf):
conf = global_conf.copy()
conf.update(local_conf)
def except_filter(app):
return TestFakeFootersMiddleware(app, conf)
return except_filter
"""
from swift.common.utils import get_logger, cache_from_env
from swift.common.wsgi import WSGIContext
MEMCACHE_TIMEOUT = 3600
class FakeFootersContext(WSGIContext):
def __init__(self, app, logger):
super(FakeFootersContext, self).__init__(app)
self.logger = logger
def retrieve_footers(self, env):
key = 'swift.footer.%s' % env['PATH_INFO']
footers = self.memcache_client.get(key) or {}
self.logger.info('retrieve_footers %s %s' % (key, footers))
return footers
class ReaderWrapper(object):
def __init__(self, env, callback, context):
self.env = env
self.footer_callback = callback
self.original_read = env['wsgi.input'].read
self.context = context
def store_footers(self, footers):
key = 'swift.footer.%s' % self.env['PATH_INFO']
self.context.memcache_client.set(key, footers, MEMCACHE_TIMEOUT)
self.context.logger.info('store_footers %s %s' % (key, footers))
def read(self, size):
chunk = self.original_read(size)
if chunk == '':
# end of stream, call back for footers
footers = {}
self.footer_callback(footers)
self.store_footers(footers)
return chunk
def handle_request(self, env, start_response):
self.memcache_client = cache_from_env(env)
if not self.memcache_client:
raise Exception('Memcache required for FakeFooters')
callback = env.get('swift.update.footers')
if callback:
env['wsgi.input'] = self.ReaderWrapper(env, callback, self)
resp = self._app_call(env)
footers = self.retrieve_footers(env)
footer_keys = [f.lower() for f in footers.keys()]
# Gather the existing response headers, but do not
# include any of them that match a new footer to add.
mod_resp_headers = []
for h, v in self._response_headers:
if h.lower() in footer_keys:
# If there is a duplicate header, then the encrypter
# is not starting with a clean slate "crypto-wise",
# and needs to remove the associated remnant header.
self.logger.warning("Replacing Remnant header: %s" % h.lower())
else:
mod_resp_headers.append((h, v))
# Add the new headers from footers
for pair in footers.items():
mod_resp_headers.append(pair)
start_response(self._response_status, mod_resp_headers,
self._response_exc_info)
return resp
class FakeFootersMiddleware(object):
"""
Middleware that fakes footer handling by storing footer values in memcache.
"""
def __init__(self, app, conf):
self.app = app
self.logger = get_logger(conf, log_route='fake-footers')
def __call__(self, env, start_response):
context = FakeFootersContext(self.app,
self.logger)
return context.handle_request(env, start_response)
def filter_factory(global_conf, **local_conf):
conf = global_conf.copy()
conf.update(local_conf)
def except_filter(app):
return FakeFootersMiddleware(app, conf)
return except_filter