From 0f08962bd848ce9a0fbf71684fc72fd0776da84d Mon Sep 17 00:00:00 2001
From: Helmut Grohne <helmut@subdivi.de>
Date: Mon, 22 Sep 2008 19:06:38 +0200
Subject: added applications.StaticFile

---
 wsgitools/applications.py | 90 +++++++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 90 insertions(+)

diff --git a/wsgitools/applications.py b/wsgitools/applications.py
index 9112e45..909500e 100644
--- a/wsgitools/applications.py
+++ b/wsgitools/applications.py
@@ -1,3 +1,8 @@
+import os.path
+
+__all__ = []
+
+__all__.append("StaticContent")
 class StaticContent:
     """
     This wsgi application provides static content on whatever request it
@@ -43,3 +48,88 @@ class StaticContent:
         if environ["REQUEST_METHOD"].upper() == "HEAD":
             return []
         return self.content
+
+__all__.append("StaticFile")
+class StaticFile:
+    """
+    This wsgi application provides the content of a static file on whatever
+    request it receives with method GET or HEAD (content stripped). If not
+    present, a content-length header is computed.
+    """
+    def __init__(self, filelike, status="200 OK", headers=list(),
+                 blocksize=4096):
+        """
+        @type status: str
+        @param status: is the HTTP status returned to the browser
+        @type headers: [(str, str)]
+        @param headers: is a list of (header, value) pairs being delivered as
+                HTTP headers
+        @type filelike: str or file-like
+        @param filelike: may either be an path in the local file system or a
+                file-like that must support read(size) and seek(0). If tell()
+                is present, seek(0, 2) and tell() will be used to compute the
+                content-length.
+        @type blocksize: int
+        @param blocksize: the content is provided in chunks of this size
+        """
+        self.filelike = filelike
+        self.status = status
+        self.headers = headers
+        self.blocksize = blocksize
+
+    def _serve_in_chunks(self, stream):
+        """internal method yielding data from the given stream"""
+        while True:
+            data = stream.read(self.blocksize)
+            if not data:
+                break
+            yield data
+        if isinstance(self.filelike, basestring):
+            stream.close()
+
+    def __call__(self, environ, start_response):
+        """wsgi interface"""
+        assert isinstance(environ, dict)
+
+        if environ["REQUEST_METHOD"].upper() not in ["GET", "HEAD"]:
+            resp = "Request method not implemented"
+            start_response("501 Not Implemented",
+                           [("Content-length", str(len(resp)))])
+            return [resp]
+
+        stream = None
+        size = -1
+        try:
+            if isinstance(self.filelike, basestring):
+                # raises IOError
+                stream = file(self.filelike)
+                size = os.path.getsize(self.filelike)
+            else:
+                stream = self.filelike
+                if hasattr(stream, "tell"):
+                    stream.seek(0, 2)
+                    size = stream.tell()
+                stream.seek(0)
+        except IOError:
+            resp = "File not found"
+            start_response("404 File not found",
+                           [("Content-length", str(len(resp)))])
+            return [resp]
+
+        headers = list(self.headers)
+        if size >= 0:
+            if not [v for h, v in headers if h.lower() == "content-length"]:
+                headers.append(("Content-length", str(size)))
+
+        start_response(self.status, headers)
+        if environ["REQUEST_METHOD"].upper() == "HEAD":
+            if isinstance(self.filelike, basestring):
+                stream.close()
+            return []
+
+        if 0 <= size <= self.blocksize:
+            data = stream.read(size)
+            if isinstance(self.filelike, basestring):
+                stream.close()
+            return [data]
+        return self._serve_in_chunks(stream)
-- 
cgit v1.2.3