Merge "Support If-[Un]Modified-Since for object HEAD"
This commit is contained in:
commit
77b8d42dc8
@ -1117,6 +1117,18 @@ class Response(object):
|
||||
self.content_length = 0
|
||||
return ['']
|
||||
|
||||
if self.last_modified and self.request.if_modified_since \
|
||||
and self.last_modified <= self.request.if_modified_since:
|
||||
self.status = 304
|
||||
self.content_length = 0
|
||||
return ['']
|
||||
|
||||
if self.last_modified and self.request.if_unmodified_since \
|
||||
and self.last_modified > self.request.if_unmodified_since:
|
||||
self.status = 412
|
||||
self.content_length = 0
|
||||
return ['']
|
||||
|
||||
if self.request and self.request.method == 'HEAD':
|
||||
# We explicitly do NOT want to set self.content_length to 0 here
|
||||
return ['']
|
||||
|
@ -22,7 +22,6 @@ import time
|
||||
import traceback
|
||||
import socket
|
||||
import math
|
||||
from datetime import datetime
|
||||
from swift import gettext_ as _
|
||||
from hashlib import md5
|
||||
|
||||
@ -41,9 +40,9 @@ from swift.obj import ssync_receiver
|
||||
from swift.common.http import is_success
|
||||
from swift.common.request_helpers import split_and_validate_path, is_user_meta
|
||||
from swift.common.swob import HTTPAccepted, HTTPBadRequest, HTTPCreated, \
|
||||
HTTPInternalServerError, HTTPNoContent, HTTPNotFound, HTTPNotModified, \
|
||||
HTTPInternalServerError, HTTPNoContent, HTTPNotFound, \
|
||||
HTTPPreconditionFailed, HTTPRequestTimeout, HTTPUnprocessableEntity, \
|
||||
HTTPClientDisconnect, HTTPMethodNotAllowed, Request, Response, UTC, \
|
||||
HTTPClientDisconnect, HTTPMethodNotAllowed, Request, Response, \
|
||||
HTTPInsufficientStorage, HTTPForbidden, HTTPException, HeaderKeyDict, \
|
||||
HTTPConflict
|
||||
from swift.obj.diskfile import DATAFILE_SYSTEM_META, DiskFileManager
|
||||
@ -492,16 +491,6 @@ class ObjectController(object):
|
||||
obj_size = int(metadata['Content-Length'])
|
||||
file_x_ts = metadata['X-Timestamp']
|
||||
file_x_ts_flt = float(file_x_ts)
|
||||
file_x_ts_utc = datetime.fromtimestamp(file_x_ts_flt, UTC)
|
||||
|
||||
if_unmodified_since = request.if_unmodified_since
|
||||
if if_unmodified_since and file_x_ts_utc > if_unmodified_since:
|
||||
return HTTPPreconditionFailed(request=request)
|
||||
|
||||
if_modified_since = request.if_modified_since
|
||||
if if_modified_since and file_x_ts_utc <= if_modified_since:
|
||||
return HTTPNotModified(request=request)
|
||||
|
||||
keep_cache = (self.keep_cache_private or
|
||||
('X-Auth-Token' not in request.headers and
|
||||
'X-Storage-Token' not in request.headers))
|
||||
|
@ -1718,19 +1718,25 @@ class TestFileComparison(Base):
|
||||
for file_item in self.env.files:
|
||||
hdrs = {'If-Modified-Since': self.env.time_old_f1}
|
||||
self.assert_(file_item.read(hdrs=hdrs))
|
||||
self.assert_(file_item.info(hdrs=hdrs))
|
||||
|
||||
hdrs = {'If-Modified-Since': self.env.time_new}
|
||||
self.assertRaises(ResponseError, file_item.read, hdrs=hdrs)
|
||||
self.assert_status(304)
|
||||
self.assertRaises(ResponseError, file_item.info, hdrs=hdrs)
|
||||
self.assert_status(304)
|
||||
|
||||
def testIfUnmodifiedSince(self):
|
||||
for file_item in self.env.files:
|
||||
hdrs = {'If-Unmodified-Since': self.env.time_new}
|
||||
self.assert_(file_item.read(hdrs=hdrs))
|
||||
self.assert_(file_item.info(hdrs=hdrs))
|
||||
|
||||
hdrs = {'If-Unmodified-Since': self.env.time_old_f2}
|
||||
self.assertRaises(ResponseError, file_item.read, hdrs=hdrs)
|
||||
self.assert_status(412)
|
||||
self.assertRaises(ResponseError, file_item.info, hdrs=hdrs)
|
||||
self.assert_status(412)
|
||||
|
||||
def testIfMatchAndUnmodified(self):
|
||||
for file_item in self.env.files:
|
||||
|
@ -15,8 +15,8 @@
|
||||
|
||||
"Tests for swift.common.swob"
|
||||
|
||||
import unittest
|
||||
import datetime
|
||||
import unittest
|
||||
import re
|
||||
import time
|
||||
from StringIO import StringIO
|
||||
@ -1451,5 +1451,141 @@ class TestConditionalIfMatch(unittest.TestCase):
|
||||
self.assertEquals(body, '')
|
||||
|
||||
|
||||
class TestConditionalIfModifiedSince(unittest.TestCase):
|
||||
def fake_app(self, environ, start_response):
|
||||
start_response(
|
||||
'200 OK', [('Last-Modified', 'Thu, 27 Feb 2014 03:29:37 GMT')])
|
||||
return ['hi']
|
||||
|
||||
def fake_start_response(*a, **kw):
|
||||
pass
|
||||
|
||||
def test_absent(self):
|
||||
req = swift.common.swob.Request.blank('/')
|
||||
resp = req.get_response(self.fake_app)
|
||||
resp.conditional_response = True
|
||||
body = ''.join(resp(req.environ, self.fake_start_response))
|
||||
self.assertEquals(resp.status_int, 200)
|
||||
self.assertEquals(body, 'hi')
|
||||
|
||||
def test_before(self):
|
||||
req = swift.common.swob.Request.blank(
|
||||
'/',
|
||||
headers={'If-Modified-Since': 'Thu, 27 Feb 2014 03:29:36 GMT'})
|
||||
resp = req.get_response(self.fake_app)
|
||||
resp.conditional_response = True
|
||||
body = ''.join(resp(req.environ, self.fake_start_response))
|
||||
self.assertEquals(resp.status_int, 200)
|
||||
self.assertEquals(body, 'hi')
|
||||
|
||||
def test_same(self):
|
||||
req = swift.common.swob.Request.blank(
|
||||
'/',
|
||||
headers={'If-Modified-Since': 'Thu, 27 Feb 2014 03:29:37 GMT'})
|
||||
resp = req.get_response(self.fake_app)
|
||||
resp.conditional_response = True
|
||||
body = ''.join(resp(req.environ, self.fake_start_response))
|
||||
self.assertEquals(resp.status_int, 304)
|
||||
self.assertEquals(body, '')
|
||||
|
||||
def test_greater(self):
|
||||
req = swift.common.swob.Request.blank(
|
||||
'/',
|
||||
headers={'If-Modified-Since': 'Thu, 27 Feb 2014 03:29:38 GMT'})
|
||||
resp = req.get_response(self.fake_app)
|
||||
resp.conditional_response = True
|
||||
body = ''.join(resp(req.environ, self.fake_start_response))
|
||||
self.assertEquals(resp.status_int, 304)
|
||||
self.assertEquals(body, '')
|
||||
|
||||
def test_out_of_range_is_ignored(self):
|
||||
# All that datetime gives us is a ValueError or OverflowError when
|
||||
# something is out of range (i.e. less than datetime.datetime.min or
|
||||
# greater than datetime.datetime.max). Unfortunately, we can't
|
||||
# distinguish between a date being too old and a date being too new,
|
||||
# so the best we can do is ignore such headers.
|
||||
max_date_list = list(datetime.datetime.max.timetuple())
|
||||
max_date_list[0] += 1 # bump up the year
|
||||
too_big_date_header = time.strftime(
|
||||
"%a, %d %b %Y %H:%M:%S GMT", time.struct_time(max_date_list))
|
||||
|
||||
req = swift.common.swob.Request.blank(
|
||||
'/',
|
||||
headers={'If-Modified-Since': too_big_date_header})
|
||||
resp = req.get_response(self.fake_app)
|
||||
resp.conditional_response = True
|
||||
body = ''.join(resp(req.environ, self.fake_start_response))
|
||||
self.assertEquals(resp.status_int, 200)
|
||||
self.assertEquals(body, 'hi')
|
||||
|
||||
|
||||
class TestConditionalIfUnmodifiedSince(unittest.TestCase):
|
||||
def fake_app(self, environ, start_response):
|
||||
start_response(
|
||||
'200 OK', [('Last-Modified', 'Thu, 20 Feb 2014 03:29:37 GMT')])
|
||||
return ['hi']
|
||||
|
||||
def fake_start_response(*a, **kw):
|
||||
pass
|
||||
|
||||
def test_absent(self):
|
||||
req = swift.common.swob.Request.blank('/')
|
||||
resp = req.get_response(self.fake_app)
|
||||
resp.conditional_response = True
|
||||
body = ''.join(resp(req.environ, self.fake_start_response))
|
||||
self.assertEquals(resp.status_int, 200)
|
||||
self.assertEquals(body, 'hi')
|
||||
|
||||
def test_before(self):
|
||||
req = swift.common.swob.Request.blank(
|
||||
'/',
|
||||
headers={'If-Unmodified-Since': 'Thu, 20 Feb 2014 03:29:36 GMT'})
|
||||
resp = req.get_response(self.fake_app)
|
||||
resp.conditional_response = True
|
||||
body = ''.join(resp(req.environ, self.fake_start_response))
|
||||
self.assertEquals(resp.status_int, 412)
|
||||
self.assertEquals(body, '')
|
||||
|
||||
def test_same(self):
|
||||
req = swift.common.swob.Request.blank(
|
||||
'/',
|
||||
headers={'If-Unmodified-Since': 'Thu, 20 Feb 2014 03:29:37 GMT'})
|
||||
resp = req.get_response(self.fake_app)
|
||||
resp.conditional_response = True
|
||||
body = ''.join(resp(req.environ, self.fake_start_response))
|
||||
self.assertEquals(resp.status_int, 200)
|
||||
self.assertEquals(body, 'hi')
|
||||
|
||||
def test_greater(self):
|
||||
req = swift.common.swob.Request.blank(
|
||||
'/',
|
||||
headers={'If-Unmodified-Since': 'Thu, 20 Feb 2014 03:29:38 GMT'})
|
||||
resp = req.get_response(self.fake_app)
|
||||
resp.conditional_response = True
|
||||
body = ''.join(resp(req.environ, self.fake_start_response))
|
||||
self.assertEquals(resp.status_int, 200)
|
||||
self.assertEquals(body, 'hi')
|
||||
|
||||
def test_out_of_range_is_ignored(self):
|
||||
# All that datetime gives us is a ValueError or OverflowError when
|
||||
# something is out of range (i.e. less than datetime.datetime.min or
|
||||
# greater than datetime.datetime.max). Unfortunately, we can't
|
||||
# distinguish between a date being too old and a date being too new,
|
||||
# so the best we can do is ignore such headers.
|
||||
max_date_list = list(datetime.datetime.max.timetuple())
|
||||
max_date_list[0] += 1 # bump up the year
|
||||
too_big_date_header = time.strftime(
|
||||
"%a, %d %b %Y %H:%M:%S GMT", time.struct_time(max_date_list))
|
||||
|
||||
req = swift.common.swob.Request.blank(
|
||||
'/',
|
||||
headers={'If-Unmodified-Since': too_big_date_header})
|
||||
resp = req.get_response(self.fake_app)
|
||||
resp.conditional_response = True
|
||||
body = ''.join(resp(req.environ, self.fake_start_response))
|
||||
self.assertEquals(resp.status_int, 200)
|
||||
self.assertEquals(body, 'hi')
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest.main()
|
||||
|
@ -1274,6 +1274,78 @@ class TestObjectController(unittest.TestCase):
|
||||
resp = req.get_response(self.object_controller)
|
||||
self.assertEquals(resp.status_int, 304)
|
||||
|
||||
def test_HEAD_if_modified_since(self):
|
||||
timestamp = normalize_timestamp(time())
|
||||
req = Request.blank('/sda1/p/a/c/o', 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)
|
||||
|
||||
req = Request.blank('/sda1/p/a/c/o',
|
||||
environ={'REQUEST_METHOD': 'HEAD'})
|
||||
resp = req.get_response(self.object_controller)
|
||||
self.assertEquals(resp.status_int, 200)
|
||||
|
||||
since = strftime('%a, %d %b %Y %H:%M:%S GMT',
|
||||
gmtime(float(timestamp) + 1))
|
||||
req = Request.blank('/sda1/p/a/c/o',
|
||||
environ={'REQUEST_METHOD': 'HEAD'},
|
||||
headers={'If-Modified-Since': since})
|
||||
resp = req.get_response(self.object_controller)
|
||||
self.assertEquals(resp.status_int, 304)
|
||||
|
||||
since = \
|
||||
strftime('%a, %d %b %Y %H:%M:%S GMT', gmtime(float(timestamp) - 1))
|
||||
req = Request.blank('/sda1/p/a/c/o',
|
||||
environ={'REQUEST_METHOD': 'HEAD'},
|
||||
headers={'If-Modified-Since': since})
|
||||
resp = req.get_response(self.object_controller)
|
||||
self.assertEquals(resp.status_int, 200)
|
||||
|
||||
since = \
|
||||
strftime('%a, %d %b %Y %H:%M:%S GMT', gmtime(float(timestamp) + 1))
|
||||
req = Request.blank('/sda1/p/a/c/o',
|
||||
environ={'REQUEST_METHOD': 'HEAD'},
|
||||
headers={'If-Modified-Since': since})
|
||||
resp = req.get_response(self.object_controller)
|
||||
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': 'HEAD'},
|
||||
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': 'HEAD'},
|
||||
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):
|
||||
timestamp = normalize_timestamp(time())
|
||||
req = Request.blank('/sda1/p/a/c/o', environ={'REQUEST_METHOD': 'PUT'},
|
||||
@ -1322,6 +1394,42 @@ class TestObjectController(unittest.TestCase):
|
||||
resp = self.object_controller.GET(req)
|
||||
self.assertEquals(resp.status_int, 200)
|
||||
|
||||
def test_HEAD_if_unmodified_since(self):
|
||||
timestamp = normalize_timestamp(time())
|
||||
req = Request.blank(
|
||||
'/sda1/p/a/c/o',
|
||||
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(math.ceil(float(timestamp)) + 1))
|
||||
req = Request.blank('/sda1/p/a/c/o',
|
||||
environ={'REQUEST_METHOD': 'HEAD'},
|
||||
headers={'If-Unmodified-Since': since})
|
||||
resp = req.get_response(self.object_controller)
|
||||
self.assertEquals(resp.status_int, 200)
|
||||
|
||||
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': 'HEAD'},
|
||||
headers={'If-Unmodified-Since': since})
|
||||
resp = req.get_response(self.object_controller)
|
||||
self.assertEquals(resp.status_int, 200)
|
||||
|
||||
since = strftime('%a, %d %b %Y %H:%M:%S GMT',
|
||||
gmtime(math.ceil(float(timestamp)) - 1))
|
||||
req = Request.blank('/sda1/p/a/c/o',
|
||||
environ={'REQUEST_METHOD': 'HEAD'},
|
||||
headers={'If-Unmodified-Since': since})
|
||||
resp = req.get_response(self.object_controller)
|
||||
self.assertEquals(resp.status_int, 412)
|
||||
|
||||
def test_GET_quarantine(self):
|
||||
# Test swift.obj.server.ObjectController.GET
|
||||
timestamp = normalize_timestamp(time())
|
||||
|
Loading…
Reference in New Issue
Block a user