Compress response's content according to client's accepted encoding
Currently Glance ignores the Accept-Encoding header and returns responses as they are regardless the client accepts gzip or other type of compression. This patch adds this capability to glance (by using a middleware) supporting just gzip for now. Important note: - The patch uses a lazy compression for Content-Type application/octet-stream but in order to do that, the content-length has to be unset which means that when an image is downloaded the content-length will be unknown to the client. Fixes bug: 1150380 Change-Id: Ieb65837d4e3fe310f97d9666882ecc572b14956a
This commit is contained in:
parent
a9f9f137bb
commit
0a4f4aff6c
@ -55,3 +55,6 @@ paste.filter_factory = glance.api.middleware.context:UnauthenticatedContextMiddl
|
||||
[filter:authtoken]
|
||||
paste.filter_factory = keystoneclient.middleware.auth_token:filter_factory
|
||||
delay_auth_decision = true
|
||||
|
||||
[filter:gzip]
|
||||
paste.filter_factory = glance.api.middleware.gzip:GzipMiddleware.factory
|
||||
|
65
glance/api/middleware/gzip.py
Normal file
65
glance/api/middleware/gzip.py
Normal file
@ -0,0 +1,65 @@
|
||||
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
||||
|
||||
# Copyright 2013 Red Hat, Inc.
|
||||
# All Rights Reserved.
|
||||
#
|
||||
# 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.
|
||||
|
||||
"""
|
||||
Use gzip compression if the client accepts it.
|
||||
"""
|
||||
|
||||
import re
|
||||
|
||||
from glance.common import wsgi
|
||||
import glance.openstack.common.log as logging
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class GzipMiddleware(wsgi.Middleware):
|
||||
|
||||
re_zip = re.compile(r'\bgzip\b')
|
||||
|
||||
def __init__(self, app):
|
||||
LOG.info(_("Initialized gzip middleware"))
|
||||
super(GzipMiddleware, self).__init__(app)
|
||||
|
||||
def process_response(self, response):
|
||||
request = response.request
|
||||
accept_encoding = request.headers.get('Accept-Encoding', '')
|
||||
|
||||
if self.re_zip.search(accept_encoding):
|
||||
# NOTE(flaper87): Webob removes the content-md5 when
|
||||
# app_iter is called. We'll keep it and reset it later
|
||||
checksum = response.headers.get("Content-MD5")
|
||||
|
||||
# NOTE(flaper87): We'll use lazy for images so
|
||||
# that they can be compressed without reading
|
||||
# the whole content in memory. Notice that using
|
||||
# lazy will set response's content-length to 0.
|
||||
content_type = response.headers["Content-Type"]
|
||||
lazy = content_type == "application/octet-stream"
|
||||
|
||||
# NOTE(flaper87): Webob takes care of the compression
|
||||
# process, it will replace the body either with a
|
||||
# compressed body or a generator - used for lazy com
|
||||
# pression - depending on the lazy value.
|
||||
#
|
||||
# Webob itself will set the Content-Encoding header.
|
||||
response.encode_content(lazy=lazy)
|
||||
|
||||
if checksum:
|
||||
response.headers['Content-MD5'] = checksum
|
||||
|
||||
return response
|
@ -548,6 +548,7 @@ class Resource(object):
|
||||
may raise a webob.exc exception or return a dict, which will be
|
||||
serialized by requested content type.
|
||||
"""
|
||||
|
||||
def __init__(self, controller, deserializer=None, serializer=None):
|
||||
"""
|
||||
:param controller: object that implement methods created by routes lib
|
||||
|
@ -354,24 +354,25 @@ enable_v2_api= %(enable_v2_api)s
|
||||
flavor = %(deployment_flavor)s
|
||||
"""
|
||||
self.paste_conf_base = """[pipeline:glance-api]
|
||||
pipeline = versionnegotiation unauthenticated-context rootapp
|
||||
pipeline = versionnegotiation gzip unauthenticated-context rootapp
|
||||
|
||||
[pipeline:glance-api-caching]
|
||||
pipeline = versionnegotiation unauthenticated-context cache rootapp
|
||||
pipeline = versionnegotiation gzip unauthenticated-context cache rootapp
|
||||
|
||||
[pipeline:glance-api-cachemanagement]
|
||||
pipeline =
|
||||
versionnegotiation
|
||||
gzip
|
||||
unauthenticated-context
|
||||
cache
|
||||
cache_manage
|
||||
rootapp
|
||||
|
||||
[pipeline:glance-api-fakeauth]
|
||||
pipeline = versionnegotiation fakeauth context rootapp
|
||||
pipeline = versionnegotiation gzip fakeauth context rootapp
|
||||
|
||||
[pipeline:glance-api-noauth]
|
||||
pipeline = versionnegotiation context rootapp
|
||||
pipeline = versionnegotiation gzip context rootapp
|
||||
|
||||
[composite:rootapp]
|
||||
paste.composite_factory = glance.api:root_app_factory
|
||||
@ -392,6 +393,9 @@ paste.app_factory = glance.api.v2.router:API.factory
|
||||
paste.filter_factory =
|
||||
glance.api.middleware.version_negotiation:VersionNegotiationFilter.factory
|
||||
|
||||
[filter:gzip]
|
||||
paste.filter_factory = glance.api.middleware.gzip:GzipMiddleware.factory
|
||||
|
||||
[filter:cache]
|
||||
paste.filter_factory = glance.api.middleware.cache:CacheFilter.factory
|
||||
|
||||
|
50
glance/tests/functional/test_gzip_middleware.py
Normal file
50
glance/tests/functional/test_gzip_middleware.py
Normal file
@ -0,0 +1,50 @@
|
||||
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
||||
|
||||
# Copyright 2013 Red Hat, Inc
|
||||
# All Rights Reserved.
|
||||
#
|
||||
# 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.
|
||||
|
||||
"""Tests gzip middleware."""
|
||||
|
||||
import httplib2
|
||||
|
||||
from glance.tests import functional
|
||||
from glance.tests import utils
|
||||
|
||||
|
||||
class GzipMiddlewareTest(functional.FunctionalTest):
|
||||
|
||||
@utils.skip_if_disabled
|
||||
def test_gzip_requests(self):
|
||||
self.cleanup()
|
||||
self.start_servers(**self.__dict__.copy())
|
||||
|
||||
def request(path, headers=None):
|
||||
# We don't care what version we're using here so,
|
||||
# sticking with latest
|
||||
url = 'http://127.0.0.1:%s/v2/%s' % (self.api_port, path)
|
||||
http = httplib2.Http()
|
||||
return http.request(url, 'GET', headers=headers)
|
||||
|
||||
# Accept-Encoding: Identity
|
||||
headers = {'Accept-Encoding': 'identity'}
|
||||
response, content = request('images', headers=headers)
|
||||
self.assertEqual(response.get("-content-encoding"), None)
|
||||
|
||||
# Accept-Encoding: gzip
|
||||
headers = {'Accept-Encoding': 'gzip'}
|
||||
response, content = request('images', headers=headers)
|
||||
self.assertEqual(response.get("-content-encoding"), 'gzip')
|
||||
|
||||
self.stop_servers()
|
Loading…
Reference in New Issue
Block a user