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