change the last-modified header value with valid one
the Last-Modified header in Response didn't have a suitable value - an integer part of object's timestamp. This leads that the the if-[un]modified-since header with the value from last-modified is always earlier than timestamp and results the content is always newer than value of these conditional headers. Patched code returns math.ceil() of object's timestamp in Last-Modified header so the later conditional header works correctly Closes-Bug: #1248818 Change-Id: I1ece7d008551bf989da74d23f0ed6307c45c5436
This commit is contained in:
parent
deaddf003b
commit
d69e013519
swift
test
@ -21,6 +21,7 @@ import os
|
|||||||
import time
|
import time
|
||||||
import traceback
|
import traceback
|
||||||
import socket
|
import socket
|
||||||
|
import math
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
from swift import gettext_ as _
|
from swift import gettext_ as _
|
||||||
from hashlib import md5
|
from hashlib import md5
|
||||||
@ -483,7 +484,7 @@ class ObjectController(object):
|
|||||||
except (OverflowError, ValueError):
|
except (OverflowError, ValueError):
|
||||||
# catches timestamps before the epoch
|
# catches timestamps before the epoch
|
||||||
return HTTPPreconditionFailed(request=request)
|
return HTTPPreconditionFailed(request=request)
|
||||||
if if_modified_since and file_x_ts_utc < if_modified_since:
|
if if_modified_since and file_x_ts_utc <= if_modified_since:
|
||||||
return HTTPNotModified(request=request)
|
return HTTPNotModified(request=request)
|
||||||
keep_cache = (self.keep_cache_private or
|
keep_cache = (self.keep_cache_private or
|
||||||
('X-Auth-Token' not in request.headers and
|
('X-Auth-Token' not in request.headers and
|
||||||
@ -499,7 +500,7 @@ class ObjectController(object):
|
|||||||
key.lower() in self.allowed_headers:
|
key.lower() in self.allowed_headers:
|
||||||
response.headers[key] = value
|
response.headers[key] = value
|
||||||
response.etag = metadata['ETag']
|
response.etag = metadata['ETag']
|
||||||
response.last_modified = file_x_ts_flt
|
response.last_modified = math.ceil(file_x_ts_flt)
|
||||||
response.content_length = obj_size
|
response.content_length = obj_size
|
||||||
try:
|
try:
|
||||||
response.content_encoding = metadata[
|
response.content_encoding = metadata[
|
||||||
@ -541,7 +542,7 @@ class ObjectController(object):
|
|||||||
response.headers[key] = value
|
response.headers[key] = value
|
||||||
response.etag = metadata['ETag']
|
response.etag = metadata['ETag']
|
||||||
ts = metadata['X-Timestamp']
|
ts = metadata['X-Timestamp']
|
||||||
response.last_modified = float(ts)
|
response.last_modified = math.ceil(float(ts))
|
||||||
# Needed for container sync feature
|
# Needed for container sync feature
|
||||||
response.headers['X-Timestamp'] = ts
|
response.headers['X-Timestamp'] = ts
|
||||||
response.content_length = int(metadata['Content-Length'])
|
response.content_length = int(metadata['Content-Length'])
|
||||||
|
@ -28,6 +28,7 @@ import itertools
|
|||||||
import mimetypes
|
import mimetypes
|
||||||
import re
|
import re
|
||||||
import time
|
import time
|
||||||
|
import math
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
from swift import gettext_ as _
|
from swift import gettext_ as _
|
||||||
from urllib import unquote, quote
|
from urllib import unquote, quote
|
||||||
@ -1169,7 +1170,7 @@ class ObjectController(Controller):
|
|||||||
resp.headers['X-Copied-From-Last-Modified'] = \
|
resp.headers['X-Copied-From-Last-Modified'] = \
|
||||||
source_resp.headers['last-modified']
|
source_resp.headers['last-modified']
|
||||||
copy_headers_into(req, resp)
|
copy_headers_into(req, resp)
|
||||||
resp.last_modified = float(req.headers['X-Timestamp'])
|
resp.last_modified = math.ceil(float(req.headers['X-Timestamp']))
|
||||||
return resp
|
return resp
|
||||||
|
|
||||||
@public
|
@public
|
||||||
|
@ -697,7 +697,8 @@ class File(Base):
|
|||||||
else:
|
else:
|
||||||
raise RuntimeError
|
raise RuntimeError
|
||||||
|
|
||||||
def write(self, data='', hdrs={}, parms={}, callback=None, cfg={}):
|
def write(self, data='', hdrs={}, parms={}, callback=None, cfg={},
|
||||||
|
return_resp=False):
|
||||||
block_size = 2 ** 20
|
block_size = 2 ** 20
|
||||||
|
|
||||||
if isinstance(data, file):
|
if isinstance(data, file):
|
||||||
@ -736,6 +737,9 @@ class File(Base):
|
|||||||
|
|
||||||
self.md5 = self.compute_md5sum(data)
|
self.md5 = self.compute_md5sum(data)
|
||||||
|
|
||||||
|
if return_resp:
|
||||||
|
return self.conn.response
|
||||||
|
|
||||||
return True
|
return True
|
||||||
|
|
||||||
def write_random(self, size=None, hdrs={}, parms={}, cfg={}):
|
def write_random(self, size=None, hdrs={}, parms={}, cfg={}):
|
||||||
@ -744,3 +748,12 @@ class File(Base):
|
|||||||
raise ResponseError(self.conn.response)
|
raise ResponseError(self.conn.response)
|
||||||
self.md5 = self.compute_md5sum(StringIO.StringIO(data))
|
self.md5 = self.compute_md5sum(StringIO.StringIO(data))
|
||||||
return data
|
return data
|
||||||
|
|
||||||
|
def write_random_return_resp(self, size=None, hdrs={}, parms={}, cfg={}):
|
||||||
|
data = self.random_data(size)
|
||||||
|
resp = self.write(data, hdrs=hdrs, parms=parms, cfg=cfg,
|
||||||
|
return_resp=True)
|
||||||
|
if not resp:
|
||||||
|
raise ResponseError(self.conn.response)
|
||||||
|
self.md5 = self.compute_md5sum(StringIO.StringIO(data))
|
||||||
|
return resp
|
||||||
|
@ -1620,6 +1620,28 @@ class TestFileComparison(Base):
|
|||||||
self.assertRaises(ResponseError, file_item.read, hdrs=hdrs)
|
self.assertRaises(ResponseError, file_item.read, hdrs=hdrs)
|
||||||
self.assert_status(412)
|
self.assert_status(412)
|
||||||
|
|
||||||
|
def testLastModified(self):
|
||||||
|
file_name = Utils.create_name()
|
||||||
|
content_type = Utils.create_name()
|
||||||
|
|
||||||
|
file = self.env.container.file(file_name)
|
||||||
|
file.content_type = content_type
|
||||||
|
resp = file.write_random_return_resp(self.env.file_size)
|
||||||
|
put_last_modified = resp.getheader('last-modified')
|
||||||
|
|
||||||
|
file = self.env.container.file(file_name)
|
||||||
|
info = file.info()
|
||||||
|
self.assert_('last_modified' in info)
|
||||||
|
last_modified = info['last_modified']
|
||||||
|
self.assertEqual(put_last_modified, info['last_modified'])
|
||||||
|
|
||||||
|
hdrs = {'If-Modified-Since': last_modified}
|
||||||
|
self.assertRaises(ResponseError, file.read, hdrs=hdrs)
|
||||||
|
self.assert_status(304)
|
||||||
|
|
||||||
|
hdrs = {'If-Unmodified-Since': last_modified}
|
||||||
|
self.assert_(file.read(hdrs=hdrs))
|
||||||
|
|
||||||
|
|
||||||
class TestFileComparisonUTF8(Base2, TestFileComparison):
|
class TestFileComparisonUTF8(Base2, TestFileComparison):
|
||||||
set_up = False
|
set_up = False
|
||||||
|
@ -21,6 +21,7 @@ import operator
|
|||||||
import os
|
import os
|
||||||
import mock
|
import mock
|
||||||
import unittest
|
import unittest
|
||||||
|
import math
|
||||||
from shutil import rmtree
|
from shutil import rmtree
|
||||||
from StringIO import StringIO
|
from StringIO import StringIO
|
||||||
from time import gmtime, strftime, time
|
from time import gmtime, strftime, time
|
||||||
@ -672,7 +673,8 @@ class TestObjectController(unittest.TestCase):
|
|||||||
self.assertEquals(resp.headers['content-type'], 'application/x-test')
|
self.assertEquals(resp.headers['content-type'], 'application/x-test')
|
||||||
self.assertEquals(
|
self.assertEquals(
|
||||||
resp.headers['last-modified'],
|
resp.headers['last-modified'],
|
||||||
strftime('%a, %d %b %Y %H:%M:%S GMT', gmtime(float(timestamp))))
|
strftime('%a, %d %b %Y %H:%M:%S GMT',
|
||||||
|
gmtime(math.ceil(float(timestamp)))))
|
||||||
self.assertEquals(resp.headers['etag'],
|
self.assertEquals(resp.headers['etag'],
|
||||||
'"0b4c12d7e0a73840c1c4f148fda3b037"')
|
'"0b4c12d7e0a73840c1c4f148fda3b037"')
|
||||||
self.assertEquals(resp.headers['x-object-meta-1'], 'One')
|
self.assertEquals(resp.headers['x-object-meta-1'], 'One')
|
||||||
@ -774,7 +776,8 @@ class TestObjectController(unittest.TestCase):
|
|||||||
self.assertEquals(resp.headers['content-type'], 'application/x-test')
|
self.assertEquals(resp.headers['content-type'], 'application/x-test')
|
||||||
self.assertEquals(
|
self.assertEquals(
|
||||||
resp.headers['last-modified'],
|
resp.headers['last-modified'],
|
||||||
strftime('%a, %d %b %Y %H:%M:%S GMT', gmtime(float(timestamp))))
|
strftime('%a, %d %b %Y %H:%M:%S GMT',
|
||||||
|
gmtime(math.ceil(float(timestamp)))))
|
||||||
self.assertEquals(resp.headers['etag'],
|
self.assertEquals(resp.headers['etag'],
|
||||||
'"0b4c12d7e0a73840c1c4f148fda3b037"')
|
'"0b4c12d7e0a73840c1c4f148fda3b037"')
|
||||||
self.assertEquals(resp.headers['x-object-meta-1'], 'One')
|
self.assertEquals(resp.headers['x-object-meta-1'], 'One')
|
||||||
@ -976,6 +979,37 @@ class TestObjectController(unittest.TestCase):
|
|||||||
resp = req.get_response(self.object_controller)
|
resp = req.get_response(self.object_controller)
|
||||||
self.assertEquals(resp.status_int, 304)
|
self.assertEquals(resp.status_int, 304)
|
||||||
|
|
||||||
|
req = Request.blank('/sda1/p/a/c/o',
|
||||||
|
environ={'REQUEST_METHOD': 'HEAD'})
|
||||||
|
resp = req.get_response(self.object_controller)
|
||||||
|
since = resp.headers['Last-Modified']
|
||||||
|
self.assertEquals(since, strftime('%a, %d %b %Y %H:%M:%S GMT',
|
||||||
|
gmtime(math.ceil(float(timestamp)))))
|
||||||
|
|
||||||
|
req = Request.blank('/sda1/p/a/c/o', environ={'REQUEST_METHOD': 'GET'},
|
||||||
|
headers={'If-Modified-Since': since})
|
||||||
|
resp = self.object_controller.GET(req)
|
||||||
|
self.assertEquals(resp.status_int, 304)
|
||||||
|
|
||||||
|
timestamp = normalize_timestamp(int(time()))
|
||||||
|
req = Request.blank('/sda1/p/a/c/o2',
|
||||||
|
environ={'REQUEST_METHOD': 'PUT'},
|
||||||
|
headers={
|
||||||
|
'X-Timestamp': timestamp,
|
||||||
|
'Content-Type': 'application/octet-stream',
|
||||||
|
'Content-Length': '4'})
|
||||||
|
req.body = 'test'
|
||||||
|
resp = req.get_response(self.object_controller)
|
||||||
|
self.assertEquals(resp.status_int, 201)
|
||||||
|
|
||||||
|
since = strftime('%a, %d %b %Y %H:%M:%S GMT',
|
||||||
|
gmtime(float(timestamp)))
|
||||||
|
req = Request.blank('/sda1/p/a/c/o2',
|
||||||
|
environ={'REQUEST_METHOD': 'GET'},
|
||||||
|
headers={'If-Modified-Since': since})
|
||||||
|
resp = req.get_response(self.object_controller)
|
||||||
|
self.assertEquals(resp.status_int, 304)
|
||||||
|
|
||||||
def test_GET_if_unmodified_since(self):
|
def test_GET_if_unmodified_since(self):
|
||||||
timestamp = normalize_timestamp(time())
|
timestamp = normalize_timestamp(time())
|
||||||
req = Request.blank('/sda1/p/a/c/o', environ={'REQUEST_METHOD': 'PUT'},
|
req = Request.blank('/sda1/p/a/c/o', environ={'REQUEST_METHOD': 'PUT'},
|
||||||
@ -1012,6 +1046,18 @@ class TestObjectController(unittest.TestCase):
|
|||||||
resp = req.get_response(self.object_controller)
|
resp = req.get_response(self.object_controller)
|
||||||
self.assertEquals(resp.status_int, 200)
|
self.assertEquals(resp.status_int, 200)
|
||||||
|
|
||||||
|
req = Request.blank('/sda1/p/a/c/o',
|
||||||
|
environ={'REQUEST_METHOD': 'HEAD'})
|
||||||
|
resp = req.get_response(self.object_controller)
|
||||||
|
since = resp.headers['Last-Modified']
|
||||||
|
self.assertEquals(since, strftime('%a, %d %b %Y %H:%M:%S GMT',
|
||||||
|
gmtime(math.ceil(float(timestamp)))))
|
||||||
|
|
||||||
|
req = Request.blank('/sda1/p/a/c/o', environ={'REQUEST_METHOD': 'GET'},
|
||||||
|
headers={'If-Unmodified-Since': since})
|
||||||
|
resp = self.object_controller.GET(req)
|
||||||
|
self.assertEquals(resp.status_int, 200)
|
||||||
|
|
||||||
def test_GET_quarantine(self):
|
def test_GET_quarantine(self):
|
||||||
# Test swift.obj.server.ObjectController.GET
|
# Test swift.obj.server.ObjectController.GET
|
||||||
timestamp = normalize_timestamp(time())
|
timestamp = normalize_timestamp(time())
|
||||||
|
@ -998,6 +998,56 @@ class TestObjectController(unittest.TestCase):
|
|||||||
finally:
|
finally:
|
||||||
swift.proxy.controllers.obj.MAX_FILE_SIZE = MAX_FILE_SIZE
|
swift.proxy.controllers.obj.MAX_FILE_SIZE = MAX_FILE_SIZE
|
||||||
|
|
||||||
|
def test_PUT_last_modified(self):
|
||||||
|
prolis = _test_sockets[0]
|
||||||
|
sock = connect_tcp(('localhost', prolis.getsockname()[1]))
|
||||||
|
fd = sock.makefile()
|
||||||
|
fd.write('PUT /v1/a/c/o.last_modified HTTP/1.1\r\n'
|
||||||
|
'Host: localhost\r\nConnection: close\r\n'
|
||||||
|
'X-Storage-Token: t\r\nContent-Length: 0\r\n\r\n')
|
||||||
|
fd.flush()
|
||||||
|
headers = readuntil2crlfs(fd)
|
||||||
|
exp = 'HTTP/1.1 201'
|
||||||
|
lm_hdr = 'Last-Modified: '
|
||||||
|
self.assertEqual(headers[:len(exp)], exp)
|
||||||
|
|
||||||
|
last_modified_put = [line for line in headers.split('\r\n')
|
||||||
|
if lm_hdr in line][0][len(lm_hdr):]
|
||||||
|
sock = connect_tcp(('localhost', prolis.getsockname()[1]))
|
||||||
|
fd = sock.makefile()
|
||||||
|
fd.write('HEAD /v1/a/c/o.last_modified HTTP/1.1\r\n'
|
||||||
|
'Host: localhost\r\nConnection: close\r\n'
|
||||||
|
'X-Storage-Token: t\r\n\r\n')
|
||||||
|
fd.flush()
|
||||||
|
headers = readuntil2crlfs(fd)
|
||||||
|
exp = 'HTTP/1.1 200'
|
||||||
|
self.assertEqual(headers[:len(exp)], exp)
|
||||||
|
last_modified_head = [line for line in headers.split('\r\n')
|
||||||
|
if lm_hdr in line][0][len(lm_hdr):]
|
||||||
|
self.assertEqual(last_modified_put, last_modified_head)
|
||||||
|
|
||||||
|
sock = connect_tcp(('localhost', prolis.getsockname()[1]))
|
||||||
|
fd = sock.makefile()
|
||||||
|
fd.write('GET /v1/a/c/o.last_modified HTTP/1.1\r\n'
|
||||||
|
'Host: localhost\r\nConnection: close\r\n'
|
||||||
|
'If-Modified-Since: %s\r\n'
|
||||||
|
'X-Storage-Token: t\r\n\r\n' % last_modified_put)
|
||||||
|
fd.flush()
|
||||||
|
headers = readuntil2crlfs(fd)
|
||||||
|
exp = 'HTTP/1.1 304'
|
||||||
|
self.assertEqual(headers[:len(exp)], exp)
|
||||||
|
|
||||||
|
sock = connect_tcp(('localhost', prolis.getsockname()[1]))
|
||||||
|
fd = sock.makefile()
|
||||||
|
fd.write('GET /v1/a/c/o.last_modified HTTP/1.1\r\n'
|
||||||
|
'Host: localhost\r\nConnection: close\r\n'
|
||||||
|
'If-Unmodified-Since: %s\r\n'
|
||||||
|
'X-Storage-Token: t\r\n\r\n' % last_modified_put)
|
||||||
|
fd.flush()
|
||||||
|
headers = readuntil2crlfs(fd)
|
||||||
|
exp = 'HTTP/1.1 200'
|
||||||
|
self.assertEqual(headers[:len(exp)], exp)
|
||||||
|
|
||||||
def test_expirer_DELETE_on_versioned_object(self):
|
def test_expirer_DELETE_on_versioned_object(self):
|
||||||
test_errors = []
|
test_errors = []
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user