All pastes #35690 Raw Edit

IResource additions

public text v1 · immutable
#35690 ·published 2006-01-04 02:54 UTC
rendered paste body
Index: dav/static.py
===================================================================
--- dav/static.py       (revision 15506)
+++ dav/static.py       (working copy)
@@ -111,92 +111,9 @@
 
     ##
     # HTTP
-    #
-    # FIXME: Most of these should be in IResource, Resource
     ##
 
     #
-    # ETag
-    #
-
-    def etag(self):
-        """
-        If the resource does not exist, returns None.
-        Otherwise, returns the etag for the resource.
-        """
-        if not self.fp.exists(): return None
-
-        st = self.fp.statinfo
-
-        #
-        # Mark ETag as weak if it was modified more recently than we can
-        # measure and report, as it could be modified again in that span
-        # and we then wouldn't know to provide a new ETag.
-        #
-        weak = (time.time() - st.st_mtime <= 1)
-
-        return ETag(
-            "%X-%X-%X" % (st.st_ino, st.st_size, st.st_mtime),
-            weak=weak
-        )
-
-    def exists(self):
-        """
-        Returns True if the resource exists on the server, False otherwise.
-        """
-        return self.fp.exists()
-
-    def lastModified(self):
-        """
-        If the resource does not exist, returns None.
-        Otherwise, returns the last modified time of the resource.
-        """
-        if self.fp.exists():
-            return self.fp.getmtime()
-        else:
-            return None
-
-    def creationDate(self):
-        """
-        If the resource does not exist, returns None.
-        Otherwise, returns the creation date of the resource.
-        """
-        if self.fp.exists():
-            return self.fp.getmtime()
-        else:
-            return None
-
-    def contentLength(self):
-        """
-        If the resource does not exist, returns None.
-        Otherwise, returns the size in bytes of the resource.
-        """
-        if self.fp.exists():
-            if self.fp.isdir():
-                # Computing this would require rendering the resource; let's
-                # punt instead.
-                return None
-            return self.fp.getsize()
-        else:
-            return None
-
-    def displayName(self):
-        """
-        If the resource does not exist, returns None.
-        Otherwise, returns the display name the resource.
-        """
-        if self.fp.exists():
-            return self.fp.basename()
-        else:
-            return None
-
-    def update(self):
-        """
-        Update any cached information about the resource from its backing store.
-        """
-        self.fp.restat(False)
-
-    #
     # Preconditions
     #
 
@@ -478,18 +395,11 @@
 
         assert response, "No response"
 
-        def set_headers(response):
+        def setHeaders(response):
             response = IResponse(response)
 
             response.headers.setHeader("dav", self.davComplianceClasses)
 
-            for (header, value) in (
-                ("last-modified", self.lastModified()),
-                ("etag"         , self.etag()        ),
-            ):
-                if value is not None:
-                    response.headers.setHeader(header, value)
-
             #
             # If this is a collection and the URI doesn't end in "/", add a
             # Content-Location header.  This is needed even if we redirect such
@@ -501,20 +411,9 @@
 
             return response
 
-        response.addCallback(set_headers)
+        response.addCallback(setHeaders)
         return response
 
-    def http_OPTIONS(self, request):
-        def set_headers(response):
-            if self.type is not None:
-                response = IResponse(response)
-                response.headers.setHeader("content-type", MimeType.fromString(self.type))
-            return response
-
-        response = maybeDeferred(File.http_OPTIONS, self, request)
-        response.addCallback(set_headers)
-        return response
-
     ##
     # Workarounds for issues with File
     ##
Index: iweb.py
===================================================================
--- iweb.py     (revision 15506)
+++ iweb.py     (working copy)
@@ -14,6 +14,56 @@
         I am a web resource.
     """
 
+    def allowedMethods(self):
+        """
+        @return: A tuple of allowed methods.
+        """
+
+    def etag(self):
+        """
+        @return: The current etag for the resource if available, None otherwise.
+        """
+
+    def exists(self):
+        """
+        @return: True if the resource exists on the server, False otherwise.
+        """
+
+    def lastModified(self):
+        """
+        @return: The last modified time of the resource if available, None otherwise.
+        """
+
+    def creationDate(self):
+        """
+        @return: The creation date of the resource if available, None otherwise.
+        """
+
+    def contentLength(self):
+        """
+        @return: The size in bytes of the resource if available, None otherwise.
+        """
+
+    def contentType(self):
+        """
+        @return: The MIME type of the resource if available, None otherwise.
+        """
+
+    def contentEncoding(self):
+        """
+        @return: The encoding of the resource if available, None otherwise.
+        """
+
+    def displayName(self):
+        """
+        @return: The display name of the resource if available, None otherwise.
+        """
+
+    def update(self):
+        """
+        Update any cached information about the resource from its backing store.
+        """
+
     def locateChild(self, req, segments):
         """Locate another object which can be adapted to IResource.
 
Index: static.py
===================================================================
--- static.py   (revision 15506)
+++ static.py   (working copy)
@@ -30,17 +30,18 @@
     
     def __init__(self, data, type):
         self.data = data
-        self.type = type
+        self.type = MimeType.fromString(self.type)
         self.created_time = time.time()
     
+    def etag(self):
+        return http_headers.ETag("%X-%X" % (self.created_time, hash(self.data)),
+                                 weak=(time.time() - self.created_time <= 1))
+
+    def contentType(self):
+        return self.type
+
     def render(self, req):
-        response = http.Response(responsecode.OK, stream=self.data)
-        response.headers.setRawHeaders("content-type", (self.type, ))
-        response.headers.setHeader('etag',
-            http_headers.ETag("%X-%X" % (self.created_time, hash(self.data)),
-                              weak=(time.time() - self.created_time <= 1)))
-        
-        return response
+        return http.Response(responsecode.OK, stream=self.data)
 
 def addSlash(request):
     return "http%s://%s%s/" % (
@@ -127,30 +128,6 @@
         ".bz2": "bzip2"
         }
 
-    def _initTypeAndEncoding(self):
-        self._type, self._encoding = getTypeAndEncoding(
-            self.fp.basename(),
-            self.contentTypes,
-            self.contentEncodings,
-            self.defaultType
-        )
-
-        # Handle cases not covered by getTypeAndEncoding()
-        if self.fp.isdir(): self._type = "httpd/unix-directory"
-
-    def _getType(self):
-        if not hasattr(self, "_type"):
-            self._initTypeAndEncoding()
-        return self._type
-
-    def _getEncoding(self):
-        if not hasattr(self, "_encoding"):
-            self._initTypeAndEncoding()
-        return self._encoding
-
-    type     = property(_getType    )
-    encoding = property(_getEncoding)
-
     processors = {}
 
     indexNames = ["index", "index.html", "index.htm", "index.trp", "index.rpy"]
@@ -174,6 +151,78 @@
         if indexNames is not None:
             self.indexNames = indexNames
 
+    def etag(self):
+        if not self.exists(): return None
+
+        st = self.fp.statinfo
+
+        #
+        # Mark ETag as weak if it was modified more recently than we can
+        # measure and report, as it could be modified again in that span
+        # and we then wouldn't know to provide a new ETag.
+        #
+        weak = (time.time() - st.st_mtime <= 1)
+
+        return http_headers.ETag(
+            "%X-%X-%X" % (st.st_ino, st.st_size, st.st_mtime),
+            weak=weak
+        )
+
+    def exists(self):
+        return self.fp.exists()
+
+    def lastModified(self):
+        if self.fp.exists():
+            return self.fp.getmtime()
+        else:
+            return None
+
+    def creationDate(self):
+        if self.fp.exists():
+            return self.fp.getmtime()
+        else:
+            return None
+
+    def contentLength(self):
+        if self.fp.exists():
+            if self.fp.isdir():
+                # Computing this would require rendering the resource; let's
+                # punt instead.
+                return None
+            return self.fp.getsize()
+        else:
+            return None
+
+    def _initTypeAndEncoding(self):
+        self._type, self._encoding = getTypeAndEncoding(
+            self.fp.basename(),
+            self.contentTypes,
+            self.contentEncodings,
+            self.defaultType
+        )
+
+        # Handle cases not covered by getTypeAndEncoding()
+        if self.fp.isdir(): self._type = "httpd/unix-directory"
+
+    def contentType(self):
+        if not hasattr(self, "_type"):
+            self._initTypeAndEncoding()
+        return MimeType.fromString(self._type)
+
+    def contentEncoding(self):
+        if not hasattr(self, "_encoding"):
+            self._initTypeAndEncoding()
+        return self._encoding
+
+    def displayName(self):
+        if self.fp.exists():
+            return self.fp.basename()
+        else:
+            return None
+
+    def update(self):
+        self.fp.restat(False)
+
     def ignoreExt(self, ext):
         """Ignore the given extension.
 
@@ -230,7 +279,6 @@
     def render(self, req):
         """You know what you doing."""
         self.fp.restat()
-        response = http.Response()
         
         if not self.fp.exists():
             return responsecode.NOT_FOUND
@@ -241,11 +289,6 @@
                 responsecode.MOVED_PERMANENTLY,
                 {'location': req.unparseURL(path=req.path+'/')})
 
-        if self.type:
-            response.headers.setRawHeaders('content-type', (self.type,))
-        if self.encoding:
-            response.headers.setHeader('content-encoding', self.encoding)
-
         try:
             f = self.fp.open()
         except IOError, e:
@@ -255,27 +298,16 @@
             else:
                 raise
 
-        st = os.fstat(f.fileno())
-        
-        # Be sure it's a regular file.
-        if not stat.S_ISREG(st.st_mode):
-            return responsecode.FORBIDDEN
-        
-        #for content-length
-        size = st.st_size
-        
-        response.headers.setHeader('last-modified', st.st_mtime)
-        
-        # Mark ETag as weak if it was modified recently, as it could
-        # be modified again without changing mtime.
-        weak = (time.time() - st.st_mtime <= 1)
-        
-        etag = http_headers.ETag(
-            "%X-%X-%X" % (st.st_ino, st.st_size, st.st_mtime),
-            weak=weak)
-        
-        response.headers.setHeader('etag', etag)
-        response.headers.setHeader('content-length', size)
+        response = http.Response()
+
+        for (header, value) in (
+            ("content-length", self.contentLength()),
+            ("content-type", self.contentType()),
+            ("content-encoding", self.contentType()),
+        ):
+            if value is not None:
+                response.headers.setHeader(header, value)
+
         response.stream = stream.FileStream(f, 0, size)
         return response
 
Index: resource.py
===================================================================
--- resource.py (revision 15506)
+++ resource.py (working copy)
@@ -8,6 +8,7 @@
 
 # System Imports
 from twisted.python import components
+from twisted.internet.defer import succeed, maybeDeferred
 from zope.interface import implements
 
 from twisted.web2 import iweb, http, server, responsecode
@@ -23,10 +24,71 @@
     implements(iweb.IResource)
 
     addSlash = False
-    allowedMethods = None
     
     # Concrete HTTP interface
 
+    def allowedMethods(self):
+        """
+        @return: A tuple of allowed methods.
+        """
+        if not hasattr(self, "_allowed_methods"):
+            self._allowed_methods = tuple([name[5:] for name in dir(self) if name.startswith('http_')])
+        return self._allowed_methods
+
+    def etag(self):
+        """
+        @return: The current etag for the resource if available, None otherwise.
+        """
+        return None
+
+    def exists(self):
+        """
+        @return: True if the resource exists on the server, False otherwise.
+        """
+        return None
+
+    def lastModified(self):
+        """
+        @return: The last modified time of the resource if available, None otherwise.
+        """
+        return None
+
+    def creationDate(self):
+        """
+        @return: The creation date of the resource if available, None otherwise.
+        """
+        return None
+
+    def contentLength(self):
+        """
+        @return: The size in bytes of the resource if available, None otherwise.
+        """
+        return None
+
+    def contentType(self):
+        """
+        @return: The MIME type of the resource if available, None otherwise.
+        """
+        return None
+
+    def contentEncoding(self):
+        """
+        @return: The encoding of the resource if available, None otherwise.
+        """
+        return None
+
+    def displayName(self):
+        """
+        @return: The display name of the resource if available, None otherwise.
+        """
+        return None
+
+    def update(self):
+        """
+        Update any cached information about the resource from its backing store.
+        """
+        return None
+
     def locateChild(self, req, segments):
         """Return a tuple of (child, segments) representing the Resource
         below this after consuming the segments which are not returned.
@@ -63,7 +125,6 @@
         path to be ''.
         """
         setattr(self, 'child_%s' % (path, ), child)
-
     
     def renderHTTP(self, req):
         """Render a given resource. See L{IResource}'s render method.
@@ -78,18 +139,29 @@
         will be the rendered page, or else a deferred that results
         in a byte string.
         """
-        m = getattr(self, 'http_' + req.method, None)
-        if not m:
-            if self.allowedMethods is None:
-                # Update allowed methods
-                self.allowedMethods = [name[5:] for name in dir(self)
-                                       if name.startswith('http_')]
-            
+        method = getattr(self, 'http_' + req.method, None)
+        if not method:
             response = http.Response(responsecode.NOT_ALLOWED)
-            response.headers.setHeader('allow', self.allowedMethods)
+            response.headers.setHeader("allow", self.allowedMethods())
             return response
-        return m(req)
 
+        def setHeaders(response):
+            response = iweb.IResponse(response)
+
+            # Content-* headers refer to the response content, not (necessarily) to
+            # the resource content, so then depend on method, and therefore can't be
+            # set here.
+            for (header, value) in (
+                ("etag", self.etag()),
+                ("last-modified", self.lastModified()),
+            ):
+                if value is not None:
+                    response.headers.setHeader(header, value)
+
+            return response
+
+        return maybeDeferred(method, req).addCallback(setHeaders)
+            
     def http_HEAD(self, req):
         """By default http_HEAD just calls http_GET. The body is discarded
         when the result is being written.
@@ -112,12 +184,8 @@
 
     def http_OPTIONS(self, req):
         """Sends back OPTIONS response."""
-        if self.allowedMethods is None:
-            # Update allowed methods
-            self.allowedMethods = [name[5:] for name in dir(self)
-                                   if name.startswith('http_')]
         response = http.Response(responsecode.OK)
-        response.headers.setHeader('allow', self.allowedMethods)
+        response.headers.setHeader("allow", self.allowedMethods())
         return response
 
     def http_TRACE(self, req):
@@ -127,6 +195,7 @@
         """Your class should implement this method to do default page rendering.
         """
         raise NotImplementedError("Subclass must implement render method.")
+
 components.backwardsCompatImplements(Resource)
 
 class PostableResource(Resource):