[Zope-Checkins] CVS: Zope2 - Image.py:1.129
Martijn Pieters
mj@digicool.com
Mon, 23 Apr 2001 11:39:42 -0400 (EDT)
Update of /cvs-repository/Zope2/lib/python/OFS
In directory korak:/tmp/cvs-serv1360/lib/python/OFS
Modified Files:
Image.py
Log Message:
- Merge the mj-http_range_support-branch HTTP Range functionality.
- Update the CHANGES.txt file.
--- Updated File Image.py in package Zope2 --
--- Image.py 2001/04/12 15:55:43 1.128
+++ Image.py 2001/04/23 15:39:11 1.129
@@ -86,9 +86,9 @@
__version__='$Revision$'[11:-2]
-import Globals, string, struct, content_types
+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
@@ -100,10 +100,10 @@
from Acquisition import Implicit
from DateTime import DateTime
from Cache import Cacheable
+from mimetools import choose_boundary
+from ZPublisher import HTTPRangeSupport
-
StringType=type('')
-
manage_addFileForm=DTMLFile('dtml/imageAdd', globals(),Kind='File',kind='file')
def manage_addFile(self,id,file='',title='',precondition='', content_type='',
REQUEST=None):
@@ -137,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'
@@ -212,6 +212,8 @@
# with common servers such as Apache (which can usually
# understand the screwy date string as a lucky side effect
# of the way they parse it).
+ # This happens to be what RFC2616 tells us to do in the face of an
+ # invalid date.
try: mod_since=long(DateTime(header).timeTime())
except: mod_since=None
if mod_since is not None:
@@ -225,6 +227,7 @@
RESPONSE.setHeader('Last-Modified', rfc1123_date(self._p_mtime))
RESPONSE.setHeader('Content-Type', self.content_type)
RESPONSE.setHeader('Content-Length', self.size)
+ RESPONSE.setHeader('Accept-Ranges', 'bytes')
RESPONSE.setStatus(304)
return ''
@@ -237,9 +240,181 @@
c(REQUEST['PARENTS'][1],REQUEST)
else:
c()
+
+ # HTTP Range header handling
+ range = REQUEST.get_header('Range', None)
+ if_range = REQUEST.get_header('If-Range', None)
+ if range is not None:
+ ranges = HTTPRangeSupport.parseRange(range)
+
+ if if_range is not None:
+ # Only send ranges if the data isn't modified, otherwise send
+ # 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.
+ satisfiable = 0
+ for start, end in ranges:
+ if start < self.size:
+ satisfiable = 1
+ break
+
+ if not satisfiable:
+ RESPONSE.setHeader('Content-Range',
+ 'bytes */%d' % self.size)
+ RESPONSE.setHeader('Accept-Ranges', 'bytes')
+ RESPONSE.setHeader('Last-Modified',
+ rfc1123_date(self._p_mtime))
+ RESPONSE.setHeader('Content-Type', self.content_type)
+ RESPONSE.setHeader('Content-Length', self.size)
+ RESPONSE.setStatus(416)
+ return ''
+
+ # Can we optimize?
+ ranges = HTTPRangeSupport.optimizeRanges(ranges, self.size)
+
+ if len(ranges) == 1:
+ # Easy case, set extra header and return partial set.
+ start, end = ranges[0]
+ size = end - start
+
+ RESPONSE.setHeader('Last-Modified',
+ rfc1123_date(self._p_mtime))
+ RESPONSE.setHeader('Content-Type', self.content_type)
+ RESPONSE.setHeader('Content-Length', size)
+ RESPONSE.setHeader('Accept-Ranges', 'bytes')
+ RESPONSE.setHeader('Content-Range',
+ 'bytes %d-%d/%d' % (start, end - 1, self.size))
+ RESPONSE.setStatus(206) # Partial content
+
+ data = self.data
+ if type(data) is StringType:
+ return data[start:end]
+
+ # Linked Pdata objects. Urgh.
+ pos = 0
+ while data is not None:
+ l = len(data.data)
+ pos = pos + l
+ if pos > start:
+ # We are within the range
+ lstart = l - (pos - start)
+
+ if lstart < 0: lstart = 0
+
+ # find the endpoint
+ if end <= pos:
+ lend = l - (pos - end)
+
+ # Send and end transmission
+ RESPONSE.write(data[lstart:lend])
+ break
+
+ # Not yet at the end, transmit what we have.
+ RESPONSE.write(data[lstart:])
+
+ data = data.next
+
+ return ''
+
+ else:
+ # Ignore multi-part ranges for now, pretend we don't know
+ # about ranges at all.
+ # When we get here, ranges have been optimized, so they are
+ # in order, non-overlapping, and start and end values are
+ # positive integers.
+ boundary = choose_boundary()
+
+ # Calculate the content length
+ size = (8 + len(boundary) + # End marker length
+ len(ranges) * ( # Constant lenght per set
+ 49 + len(boundary) + len(self.content_type) +
+ len('%d' % self.size)))
+ for start, end in ranges:
+ # Variable length per set
+ size = (size + len('%d%d' % (start, end - 1)) +
+ end - start)
+
+
+ RESPONSE.setHeader('Content-Length', size)
+ RESPONSE.setHeader('Accept-Ranges', 'bytes')
+ RESPONSE.setHeader('Last-Modified',
+ rfc1123_date(self._p_mtime))
+ RESPONSE.setHeader('Content-Type',
+ 'multipart/byteranges; boundary=%s' % boundary)
+ RESPONSE.setStatus(206) # Partial content
+
+ pos = 0
+ data = self.data
+
+ for start, end in ranges:
+ RESPONSE.write('\r\n--%s\r\n' % boundary)
+ RESPONSE.write('Content-Type: %s\r\n' %
+ self.content_type)
+ RESPONSE.write(
+ 'Content-Range: bytes %d-%d/%d\r\n\r\n' % (
+ start, end - 1, self.size))
+
+ if type(data) is StringType:
+ RESPONSE.write(data[start:end])
+
+ else:
+ # Yippee. Linked Pdata objects.
+ while data is not None:
+ l = len(data.data)
+ pos = pos + l
+ if pos > start:
+ # We are within the range
+ lstart = l - (pos - start)
+
+ if lstart < 0: lstart = 0
+
+ # find the endpoint
+ if end <= pos:
+ lend = l - (pos - end)
+
+ # Send and loop to next range
+ RESPONSE.write(data[lstart:lend])
+ # Back up the position marker, it will
+ # be incremented again for the next
+ # part.
+ pos = pos - l
+ break
+
+ # Not yet at the end, transmit what we have.
+ RESPONSE.write(data[lstart:])
+
+ data = data.next
+
+ RESPONSE.write('\r\n--%s--\r\n' % boundary)
+ return ''
+
RESPONSE.setHeader('Last-Modified', rfc1123_date(self._p_mtime))
RESPONSE.setHeader('Content-Type', self.content_type)
RESPONSE.setHeader('Content-Length', self.size)
+ RESPONSE.setHeader('Accept-Ranges', 'bytes')
# Don't cache the data itself, but provide an opportunity
# for a cache manager to set response headers.
@@ -268,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):
"""