Move files out of the namespace package
Move the public API out of oslo.middleware to oslo_middleware. Retain the ability to import from the old namespace package for backwards compatibility for this release cycle. bp/drop-namespace-packages Change-Id: If88c65c82b64f096a02f7ec62e019aea4de2f9d3
This commit is contained in:
parent
860cdee514
commit
8e06ca5bd0
@ -2,5 +2,5 @@
|
||||
API
|
||||
=====
|
||||
|
||||
.. automodule:: oslo.middleware
|
||||
.. automodule:: oslo_middleware
|
||||
:members:
|
||||
|
@ -10,14 +10,19 @@
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
__all__ = ['CatchErrors',
|
||||
'CorrelationId',
|
||||
'Debug',
|
||||
'RequestId',
|
||||
'RequestBodySizeLimiter']
|
||||
import warnings
|
||||
|
||||
from oslo.middleware.catch_errors import CatchErrors
|
||||
from oslo.middleware.correlation_id import CorrelationId
|
||||
from oslo.middleware.debug import Debug
|
||||
from oslo.middleware.request_id import RequestId
|
||||
from oslo.middleware.sizelimit import RequestBodySizeLimiter
|
||||
from oslo_middleware import *
|
||||
|
||||
|
||||
def deprecated():
|
||||
new_name = __name__.replace('.', '_')
|
||||
warnings.warn(
|
||||
('The oslo namespace package is deprecated. Please use %s instead.' %
|
||||
new_name),
|
||||
DeprecationWarning,
|
||||
stacklevel=3,
|
||||
)
|
||||
|
||||
|
||||
deprecated()
|
||||
|
@ -1,6 +1,3 @@
|
||||
# Copyright 2011 OpenStack Foundation.
|
||||
# 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
|
||||
@ -13,44 +10,4 @@
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
"""Base class(es) for WSGI Middleware."""
|
||||
|
||||
import webob.dec
|
||||
|
||||
|
||||
class Middleware(object):
|
||||
"""Base WSGI middleware wrapper.
|
||||
|
||||
These classes require an application to be initialized that will be called
|
||||
next. By default the middleware will simply call its wrapped app, or you
|
||||
can override __call__ to customize its behavior.
|
||||
"""
|
||||
|
||||
@classmethod
|
||||
def factory(cls, global_conf, **local_conf):
|
||||
"""Factory method for paste.deploy."""
|
||||
return cls
|
||||
|
||||
def __init__(self, application):
|
||||
self.application = application
|
||||
|
||||
def process_request(self, req):
|
||||
"""Called on each request.
|
||||
|
||||
If this returns None, the next application down the stack will be
|
||||
executed. If it returns a response then that response will be returned
|
||||
and execution will stop here.
|
||||
"""
|
||||
return None
|
||||
|
||||
def process_response(self, response):
|
||||
"""Do whatever you'd like to the response."""
|
||||
return response
|
||||
|
||||
@webob.dec.wsgify
|
||||
def __call__(self, req):
|
||||
response = self.process_request(req)
|
||||
if response:
|
||||
return response
|
||||
response = req.get_response(self.application)
|
||||
return self.process_response(response)
|
||||
from oslo_middleware.base import * # noqa
|
||||
|
@ -1,6 +1,3 @@
|
||||
# Copyright (c) 2013 NEC Corporation
|
||||
# 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
|
||||
@ -13,31 +10,4 @@
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
import logging
|
||||
|
||||
import webob.dec
|
||||
import webob.exc
|
||||
|
||||
from oslo.middleware import base
|
||||
from oslo.middleware.i18n import _LE
|
||||
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class CatchErrors(base.Middleware):
|
||||
"""Middleware that provides high-level error handling.
|
||||
|
||||
It catches all exceptions from subsequent applications in WSGI pipeline
|
||||
to hide internal errors from API response.
|
||||
"""
|
||||
|
||||
@webob.dec.wsgify
|
||||
def __call__(self, req):
|
||||
try:
|
||||
response = req.get_response(self.application)
|
||||
except Exception:
|
||||
LOG.exception(_LE('An error occurred during '
|
||||
'processing the request: %s'))
|
||||
response = webob.exc.HTTPInternalServerError()
|
||||
return response
|
||||
from oslo_middleware.catch_errors import * # noqa
|
||||
|
@ -1,6 +1,3 @@
|
||||
# Copyright (c) 2013 Rackspace Hosting
|
||||
# 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
|
||||
@ -13,15 +10,4 @@
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
import uuid
|
||||
|
||||
from oslo.middleware import base
|
||||
|
||||
|
||||
class CorrelationId(base.Middleware):
|
||||
"Middleware that attaches a correlation id to WSGI request"
|
||||
|
||||
def process_request(self, req):
|
||||
correlation_id = (req.headers.get("X_CORRELATION_ID") or
|
||||
str(uuid.uuid4()))
|
||||
req.headers['X_CORRELATION_ID'] = correlation_id
|
||||
from oslo_middleware.correlation_id import * # noqa
|
||||
|
@ -1,6 +1,3 @@
|
||||
# Copyright 2011 OpenStack Foundation.
|
||||
# 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
|
||||
@ -13,48 +10,4 @@
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
"""Debug middleware"""
|
||||
|
||||
from __future__ import print_function
|
||||
|
||||
import sys
|
||||
|
||||
import six
|
||||
import webob.dec
|
||||
|
||||
from oslo.middleware import base
|
||||
|
||||
|
||||
class Debug(base.Middleware):
|
||||
"""Helper class that returns debug information.
|
||||
|
||||
Can be inserted into any WSGI application chain to get information about
|
||||
the request and response.
|
||||
"""
|
||||
|
||||
@webob.dec.wsgify
|
||||
def __call__(self, req):
|
||||
print(("*" * 40) + " REQUEST ENVIRON")
|
||||
for key, value in req.environ.items():
|
||||
print(key, "=", value)
|
||||
print()
|
||||
resp = req.get_response(self.application)
|
||||
|
||||
print(("*" * 40) + " RESPONSE HEADERS")
|
||||
for (key, value) in six.iteritems(resp.headers):
|
||||
print(key, "=", value)
|
||||
print()
|
||||
|
||||
resp.app_iter = self.print_generator(resp.app_iter)
|
||||
|
||||
return resp
|
||||
|
||||
@staticmethod
|
||||
def print_generator(app_iter):
|
||||
"""Prints the contents of a wrapper string iterator when iterated."""
|
||||
print(("*" * 40) + " BODY")
|
||||
for part in app_iter:
|
||||
sys.stdout.write(part)
|
||||
sys.stdout.flush()
|
||||
yield part
|
||||
print()
|
||||
from oslo_middleware.debug import * # noqa
|
||||
|
@ -1,6 +1,3 @@
|
||||
# Copyright (c) 2013 NEC Corporation
|
||||
# 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
|
||||
@ -13,28 +10,4 @@
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
from oslo_context import context
|
||||
import webob.dec
|
||||
|
||||
from oslo.middleware import base
|
||||
|
||||
|
||||
ENV_REQUEST_ID = 'openstack.request_id'
|
||||
HTTP_RESP_HEADER_REQUEST_ID = 'x-openstack-request-id'
|
||||
|
||||
|
||||
class RequestId(base.Middleware):
|
||||
"""Middleware that ensures request ID.
|
||||
|
||||
It ensures to assign request ID for each API request and set it to
|
||||
request environment. The request ID is also added to API response.
|
||||
"""
|
||||
|
||||
@webob.dec.wsgify
|
||||
def __call__(self, req):
|
||||
req_id = context.generate_request_id()
|
||||
req.environ[ENV_REQUEST_ID] = req_id
|
||||
response = req.get_response(self.application)
|
||||
if HTTP_RESP_HEADER_REQUEST_ID not in response.headers:
|
||||
response.headers.add(HTTP_RESP_HEADER_REQUEST_ID, req_id)
|
||||
return response
|
||||
from oslo_middleware.request_id import * # noqa
|
||||
|
@ -1,5 +1,3 @@
|
||||
# Copyright (c) 2012 Red Hat, 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
|
||||
@ -12,84 +10,4 @@
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
"""
|
||||
Request Body limiting middleware.
|
||||
|
||||
"""
|
||||
|
||||
from oslo.config import cfg
|
||||
from oslo.config import cfgfilter
|
||||
import webob.dec
|
||||
import webob.exc
|
||||
|
||||
from oslo.middleware import base
|
||||
from oslo.middleware.i18n import _
|
||||
|
||||
|
||||
_oldopts = [cfg.DeprecatedOpt('osapi_max_request_body_size',
|
||||
group='DEFAULT'),
|
||||
cfg.DeprecatedOpt('max_request_body_size',
|
||||
group='DEFAULT')]
|
||||
|
||||
_opts = [
|
||||
# default request size is 112k
|
||||
cfg.IntOpt('max_request_body_size',
|
||||
default=114688,
|
||||
help='The maximum body size for each '
|
||||
' request, in bytes.',
|
||||
deprecated_opts=_oldopts)
|
||||
]
|
||||
|
||||
CONF = cfgfilter.ConfigFilter(cfg.CONF)
|
||||
CONF.register_opts(_opts, group='oslo_middleware')
|
||||
|
||||
|
||||
class LimitingReader(object):
|
||||
"""Reader to limit the size of an incoming request."""
|
||||
def __init__(self, data, limit):
|
||||
"""Initiates LimitingReader object.
|
||||
|
||||
:param data: Underlying data object
|
||||
:param limit: maximum number of bytes the reader should allow
|
||||
"""
|
||||
self.data = data
|
||||
self.limit = limit
|
||||
self.bytes_read = 0
|
||||
|
||||
def __iter__(self):
|
||||
for chunk in self.data:
|
||||
self.bytes_read += len(chunk)
|
||||
if self.bytes_read > self.limit:
|
||||
msg = _("Request is too large.")
|
||||
raise webob.exc.HTTPRequestEntityTooLarge(explanation=msg)
|
||||
else:
|
||||
yield chunk
|
||||
|
||||
def read(self, i=None):
|
||||
# NOTE(jamielennox): We can't simply provide the default to the read()
|
||||
# call as the expected default differs between mod_wsgi and eventlet
|
||||
if i is None:
|
||||
result = self.data.read()
|
||||
else:
|
||||
result = self.data.read(i)
|
||||
self.bytes_read += len(result)
|
||||
if self.bytes_read > self.limit:
|
||||
msg = _("Request is too large.")
|
||||
raise webob.exc.HTTPRequestEntityTooLarge(explanation=msg)
|
||||
return result
|
||||
|
||||
|
||||
class RequestBodySizeLimiter(base.Middleware):
|
||||
"""Limit the size of incoming requests."""
|
||||
|
||||
@webob.dec.wsgify
|
||||
def __call__(self, req):
|
||||
max_size = CONF.oslo_middleware.max_request_body_size
|
||||
if (req.content_length is not None and
|
||||
req.content_length > max_size):
|
||||
msg = _("Request is too large.")
|
||||
raise webob.exc.HTTPRequestEntityTooLarge(explanation=msg)
|
||||
if req.content_length is None and req.is_body_readable:
|
||||
limiter = LimitingReader(req.body_file, max_size)
|
||||
req.body_file = limiter
|
||||
return self.application
|
||||
from oslo_middleware.sizelimit import * # noqa
|
||||
|
23
oslo_middleware/__init__.py
Normal file
23
oslo_middleware/__init__.py
Normal file
@ -0,0 +1,23 @@
|
||||
# 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.
|
||||
|
||||
__all__ = ['CatchErrors',
|
||||
'CorrelationId',
|
||||
'Debug',
|
||||
'RequestId',
|
||||
'RequestBodySizeLimiter']
|
||||
|
||||
from oslo_middleware.catch_errors import CatchErrors
|
||||
from oslo_middleware.correlation_id import CorrelationId
|
||||
from oslo_middleware.debug import Debug
|
||||
from oslo_middleware.request_id import RequestId
|
||||
from oslo_middleware.sizelimit import RequestBodySizeLimiter
|
56
oslo_middleware/base.py
Normal file
56
oslo_middleware/base.py
Normal file
@ -0,0 +1,56 @@
|
||||
# Copyright 2011 OpenStack Foundation.
|
||||
# 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.
|
||||
|
||||
"""Base class(es) for WSGI Middleware."""
|
||||
|
||||
import webob.dec
|
||||
|
||||
|
||||
class Middleware(object):
|
||||
"""Base WSGI middleware wrapper.
|
||||
|
||||
These classes require an application to be initialized that will be called
|
||||
next. By default the middleware will simply call its wrapped app, or you
|
||||
can override __call__ to customize its behavior.
|
||||
"""
|
||||
|
||||
@classmethod
|
||||
def factory(cls, global_conf, **local_conf):
|
||||
"""Factory method for paste.deploy."""
|
||||
return cls
|
||||
|
||||
def __init__(self, application):
|
||||
self.application = application
|
||||
|
||||
def process_request(self, req):
|
||||
"""Called on each request.
|
||||
|
||||
If this returns None, the next application down the stack will be
|
||||
executed. If it returns a response then that response will be returned
|
||||
and execution will stop here.
|
||||
"""
|
||||
return None
|
||||
|
||||
def process_response(self, response):
|
||||
"""Do whatever you'd like to the response."""
|
||||
return response
|
||||
|
||||
@webob.dec.wsgify
|
||||
def __call__(self, req):
|
||||
response = self.process_request(req)
|
||||
if response:
|
||||
return response
|
||||
response = req.get_response(self.application)
|
||||
return self.process_response(response)
|
43
oslo_middleware/catch_errors.py
Normal file
43
oslo_middleware/catch_errors.py
Normal file
@ -0,0 +1,43 @@
|
||||
# Copyright (c) 2013 NEC Corporation
|
||||
# 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.
|
||||
|
||||
import logging
|
||||
|
||||
import webob.dec
|
||||
import webob.exc
|
||||
|
||||
from oslo_middleware import base
|
||||
from oslo_middleware.i18n import _LE
|
||||
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class CatchErrors(base.Middleware):
|
||||
"""Middleware that provides high-level error handling.
|
||||
|
||||
It catches all exceptions from subsequent applications in WSGI pipeline
|
||||
to hide internal errors from API response.
|
||||
"""
|
||||
|
||||
@webob.dec.wsgify
|
||||
def __call__(self, req):
|
||||
try:
|
||||
response = req.get_response(self.application)
|
||||
except Exception:
|
||||
LOG.exception(_LE('An error occurred during '
|
||||
'processing the request: %s'))
|
||||
response = webob.exc.HTTPInternalServerError()
|
||||
return response
|
27
oslo_middleware/correlation_id.py
Normal file
27
oslo_middleware/correlation_id.py
Normal file
@ -0,0 +1,27 @@
|
||||
# Copyright (c) 2013 Rackspace Hosting
|
||||
# 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.
|
||||
|
||||
import uuid
|
||||
|
||||
from oslo_middleware import base
|
||||
|
||||
|
||||
class CorrelationId(base.Middleware):
|
||||
"Middleware that attaches a correlation id to WSGI request"
|
||||
|
||||
def process_request(self, req):
|
||||
correlation_id = (req.headers.get("X_CORRELATION_ID") or
|
||||
str(uuid.uuid4()))
|
||||
req.headers['X_CORRELATION_ID'] = correlation_id
|
60
oslo_middleware/debug.py
Normal file
60
oslo_middleware/debug.py
Normal file
@ -0,0 +1,60 @@
|
||||
# Copyright 2011 OpenStack Foundation.
|
||||
# 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.
|
||||
|
||||
"""Debug middleware"""
|
||||
|
||||
from __future__ import print_function
|
||||
|
||||
import sys
|
||||
|
||||
import six
|
||||
import webob.dec
|
||||
|
||||
from oslo_middleware import base
|
||||
|
||||
|
||||
class Debug(base.Middleware):
|
||||
"""Helper class that returns debug information.
|
||||
|
||||
Can be inserted into any WSGI application chain to get information about
|
||||
the request and response.
|
||||
"""
|
||||
|
||||
@webob.dec.wsgify
|
||||
def __call__(self, req):
|
||||
print(("*" * 40) + " REQUEST ENVIRON")
|
||||
for key, value in req.environ.items():
|
||||
print(key, "=", value)
|
||||
print()
|
||||
resp = req.get_response(self.application)
|
||||
|
||||
print(("*" * 40) + " RESPONSE HEADERS")
|
||||
for (key, value) in six.iteritems(resp.headers):
|
||||
print(key, "=", value)
|
||||
print()
|
||||
|
||||
resp.app_iter = self.print_generator(resp.app_iter)
|
||||
|
||||
return resp
|
||||
|
||||
@staticmethod
|
||||
def print_generator(app_iter):
|
||||
"""Prints the contents of a wrapper string iterator when iterated."""
|
||||
print(("*" * 40) + " BODY")
|
||||
for part in app_iter:
|
||||
sys.stdout.write(part)
|
||||
sys.stdout.flush()
|
||||
yield part
|
||||
print()
|
@ -20,7 +20,7 @@ __all__ = [
|
||||
|
||||
import copy
|
||||
|
||||
from oslo.middleware import sizelimit
|
||||
from oslo_middleware import sizelimit
|
||||
|
||||
|
||||
def list_opts():
|
||||
@ -42,4 +42,4 @@ def list_opts():
|
||||
|
||||
:returns: a list of (group_name, opts) tuples
|
||||
"""
|
||||
return [('oslo_middleware', copy.deepcopy(sizelimit._opts))]
|
||||
return [('oslo_middleware', copy.deepcopy(sizelimit._opts))]
|
40
oslo_middleware/request_id.py
Normal file
40
oslo_middleware/request_id.py
Normal file
@ -0,0 +1,40 @@
|
||||
# Copyright (c) 2013 NEC Corporation
|
||||
# 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.
|
||||
|
||||
from oslo_context import context
|
||||
import webob.dec
|
||||
|
||||
from oslo_middleware import base
|
||||
|
||||
|
||||
ENV_REQUEST_ID = 'openstack.request_id'
|
||||
HTTP_RESP_HEADER_REQUEST_ID = 'x-openstack-request-id'
|
||||
|
||||
|
||||
class RequestId(base.Middleware):
|
||||
"""Middleware that ensures request ID.
|
||||
|
||||
It ensures to assign request ID for each API request and set it to
|
||||
request environment. The request ID is also added to API response.
|
||||
"""
|
||||
|
||||
@webob.dec.wsgify
|
||||
def __call__(self, req):
|
||||
req_id = context.generate_request_id()
|
||||
req.environ[ENV_REQUEST_ID] = req_id
|
||||
response = req.get_response(self.application)
|
||||
if HTTP_RESP_HEADER_REQUEST_ID not in response.headers:
|
||||
response.headers.add(HTTP_RESP_HEADER_REQUEST_ID, req_id)
|
||||
return response
|
95
oslo_middleware/sizelimit.py
Normal file
95
oslo_middleware/sizelimit.py
Normal file
@ -0,0 +1,95 @@
|
||||
# Copyright (c) 2012 Red Hat, 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.
|
||||
|
||||
"""
|
||||
Request Body limiting middleware.
|
||||
|
||||
"""
|
||||
|
||||
from oslo.config import cfg
|
||||
from oslo.config import cfgfilter
|
||||
import webob.dec
|
||||
import webob.exc
|
||||
|
||||
from oslo_middleware import base
|
||||
from oslo_middleware.i18n import _
|
||||
|
||||
|
||||
_oldopts = [cfg.DeprecatedOpt('osapi_max_request_body_size',
|
||||
group='DEFAULT'),
|
||||
cfg.DeprecatedOpt('max_request_body_size',
|
||||
group='DEFAULT')]
|
||||
|
||||
_opts = [
|
||||
# default request size is 112k
|
||||
cfg.IntOpt('max_request_body_size',
|
||||
default=114688,
|
||||
help='The maximum body size for each '
|
||||
' request, in bytes.',
|
||||
deprecated_opts=_oldopts)
|
||||
]
|
||||
|
||||
CONF = cfgfilter.ConfigFilter(cfg.CONF)
|
||||
CONF.register_opts(_opts, group='oslo_middleware')
|
||||
|
||||
|
||||
class LimitingReader(object):
|
||||
"""Reader to limit the size of an incoming request."""
|
||||
def __init__(self, data, limit):
|
||||
"""Initiates LimitingReader object.
|
||||
|
||||
:param data: Underlying data object
|
||||
:param limit: maximum number of bytes the reader should allow
|
||||
"""
|
||||
self.data = data
|
||||
self.limit = limit
|
||||
self.bytes_read = 0
|
||||
|
||||
def __iter__(self):
|
||||
for chunk in self.data:
|
||||
self.bytes_read += len(chunk)
|
||||
if self.bytes_read > self.limit:
|
||||
msg = _("Request is too large.")
|
||||
raise webob.exc.HTTPRequestEntityTooLarge(explanation=msg)
|
||||
else:
|
||||
yield chunk
|
||||
|
||||
def read(self, i=None):
|
||||
# NOTE(jamielennox): We can't simply provide the default to the read()
|
||||
# call as the expected default differs between mod_wsgi and eventlet
|
||||
if i is None:
|
||||
result = self.data.read()
|
||||
else:
|
||||
result = self.data.read(i)
|
||||
self.bytes_read += len(result)
|
||||
if self.bytes_read > self.limit:
|
||||
msg = _("Request is too large.")
|
||||
raise webob.exc.HTTPRequestEntityTooLarge(explanation=msg)
|
||||
return result
|
||||
|
||||
|
||||
class RequestBodySizeLimiter(base.Middleware):
|
||||
"""Limit the size of incoming requests."""
|
||||
|
||||
@webob.dec.wsgify
|
||||
def __call__(self, req):
|
||||
max_size = CONF.oslo_middleware.max_request_body_size
|
||||
if (req.content_length is not None and
|
||||
req.content_length > max_size):
|
||||
msg = _("Request is too large.")
|
||||
raise webob.exc.HTTPRequestEntityTooLarge(explanation=msg)
|
||||
if req.content_length is None and req.is_body_readable:
|
||||
limiter = LimitingReader(req.body_file, max_size)
|
||||
req.body_file = limiter
|
||||
return self.application
|
0
oslo_middleware/tests/__init__.py
Normal file
0
oslo_middleware/tests/__init__.py
Normal file
47
oslo_middleware/tests/test_catch_errors.py
Normal file
47
oslo_middleware/tests/test_catch_errors.py
Normal file
@ -0,0 +1,47 @@
|
||||
# Copyright (c) 2013 NEC Corporation
|
||||
# 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.
|
||||
|
||||
import mock
|
||||
from oslotest import base as test_base
|
||||
import webob.dec
|
||||
import webob.exc
|
||||
|
||||
from oslo_middleware import catch_errors
|
||||
|
||||
|
||||
class CatchErrorsTest(test_base.BaseTestCase):
|
||||
|
||||
def _test_has_request_id(self, application, expected_code=None):
|
||||
app = catch_errors.CatchErrors(application)
|
||||
req = webob.Request.blank('/test')
|
||||
res = req.get_response(app)
|
||||
self.assertEqual(expected_code, res.status_int)
|
||||
|
||||
def test_success_response(self):
|
||||
@webob.dec.wsgify
|
||||
def application(req):
|
||||
return 'Hello, World!!!'
|
||||
|
||||
self._test_has_request_id(application, webob.exc.HTTPOk.code)
|
||||
|
||||
def test_internal_server_error(self):
|
||||
@webob.dec.wsgify
|
||||
def application(req):
|
||||
raise Exception()
|
||||
|
||||
with mock.patch.object(catch_errors.LOG, 'exception') as log_exc:
|
||||
self._test_has_request_id(application,
|
||||
webob.exc.HTTPInternalServerError.code)
|
||||
self.assertEqual(1, log_exc.call_count)
|
53
oslo_middleware/tests/test_correlation_id.py
Normal file
53
oslo_middleware/tests/test_correlation_id.py
Normal file
@ -0,0 +1,53 @@
|
||||
# Copyright (c) 2013 Rackspace Hosting
|
||||
# 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.
|
||||
|
||||
import uuid
|
||||
|
||||
import mock
|
||||
from oslotest import base as test_base
|
||||
from oslotest import moxstubout
|
||||
|
||||
from oslo_middleware import correlation_id
|
||||
|
||||
|
||||
class CorrelationIdTest(test_base.BaseTestCase):
|
||||
|
||||
def setUp(self):
|
||||
super(CorrelationIdTest, self).setUp()
|
||||
self.stubs = self.useFixture(moxstubout.MoxStubout()).stubs
|
||||
|
||||
def test_process_request(self):
|
||||
app = mock.Mock()
|
||||
req = mock.Mock()
|
||||
req.headers = {}
|
||||
|
||||
mock_uuid4 = mock.Mock()
|
||||
mock_uuid4.return_value = "fake_uuid"
|
||||
self.stubs.Set(uuid, 'uuid4', mock_uuid4)
|
||||
|
||||
middleware = correlation_id.CorrelationId(app)
|
||||
middleware(req)
|
||||
|
||||
self.assertEqual(req.headers.get("X_CORRELATION_ID"), "fake_uuid")
|
||||
|
||||
def test_process_request_should_not_regenerate_correlation_id(self):
|
||||
app = mock.Mock()
|
||||
req = mock.Mock()
|
||||
req.headers = {"X_CORRELATION_ID": "correlation_id"}
|
||||
|
||||
middleware = correlation_id.CorrelationId(app)
|
||||
middleware(req)
|
||||
|
||||
self.assertEqual(req.headers.get("X_CORRELATION_ID"), "correlation_id")
|
37
oslo_middleware/tests/test_request_id.py
Normal file
37
oslo_middleware/tests/test_request_id.py
Normal file
@ -0,0 +1,37 @@
|
||||
# Copyright (c) 2013 NEC Corporation
|
||||
# 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.
|
||||
|
||||
|
||||
from oslotest import base as test_base
|
||||
from testtools import matchers
|
||||
import webob
|
||||
import webob.dec
|
||||
|
||||
from oslo_middleware import request_id
|
||||
|
||||
|
||||
class RequestIdTest(test_base.BaseTestCase):
|
||||
def test_generate_request_id(self):
|
||||
@webob.dec.wsgify
|
||||
def application(req):
|
||||
return req.environ[request_id.ENV_REQUEST_ID]
|
||||
|
||||
app = request_id.RequestId(application)
|
||||
req = webob.Request.blank('/test')
|
||||
res = req.get_response(app)
|
||||
res_req_id = res.headers.get(request_id.HTTP_RESP_HEADER_REQUEST_ID)
|
||||
self.assertThat(res_req_id, matchers.StartsWith(b'req-'))
|
||||
# request-id in request environ is returned as response body
|
||||
self.assertEqual(res_req_id, res.body)
|
108
oslo_middleware/tests/test_sizelimit.py
Normal file
108
oslo_middleware/tests/test_sizelimit.py
Normal file
@ -0,0 +1,108 @@
|
||||
# Copyright (c) 2012 Red Hat, 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.
|
||||
|
||||
from oslotest import base as test_base
|
||||
import six
|
||||
import webob
|
||||
|
||||
from oslo.config import fixture as config
|
||||
from oslo_middleware import sizelimit
|
||||
|
||||
|
||||
class TestLimitingReader(test_base.BaseTestCase):
|
||||
|
||||
def test_limiting_reader(self):
|
||||
BYTES = 1024
|
||||
bytes_read = 0
|
||||
data = six.StringIO("*" * BYTES)
|
||||
for chunk in sizelimit.LimitingReader(data, BYTES):
|
||||
bytes_read += len(chunk)
|
||||
|
||||
self.assertEqual(bytes_read, BYTES)
|
||||
|
||||
bytes_read = 0
|
||||
data = six.StringIO("*" * BYTES)
|
||||
reader = sizelimit.LimitingReader(data, BYTES)
|
||||
byte = reader.read(1)
|
||||
while len(byte) != 0:
|
||||
bytes_read += 1
|
||||
byte = reader.read(1)
|
||||
|
||||
self.assertEqual(bytes_read, BYTES)
|
||||
|
||||
def test_read_default_value(self):
|
||||
BYTES = 1024
|
||||
data_str = "*" * BYTES
|
||||
data = six.StringIO(data_str)
|
||||
reader = sizelimit.LimitingReader(data, BYTES)
|
||||
res = reader.read()
|
||||
self.assertEqual(data_str, res)
|
||||
|
||||
def test_limiting_reader_fails(self):
|
||||
BYTES = 1024
|
||||
|
||||
def _consume_all_iter():
|
||||
bytes_read = 0
|
||||
data = six.StringIO("*" * BYTES)
|
||||
for chunk in sizelimit.LimitingReader(data, BYTES - 1):
|
||||
bytes_read += len(chunk)
|
||||
|
||||
self.assertRaises(webob.exc.HTTPRequestEntityTooLarge,
|
||||
_consume_all_iter)
|
||||
|
||||
def _consume_all_read():
|
||||
bytes_read = 0
|
||||
data = six.StringIO("*" * BYTES)
|
||||
reader = sizelimit.LimitingReader(data, BYTES - 1)
|
||||
byte = reader.read(1)
|
||||
while len(byte) != 0:
|
||||
bytes_read += 1
|
||||
byte = reader.read(1)
|
||||
|
||||
self.assertRaises(webob.exc.HTTPRequestEntityTooLarge,
|
||||
_consume_all_read)
|
||||
|
||||
|
||||
class TestRequestBodySizeLimiter(test_base.BaseTestCase):
|
||||
|
||||
def setUp(self):
|
||||
super(TestRequestBodySizeLimiter, self).setUp()
|
||||
fixture = self.useFixture(config.Config(sizelimit.CONF))
|
||||
self.MAX_REQUEST_BODY_SIZE = \
|
||||
fixture.conf.oslo_middleware.max_request_body_size
|
||||
|
||||
@webob.dec.wsgify()
|
||||
def fake_app(req):
|
||||
return webob.Response(req.body)
|
||||
|
||||
self.middleware = sizelimit.RequestBodySizeLimiter(fake_app)
|
||||
self.request = webob.Request.blank('/', method='POST')
|
||||
|
||||
def test_content_length_acceptable(self):
|
||||
self.request.headers['Content-Length'] = self.MAX_REQUEST_BODY_SIZE
|
||||
self.request.body = b"0" * self.MAX_REQUEST_BODY_SIZE
|
||||
response = self.request.get_response(self.middleware)
|
||||
self.assertEqual(response.status_int, 200)
|
||||
|
||||
def test_content_length_too_large(self):
|
||||
self.request.headers['Content-Length'] = self.MAX_REQUEST_BODY_SIZE + 1
|
||||
self.request.body = b"0" * (self.MAX_REQUEST_BODY_SIZE + 1)
|
||||
response = self.request.get_response(self.middleware)
|
||||
self.assertEqual(response.status_int, 413)
|
||||
|
||||
def test_request_too_large_no_content_length(self):
|
||||
self.request.body = b"0" * (self.MAX_REQUEST_BODY_SIZE + 1)
|
||||
self.request.headers['Content-Length'] = None
|
||||
response = self.request.get_response(self.middleware)
|
||||
self.assertEqual(response.status_int, 413)
|
@ -22,12 +22,13 @@ classifier =
|
||||
[files]
|
||||
packages =
|
||||
oslo
|
||||
oslo_middleware
|
||||
namespace_packages =
|
||||
oslo
|
||||
|
||||
[entry_points]
|
||||
oslo.config.opts =
|
||||
oslo.middleware = oslo.middleware.opts:list_opts
|
||||
oslo.middleware = oslo_middleware.opts:list_opts
|
||||
|
||||
[build_sphinx]
|
||||
source-dir = doc/source
|
||||
|
61
tests/test_warning.py
Normal file
61
tests/test_warning.py
Normal file
@ -0,0 +1,61 @@
|
||||
# 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 imp
|
||||
import os
|
||||
import warnings
|
||||
|
||||
import mock
|
||||
from oslotest import base as test_base
|
||||
import six
|
||||
|
||||
|
||||
class DeprecationWarningTest(test_base.BaseTestCase):
|
||||
|
||||
@mock.patch('warnings.warn')
|
||||
def test_warning(self, mock_warn):
|
||||
import oslo.middleware
|
||||
imp.reload(oslo.middleware)
|
||||
self.assertTrue(mock_warn.called)
|
||||
args = mock_warn.call_args
|
||||
self.assertIn('oslo_middleware', args[0][0])
|
||||
self.assertIn('deprecated', args[0][0])
|
||||
self.assertTrue(issubclass(args[0][1], DeprecationWarning))
|
||||
|
||||
def test_real_warning(self):
|
||||
with warnings.catch_warnings(record=True) as warning_msgs:
|
||||
warnings.resetwarnings()
|
||||
warnings.simplefilter('always', DeprecationWarning)
|
||||
import oslo.middleware
|
||||
|
||||
# Use a separate function to get the stack level correct
|
||||
# so we know the message points back to this file. This
|
||||
# corresponds to an import or reload, which isn't working
|
||||
# inside the test under Python 3.3. That may be due to a
|
||||
# difference in the import implementation not triggering
|
||||
# warnings properly when the module is reloaded, or
|
||||
# because the warnings module is mostly implemented in C
|
||||
# and something isn't cleanly resetting the global state
|
||||
# used to track whether a warning needs to be
|
||||
# emitted. Whatever the cause, we definitely see the
|
||||
# warnings.warn() being invoked on a reload (see the test
|
||||
# above) and warnings are reported on the console when we
|
||||
# run the tests. A simpler test script run outside of
|
||||
# testr does correctly report the warnings.
|
||||
def foo():
|
||||
oslo.middleware.deprecated()
|
||||
|
||||
foo()
|
||||
self.assertEqual(1, len(warning_msgs))
|
||||
msg = warning_msgs[0]
|
||||
self.assertIn('oslo_middleware', six.text_type(msg.message))
|
||||
self.assertEqual('test_warning.py', os.path.basename(msg.filename))
|
4
tox.ini
4
tox.ini
@ -1,6 +1,6 @@
|
||||
[tox]
|
||||
minversion = 1.6
|
||||
envlist = py26,py27,py33,py34,pypy,pep8
|
||||
envlist = py33,py34,py26,py27,pypy,pep8
|
||||
# NOTE(dhellmann): We cannot set skipdist=True
|
||||
# for oslo libraries because of the namespace package.
|
||||
#skipsdist = True
|
||||
@ -37,4 +37,4 @@ ignore = E123,E125,H305,H803,H904
|
||||
exclude=.venv,.git,.tox,dist,doc,*lib/python*,*egg,build,__init__.py
|
||||
|
||||
[hacking]
|
||||
import_exceptions = oslo.middleware.i18n
|
||||
import_exceptions = oslo_middleware.i18n
|
||||
|
Loading…
x
Reference in New Issue
Block a user