Back to index

moin  1.9.0~rc2
ajp_base.py
Go to the documentation of this file.
00001 # Copyright (c) 2005, 2006 Allan Saddi <allan@saddi.com>
00002 # All rights reserved.
00003 #
00004 # Redistribution and use in source and binary forms, with or without
00005 # modification, are permitted provided that the following conditions
00006 # are met:
00007 # 1. Redistributions of source code must retain the above copyright
00008 #    notice, this list of conditions and the following disclaimer.
00009 # 2. Redistributions in binary form must reproduce the above copyright
00010 #    notice, this list of conditions and the following disclaimer in the
00011 #    documentation and/or other materials provided with the distribution.
00012 #
00013 # THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
00014 # ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
00015 # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
00016 # ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
00017 # FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
00018 # DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
00019 # OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
00020 # HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
00021 # LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
00022 # OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
00023 # SUCH DAMAGE.
00024 #
00025 # $Id$
00026 
00027 __author__ = 'Allan Saddi <allan@saddi.com>'
00028 __version__ = '$Revision$'
00029 
00030 import sys
00031 import socket
00032 import select
00033 import struct
00034 import signal
00035 import logging
00036 import errno
00037 import datetime
00038 import time
00039 
00040 # Unfortunately, for now, threads are required.
00041 import thread
00042 import threading
00043 
00044 from flup.server import NoDefault
00045 
00046 __all__ = ['BaseAJPServer']
00047 
00048 # Packet header prefixes.
00049 SERVER_PREFIX = '\x12\x34'
00050 CONTAINER_PREFIX = 'AB'
00051 
00052 # Server packet types.
00053 PKTTYPE_FWD_REQ = '\x02'
00054 PKTTYPE_SHUTDOWN = '\x07'
00055 PKTTYPE_PING = '\x08'
00056 PKTTYPE_CPING = '\x0a'
00057 
00058 # Container packet types.
00059 PKTTYPE_SEND_BODY = '\x03'
00060 PKTTYPE_SEND_HEADERS = '\x04'
00061 PKTTYPE_END_RESPONSE = '\x05'
00062 PKTTYPE_GET_BODY = '\x06'
00063 PKTTYPE_CPONG = '\x09'
00064 
00065 # Code tables for methods/headers/attributes.
00066 methodTable = [
00067     None,
00068     'OPTIONS',
00069     'GET',
00070     'HEAD',
00071     'POST',
00072     'PUT',
00073     'DELETE',
00074     'TRACE',
00075     'PROPFIND',
00076     'PROPPATCH',
00077     'MKCOL',
00078     'COPY',
00079     'MOVE',
00080     'LOCK',
00081     'UNLOCK',
00082     'ACL',
00083     'REPORT',
00084     'VERSION-CONTROL',
00085     'CHECKIN',
00086     'CHECKOUT',
00087     'UNCHECKOUT',
00088     'SEARCH',
00089     'MKWORKSPACE',
00090     'UPDATE',
00091     'LABEL',
00092     'MERGE',
00093     'BASELINE_CONTROL',
00094     'MKACTIVITY'
00095     ]
00096 
00097 requestHeaderTable = [
00098     None,
00099     'Accept',
00100     'Accept-Charset',
00101     'Accept-Encoding',
00102     'Accept-Language',
00103     'Authorization',
00104     'Connection',
00105     'Content-Type',
00106     'Content-Length',
00107     'Cookie',
00108     'Cookie2',
00109     'Host',
00110     'Pragma',
00111     'Referer',
00112     'User-Agent'
00113     ]
00114 
00115 attributeTable = [
00116     None,
00117     'CONTEXT',
00118     'SERVLET_PATH',
00119     'REMOTE_USER',
00120     'AUTH_TYPE',
00121     'QUERY_STRING',
00122     'JVM_ROUTE',
00123     'SSL_CERT',
00124     'SSL_CIPHER',
00125     'SSL_SESSION',
00126     None, # name follows
00127     'SSL_KEY_SIZE'
00128     ]
00129 
00130 responseHeaderTable = [
00131     None,
00132     'content-type',
00133     'content-language',
00134     'content-length',
00135     'date',
00136     'last-modified',
00137     'location',
00138     'set-cookie',
00139     'set-cookie2',
00140     'servlet-engine',
00141     'status',
00142     'www-authenticate'
00143     ]
00144 
00145 # The main classes use this name for logging.
00146 LoggerName = 'ajp-wsgi'
00147 
00148 # Set up module-level logger.
00149 console = logging.StreamHandler()
00150 console.setLevel(logging.DEBUG)
00151 console.setFormatter(logging.Formatter('%(asctime)s : %(message)s',
00152                                        '%Y-%m-%d %H:%M:%S'))
00153 logging.getLogger(LoggerName).addHandler(console)
00154 del console
00155 
00156 class ProtocolError(Exception):
00157     """
00158     Exception raised when the server does something unexpected or
00159     sends garbled data. Usually leads to a Connection closing.
00160     """
00161     pass
00162 
00163 def decodeString(data, pos=0):
00164     """Decode a string."""
00165     try:
00166         length = struct.unpack('>H', data[pos:pos+2])[0]
00167         pos += 2
00168         if length == 0xffff: # This was undocumented!
00169             return '', pos
00170         s = data[pos:pos+length]
00171         return s, pos+length+1 # Don't forget NUL
00172     except Exception, e:
00173         raise ProtocolError, 'decodeString: '+str(e)
00174 
00175 def decodeRequestHeader(data, pos=0):
00176     """Decode a request header/value pair."""
00177     try:
00178         if data[pos] == '\xa0':
00179             # Use table
00180             i = ord(data[pos+1])
00181             name = requestHeaderTable[i]
00182             if name is None:
00183                 raise ValueError, 'bad request header code'
00184             pos += 2
00185         else:
00186             name, pos = decodeString(data, pos)
00187         value, pos = decodeString(data, pos)
00188         return name, value, pos
00189     except Exception, e:
00190         raise ProtocolError, 'decodeRequestHeader: '+str(e)
00191 
00192 def decodeAttribute(data, pos=0):
00193     """Decode a request attribute."""
00194     try:
00195         i = ord(data[pos])
00196         pos += 1
00197         if i == 0xff:
00198             # end
00199             return None, None, pos
00200         elif i == 0x0a:
00201             # name follows
00202             name, pos = decodeString(data, pos)
00203         elif i == 0x0b:
00204             # Special handling of SSL_KEY_SIZE.
00205             name = attributeTable[i]
00206             # Value is an int, not a string.
00207             value = struct.unpack('>H', data[pos:pos+2])[0]
00208             return name, str(value), pos+2
00209         else:
00210             name = attributeTable[i]
00211             if name is None:
00212                 raise ValueError, 'bad attribute code'
00213         value, pos = decodeString(data, pos)
00214         return name, value, pos
00215     except Exception, e:
00216         raise ProtocolError, 'decodeAttribute: '+str(e)
00217 
00218 def encodeString(s):
00219     """Encode a string."""
00220     return struct.pack('>H', len(s)) + s + '\x00'
00221 
00222 def encodeResponseHeader(name, value):
00223     """Encode a response header/value pair."""
00224     lname = name.lower()
00225     if lname in responseHeaderTable:
00226         # Use table
00227         i = responseHeaderTable.index(lname)
00228         out = '\xa0' + chr(i)
00229     else:
00230         out = encodeString(name)
00231     out += encodeString(value)
00232     return out
00233 
00234 class Packet(object):
00235     """An AJP message packet."""
00236     def __init__(self):
00237         self.data = ''
00238         # Don't set this on write, it will be calculated automatically.
00239         self.length = 0
00240 
00241     def _recvall(sock, length):
00242         """
00243         Attempts to receive length bytes from a socket, blocking if necessary.
00244         (Socket may be blocking or non-blocking.)
00245         """
00246         dataList = []
00247         recvLen = 0
00248         while length:
00249             try:
00250                 data = sock.recv(length)
00251             except socket.error, e:
00252                 if e[0] == errno.EAGAIN:
00253                     select.select([sock], [], [])
00254                     continue
00255                 else:
00256                     raise
00257             if not data: # EOF
00258                 break
00259             dataList.append(data)
00260             dataLen = len(data)
00261             recvLen += dataLen
00262             length -= dataLen
00263         return ''.join(dataList), recvLen
00264     _recvall = staticmethod(_recvall)
00265 
00266     def read(self, sock):
00267         """Attempt to read a packet from the server."""
00268         try:
00269             header, length = self._recvall(sock, 4)
00270         except socket.error:
00271             # Treat any sort of socket errors as EOF (close Connection).
00272             raise EOFError
00273 
00274         if length < 4:
00275             raise EOFError
00276 
00277         if header[:2] != SERVER_PREFIX:
00278             raise ProtocolError, 'invalid header'
00279 
00280         self.length = struct.unpack('>H', header[2:4])[0]
00281         if self.length:
00282             try:
00283                 self.data, length = self._recvall(sock, self.length)
00284             except socket.error:
00285                 raise EOFError
00286 
00287             if length < self.length:
00288                 raise EOFError
00289 
00290     def _sendall(sock, data):
00291         """
00292         Writes data to a socket and does not return until all the data is sent.
00293         """
00294         length = len(data)
00295         while length:
00296             try:
00297                 sent = sock.send(data)
00298             except socket.error, e:
00299                 if e[0] == errno.EAGAIN:
00300                     select.select([], [sock], [])
00301                     continue
00302                 else:
00303                     raise
00304             data = data[sent:]
00305             length -= sent
00306     _sendall = staticmethod(_sendall)
00307 
00308     def write(self, sock):
00309         """Send a packet to the server."""
00310         self.length = len(self.data)
00311         self._sendall(sock, CONTAINER_PREFIX + struct.pack('>H', self.length))
00312         if self.length:
00313             self._sendall(sock, self.data)
00314 
00315 class InputStream(object):
00316     """
00317     File-like object that represents the request body (if any). Supports
00318     the bare mininum methods required by the WSGI spec. Thanks to
00319     StringIO for ideas.
00320     """
00321     def __init__(self, conn):
00322         self._conn = conn
00323 
00324         # See WSGIServer.
00325         self._shrinkThreshold = conn.server.inputStreamShrinkThreshold
00326 
00327         self._buf = ''
00328         self._bufList = []
00329         self._pos = 0 # Current read position.
00330         self._avail = 0 # Number of bytes currently available.
00331         self._length = 0 # Set to Content-Length in request.
00332 
00333         self.logger = logging.getLogger(LoggerName)
00334 
00335     def bytesAvailForAdd(self):
00336         return self._length - self._avail
00337 
00338     def _shrinkBuffer(self):
00339         """Gets rid of already read data (since we can't rewind)."""
00340         if self._pos >= self._shrinkThreshold:
00341             self._buf = self._buf[self._pos:]
00342             self._avail -= self._pos
00343             self._length -= self._pos
00344             self._pos = 0
00345 
00346             assert self._avail >= 0 and self._length >= 0
00347 
00348     def _waitForData(self):
00349         toAdd = min(self.bytesAvailForAdd(), 0xffff)
00350         assert toAdd > 0
00351         pkt = Packet()
00352         pkt.data = PKTTYPE_GET_BODY + \
00353                    struct.pack('>H', toAdd)
00354         self._conn.writePacket(pkt)
00355         self._conn.processInput()
00356 
00357     def read(self, n=-1):
00358         if self._pos == self._length:
00359             return ''
00360         while True:
00361             if n < 0 or (self._avail - self._pos) < n:
00362                 # Not enough data available.
00363                 if not self.bytesAvailForAdd():
00364                     # And there's no more coming.
00365                     newPos = self._avail
00366                     break
00367                 else:
00368                     # Ask for more data and wait.
00369                     self._waitForData()
00370                     continue
00371             else:
00372                 newPos = self._pos + n
00373                 break
00374         # Merge buffer list, if necessary.
00375         if self._bufList:
00376             self._buf += ''.join(self._bufList)
00377             self._bufList = []
00378         r = self._buf[self._pos:newPos]
00379         self._pos = newPos
00380         self._shrinkBuffer()
00381         return r
00382 
00383     def readline(self, length=None):
00384         if self._pos == self._length:
00385             return ''
00386         while True:
00387             # Unfortunately, we need to merge the buffer list early.
00388             if self._bufList:
00389                 self._buf += ''.join(self._bufList)
00390                 self._bufList = []
00391             # Find newline.
00392             i = self._buf.find('\n', self._pos)
00393             if i < 0:
00394                 # Not found?
00395                 if not self.bytesAvailForAdd():
00396                     # No more data coming.
00397                     newPos = self._avail
00398                     break
00399                 else:
00400                     if length is not None and len(self._buf) >= length + self._pos:
00401                         newPos = self._pos + length
00402                         break
00403                     # Wait for more to come.
00404                     self._waitForData()
00405                     continue
00406             else:
00407                 newPos = i + 1
00408                 break
00409         r = self._buf[self._pos:newPos]
00410         self._pos = newPos
00411         self._shrinkBuffer()
00412         return r
00413 
00414     def readlines(self, sizehint=0):
00415         total = 0
00416         lines = []
00417         line = self.readline()
00418         while line:
00419             lines.append(line)
00420             total += len(line)
00421             if 0 < sizehint <= total:
00422                 break
00423             line = self.readline()
00424         return lines
00425 
00426     def __iter__(self):
00427         return self
00428 
00429     def next(self):
00430         r = self.readline()
00431         if not r:
00432             raise StopIteration
00433         return r
00434 
00435     def setDataLength(self, length):
00436         """
00437         Once Content-Length is known, Request calls this method to set it.
00438         """
00439         self._length = length
00440 
00441     def addData(self, data):
00442         """
00443         Adds data from the server to this InputStream. Note that we never ask
00444         the server for data beyond the Content-Length, so the server should
00445         never send us an EOF (empty string argument).
00446         """
00447         if not data:
00448             raise ProtocolError, 'short data'
00449         self._bufList.append(data)
00450         length = len(data)
00451         self._avail += length
00452         if self._avail > self._length:
00453             raise ProtocolError, 'too much data'
00454 
00455 class Request(object):
00456     """
00457     A Request object. A more fitting name would probably be Transaction, but
00458     it's named Request to mirror my FastCGI driver. :) This object
00459     encapsulates all the data about the HTTP request and allows the handler
00460     to send a response.
00461 
00462     The only attributes/methods that the handler should concern itself
00463     with are: environ, input, startResponse(), and write().
00464     """
00465     # Do not ever change the following value.
00466     _maxWrite = 8192 - 4 - 3 - 1 # 8k - pkt header - send body header - NUL
00467 
00468     def __init__(self, conn):
00469         self._conn = conn
00470 
00471         self.environ = {}
00472         self.input = InputStream(conn)
00473 
00474         self._headersSent = False
00475 
00476         self.logger = logging.getLogger(LoggerName)
00477 
00478     def run(self):
00479         self.logger.info('%s %s',
00480                          self.environ['REQUEST_METHOD'],
00481                          self.environ['REQUEST_URI'])
00482 
00483         start = datetime.datetime.now()
00484 
00485         try:
00486             self._conn.server.handler(self)
00487         except:
00488             self.logger.exception('Exception caught from handler')
00489             if not self._headersSent:
00490                 self._conn.server.error(self)
00491 
00492         end = datetime.datetime.now()
00493 
00494         # Notify server of end of response (reuse flag is set to true).
00495         pkt = Packet()
00496         pkt.data = PKTTYPE_END_RESPONSE + '\x01'
00497         self._conn.writePacket(pkt)
00498 
00499         handlerTime = end - start
00500         self.logger.debug('%s %s done (%.3f secs)',
00501                           self.environ['REQUEST_METHOD'],
00502                           self.environ['REQUEST_URI'],
00503                           handlerTime.seconds +
00504                           handlerTime.microseconds / 1000000.0)
00505 
00506     # The following methods are called from the Connection to set up this
00507     # Request.
00508 
00509     def setMethod(self, value):
00510         self.environ['REQUEST_METHOD'] = value
00511 
00512     def setProtocol(self, value):
00513         self.environ['SERVER_PROTOCOL'] = value
00514 
00515     def setRequestURI(self, value):
00516         self.environ['REQUEST_URI'] = value
00517 
00518     def setRemoteAddr(self, value):
00519         self.environ['REMOTE_ADDR'] = value
00520 
00521     def setRemoteHost(self, value):
00522         self.environ['REMOTE_HOST'] = value
00523 
00524     def setServerName(self, value):
00525         self.environ['SERVER_NAME'] = value
00526 
00527     def setServerPort(self, value):
00528         self.environ['SERVER_PORT'] = str(value)
00529 
00530     def setIsSSL(self, value):
00531         if value:
00532             self.environ['HTTPS'] = 'on'
00533 
00534     def addHeader(self, name, value):
00535         name = name.replace('-', '_').upper()
00536         if name in ('CONTENT_TYPE', 'CONTENT_LENGTH'):
00537             self.environ[name] = value
00538             if name == 'CONTENT_LENGTH':
00539                 length = int(value)
00540                 self.input.setDataLength(length)
00541         else:
00542             self.environ['HTTP_'+name] = value
00543 
00544     def addAttribute(self, name, value):
00545         self.environ[name] = value
00546 
00547     # The only two methods that should be called from the handler.
00548 
00549     def startResponse(self, statusCode, statusMsg, headers):
00550         """
00551         Begin the HTTP response. This must only be called once and it
00552         must be called before any calls to write().
00553 
00554         statusCode is the integer status code (e.g. 200). statusMsg
00555         is the associated reason message (e.g.'OK'). headers is a list
00556         of 2-tuples - header name/value pairs. (Both header name and value
00557         must be strings.)
00558         """
00559         assert not self._headersSent, 'Headers already sent!'
00560 
00561         pkt = Packet()
00562         pkt.data = PKTTYPE_SEND_HEADERS + \
00563                    struct.pack('>H', statusCode) + \
00564                    encodeString(statusMsg) + \
00565                    struct.pack('>H', len(headers)) + \
00566                    ''.join([encodeResponseHeader(name, value)
00567                             for name,value in headers])
00568 
00569         self._conn.writePacket(pkt)
00570 
00571         self._headersSent = True
00572 
00573     def write(self, data):
00574         """
00575         Write data (which comprises the response body). Note that due to
00576         restrictions on AJP packet size, we limit our writes to 8185 bytes
00577         each packet.
00578         """
00579         assert self._headersSent, 'Headers must be sent first!'
00580 
00581         bytesLeft = len(data)
00582         while bytesLeft:
00583             toWrite = min(bytesLeft, self._maxWrite)
00584 
00585             pkt = Packet()
00586             pkt.data = PKTTYPE_SEND_BODY + \
00587                        struct.pack('>H', toWrite) + \
00588                        data[:toWrite] + '\x00' # Undocumented
00589             self._conn.writePacket(pkt)
00590 
00591             data = data[toWrite:]
00592             bytesLeft -= toWrite
00593 
00594 class Connection(object):
00595     """
00596     A single Connection with the server. Requests are not multiplexed over the
00597     same connection, so at any given time, the Connection is either
00598     waiting for a request, or processing a single request.
00599     """
00600     def __init__(self, sock, addr, server):
00601         self.server = server
00602         self._sock = sock
00603         self._addr = addr
00604 
00605         self._request = None
00606 
00607         self.logger = logging.getLogger(LoggerName)
00608 
00609     def run(self):
00610         self.logger.debug('Connection starting up (%s:%d)',
00611                           self._addr[0], self._addr[1])
00612 
00613         # Main loop. Errors will cause the loop to be exited and
00614         # the socket to be closed.
00615         while True:
00616             try:
00617                 self.processInput()
00618             except ProtocolError, e:
00619                 self.logger.error("Protocol error '%s'", str(e))
00620                 break
00621             except (EOFError, KeyboardInterrupt):
00622                 break
00623             except:
00624                 self.logger.exception('Exception caught in Connection')
00625                 break
00626 
00627         self.logger.debug('Connection shutting down (%s:%d)',
00628                           self._addr[0], self._addr[1])
00629 
00630         self._sock.close()
00631 
00632     def processInput(self):
00633         """Wait for and process a single packet."""
00634         pkt = Packet()
00635         select.select([self._sock], [], [])
00636         pkt.read(self._sock)
00637 
00638         # Body chunks have no packet type code.
00639         if self._request is not None:
00640             self._processBody(pkt)
00641             return
00642 
00643         if not pkt.length:
00644             raise ProtocolError, 'unexpected empty packet'
00645 
00646         pkttype = pkt.data[0]
00647         if pkttype == PKTTYPE_FWD_REQ:
00648             self._forwardRequest(pkt)
00649         elif pkttype == PKTTYPE_SHUTDOWN:
00650             self._shutdown(pkt)
00651         elif pkttype == PKTTYPE_PING:
00652             self._ping(pkt)
00653         elif pkttype == PKTTYPE_CPING:
00654             self._cping(pkt)
00655         else:
00656             raise ProtocolError, 'unknown packet type'
00657 
00658     def _forwardRequest(self, pkt):
00659         """
00660         Creates a Request object, fills it in from the packet, then runs it.
00661         """
00662         assert self._request is None
00663 
00664         req = self.server.requestClass(self)
00665         i = ord(pkt.data[1])
00666         method = methodTable[i]
00667         if method is None:
00668             raise ValueError, 'bad method field'
00669         req.setMethod(method)
00670         value, pos = decodeString(pkt.data, 2)
00671         req.setProtocol(value)
00672         value, pos = decodeString(pkt.data, pos)
00673         req.setRequestURI(value)
00674         value, pos = decodeString(pkt.data, pos)
00675         req.setRemoteAddr(value)
00676         value, pos = decodeString(pkt.data, pos)
00677         req.setRemoteHost(value)
00678         value, pos = decodeString(pkt.data, pos)
00679         req.setServerName(value)
00680         value = struct.unpack('>H', pkt.data[pos:pos+2])[0]
00681         req.setServerPort(value)
00682         i = ord(pkt.data[pos+2])
00683         req.setIsSSL(i != 0)
00684 
00685         # Request headers.
00686         numHeaders = struct.unpack('>H', pkt.data[pos+3:pos+5])[0]
00687         pos += 5
00688         for i in range(numHeaders):
00689             name, value, pos = decodeRequestHeader(pkt.data, pos)
00690             req.addHeader(name, value)
00691 
00692         # Attributes.
00693         while True:
00694             name, value, pos = decodeAttribute(pkt.data, pos)
00695             if name is None:
00696                 break
00697             req.addAttribute(name, value)
00698 
00699         self._request = req
00700 
00701         # Read first body chunk, if needed.
00702         if req.input.bytesAvailForAdd():
00703             self.processInput()
00704 
00705         # Run Request.
00706         req.run()
00707 
00708         self._request = None
00709 
00710     def _shutdown(self, pkt):
00711         """Not sure what to do with this yet."""
00712         self.logger.info('Received shutdown request from server')
00713 
00714     def _ping(self, pkt):
00715         """I have no idea what this packet means."""
00716         self.logger.debug('Received ping')
00717 
00718     def _cping(self, pkt):
00719         """Respond to a PING (CPING) packet."""
00720         self.logger.debug('Received PING, sending PONG')
00721         pkt = Packet()
00722         pkt.data = PKTTYPE_CPONG
00723         self.writePacket(pkt)
00724 
00725     def _processBody(self, pkt):
00726         """
00727         Handles a body chunk from the server by appending it to the
00728         InputStream.
00729         """
00730         if pkt.length:
00731             length = struct.unpack('>H', pkt.data[:2])[0]
00732             self._request.input.addData(pkt.data[2:2+length])
00733         else:
00734             # Shouldn't really ever get here.
00735             self._request.input.addData('')
00736 
00737     def writePacket(self, pkt):
00738         """Sends a Packet to the server."""
00739         pkt.write(self._sock)
00740 
00741 class BaseAJPServer(object):
00742     # What Request class to use.
00743     requestClass = Request
00744 
00745     # Limits the size of the InputStream's string buffer to this size + 8k.
00746     # Since the InputStream is not seekable, we throw away already-read
00747     # data once this certain amount has been read. (The 8k is there because
00748     # it is the maximum size of new data added per chunk.)
00749     inputStreamShrinkThreshold = 102400 - 8192
00750 
00751     def __init__(self, application, scriptName='', environ=None,
00752                  multithreaded=True, multiprocess=False,
00753                  bindAddress=('localhost', 8009), allowedServers=NoDefault,
00754                  loggingLevel=logging.INFO, debug=False):
00755         """
00756         scriptName is the initial portion of the URL path that "belongs"
00757         to your application. It is used to determine PATH_INFO (which doesn't
00758         seem to be passed in). An empty scriptName means your application
00759         is mounted at the root of your virtual host.
00760 
00761         environ, which must be a dictionary, can contain any additional
00762         environment variables you want to pass to your application.
00763 
00764         Set multithreaded to False if your application is not thread-safe.
00765 
00766         Set multiprocess to True to explicitly set wsgi.multiprocess to
00767         True. (Only makes sense with threaded servers.)
00768 
00769         bindAddress is the address to bind to, which must be a tuple of
00770         length 2. The first element is a string, which is the host name
00771         or IPv4 address of a local interface. The 2nd element is the port
00772         number.
00773 
00774         allowedServers must be None or a list of strings representing the
00775         IPv4 addresses of servers allowed to connect. None means accept
00776         connections from anywhere. By default, it is a list containing
00777         the single item '127.0.0.1'.
00778 
00779         loggingLevel sets the logging level of the module-level logger.
00780         """
00781         if environ is None:
00782             environ = {}
00783 
00784         self.application = application
00785         self.scriptName = scriptName
00786         self.environ = environ
00787         self.multithreaded = multithreaded
00788         self.multiprocess = multiprocess
00789         self.debug = debug
00790         self._bindAddress = bindAddress
00791         if allowedServers is NoDefault:
00792             allowedServers = ['127.0.0.1']
00793         self._allowedServers = allowedServers
00794 
00795         # Used to force single-threadedness.
00796         self._appLock = thread.allocate_lock()
00797 
00798         self.logger = logging.getLogger(LoggerName)
00799         self.logger.setLevel(loggingLevel)
00800 
00801     def _setupSocket(self):
00802         """Creates and binds the socket for communication with the server."""
00803         sock = socket.socket()
00804         sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
00805         sock.bind(self._bindAddress)
00806         sock.listen(socket.SOMAXCONN)
00807         return sock
00808 
00809     def _cleanupSocket(self, sock):
00810         """Closes the main socket."""
00811         sock.close()
00812 
00813     def _isClientAllowed(self, addr):
00814         ret = self._allowedServers is None or addr[0] in self._allowedServers
00815         if not ret:
00816             self.logger.warning('Server connection from %s disallowed',
00817                                 addr[0])
00818         return ret
00819 
00820     def handler(self, request):
00821         """
00822         WSGI handler. Sets up WSGI environment, calls the application,
00823         and sends the application's response.
00824         """
00825         environ = request.environ
00826         environ.update(self.environ)
00827 
00828         environ['wsgi.version'] = (1,0)
00829         environ['wsgi.input'] = request.input
00830         environ['wsgi.errors'] = sys.stderr
00831         environ['wsgi.multithread'] = self.multithreaded
00832         environ['wsgi.multiprocess'] = self.multiprocess
00833         environ['wsgi.run_once'] = False
00834 
00835         if environ.get('HTTPS', 'off') in ('on', '1'):
00836             environ['wsgi.url_scheme'] = 'https'
00837         else:
00838             environ['wsgi.url_scheme'] = 'http'
00839 
00840         self._sanitizeEnv(environ)
00841 
00842         headers_set = []
00843         headers_sent = []
00844         result = None
00845 
00846         def write(data):
00847             assert type(data) is str, 'write() argument must be string'
00848             assert headers_set, 'write() before start_response()'
00849 
00850             if not headers_sent:
00851                 status, responseHeaders = headers_sent[:] = headers_set
00852                 statusCode = int(status[:3])
00853                 statusMsg = status[4:]
00854                 found = False
00855                 for header,value in responseHeaders:
00856                     if header.lower() == 'content-length':
00857                         found = True
00858                         break
00859                 if not found and result is not None:
00860                     try:
00861                         if len(result) == 1:
00862                             responseHeaders.append(('Content-Length',
00863                                                     str(len(data))))
00864                     except:
00865                         pass
00866                 request.startResponse(statusCode, statusMsg, responseHeaders)
00867 
00868             request.write(data)
00869 
00870         def start_response(status, response_headers, exc_info=None):
00871             if exc_info:
00872                 try:
00873                     if headers_sent:
00874                         # Re-raise if too late
00875                         raise exc_info[0], exc_info[1], exc_info[2]
00876                 finally:
00877                     exc_info = None # avoid dangling circular ref
00878             else:
00879                 assert not headers_set, 'Headers already set!'
00880 
00881             assert type(status) is str, 'Status must be a string'
00882             assert len(status) >= 4, 'Status must be at least 4 characters'
00883             assert int(status[:3]), 'Status must begin with 3-digit code'
00884             assert status[3] == ' ', 'Status must have a space after code'
00885             assert type(response_headers) is list, 'Headers must be a list'
00886             if __debug__:
00887                 for name,val in response_headers:
00888                     assert type(name) is str, 'Header name "%s" must be a string' % name
00889                     assert type(val) is str, 'Value of header "%s" must be a string' % name
00890 
00891             headers_set[:] = [status, response_headers]
00892             return write
00893 
00894         if not self.multithreaded:
00895             self._appLock.acquire()
00896         try:
00897             try:
00898                 result = self.application(environ, start_response)
00899                 try:
00900                     for data in result:
00901                         if data:
00902                             write(data)
00903                     if not headers_sent:
00904                         write('') # in case body was empty
00905                 finally:
00906                     if hasattr(result, 'close'):
00907                         result.close()
00908             except socket.error, e:
00909                 if e[0] != errno.EPIPE:
00910                     raise # Don't let EPIPE propagate beyond server
00911         finally:
00912             if not self.multithreaded:
00913                 self._appLock.release()
00914 
00915     def _sanitizeEnv(self, environ):
00916         """Fill-in/deduce missing values in environ."""
00917         # Namely SCRIPT_NAME/PATH_INFO
00918         value = environ['REQUEST_URI']
00919         scriptName = environ.get('WSGI_SCRIPT_NAME', self.scriptName)
00920         if not value.startswith(scriptName):
00921             self.logger.warning('scriptName does not match request URI')
00922 
00923         environ['PATH_INFO'] = value[len(scriptName):]
00924         environ['SCRIPT_NAME'] = scriptName
00925 
00926         reqUri = None
00927         if environ.has_key('REQUEST_URI'):
00928             reqUri = environ['REQUEST_URI'].split('?', 1)
00929 
00930         if not environ.has_key('QUERY_STRING') or not environ['QUERY_STRING']:
00931             if reqUri is not None and len(reqUri) > 1:
00932                 environ['QUERY_STRING'] = reqUri[1]
00933             else:
00934                 environ['QUERY_STRING'] = ''
00935 
00936     def error(self, request):
00937         """
00938         Override to provide custom error handling. Ideally, however,
00939         all errors should be caught at the application level.
00940         """
00941         if self.debug:
00942             request.startResponse(200, 'OK', [('Content-Type', 'text/html')])
00943             import cgitb
00944             request.write(cgitb.html(sys.exc_info()))
00945         else:
00946             errorpage = """<!DOCTYPE HTML PUBLIC "-//IETF//DTD HTML 2.0//EN">
00947 <html><head>
00948 <title>Unhandled Exception</title>
00949 </head><body>
00950 <h1>Unhandled Exception</h1>
00951 <p>An unhandled exception was thrown by the application.</p>
00952 </body></html>
00953 """
00954             request.startResponse(200, 'OK', [('Content-Type', 'text/html')])
00955             request.write(errorpage)