1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17 """
18 HTTP Server Implementation
19 ==========================
20
21 Here's the http handling implemented.
22 """
23 __author__ = u"Andr\xe9 Malo"
24 __docformat__ = "restructuredtext en"
25
26 import errno as _errno
27 import re as _re
28 import socket as _socket
29 import sys as _sys
30 import traceback as _traceback
31 import urlparse as _urlparse
32
33 from wtf import impl as _impl
34 from wtf import stream as _stream
35 from wtf import webutil as _webutil
36 from wtf.impl import _connection
37 from wtf.impl import _gateway
38 from wtf.impl.http import _request
39
40
42 """
43 HTTP server
44
45 :IVariables:
46 - `config`: Configuration
47 - `opts`: Command line options
48 - `args`: Positioned command line arguments
49 - `timeouts`: Timeout specs
50 - `http_version`: Supported HTTP version (``(major, minor)``)
51 - `_gateway`: Gateway instance
52
53 :Types:
54 - `config`: `wtf.config.Config`
55 - `opts`: ``optparse.OptionContainer``
56 - `args`: ``list``
57 - `timeouts`: `_TimeOuts`
58 - `http_version`: ``tuple``
59 - `_gateway`: `Gateway`
60 """
61 __implements__ = [_impl.ServerInterface]
62
64 """
65 Initialization
66
67 :See: `wtf.impl.ServerInterface`
68 """
69 self.config, self.opts, self.args = config, opts, args
70 self.timeouts = _TimeOuts(config)
71 version = unicode(config.wtf('http-version', '1.1'))
72 vtuple = tuple(map(int, version.split('.')))
73 if len(vtuple) != 2 or not((1, 0) <= vtuple <= (1, 1)):
74 raise ValueError("Unrecognized HTTP version %s" % version)
75 self.http_version = vtuple
76 self.keep_alive = not config.wtf('autoreload', False) \
77 and config.wtf('keep-alive', True)
78 self._gateway = Gateway(config, opts, args)
79
80 - def handle(self, (sock, peername), application, flags):
81 """
82 Handle an accepted socket
83
84 :See: `wtf.impl.ServerInterface`
85 """
86
87
88 conn = _connection.Connection(sock, peername)
89 try:
90 conn.settimeout(self.timeouts.general)
91 gateway, first = self._gateway.handle, True
92 while True:
93 request = _request.HTTPRequest(self, conn, flags)
94 try:
95 try:
96 try:
97 gateway(conn, request, application)
98 except _request.RequestTimeout:
99 if first:
100 raise
101 break
102 except _socket.error, e:
103 if e[0] not in (_errno.EPIPE, _errno.ECONNRESET):
104 raise
105 break
106 except (SystemExit, KeyboardInterrupt):
107 raise
108 except _request.ParseError, e:
109 try:
110 request.error(e.status, e.msg)
111 except _socket.error:
112 pass
113 break
114 except:
115 if not request.response_started:
116 try:
117 request.error(
118 "500 Internal Server Error",
119 "Something went wrong while processing "
120 "the request. You might want to try "
121 "again later. Sorry for the "
122 "inconvenience."
123 )
124 except _socket.error:
125 pass
126 print >> _sys.stderr, \
127 "Request aborted due to exception:\n" + ''.join(
128 _traceback.format_exception(*_sys.exc_info())
129 )
130 break
131 else:
132 if not request.connection.persist:
133 break
134 finally:
135 request.close()
136 first, _ = False, conn.settimeout(self.timeouts.keep_alive)
137 finally:
138 try:
139 conn.close()
140 except (SystemExit, KeyboardInterrupt):
141 raise
142 except:
143 pass
144
145
147 """
148 HTTP implementation specific gateway
149
150 :CVariables:
151 - `_NORM_SUB`: Regex substitution callable for norming HTTP header names
152 - `_SLASH_SPLIT`: Regex splitter callable for encoded slashes
153 - `_STRIPPED`: List of HTTP variable names, which are expected to
154 appear without the ``HTTP_`` prefix
155 - `_HOPS`: Set of standard Hop-by-Hop headers
156
157 :Types:
158 - `_NORM_SUB`: ``callable``
159 - `_SLASH_SPLIT`: ``callable``
160 - `_STRIPPED`: ``tuple``
161 - `_HOPS`: ``set``
162 """
163 _NORM_SUB = _re.compile(r'[^a-zA-Z\d]').sub
164 _SLASH_SPLIT = _re.compile(r'%2[fF]').split
165 _STRIPPED = ("HTTP_CONTENT_TYPE", "HTTP_CONTENT_LENGTH")
166 _HOPS = set(["HTTP_" + _NORM_SUB("_", _HOPS).upper() for _HOPS in """
167 Connection
168 Keep-Alive
169 Proxy-Authenticate
170 Proxy-Authorization
171 TE
172 Trailers
173 Transfer-Encoding
174 Upgrade
175 """.split()])
176
178 """
179 Add HTTP implementation specific env constants
180
181 :See: `_gateway.Gateway._populate_base_env`
182 """
183 base_env.update({
184 'SERVER_NAME': self.config.wtf.servername,
185 'SCRIPT_NAME': '',
186 })
187 return base_env
188
190 """
191 Create HTTP implementation specific request environment
192
193 :See: `_gateway.Gateway._init_from_request`
194 """
195 request.parse()
196 environ = dict(("HTTP_" + self._NORM_SUB("_", key).upper(), value)
197 for key, value in request.headers.iteritems())
198 for key in self._STRIPPED:
199 if key in environ:
200 environ[key[5:]] = environ.pop(key)
201 if 'HTTP_TRANSFER_ENCODING' in environ:
202 environ['CONTENT_LENGTH'] = '-1'
203 if 'HTTP_CONNECTION' in environ:
204 connhops = set(
205 "HTTP_" + self._NORM_SUB("_", key.strip()).upper()
206 for key in environ['HTTP_CONNECTION'].split(',')
207 )
208 else:
209 connhops = set()
210 for key in (self._HOPS | connhops):
211 if key in environ:
212 del environ[key]
213
214 _, _, path, query, _ = _urlparse.urlsplit(request.url)
215 environ.update({
216 'REQUEST_METHOD': request.method,
217 'SERVER_PROTOCOL': "HTTP/%d.%d" % request.protocol,
218 'REQUEST_URI': _urlparse.urlunsplit((
219 "", "", path, query, "")),
220 'QUERY_STRING': query,
221 'SERVER_ADDR': connection.server_addr[0],
222 'SERVER_PORT': str(connection.server_addr[1]),
223 'REMOTE_ADDR': connection.remote_addr[0],
224 'REMOTE_PORT': str(connection.remote_addr[1]),
225
226 'wsgi.multithread': request.flags.multithread,
227 'wsgi.multiprocess': request.flags.multiprocess,
228 'wsgi.run_once': request.flags.run_once,
229 'wsgi.input': request.request_body_stream() or
230 _stream.dev_null,
231 'wsgi.url_scheme': 'http',
232 })
233 if '%' in path:
234 path = '%2F'.join(_webutil.unquote(item)
235 for item in self._SLASH_SPLIT(path))
236 environ['PATH_INFO'] = path
237
238 def start_response(status, headers):
239 """ HTTP response starter """
240 request.send_status(status)
241 request.send_headers(headers)
242 request.finish_headers()
243 return request.response_body_stream()
244
245 return environ, start_response
246
247
249 """
250 Timeout specificiations
251
252 :IVariables:
253 - `general`: General timeout, defaults to 300.0 secs
254 - `keep_alive`: Keep-alive timeout, defaults to 5.0 secs
255
256 :Types:
257 - `general`: ``float``
258 - `keep_alive`: ``float``
259 """
260
262 """
263 Initialization
264
265 :Parameters:
266 - `config`: Configuration
267
268 :Types:
269 - `config`: `wtf.config.Config`
270 """
271 self.general = float(config.wtf.timeout('general', 300))
272 self.keep_alive = float(config.wtf.timeout('keep-alive', 5))
273