Fix incompatibilities with WebOb 1.7

WebOb 1.7 changed [0] how request bodies are determined to be
readable. Prior to version 1.7, the following is how WebOb
determined if a request body is readable:
  #1 Request method is one of POST, PUT or PATCH
  #2 ``content_length`` length is set
  #3 Special flag ``webob.is_body_readable`` is set

The special flag ``webob.is_body_readable`` was used to signal
WebOb to consider a request body readable despite the content length
not being set. #1 above is how ``chunked`` Transfer Encoding was
supported implicitly in WebOb < 1.7.

Now with WebOb 1.7, a request body is considered readable only if
``content_length`` is set and it's non-zero [1]. So, we are only left
with #2 and #3 now. This drops implicit support for ``chunked``
Transfer Encoding Glance relied on. Hence, to emulate #1, Glance must
set the the special flag upon checking the HTTP methods that may have
bodies. This is precisely what this patch attemps to do.

[0] https://github.com/Pylons/webob/pull/283
[1] https://github.com/Pylons/webob/pull/283/files#diff-706d71e82f473a3b61d95c2c0d833b60R894

Closes-bug: #1657459
Closes-bug: #1657452
Co-Authored-By: Hemanth Makkapati <hemanth.makkapati@rackspace.com>
Change-Id: I19f15165a3d664d5f3a361f29ad7000ba2465a85
This commit is contained in:
Ian Cordasco 2017-01-20 16:38:10 +00:00 committed by Hemanth Makkapati
parent 9de043acb0
commit 7a843f7e1f
4 changed files with 35 additions and 8 deletions

View File

@ -48,7 +48,7 @@ class GzipMiddleware(wsgi.Middleware):
# 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"]
content_type = response.headers.get("Content-Type", "")
lazy = content_type == "application/octet-stream"
# NOTE(flaper87): Webob takes care of the compression

View File

@ -275,6 +275,9 @@ class RequestDeserializer(wsgi.JSONRequestDeserializer):
except exception.InvalidContentType as e:
raise webob.exc.HTTPUnsupportedMediaType(explanation=e.msg)
if self.is_valid_encoding(request) and self.is_valid_method(request):
request.is_body_readable = True
image_size = request.content_length or None
return {'size': image_size, 'data': request.body_file}

View File

@ -982,6 +982,16 @@ class Request(webob.Request):
class JSONRequestDeserializer(object):
valid_transfer_encoding = frozenset(['chunked', 'compress', 'deflate',
'gzip', 'identity'])
httpverb_may_have_body = frozenset({'POST', 'PUT', 'PATCH'})
@classmethod
def is_valid_encoding(cls, request):
request_encoding = request.headers.get('transfer-encoding', '').lower()
return request_encoding in cls.valid_transfer_encoding
@classmethod
def is_valid_method(cls, request):
return request.method.upper() in cls.httpverb_may_have_body
def has_body(self, request):
"""
@ -989,11 +999,12 @@ class JSONRequestDeserializer(object):
:param request: Webob.Request object
"""
request_encoding = request.headers.get('transfer-encoding', '').lower()
is_valid_encoding = request_encoding in self.valid_transfer_encoding
if is_valid_encoding and request.is_body_readable:
if self.is_valid_encoding(request) and self.is_valid_method(request):
request.is_body_readable = True
return True
elif request.content_length is not None and request.content_length > 0:
if request.content_length is not None and request.content_length > 0:
return True
return False

View File

@ -506,19 +506,32 @@ class JSONRequestDeserializerTest(test_utils.BaseTestCase):
self.assertFalse(self._check_transfer_encoding(
transfer_encoding='invalid', content_length=0))
def test_has_body_invalid_transfer_encoding_no_content_len_and_body(self):
self.assertFalse(self._check_transfer_encoding(
transfer_encoding='invalid', include_body=False))
def test_has_body_invalid_transfer_encoding_no_content_len_but_body(self):
self.assertTrue(self._check_transfer_encoding(
transfer_encoding='invalid', include_body=True))
def test_has_body_invalid_transfer_encoding_with_content_length(self):
self.assertTrue(self._check_transfer_encoding(
transfer_encoding='invalid', content_length=5))
def test_has_body_valid_transfer_encoding_with_content_length(self):
self.assertTrue(self._check_transfer_encoding(
transfer_encoding='chunked', content_length=0))
transfer_encoding='chunked', content_length=1))
def test_has_body_valid_transfer_encoding_without_content_length(self):
self.assertTrue(self._check_transfer_encoding(
transfer_encoding='chunked'))
def _check_transfer_encoding(self, transfer_encoding=None,
content_length=None):
content_length=None, include_body=True):
request = wsgi.Request.blank('/')
request.method = 'POST'
request.body = b'fake_body'
if include_body:
request.body = b'fake_body'
request.headers['transfer-encoding'] = transfer_encoding
if content_length is not None:
request.headers['content-length'] = content_length