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

Source Code for Module wtf.impl.http._server

  1  # -*- coding: ascii -*- 
  2  # 
  3  # Copyright 2006-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  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   
41 -class HTTPServer(object):
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
63 - def __init__(self, config, opts, args):
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 # pylint: disable = R0912, R0915 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 # kept alive, but no further request 102 except _socket.error, e: 103 if e[0] not in (_errno.EPIPE, _errno.ECONNRESET): 104 raise 105 break # no matter what, connection is done. 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 # who cares? 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 # nothing we could do here anyway, maybe log it?
144 145
146 -class Gateway(_gateway.Gateway):
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
177 - def _populate_base_env(self, base_env):
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
189 - def _init_from_request(self, connection, request):
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', # no ssl for now 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
248 -class _TimeOuts(object):
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
261 - def __init__(self, config):
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