[Zope-Checkins] CVS: Zope2 - Image.py:1.128.6.5
Martijn Pieters
mj@digicool.com
Mon, 23 Apr 2001 10:28:58 -0400 (EDT)
Update of /cvs-repository/Zope2/lib/python/OFS
In directory korak:/tmp/cvs-serv31242/OFS
Modified Files:
Tag: mj-http_range_support-branch
Image.py
Log Message:
- Move HTTP Range methods to own module.
- Create a HTTPRangeInterface flag interface so we can test for support.
- Have WebDAV enabled objects set 'Accept-Ranges: bytes' for supporting
objects and 'Accept-Ranges: none' for the rest.
- Add ETag support to If-Range (ETags were already generated for Image and
File objects, just not updated when the contents of the resource changed).
- Removed some unused imports.
--- Updated File Image.py in package Zope2 --
--- Image.py 2001/04/20 15:03:52 1.128.6.4
+++ Image.py 2001/04/23 14:28:27 1.128.6.5
@@ -86,9 +86,9 @@
__version__='$Revision$'[11:-2]
-import Globals, string, struct, content_types, re, sys
+import Globals, string, struct
from OFS.content_types import guess_content_type
-from Globals import DTMLFile, MessageDialog
+from Globals import DTMLFile
from PropertyManager import PropertyManager
from AccessControl.Role import RoleManager
from webdav.common import rfc1123_date
@@ -101,119 +101,9 @@
from DateTime import DateTime
from Cache import Cacheable
from mimetools import choose_boundary
+from ZPublisher import HTTPRangeSupport
-
StringType=type('')
-WHITESPACE = re.compile('\s*', re.MULTILINE)
-
-# RFC 2616 (HTTP 1.1) Range header parsing
-# Convert a range header to a list of slice indexes, returned as (start, end)
-# tuples. If no end was given, end is None. Note that the RFC specifies the end
-# offset to be inclusive, we return python convention indexes, where the end is
-# exclusive. Syntactically incorrect headers are to be ignored, so if we
-# encounter one we return None.
-def parseRange(header):
- ranges = []
- add = ranges.append
-
- # First, clean out *all* whitespace. This is slightly more tolerant
- # than the spec asks for, but hey, it makes this function much easier.
- header = WHITESPACE.sub('', header)
-
- # A range header only can specify a byte range
- try: spec, sets = string.split(header, '=')
- except ValueError: return None
- if spec != 'bytes':
- return None
-
- # The sets are delimited by commas.
- sets = string.split(sets, ',')
- # Filter out empty values, things like ',,' are allowed in the spec
- sets = filter(None, sets)
- # We need at least one set
- if not sets:
- return None
-
- for set in sets:
- try: start, end = string.split(set, '-')
- except ValueError: return None
-
- # Catch empty sets
- if not start and not end:
- return None
-
- # Convert to integers or None (which will raise errors if
- # non-integers were used (which is what we want)).
- try:
- if start == '': start = None
- else: start = int(start)
- if end == '': end = None
- else: end = int(end)
- except ValueError:
- return None
-
- # Special case: No start means the suffix format was used, which
- # means the end value is actually a negative start value.
- # Convert this by making it absolute.
- # A -0 range is converted to sys.maxint, which will result in a
- # Unsatisfiable response if no other ranges can by satisfied either.
- if start is None:
- start, end = -end, None
- if not start:
- start = sys.maxint
- elif end is not None:
- end = end + 1 # Make the end of the range exclusive
-
- if end is not None and end <= start:
- return None
-
- # And store
- add((start, end))
-
- return ranges
-
-# Optimize Range sets, given those sets and the length of the resource
-# Optimisation is done by first expanding relative start values and open ends,
-# then sorting and combining overlapping or adjacent ranges. We also remove
-# unsatisfiable ranges (where the start lies beyond the size of the resource).
-def optimizeRanges(ranges, size):
- expanded = []
- add = expanded.append
- for start, end in ranges:
- if start < 0:
- start = size + start
- end = end or size
- if end > size: end = size
- # Only use satisfiable ranges
- if start < size:
- add((start, end))
-
- ranges = expanded
- ranges.sort()
- ranges.reverse()
- optimized = []
- add = optimized.append
- start, end = ranges.pop()
-
- while ranges:
- nextstart, nextend = ranges.pop()
- # If the next range overlaps or is adjacent
- if nextstart <= end:
- # If it falls within the current range, discard
- if nextend <= end:
- continue
-
- # Overlap, adjust end
- end = nextend
- else:
- add((start, end))
- start, end = nextstart, nextend
-
- # Add the remaining optimized range
- add((start, end))
-
- return optimized
-
manage_addFileForm=DTMLFile('dtml/imageAdd', globals(),Kind='File',kind='file')
def manage_addFile(self,id,file='',title='',precondition='', content_type='',
REQUEST=None):
@@ -247,7 +137,7 @@
RoleManager, Item_w__name__, Cacheable):
"""A File object is a content object for arbitrary files."""
- __implements__ = (WriteLockInterface,)
+ __implements__ = (WriteLockInterface, HTTPRangeSupport.HTTPRangeInterface)
meta_type='File'
@@ -355,23 +245,33 @@
range = REQUEST.get_header('Range', None)
if_range = REQUEST.get_header('If-Range', None)
if range is not None:
- ranges = parseRange(range)
+ ranges = HTTPRangeSupport.parseRange(range)
if if_range is not None:
# Only send ranges if the data isn't modified, otherwise send
- # the whole object.
- date = string.split(if_range, ';')[0]
- try: mod_since=long(DateTime(date).timeTime())
- except: mod_since=None
- if mod_since is not None:
- if self._p_mtime:
- last_mod = long(self._p_mtime)
- else:
- last_mod = long(0)
- if last_mod > mod_since:
- # Modified, so send a normal response. We delete the
- # ranges, which causes us to skip to the 200 response.
+ # the whole object. Support both ETags and Last-Modified dates!
+ if len(if_range) > 1 and if_range[:2] == 'ts':
+ # ETag:
+ if if_range != self.http__etag():
+ # Modified, so send a normal response. We delete
+ # the ranges, which causes us to skip to the 200
+ # response.
ranges = None
+ else:
+ # Date
+ date = string.split(if_range, ';')[0]
+ try: mod_since=long(DateTime(date).timeTime())
+ except: mod_since=None
+ if mod_since is not None:
+ if self._p_mtime:
+ last_mod = long(self._p_mtime)
+ else:
+ last_mod = long(0)
+ if last_mod > mod_since:
+ # Modified, so send a normal response. We delete
+ # the ranges, which causes us to skip to the 200
+ # response.
+ ranges = None
if ranges:
# Search for satisfiable ranges.
@@ -393,7 +293,7 @@
return ''
# Can we optimize?
- ranges = optimizeRanges(ranges, self.size)
+ ranges = HTTPRangeSupport.optimizeRanges(ranges, self.size)
if len(ranges) == 1:
# Easy case, set extra header and return partial set.
@@ -543,6 +443,7 @@
self.size=size
self.data=data
self.ZCacheable_invalidate()
+ self.http__refreshETag()
def manage_edit(self, title, content_type, precondition='', REQUEST=None):
"""