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):