Package wtf :: Package impl :: Module scgi
[hide private]
[frames] | no frames]

Source Code for Module wtf.impl.scgi

  1  # -*- coding: ascii -*- 
  2  # 
  3  # Copyright 2007-2012 
  4  # Andr\xe9 Malo or his licensors, as applicable 
  5  # 
  6  # Licensed under the Apache License, Version 2.0 (the "License"); 
  7  # you may not use this file except in compliance with the License. 
  8  # You may obtain a copy of the License at 
  9  # 
 10  #     http://www.apache.org/licenses/LICENSE-2.0 
 11  # 
 12  # Unless required by applicable law or agreed to in writing, software 
 13  # distributed under the License is distributed on an "AS IS" BASIS, 
 14  # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 
 15  # See the License for the specific language governing permissions and 
 16  # limitations under the License. 
 17  """ 
 18  SCGI Implementation 
 19  =================== 
 20   
 21  Here's the SCGI handling implemented. 
 22  """ 
 23  __author__ = u"Andr\xe9 Malo" 
 24  __docformat__ = "restructuredtext en" 
 25   
 26  import errno as _errno 
 27  import itertools as _it 
 28  import socket as _socket 
 29  import sys as _sys 
 30  import traceback as _traceback 
 31   
 32  from wtf import Error 
 33  from wtf import impl as _impl 
 34  from wtf import stream as _stream 
 35  from wtf.config import ConfigurationError 
 36  from wtf.impl import _connection 
 37  from wtf.impl import _gateway 
 38  from wtf.impl import _util as _impl_util 
 39   
 40   
41 -class NetStringError(Error):
42 """ Netstring error """
43 44
45 -class SCGIServer(object):
46 """ 47 SCGI server 48 49 :IVariables: 50 - `config`: Configuration 51 - `opts`: Command line options 52 - `args`: Positioned command line arguments 53 54 :Types: 55 - `config`: `wtf.config.Config` 56 - `opts`: ``optparse.OptionContainer`` 57 - `args`: ``list`` 58 """ 59 __implements__ = [_impl.ServerInterface] 60
61 - def __init__(self, config, opts, args):
62 """ 63 Initialization 64 65 :See: `wtf.impl.ServerInterface` 66 """ 67 self.config, self.opts, self.args = config, opts, args 68 self._gateway = Gateway(config, opts, args)
69
70 - def handle(self, (sock, peername), application, flags):
71 """ 72 Handle an accepted socket 73 74 :See: `wtf.impl.ServerInterface` 75 """ 76 conn = _connection.Connection(sock, peername) 77 try: 78 conn.settimeout(None) 79 request = SCGIRequest(self, conn, flags) 80 try: 81 try: 82 self._gateway.handle(conn, request, application) 83 except _socket.error, e: 84 if e[0] not in (_errno.EPIPE, _errno.ECONNRESET): 85 raise 86 print >> _sys.stderr, "Connection to webserver died." 87 except (SystemExit, KeyboardInterrupt): 88 raise 89 except: 90 try: 91 request.error( 92 "500 Internal Server Error", 93 "Something went wrong while processing the " 94 "request. You might want to try again later. " 95 "Sorry for the inconvenience." 96 ) 97 except _socket.error: 98 pass 99 print >> _sys.stderr, \ 100 "Request aborted due to exception:\n" + ''.join( 101 _traceback.format_exception(*_sys.exc_info()) 102 ) 103 finally: 104 request.close() 105 finally: 106 try: 107 conn.close() 108 except (SystemExit, KeyboardInterrupt): 109 raise 110 except: 111 pass # not much we can do here anyway. Log it?
112 113
114 -class Gateway(_gateway.Gateway):
115 """ SCGI implementation specific gateway """ 116
117 - def __init__(self, config, opts, args):
118 """ 119 Initialization 120 121 :Parameters: 122 - `config`: Configuration 123 - `opts`: Command line options 124 - `args`: Positioned command line arguments 125 126 :Types: 127 - `config`: `wtf.config.Config` 128 - `opts`: ``optparse.OptionContainer`` 129 - `args`: ``list`` 130 """ 131 super(Gateway, self).__init__(config, opts, args) 132 133 detector = self._detect_scheme_default 134 if 'scgi' in config.wtf: 135 option = config.wtf.scgi('ssl_detection', 'default') 136 if option: 137 try: 138 detector = getattr(self, 139 "_detect_scheme_%s" % option.encode('ascii') 140 ) 141 except (AttributeError, UnicodeError): 142 raise ConfigurationError( 143 "Unrecognized SSL detection method %r" % (option,) 144 ) 145 self._detect_scheme = detector
146
147 - def _init_from_request(self, connection, request):
148 """ 149 Create SCGI implementation specific request environment 150 151 :See: `_gateway.Gateway._init_from_request` 152 """ 153 environ = request.read_environ() 154 if 'SCGI' in environ: 155 del environ['SCGI'] 156 environ.update({ 157 'wsgi.multithread': request.flags.multithread, 158 'wsgi.multiprocess': request.flags.multiprocess, 159 'wsgi.run_once': request.flags.run_once, 160 'wsgi.input': request.request_body_stream or 161 _stream.dev_null, 162 'wsgi.url_scheme': self._detect_scheme(environ) or 163 self._detect_scheme_default(environ), 164 }) 165 166 return environ, request.start_response
167
168 - def _detect_scheme_default(self, environ):
169 """ HTTPS environment scheme detector (SSL directly in gateway) """ 170 if environ.get('HTTPS', '').lower() == 'on': 171 return 'https' 172 return 'http'
173
174 - def _detect_scheme_remote_lighttpd(self, environ):
175 """ 176 Scheme detector when lighttpd offloads SSL in the front of the GW 177 178 The lighttpd acts as a HTTP proxy. 179 """ 180 scheme = environ.get('HTTP_X_FORWARDED_PROTO', '').lower() 181 if scheme in ('http', 'https'): 182 # TODO: allow non-default port configuration 183 port = dict(http=80, https=443)[scheme] 184 environ['SERVER_PORT'] = str(port) 185 if ':' in environ['SERVER_NAME']: 186 shost, sport = environ['SERVER_NAME'].rsplit(':', 1) 187 try: 188 int(sport) 189 except ValueError: 190 pass 191 else: 192 environ['SERVER_NAME'] = '%s:%s' % (shost, port) 193 return scheme 194 return None
195 196
197 -class SCGIRequest(object):
198 """ SCGI Request abstraction """ 199 _response_body_stream = None 200 request_body_stream = None 201 _env = None 202
203 - def __init__(self, server, connection, flags):
204 """ 205 Initialization 206 207 :Parameters: 208 - `server`: Server instance 209 - `connection`: Connection, this request is served on 210 - `flags`: Worker flags 211 212 :Types: 213 - `server`: `SCGIServer` 214 - `connection`: `Connection` 215 - `flags`: `FlagsInterface` 216 """ 217 self._server = server 218 self.connection = SCGIConnection(connection) 219 self.flags = flags
220
221 - def close(self):
222 """ Close all streams """ 223 if self._response_body_stream is not None: 224 self._response_body_stream.close() # fluuuush 225 connection, self.connection = self.connection, None 226 if connection is not None: 227 connection.close()
228
229 - def read_environ(self):
230 """ 231 Read the environ from the socket 232 233 :return: The header dict 234 :rtype: ``dict`` 235 236 :Exceptions: 237 - `NetStringError`: Error reading or interpreting the netstring 238 """ 239 block = iter(self.connection.read_netstring().split('\0')) 240 env = dict(_it.izip(block, block)) 241 clen = int(env['CONTENT_LENGTH']) 242 if clen > 0: 243 self.request_body_stream = _stream.GenericStream( 244 _impl_util.ContentLengthReader( 245 self.connection.reader, clen 246 ), 247 read_exact=True, 248 ) 249 self._env = env 250 return env
251
252 - def start_response(self, status, headers):
253 """ 254 Start response and determine output stream 255 256 :Parameters: 257 - `status`: Response status line 258 - `headers`: Response headers (``[(key, value), ...]``) 259 260 :Types: 261 - `status`: ``str`` 262 - `headers`: ``list`` 263 264 :return: response stream 265 :rtype: `stream.GenericStream` 266 """ 267 writer = self.connection.writer 268 writer.write("Status: %s\n" % status) 269 writer.writelines("%s: %s\n" % (key, value) for key, value in headers) 270 writer.write("\n") 271 if self._env['REQUEST_METHOD'] == 'HEAD': 272 stream = _stream.dev_null 273 else: 274 self._response_body_stream = stream = writer 275 return stream
276
277 - def error(self, status, message):
278 """ 279 Emit a simple error 280 281 :Parameters: 282 - `status`: Status line 283 - `message`: Message 284 285 :Types: 286 - `status`: ``str`` 287 - `message`: ``str`` 288 """ 289 out = "%s\n%s\n" % (status, message) 290 write = self.connection.writer.write 291 write("Status: %s\n" % status) 292 write("Content-Type: text/plain\n") 293 write("Content-Length: %s\n" % len(out)) 294 write("\n") 295 write(out)
296 297
298 -class SCGIConnection(object):
299 """ 300 SCGI connection 301 302 :IVariables: 303 - `reader`: Connection read stream 304 - `writer`: Connection write stream 305 - `settimeout`: Timeout setter 306 307 :Types: 308 - `reader`: `stream.GenericStream` 309 - `writer`: `stream.GenericStream` 310 - `settimeout`: ``callable`` 311 """ 312
313 - def __init__(self, connection):
314 """ 315 Initialization 316 317 :Parameters: 318 - `connection`: Socket connection 319 320 :Types: 321 - `connection`: `Connection` 322 """ 323 self.reader = connection.reader() 324 self.writer = connection.writer() 325 self.settimeout = connection.settimeout
326
327 - def __del__(self):
328 self.close()
329
330 - def close(self):
331 """ Close the streams """ 332 try: 333 reader, self.reader = self.reader, None 334 if reader is not None: 335 reader.close() 336 finally: 337 writer, self.writer = self.writer, None 338 if writer is not None: 339 writer.close()
340
341 - def read_netstring(self):
342 """ 343 Read "netstring" from connection 344 345 :return: The netstring value 346 :rtype: ``str`` 347 348 :Exceptions: 349 - `NetStringError`: Error reading or interpreting the netstring 350 """ 351 data = _stream.read_exact(self.reader, self._netstring_size()) 352 if self.reader.read(1) != ',': 353 raise NetStringError("EOS before netstring delimiter") 354 return data
355
356 - def _netstring_size(self):
357 """ 358 Read netstring size from connection 359 360 :return: The netstring size 361 :rtype: ``int`` 362 363 :Exceptions: 364 - `NetStringError`: Error reading or interpreting the number 365 """ 366 chars, read = [], self.reader.read 367 push = chars.append 368 while True: 369 char = read(1) 370 if not char: 371 raise NetStringError("EOS before netstring size delimiter") 372 if char == ':': 373 break 374 push(char) 375 chars = ''.join(chars) 376 try: 377 return int(chars) 378 except ValueError: 379 raise NetStringError("Invalid netstring size: %r" % chars)
380