Skip to content

Commit

Permalink
LastModified should be in UTC
Browse files Browse the repository at this point in the history
  • Loading branch information
Bladrak committed Nov 19, 2015
1 parent 6885b88 commit def9ea1
Show file tree
Hide file tree
Showing 3 changed files with 71 additions and 3 deletions.
56 changes: 56 additions & 0 deletions tests/handlers/test_base_handler.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@
import shutil
from os.path import abspath, join, dirname
import os
from datetime import datetime, timedelta
import pytz

import tornado.web
from preggy import expect
Expand Down Expand Up @@ -370,6 +372,60 @@ def test_can_get_image_without_etags(self):
expect(response.code).to_equal(200)
expect(response.headers).not_to_include('Etag')

class ImageOperationsWithLastModifiedTestCase(BaseImagingTestCase):
def get_context(self):
cfg = Config(SECURITY_KEY='ACME-SEC')
cfg.LOADER = "thumbor.loaders.file_loader"
cfg.FILE_LOADER_ROOT_PATH = self.loader_path

cfg.RESULT_STORAGE = 'thumbor.result_storages.file_storage'
cfg.RESULT_STORAGE_EXPIRATION_SECONDS = 60
cfg.RESULT_STORAGE_FILE_STORAGE_ROOT_PATH = self.root_path

cfg.SEND_IF_MODIFIED_LAST_MODIFIED_HEADERS = True

importer = Importer(cfg)
importer.import_modules()
server = ServerParameters(8889, 'localhost', 'thumbor.conf', None, 'info', None)
server.security_key = 'ACME-SEC'
return Context(server, cfg, importer)

@property
def result_storage(self):
return self.context.modules.result_storage

def write_image(self):
expected_path = self.result_storage.normalize_path('_wIUeSaeHw8dricKG2MGhqu5thk=/smart/image.jpg')

if not os.path.exists(dirname(expected_path)):
os.makedirs(dirname(expected_path))

if not os.path.exists(expected_path):
with open(expected_path, 'w') as img:
img.write(default_image())

def test_can_get_304_with_last_modified(self):
self.write_image()
response = self.fetch('/_wIUeSaeHw8dricKG2MGhqu5thk=/smart/image.jpg', headers={
"Accept": 'image/webp,*/*;q=0.8',
"If-Modified-Since": (datetime.utcnow() + timedelta(seconds=1))
.replace(tzinfo=pytz.utc).strftime("%a, %d %b %Y %H:%M:%S GMT"), # NOW +1 sec UTC
})

expect(response.code).to_equal(304)
expect(response.headers).to_include('Last-Modified')

def test_can_get_image_with_last_modified(self):
self.write_image()
response = self.fetch('/_wIUeSaeHw8dricKG2MGhqu5thk=/smart/image.jpg', headers={
"Accept": 'image/webp,*/*;q=0.8',
"If-Modified-Since": (datetime.utcnow() - timedelta(days=365))
.replace(tzinfo=pytz.utc).strftime("%a, %d %b %Y %H:%M:%S GMT"), # Last Year
})

expect(response.code).to_equal(200)
expect(response.headers).to_include('Last-Modified')


class ImageOperationsWithGifVTestCase(BaseImagingTestCase):
def get_context(self):
Expand Down
13 changes: 12 additions & 1 deletion thumbor/handlers/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
import sys
import functools
import datetime
import pytz
import traceback

import tornado.web
Expand Down Expand Up @@ -263,22 +264,32 @@ def _load_results(self, context):
@gen.coroutine
def _process_result_from_storage(self, result):
if self.context.config.SEND_IF_MODIFIED_LAST_MODIFIED_HEADERS:
logger.debug('Sending last modified headers')
# Handle If-Modified-Since & Last-Modified header
try:
if isinstance(result, ResultStorageResult):
result_last_modified = result.last_modified
logger.debug('Got last modified from result object')
else:
result_last_modified = yield gen.maybe_future(self.context.modules.result_storage.last_updated())
logger.debug('Getting last modified from result storage')

logger.debug('Last-Modified date is: {lm}'.format(lm=result_last_modified))

if result_last_modified:
if 'If-Modified-Since' in self.request.headers:
date_modified_since = datetime.datetime.strptime(self.request.headers['If-Modified-Since'], HTTP_DATE_FMT)
logger.debug('Asking for last-modified (if-modified header set to: {im})'.format(im=self.request.headers['If-Modified-Since']))
date_modified_since = datetime.datetime.strptime(self.request.headers['If-Modified-Since'], HTTP_DATE_FMT).replace(tzinfo=pytz.utc)

if result_last_modified <= date_modified_since:
logger.debug('if modified is later than last modified, returning 304')
self.set_status(304)
self.finish()
return

logger.debug('if modified is sooner than last modified, returning 200')

logger.debug('Setting last-modified header')
self.set_header('Last-Modified', result_last_modified.strftime(HTTP_DATE_FMT))
except NotImplementedError:
logger.warn('last_updated method is not supported by your result storage service, hence If-Modified-Since & '
Expand Down
5 changes: 3 additions & 2 deletions thumbor/result_storages/file_storage.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
from datetime import datetime
from uuid import uuid4
from shutil import move
import pytz

from os.path import exists, dirname, join, getmtime, abspath

Expand Down Expand Up @@ -64,7 +65,7 @@ def get(self, callback):
result = ResultStorageResult(
buffer=buffer,
metadata={
'LastModified': datetime.fromtimestamp(getmtime(file_abspath)),
'LastModified': datetime.fromtimestamp(getmtime(file_abspath)).replace(tzinfo=pytz.utc),
'ContentLength': len(buffer),
'ContentType': BaseEngine.get_mimetype(buffer)
}
Expand Down Expand Up @@ -111,4 +112,4 @@ def last_updated(self):
logger.debug("[RESULT_STORAGE] image not found at %s" % file_abspath)
return True

return datetime.fromtimestamp(getmtime(file_abspath))
return datetime.fromtimestamp(getmtime(file_abspath)).replace(tzinfo=pytz.utc)

0 comments on commit def9ea1

Please sign in to comment.