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:
parent
1f232e19cf
commit
6c6b84b3f5
@ -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())
|
||||
|
@ -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',
|
||||
|
Loading…
Reference in New Issue
Block a user