summaryrefslogtreecommitdiff
path: root/wsgitools/applications.py
blob: df304db55fd6f7b92f8ecb12215c6f21b95b6170 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
import os.path

__all__ = []

try:
    basestring
except NameError:
    basestring = str

__all__.append("StaticContent")
class StaticContent(object):
    """
    This wsgi application provides static content on whatever request it
    receives with method GET or HEAD (content stripped). If not present, a
    content-length header is computed.
    """
    def __init__(self, status, headers, content, anymethod=False):
        """
        @type status: str
        @param status: is the HTTP status returned to the browser (ex: "200 OK")
        @type headers: list
        @param headers: is a list of C{(header, value)} pairs being delivered as
                HTTP headers
        @type content: bytes
        @param content: contains the data to be delivered to the client. It is
                either a string or some kind of iterable yielding strings.
        @type anymethod: boolean
        @param anymethod: determines whether any request method should be
                answered with this response instead of a 501
        """
        assert isinstance(status, str)
        assert isinstance(headers, list)
        assert isinstance(content, bytes) or hasattr(content, "__iter__")
        self.status = status
        self.headers = headers
        self.anymethod = anymethod
        length = -1
        if isinstance(content, bytes):
            self.content = [content]
            length = len(content)
        else:
            self.content = content
            if isinstance(self.content, list):
                length = sum(map(len, self.content))
        if length >= 0:
            if  not [v for h, v in headers if h.lower() == "content-length"]:
                headers.append(("Content-length", str(length)))
    def __call__(self, environ, start_response):
        """wsgi interface"""
        assert isinstance(environ, dict)
        if environ["REQUEST_METHOD"].upper() not in ["GET", "HEAD"] and \
                not self.anymethod:
            resp = b"Request method not implemented"
            start_response("501 Not Implemented",
                           [("Content-length", str(len(resp)))])
            return [resp]
        start_response(self.status, list(self.headers))
        if environ["REQUEST_METHOD"].upper() == "HEAD":
            return []
        return self.content

__all__.append("StaticFile")
class StaticFile(object):
    """
    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 C{(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 C{read(size)} and C{seek(0)}. If
                C{tell()} is present, C{seek(0, 2)} and C{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 = b"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 = open(self.filelike, "rb")
                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 = b"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 isinstance(self.filelike, basestring) and 'wsgi.file_wrapper' in environ:
            return environ['wsgi.file_wrapper'](stream, self.blocksize)

        if 0 <= size <= self.blocksize:
            data = stream.read(size)
            if isinstance(self.filelike, basestring):
                stream.close()
            return [data]
        return self._serve_in_chunks(stream)