Fix non standard 100-continue behaviour

Swift proxy server, when communicating with
the back-end servers ALWAYS sends 100-continue.
Even if the length headers indicate that there
is no body (content-length: 0).
This behavior is not inline with the standard.
RFC2616:8.2.3 disallows 100-continue
        for req.content_length==0

This fix removes 100-continue during PUT requests
without a body while maintining the ability for
handoff in case of faliure.

On branch bp/wsgi-application-interface-5
   modified:   swift/proxy/controllers/obj.py

Fixes: Bug #1070025
Implements Blueprint:  	wsgi-application-interface

Change-Id: Ia4eb8b886a74968cca4e8bde208641b37f2c104c
This commit is contained in:
David Hadas 2012-10-23 10:07:53 +02:00
parent 1f232e19cf
commit 6c6b84b3f5
2 changed files with 80 additions and 8 deletions

View File

@ -107,7 +107,7 @@ class SegmentedIterable(object):
self.controller.account_name, self.container,
self.segment_dict['name'])
path = '/%s/%s/%s' % (self.controller.account_name, self.container,
self.segment_dict['name'])
self.segment_dict['name'])
req = Request.blank(path)
if self.seek:
req.range = 'bytes=%s-' % self.seek
@ -490,6 +490,11 @@ class ObjectController(Controller):
with Timeout(self.app.node_timeout):
resp = conn.getexpect()
if resp.status == HTTP_CONTINUE:
conn.resp = None
conn.node = node
return conn
elif is_success(resp.status):
conn.resp = resp
conn.node = node
return conn
elif resp.status == HTTP_INSUFFICIENT_STORAGE:
@ -666,13 +671,16 @@ class ObjectController(Controller):
req = new_req
node_iter = self.iter_nodes(partition, nodes, self.app.object_ring)
pile = GreenPile(len(nodes))
chunked = req.headers.get('transfer-encoding')
for container in containers:
nheaders = dict(req.headers.iteritems())
nheaders['Connection'] = 'close'
nheaders['X-Container-Host'] = '%(ip)s:%(port)s' % container
nheaders['X-Container-Partition'] = container_partition
nheaders['X-Container-Device'] = container['device']
nheaders['Expect'] = '100-continue'
# RFC2616:8.2.3 disallows 100-continue without a body
if (req.content_length > 0) or chunked:
nheaders['Expect'] = '100-continue'
if delete_at_nodes:
node = delete_at_nodes.pop(0)
nheaders['X-Delete-At-Host'] = '%(ip)s:%(port)s' % node
@ -687,7 +695,6 @@ class ObjectController(Controller):
'required connections'),
{'conns': len(conns), 'nodes': len(nodes) // 2 + 1})
return HTTPServiceUnavailable(request=req)
chunked = req.headers.get('transfer-encoding')
bytes_transferred = 0
try:
with ContextPool(len(nodes)) as pool:
@ -743,7 +750,10 @@ class ObjectController(Controller):
for conn in conns:
try:
with Timeout(self.app.node_timeout):
response = conn.getresponse()
if conn.resp:
response = conn.resp
else:
response = conn.getresponse()
statuses.append(response.status)
reasons.append(response.reason)
bodies.append(response.read())

View File

@ -185,8 +185,13 @@ def fake_http_connect(*code_iter, **kwargs):
class FakeConn(object):
def __init__(self, status, etag=None, body='', timestamp='1'):
def __init__(self, status, etag=None, body='', timestamp='1',
expect_status=None):
self.status = status
if expect_status is None:
self.expect_status = self.status
else:
self.expect_status = expect_status
self.reason = 'Fake'
self.host = '1.2.3.4'
self.port = '1234'
@ -204,10 +209,12 @@ def fake_http_connect(*code_iter, **kwargs):
return self
def getexpect(self):
if self.status == -2:
if self.expect_status == -2:
raise HTTPException()
if self.status == -3:
if self.expect_status == -3:
return FakeConn(507)
if self.expect_status == -4:
return FakeConn(201)
return FakeConn(100)
def getheaders(self):
@ -269,12 +276,17 @@ def fake_http_connect(*code_iter, **kwargs):
if 'give_connect' in kwargs:
kwargs['give_connect'](*args, **ckwargs)
status = code_iter.next()
if isinstance(status, tuple):
status, expect_status = status
else:
expect_status = status
etag = etag_iter.next()
timestamp = timestamps_iter.next()
if status <= 0:
raise HTTPException()
return FakeConn(status, etag, body=kwargs.get('body', ''),
timestamp=timestamp)
timestamp=timestamp, expect_status=expect_status)
return connect
@ -782,6 +794,56 @@ class TestObjectController(unittest.TestCase):
finally:
signal.signal(signal.SIGPIPE, old_handler)
def test_PUT_expect_header_zero_content_length(self):
test_errors = []
def test_connect(ipaddr, port, device, partition, method, path,
headers=None, query_string=None):
if path == '/a/c/o.jpg':
if 'expect' in headers or 'Expect' in headers:
test_errors.append('Expect was in headers for object '
'server!')
with save_globals():
controller = proxy_server.ObjectController(self.app, 'account',
'container', 'object')
# The (201, -4) tuples in there have the effect of letting the
# initial connect succeed, after which getexpect() gets called and
# then the -4 makes the response of that actually be 201 instead of
# 100. Perfectly straightforward.
set_http_connect(200, 200, (201, -4), (201, -4), (201, -4),
give_connect=test_connect)
req = Request.blank('/a/c/o.jpg', {})
req.content_length = 0
self.app.update_request(req)
self.app.memcache.store = {}
res = controller.PUT(req)
self.assertEqual(test_errors, [])
self.assertTrue(res.status.startswith('201 '), res.status)
def test_PUT_expect_header_nonzero_content_length(self):
test_errors = []
def test_connect(ipaddr, port, device, partition, method, path,
headers=None, query_string=None):
if path == '/a/c/o.jpg':
if 'Expect' not in headers:
test_errors.append('Expect was not in headers for '
'non-zero byte PUT!')
with save_globals():
controller = \
proxy_server.ObjectController(self.app, 'a', 'c', 'o.jpg')
set_http_connect(200, 200, 201, 201, 201,
give_connect=test_connect)
req = Request.blank('/a/c/o.jpg', {})
req.content_length = 1
req.body = 'a'
self.app.update_request(req)
self.app.memcache.store = {}
res = controller.PUT(req)
self.assertTrue(res.status.startswith('201 '))
def test_PUT_auto_content_type(self):
with save_globals():
controller = proxy_server.ObjectController(self.app, 'account',