Package wsgitools :: Package scgi :: Module asynchronous
[hide private]
[frames] | no frames]

Source Code for Module wsgitools.scgi.asynchronous

  1  __all__ = [] 
  2   
  3  import asyncore 
  4  import socket 
  5  import sys 
  6  try: 
  7      import io 
  8  except ImportError: 
  9      try: 
 10          import cStringIO as io 
 11      except ImportError: 
 12          import StringIO as io 
 13   
14 -class SCGIConnection(asyncore.dispatcher):
15 """SCGI connection class used by L{SCGIServer}.""" 16 # connection states 17 NEW = 0*4 | 1 # connection established, waiting for request 18 HEADER = 1*4 | 1 # the request length was received, waiting for the rest 19 BODY = 2*4 | 1 # the request header was received, waiting for the body 20 REQ = 3*4 | 2 # request received, sending response
21 - def __init__(self, server, connection, addr, maxrequestsize=65536, 22 maxpostsize=8<<20, blocksize=4096, config={}):
23 asyncore.dispatcher.__init__(self, connection) 24 25 self.server = server # WSGISCGIServer instance 26 self.addr = addr # scgi client address 27 self.maxrequestsize = maxrequestsize 28 self.maxpostsize = maxpostsize 29 self.blocksize = blocksize 30 self.state = SCGIConnection.NEW # internal state 31 self.environ = config.copy() # environment passed to wsgi app 32 self.reqlen = -1 # request length used in two different meanings 33 self.inbuff = "" # input buffer 34 self.outbuff = "" # output buffer 35 self.wsgihandler = None # wsgi application iterator 36 self.outheaders = () # headers to be sent 37 # () -> unset, (..,..) -> set, True -> sent 38 self.body = io.StringIO() # request body
39
40 - def _wsgi_headers(self):
41 return {"wsgi.version": (1, 0), 42 "wsgi.input": self.body, 43 "wsgi.errors": self.server.error, 44 "wsgi.url_scheme": "http", 45 "wsgi.multithread": False, 46 "wsgi.multiprocess": False, 47 "wsgi.run_once": False}
48
49 - def _try_send_headers(self):
50 if self.outheaders != True: 51 assert not self.outbuff 52 status, headers = self.outheaders 53 headdata = "".join(map("%s: %s\r\n".__mod__, headers)) 54 self.outbuff = "Status: %s\r\n%s\r\n" % (status, headdata) 55 self.outheaders = True
56
57 - def _wsgi_write(self, data):
58 assert self.state >= SCGIConnection.REQ 59 assert isinstance(data, str) 60 if data: 61 self._try_send_headers() 62 self.outbuff += data
63
64 - def readable(self):
65 """C{asyncore} interface""" 66 return self.state & 1 == 1
67
68 - def writable(self):
69 """C{asyncore} interface""" 70 return self.state & 2 == 2
71
72 - def handle_read(self):
73 """C{asyncore} interface""" 74 data = self.recv(self.blocksize) 75 self.inbuff += data 76 if self.state == SCGIConnection.NEW: 77 if ':' in self.inbuff: 78 reqlen, self.inbuff = self.inbuff.split(':', 1) 79 if not reqlen.isdigit(): 80 self.close() 81 return # invalid request format 82 reqlen = int(reqlen) 83 if reqlen > self.maxrequestsize: 84 self.close() 85 return # request too long 86 self.reqlen = reqlen 87 self.state = SCGIConnection.HEADER 88 elif len(self.inbuff) > self.maxrequestsize: 89 self.close() 90 return # request too long 91 92 if self.state == SCGIConnection.HEADER: 93 buff = self.inbuff[:self.reqlen] 94 remainder = self.inbuff[self.reqlen:] 95 96 while buff.count('\0') >= 2: 97 key, value, buff = buff.split('\0', 2) 98 self.environ[key] = value 99 self.reqlen -= len(key) + len(value) + 2 100 101 self.inbuff = buff + remainder 102 103 if self.reqlen == 0: 104 if self.inbuff.startswith(','): 105 self.inbuff = self.inbuff[1:] 106 if not self.environ.get("CONTENT_LENGTH", "bad").isdigit(): 107 self.close() 108 return 109 self.reqlen = int(self.environ["CONTENT_LENGTH"]) 110 if self.reqlen > self.maxpostsize: 111 self.close() 112 return 113 self.state = SCGIConnection.BODY 114 else: 115 self.close() 116 return # protocol violation 117 118 if self.state == SCGIConnection.BODY: 119 if len(self.inbuff) >= self.reqlen: 120 self.body.write(self.inbuff[:self.reqlen]) 121 self.body.seek(0) 122 self.inbuff = "" 123 self.reqlen = 0 124 self.environ.update(self._wsgi_headers()) 125 if self.environ.get("HTTPS", "no").lower() in ('yes', 'y', '1'): 126 self.environ["wsgi.url_scheme"] = "https" 127 if "HTTP_CONTENT_TYPE" in self.environ: 128 self.environ["CONTENT_TYPE"] = \ 129 self.environ.pop("HTTP_CONTENT_TYPE") 130 if "HTTP_CONTENT_LENGTH" in self.environ: 131 del self.environ["HTTP_CONTENT_LENGTH"] # TODO: better way? 132 self.wsgihandler = iter(self.server.wsgiapp( 133 self.environ, self.start_response)) 134 self.state = SCGIConnection.REQ 135 else: 136 self.body.write(self.inbuff) 137 self.reqlen -= len(self.inbuff) 138 self.inbuff = ""
139
140 - def start_response(self, status, headers, exc_info=None):
141 assert isinstance(status, str) 142 assert isinstance(headers, list) 143 if exc_info: 144 if self.outheaders == True: 145 try: 146 raise exc_info[0], exc_info[1], exc_info[2] 147 finally: 148 exc_info = None 149 assert self.outheaders != True # unsent 150 self.outheaders = (status, headers) 151 return self._wsgi_write
152
153 - def handle_write(self):
154 """C{asyncore} interface""" 155 assert self.state >= SCGIConnection.REQ 156 if len(self.outbuff) < self.blocksize: 157 self._try_send_headers() 158 for data in self.wsgihandler: 159 assert isinstance(data, str) 160 if data: 161 self.outbuff += data 162 break 163 if len(self.outbuff) == 0: 164 if hasattr(self.wsgihandler, "close"): 165 self.wsgihandler.close() 166 self.close() 167 return 168 try: 169 sentbytes = self.send(self.outbuff[:self.blocksize]) 170 except socket.error: 171 if hasattr(self.wsgihandler, "close"): 172 self.wsgihandler.close() 173 self.close() 174 return 175 self.outbuff = self.outbuff[sentbytes:]
176
177 - def handle_close(self):
178 """C{asyncore} interface""" 179 self.close()
180 181 __all__.append("SCGIServer")
182 -class SCGIServer(asyncore.dispatcher):
183 """SCGI Server for WSGI applications. It does not use multiple processes or 184 multiple threads."""
185 - def __init__(self, wsgiapp, port, interface="localhost", error=sys.stderr, 186 maxrequestsize=None, maxpostsize=None, blocksize=None, 187 config={}):
188 """ 189 @param wsgiapp: is the wsgi application to be run. 190 @type port: int 191 @param port: is an int representing the TCP port number to be used. 192 @type interface: str 193 @param interface: is a string specifying the network interface to bind 194 which defaults to C{"localhost"} making the server inaccessible 195 over network. 196 @param error: is a file-like object being passed as C{wsgi.error} in the 197 environ parameter defaulting to stderr. 198 @type maxrequestsize: int 199 @param maxrequestsize: limit the size of request blocks in scgi 200 connections. Connections are dropped when this limit is hit. 201 @type maxpostsize: int 202 @param maxpostsize: limit the size of post bodies that may be processed 203 by this instance. Connections are dropped when this limit is 204 hit. 205 @type blocksize: int 206 @param blocksize: is amount of data to read or write from or to the 207 network at once 208 @type config: {} 209 @param config: the environ dictionary is updated using these values for 210 each request. 211 """ 212 asyncore.dispatcher.__init__(self) 213 214 self.wsgiapp = wsgiapp 215 self.error = error 216 self.conf = {} 217 if maxrequestsize is not None: 218 self.conf["maxrequestsize"] = maxrequestsize 219 if maxpostsize is not None: 220 self.conf["maxpostsize"] = maxpostsize 221 if blocksize is not None: 222 self.conf["blocksize"] = blocksize 223 self.conf["config"] = config 224 225 self.create_socket(socket.AF_INET, socket.SOCK_STREAM) 226 self.set_reuse_addr() 227 self.bind((interface, port)) 228 self.listen(5)
229
230 - def handle_accept(self):
231 """asyncore interface""" 232 ret = self.accept() 233 if ret is not None: 234 conn, addr = ret 235 SCGIConnection(self, conn, addr, **self.conf)
236
237 - def run(self):
238 """Runs the server. It will not return and you can invoke 239 C{asyncore.loop()} instead achieving the same effect.""" 240 asyncore.loop()
241