ajp_base.py 30 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956
  1. # Copyright (c) 2005, 2006 Allan Saddi <allan@saddi.com>
  2. # All rights reserved.
  3. #
  4. # Redistribution and use in source and binary forms, with or without
  5. # modification, are permitted provided that the following conditions
  6. # are met:
  7. # 1. Redistributions of source code must retain the above copyright
  8. # notice, this list of conditions and the following disclaimer.
  9. # 2. Redistributions in binary form must reproduce the above copyright
  10. # notice, this list of conditions and the following disclaimer in the
  11. # documentation and/or other materials provided with the distribution.
  12. #
  13. # THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
  14. # ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
  15. # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
  16. # ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
  17. # FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
  18. # DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
  19. # OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
  20. # HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
  21. # LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
  22. # OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
  23. # SUCH DAMAGE.
  24. #
  25. # $Id$
  26. __author__ = 'Allan Saddi <allan@saddi.com>'
  27. __version__ = '$Revision$'
  28. import sys
  29. import socket
  30. import select
  31. import struct
  32. import signal
  33. import logging
  34. import errno
  35. import datetime
  36. import time
  37. # Unfortunately, for now, threads are required.
  38. import thread
  39. import threading
  40. __all__ = ['BaseAJPServer']
  41. class NoDefault(object):
  42. pass
  43. # Packet header prefixes.
  44. SERVER_PREFIX = '\x12\x34'
  45. CONTAINER_PREFIX = 'AB'
  46. # Server packet types.
  47. PKTTYPE_FWD_REQ = '\x02'
  48. PKTTYPE_SHUTDOWN = '\x07'
  49. PKTTYPE_PING = '\x08'
  50. PKTTYPE_CPING = '\x0a'
  51. # Container packet types.
  52. PKTTYPE_SEND_BODY = '\x03'
  53. PKTTYPE_SEND_HEADERS = '\x04'
  54. PKTTYPE_END_RESPONSE = '\x05'
  55. PKTTYPE_GET_BODY = '\x06'
  56. PKTTYPE_CPONG = '\x09'
  57. # Code tables for methods/headers/attributes.
  58. methodTable = [
  59. None,
  60. 'OPTIONS',
  61. 'GET',
  62. 'HEAD',
  63. 'POST',
  64. 'PUT',
  65. 'DELETE',
  66. 'TRACE',
  67. 'PROPFIND',
  68. 'PROPPATCH',
  69. 'MKCOL',
  70. 'COPY',
  71. 'MOVE',
  72. 'LOCK',
  73. 'UNLOCK',
  74. 'ACL',
  75. 'REPORT',
  76. 'VERSION-CONTROL',
  77. 'CHECKIN',
  78. 'CHECKOUT',
  79. 'UNCHECKOUT',
  80. 'SEARCH',
  81. 'MKWORKSPACE',
  82. 'UPDATE',
  83. 'LABEL',
  84. 'MERGE',
  85. 'BASELINE_CONTROL',
  86. 'MKACTIVITY'
  87. ]
  88. requestHeaderTable = [
  89. None,
  90. 'Accept',
  91. 'Accept-Charset',
  92. 'Accept-Encoding',
  93. 'Accept-Language',
  94. 'Authorization',
  95. 'Connection',
  96. 'Content-Type',
  97. 'Content-Length',
  98. 'Cookie',
  99. 'Cookie2',
  100. 'Host',
  101. 'Pragma',
  102. 'Referer',
  103. 'User-Agent'
  104. ]
  105. attributeTable = [
  106. None,
  107. 'CONTEXT',
  108. 'SERVLET_PATH',
  109. 'REMOTE_USER',
  110. 'AUTH_TYPE',
  111. 'QUERY_STRING',
  112. 'JVM_ROUTE',
  113. 'SSL_CERT',
  114. 'SSL_CIPHER',
  115. 'SSL_SESSION',
  116. None, # name follows
  117. 'SSL_KEY_SIZE'
  118. ]
  119. responseHeaderTable = [
  120. None,
  121. 'content-type',
  122. 'content-language',
  123. 'content-length',
  124. 'date',
  125. 'last-modified',
  126. 'location',
  127. 'set-cookie',
  128. 'set-cookie2',
  129. 'servlet-engine',
  130. 'status',
  131. 'www-authenticate'
  132. ]
  133. # The main classes use this name for logging.
  134. LoggerName = 'ajp-wsgi'
  135. # Set up module-level logger.
  136. console = logging.StreamHandler()
  137. console.setLevel(logging.DEBUG)
  138. console.setFormatter(logging.Formatter('%(asctime)s : %(message)s',
  139. '%Y-%m-%d %H:%M:%S'))
  140. logging.getLogger(LoggerName).addHandler(console)
  141. del console
  142. class ProtocolError(Exception):
  143. """
  144. Exception raised when the server does something unexpected or
  145. sends garbled data. Usually leads to a Connection closing.
  146. """
  147. pass
  148. def decodeString(data, pos=0):
  149. """Decode a string."""
  150. try:
  151. length = struct.unpack('>H', data[pos:pos+2])[0]
  152. pos += 2
  153. if length == 0xffff: # This was undocumented!
  154. return '', pos
  155. s = data[pos:pos+length]
  156. return s, pos+length+1 # Don't forget NUL
  157. except Exception, e:
  158. raise ProtocolError, 'decodeString: '+str(e)
  159. def decodeRequestHeader(data, pos=0):
  160. """Decode a request header/value pair."""
  161. try:
  162. if data[pos] == '\xa0':
  163. # Use table
  164. i = ord(data[pos+1])
  165. name = requestHeaderTable[i]
  166. if name is None:
  167. raise ValueError, 'bad request header code'
  168. pos += 2
  169. else:
  170. name, pos = decodeString(data, pos)
  171. value, pos = decodeString(data, pos)
  172. return name, value, pos
  173. except Exception, e:
  174. raise ProtocolError, 'decodeRequestHeader: '+str(e)
  175. def decodeAttribute(data, pos=0):
  176. """Decode a request attribute."""
  177. try:
  178. i = ord(data[pos])
  179. pos += 1
  180. if i == 0xff:
  181. # end
  182. return None, None, pos
  183. elif i == 0x0a:
  184. # name follows
  185. name, pos = decodeString(data, pos)
  186. elif i == 0x0b:
  187. # Special handling of SSL_KEY_SIZE.
  188. name = attributeTable[i]
  189. # Value is an int, not a string.
  190. value = struct.unpack('>H', data[pos:pos+2])[0]
  191. return name, str(value), pos+2
  192. else:
  193. name = attributeTable[i]
  194. if name is None:
  195. raise ValueError, 'bad attribute code'
  196. value, pos = decodeString(data, pos)
  197. return name, value, pos
  198. except Exception, e:
  199. raise ProtocolError, 'decodeAttribute: '+str(e)
  200. def encodeString(s):
  201. """Encode a string."""
  202. return struct.pack('>H', len(s)) + s + '\x00'
  203. def encodeResponseHeader(name, value):
  204. """Encode a response header/value pair."""
  205. lname = name.lower()
  206. if lname in responseHeaderTable:
  207. # Use table
  208. i = responseHeaderTable.index(lname)
  209. out = '\xa0' + chr(i)
  210. else:
  211. out = encodeString(name)
  212. out += encodeString(value)
  213. return out
  214. class Packet(object):
  215. """An AJP message packet."""
  216. def __init__(self):
  217. self.data = ''
  218. # Don't set this on write, it will be calculated automatically.
  219. self.length = 0
  220. def _recvall(sock, length):
  221. """
  222. Attempts to receive length bytes from a socket, blocking if necessary.
  223. (Socket may be blocking or non-blocking.)
  224. """
  225. dataList = []
  226. recvLen = 0
  227. while length:
  228. try:
  229. data = sock.recv(length)
  230. except socket.error, e:
  231. if e[0] == errno.EAGAIN:
  232. select.select([sock], [], [])
  233. continue
  234. else:
  235. raise
  236. if not data: # EOF
  237. break
  238. dataList.append(data)
  239. dataLen = len(data)
  240. recvLen += dataLen
  241. length -= dataLen
  242. return ''.join(dataList), recvLen
  243. _recvall = staticmethod(_recvall)
  244. def read(self, sock):
  245. """Attempt to read a packet from the server."""
  246. try:
  247. header, length = self._recvall(sock, 4)
  248. except socket.error:
  249. # Treat any sort of socket errors as EOF (close Connection).
  250. raise EOFError
  251. if length < 4:
  252. raise EOFError
  253. if header[:2] != SERVER_PREFIX:
  254. raise ProtocolError, 'invalid header'
  255. self.length = struct.unpack('>H', header[2:4])[0]
  256. if self.length:
  257. try:
  258. self.data, length = self._recvall(sock, self.length)
  259. except socket.error:
  260. raise EOFError
  261. if length < self.length:
  262. raise EOFError
  263. def _sendall(sock, data):
  264. """
  265. Writes data to a socket and does not return until all the data is sent.
  266. """
  267. length = len(data)
  268. while length:
  269. try:
  270. sent = sock.send(data)
  271. except socket.error, e:
  272. if e[0] == errno.EAGAIN:
  273. select.select([], [sock], [])
  274. continue
  275. else:
  276. raise
  277. data = data[sent:]
  278. length -= sent
  279. _sendall = staticmethod(_sendall)
  280. def write(self, sock):
  281. """Send a packet to the server."""
  282. self.length = len(self.data)
  283. self._sendall(sock, CONTAINER_PREFIX + struct.pack('>H', self.length))
  284. if self.length:
  285. self._sendall(sock, self.data)
  286. class InputStream(object):
  287. """
  288. File-like object that represents the request body (if any). Supports
  289. the bare mininum methods required by the WSGI spec. Thanks to
  290. StringIO for ideas.
  291. """
  292. def __init__(self, conn):
  293. self._conn = conn
  294. # See WSGIServer.
  295. self._shrinkThreshold = conn.server.inputStreamShrinkThreshold
  296. self._buf = ''
  297. self._bufList = []
  298. self._pos = 0 # Current read position.
  299. self._avail = 0 # Number of bytes currently available.
  300. self._length = 0 # Set to Content-Length in request.
  301. self.logger = logging.getLogger(LoggerName)
  302. def bytesAvailForAdd(self):
  303. return self._length - self._avail
  304. def _shrinkBuffer(self):
  305. """Gets rid of already read data (since we can't rewind)."""
  306. if self._pos >= self._shrinkThreshold:
  307. self._buf = self._buf[self._pos:]
  308. self._avail -= self._pos
  309. self._length -= self._pos
  310. self._pos = 0
  311. assert self._avail >= 0 and self._length >= 0
  312. def _waitForData(self):
  313. toAdd = min(self.bytesAvailForAdd(), 0xffff)
  314. assert toAdd > 0
  315. pkt = Packet()
  316. pkt.data = PKTTYPE_GET_BODY + \
  317. struct.pack('>H', toAdd)
  318. self._conn.writePacket(pkt)
  319. self._conn.processInput()
  320. def read(self, n=-1):
  321. if self._pos == self._length:
  322. return ''
  323. while True:
  324. if n < 0 or (self._avail - self._pos) < n:
  325. # Not enough data available.
  326. if not self.bytesAvailForAdd():
  327. # And there's no more coming.
  328. newPos = self._avail
  329. break
  330. else:
  331. # Ask for more data and wait.
  332. self._waitForData()
  333. continue
  334. else:
  335. newPos = self._pos + n
  336. break
  337. # Merge buffer list, if necessary.
  338. if self._bufList:
  339. self._buf += ''.join(self._bufList)
  340. self._bufList = []
  341. r = self._buf[self._pos:newPos]
  342. self._pos = newPos
  343. self._shrinkBuffer()
  344. return r
  345. def readline(self, length=None):
  346. if self._pos == self._length:
  347. return ''
  348. while True:
  349. # Unfortunately, we need to merge the buffer list early.
  350. if self._bufList:
  351. self._buf += ''.join(self._bufList)
  352. self._bufList = []
  353. # Find newline.
  354. i = self._buf.find('\n', self._pos)
  355. if i < 0:
  356. # Not found?
  357. if not self.bytesAvailForAdd():
  358. # No more data coming.
  359. newPos = self._avail
  360. break
  361. else:
  362. if length is not None and len(self._buf) >= length + self._pos:
  363. newPos = self._pos + length
  364. break
  365. # Wait for more to come.
  366. self._waitForData()
  367. continue
  368. else:
  369. newPos = i + 1
  370. break
  371. r = self._buf[self._pos:newPos]
  372. self._pos = newPos
  373. self._shrinkBuffer()
  374. return r
  375. def readlines(self, sizehint=0):
  376. total = 0
  377. lines = []
  378. line = self.readline()
  379. while line:
  380. lines.append(line)
  381. total += len(line)
  382. if 0 < sizehint <= total:
  383. break
  384. line = self.readline()
  385. return lines
  386. def __iter__(self):
  387. return self
  388. def next(self):
  389. r = self.readline()
  390. if not r:
  391. raise StopIteration
  392. return r
  393. def setDataLength(self, length):
  394. """
  395. Once Content-Length is known, Request calls this method to set it.
  396. """
  397. self._length = length
  398. def addData(self, data):
  399. """
  400. Adds data from the server to this InputStream. Note that we never ask
  401. the server for data beyond the Content-Length, so the server should
  402. never send us an EOF (empty string argument).
  403. """
  404. if not data:
  405. raise ProtocolError, 'short data'
  406. self._bufList.append(data)
  407. length = len(data)
  408. self._avail += length
  409. if self._avail > self._length:
  410. raise ProtocolError, 'too much data'
  411. class Request(object):
  412. """
  413. A Request object. A more fitting name would probably be Transaction, but
  414. it's named Request to mirror my FastCGI driver. :) This object
  415. encapsulates all the data about the HTTP request and allows the handler
  416. to send a response.
  417. The only attributes/methods that the handler should concern itself
  418. with are: environ, input, startResponse(), and write().
  419. """
  420. # Do not ever change the following value.
  421. _maxWrite = 8192 - 4 - 3 - 1 # 8k - pkt header - send body header - NUL
  422. def __init__(self, conn):
  423. self._conn = conn
  424. self.environ = {}
  425. self.input = InputStream(conn)
  426. self._headersSent = False
  427. self.logger = logging.getLogger(LoggerName)
  428. def run(self):
  429. self.logger.info('%s %s',
  430. self.environ['REQUEST_METHOD'],
  431. self.environ['REQUEST_URI'])
  432. start = datetime.datetime.now()
  433. try:
  434. self._conn.server.handler(self)
  435. except:
  436. self.logger.exception('Exception caught from handler')
  437. if not self._headersSent:
  438. self._conn.server.error(self)
  439. end = datetime.datetime.now()
  440. # Notify server of end of response (reuse flag is set to true).
  441. pkt = Packet()
  442. pkt.data = PKTTYPE_END_RESPONSE + '\x01'
  443. self._conn.writePacket(pkt)
  444. handlerTime = end - start
  445. self.logger.debug('%s %s done (%.3f secs)',
  446. self.environ['REQUEST_METHOD'],
  447. self.environ['REQUEST_URI'],
  448. handlerTime.seconds +
  449. handlerTime.microseconds / 1000000.0)
  450. # The following methods are called from the Connection to set up this
  451. # Request.
  452. def setMethod(self, value):
  453. self.environ['REQUEST_METHOD'] = value
  454. def setProtocol(self, value):
  455. self.environ['SERVER_PROTOCOL'] = value
  456. def setRequestURI(self, value):
  457. self.environ['REQUEST_URI'] = value
  458. def setRemoteAddr(self, value):
  459. self.environ['REMOTE_ADDR'] = value
  460. def setRemoteHost(self, value):
  461. self.environ['REMOTE_HOST'] = value
  462. def setServerName(self, value):
  463. self.environ['SERVER_NAME'] = value
  464. def setServerPort(self, value):
  465. self.environ['SERVER_PORT'] = str(value)
  466. def setIsSSL(self, value):
  467. if value:
  468. self.environ['HTTPS'] = 'on'
  469. def addHeader(self, name, value):
  470. name = name.replace('-', '_').upper()
  471. if name in ('CONTENT_TYPE', 'CONTENT_LENGTH'):
  472. self.environ[name] = value
  473. if name == 'CONTENT_LENGTH':
  474. length = int(value)
  475. self.input.setDataLength(length)
  476. else:
  477. self.environ['HTTP_'+name] = value
  478. def addAttribute(self, name, value):
  479. self.environ[name] = value
  480. # The only two methods that should be called from the handler.
  481. def startResponse(self, statusCode, statusMsg, headers):
  482. """
  483. Begin the HTTP response. This must only be called once and it
  484. must be called before any calls to write().
  485. statusCode is the integer status code (e.g. 200). statusMsg
  486. is the associated reason message (e.g.'OK'). headers is a list
  487. of 2-tuples - header name/value pairs. (Both header name and value
  488. must be strings.)
  489. """
  490. assert not self._headersSent, 'Headers already sent!'
  491. pkt = Packet()
  492. pkt.data = PKTTYPE_SEND_HEADERS + \
  493. struct.pack('>H', statusCode) + \
  494. encodeString(statusMsg) + \
  495. struct.pack('>H', len(headers)) + \
  496. ''.join([encodeResponseHeader(name, value)
  497. for name,value in headers])
  498. self._conn.writePacket(pkt)
  499. self._headersSent = True
  500. def write(self, data):
  501. """
  502. Write data (which comprises the response body). Note that due to
  503. restrictions on AJP packet size, we limit our writes to 8185 bytes
  504. each packet.
  505. """
  506. assert self._headersSent, 'Headers must be sent first!'
  507. bytesLeft = len(data)
  508. while bytesLeft:
  509. toWrite = min(bytesLeft, self._maxWrite)
  510. pkt = Packet()
  511. pkt.data = PKTTYPE_SEND_BODY + \
  512. struct.pack('>H', toWrite) + \
  513. data[:toWrite] + '\x00' # Undocumented
  514. self._conn.writePacket(pkt)
  515. data = data[toWrite:]
  516. bytesLeft -= toWrite
  517. class Connection(object):
  518. """
  519. A single Connection with the server. Requests are not multiplexed over the
  520. same connection, so at any given time, the Connection is either
  521. waiting for a request, or processing a single request.
  522. """
  523. def __init__(self, sock, addr, server):
  524. self.server = server
  525. self._sock = sock
  526. self._addr = addr
  527. self._request = None
  528. self.logger = logging.getLogger(LoggerName)
  529. def run(self):
  530. self.logger.debug('Connection starting up (%s:%d)',
  531. self._addr[0], self._addr[1])
  532. # Main loop. Errors will cause the loop to be exited and
  533. # the socket to be closed.
  534. while True:
  535. try:
  536. self.processInput()
  537. except ProtocolError, e:
  538. self.logger.error("Protocol error '%s'", str(e))
  539. break
  540. except (EOFError, KeyboardInterrupt):
  541. break
  542. except:
  543. self.logger.exception('Exception caught in Connection')
  544. break
  545. self.logger.debug('Connection shutting down (%s:%d)',
  546. self._addr[0], self._addr[1])
  547. self._sock.close()
  548. def processInput(self):
  549. """Wait for and process a single packet."""
  550. pkt = Packet()
  551. select.select([self._sock], [], [])
  552. pkt.read(self._sock)
  553. # Body chunks have no packet type code.
  554. if self._request is not None:
  555. self._processBody(pkt)
  556. return
  557. if not pkt.length:
  558. raise ProtocolError, 'unexpected empty packet'
  559. pkttype = pkt.data[0]
  560. if pkttype == PKTTYPE_FWD_REQ:
  561. self._forwardRequest(pkt)
  562. elif pkttype == PKTTYPE_SHUTDOWN:
  563. self._shutdown(pkt)
  564. elif pkttype == PKTTYPE_PING:
  565. self._ping(pkt)
  566. elif pkttype == PKTTYPE_CPING:
  567. self._cping(pkt)
  568. else:
  569. raise ProtocolError, 'unknown packet type'
  570. def _forwardRequest(self, pkt):
  571. """
  572. Creates a Request object, fills it in from the packet, then runs it.
  573. """
  574. assert self._request is None
  575. req = self.server.requestClass(self)
  576. i = ord(pkt.data[1])
  577. method = methodTable[i]
  578. if method is None:
  579. raise ValueError, 'bad method field'
  580. req.setMethod(method)
  581. value, pos = decodeString(pkt.data, 2)
  582. req.setProtocol(value)
  583. value, pos = decodeString(pkt.data, pos)
  584. req.setRequestURI(value)
  585. value, pos = decodeString(pkt.data, pos)
  586. req.setRemoteAddr(value)
  587. value, pos = decodeString(pkt.data, pos)
  588. req.setRemoteHost(value)
  589. value, pos = decodeString(pkt.data, pos)
  590. req.setServerName(value)
  591. value = struct.unpack('>H', pkt.data[pos:pos+2])[0]
  592. req.setServerPort(value)
  593. i = ord(pkt.data[pos+2])
  594. req.setIsSSL(i != 0)
  595. # Request headers.
  596. numHeaders = struct.unpack('>H', pkt.data[pos+3:pos+5])[0]
  597. pos += 5
  598. for i in range(numHeaders):
  599. name, value, pos = decodeRequestHeader(pkt.data, pos)
  600. req.addHeader(name, value)
  601. # Attributes.
  602. while True:
  603. name, value, pos = decodeAttribute(pkt.data, pos)
  604. if name is None:
  605. break
  606. req.addAttribute(name, value)
  607. self._request = req
  608. # Read first body chunk, if needed.
  609. if req.input.bytesAvailForAdd():
  610. self.processInput()
  611. # Run Request.
  612. req.run()
  613. self._request = None
  614. def _shutdown(self, pkt):
  615. """Not sure what to do with this yet."""
  616. self.logger.info('Received shutdown request from server')
  617. def _ping(self, pkt):
  618. """I have no idea what this packet means."""
  619. self.logger.debug('Received ping')
  620. def _cping(self, pkt):
  621. """Respond to a PING (CPING) packet."""
  622. self.logger.debug('Received PING, sending PONG')
  623. pkt = Packet()
  624. pkt.data = PKTTYPE_CPONG
  625. self.writePacket(pkt)
  626. def _processBody(self, pkt):
  627. """
  628. Handles a body chunk from the server by appending it to the
  629. InputStream.
  630. """
  631. if pkt.length:
  632. length = struct.unpack('>H', pkt.data[:2])[0]
  633. self._request.input.addData(pkt.data[2:2+length])
  634. else:
  635. # Shouldn't really ever get here.
  636. self._request.input.addData('')
  637. def writePacket(self, pkt):
  638. """Sends a Packet to the server."""
  639. pkt.write(self._sock)
  640. class BaseAJPServer(object):
  641. # What Request class to use.
  642. requestClass = Request
  643. # Limits the size of the InputStream's string buffer to this size + 8k.
  644. # Since the InputStream is not seekable, we throw away already-read
  645. # data once this certain amount has been read. (The 8k is there because
  646. # it is the maximum size of new data added per chunk.)
  647. inputStreamShrinkThreshold = 102400 - 8192
  648. def __init__(self, application, scriptName='', environ=None,
  649. multithreaded=True, multiprocess=False,
  650. bindAddress=('localhost', 8009), allowedServers=NoDefault,
  651. loggingLevel=logging.INFO, debug=True):
  652. """
  653. scriptName is the initial portion of the URL path that "belongs"
  654. to your application. It is used to determine PATH_INFO (which doesn't
  655. seem to be passed in). An empty scriptName means your application
  656. is mounted at the root of your virtual host.
  657. environ, which must be a dictionary, can contain any additional
  658. environment variables you want to pass to your application.
  659. Set multithreaded to False if your application is not thread-safe.
  660. Set multiprocess to True to explicitly set wsgi.multiprocess to
  661. True. (Only makes sense with threaded servers.)
  662. bindAddress is the address to bind to, which must be a tuple of
  663. length 2. The first element is a string, which is the host name
  664. or IPv4 address of a local interface. The 2nd element is the port
  665. number.
  666. allowedServers must be None or a list of strings representing the
  667. IPv4 addresses of servers allowed to connect. None means accept
  668. connections from anywhere. By default, it is a list containing
  669. the single item '127.0.0.1'.
  670. loggingLevel sets the logging level of the module-level logger.
  671. """
  672. if environ is None:
  673. environ = {}
  674. self.application = application
  675. self.scriptName = scriptName
  676. self.environ = environ
  677. self.multithreaded = multithreaded
  678. self.multiprocess = multiprocess
  679. self.debug = debug
  680. self._bindAddress = bindAddress
  681. if allowedServers is NoDefault:
  682. allowedServers = ['127.0.0.1']
  683. self._allowedServers = allowedServers
  684. # Used to force single-threadedness.
  685. self._appLock = thread.allocate_lock()
  686. self.logger = logging.getLogger(LoggerName)
  687. self.logger.setLevel(loggingLevel)
  688. def _setupSocket(self):
  689. """Creates and binds the socket for communication with the server."""
  690. sock = socket.socket()
  691. sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
  692. sock.bind(self._bindAddress)
  693. sock.listen(socket.SOMAXCONN)
  694. return sock
  695. def _cleanupSocket(self, sock):
  696. """Closes the main socket."""
  697. sock.close()
  698. def _isClientAllowed(self, addr):
  699. ret = self._allowedServers is None or addr[0] in self._allowedServers
  700. if not ret:
  701. self.logger.warning('Server connection from %s disallowed',
  702. addr[0])
  703. return ret
  704. def handler(self, request):
  705. """
  706. WSGI handler. Sets up WSGI environment, calls the application,
  707. and sends the application's response.
  708. """
  709. environ = request.environ
  710. environ.update(self.environ)
  711. environ['wsgi.version'] = (1,0)
  712. environ['wsgi.input'] = request.input
  713. environ['wsgi.errors'] = sys.stderr
  714. environ['wsgi.multithread'] = self.multithreaded
  715. environ['wsgi.multiprocess'] = self.multiprocess
  716. environ['wsgi.run_once'] = False
  717. if environ.get('HTTPS', 'off') in ('on', '1'):
  718. environ['wsgi.url_scheme'] = 'https'
  719. else:
  720. environ['wsgi.url_scheme'] = 'http'
  721. self._sanitizeEnv(environ)
  722. headers_set = []
  723. headers_sent = []
  724. result = None
  725. def write(data):
  726. assert type(data) is str, 'write() argument must be string'
  727. assert headers_set, 'write() before start_response()'
  728. if not headers_sent:
  729. status, responseHeaders = headers_sent[:] = headers_set
  730. statusCode = int(status[:3])
  731. statusMsg = status[4:]
  732. found = False
  733. for header,value in responseHeaders:
  734. if header.lower() == 'content-length':
  735. found = True
  736. break
  737. if not found and result is not None:
  738. try:
  739. if len(result) == 1:
  740. responseHeaders.append(('Content-Length',
  741. str(len(data))))
  742. except:
  743. pass
  744. request.startResponse(statusCode, statusMsg, responseHeaders)
  745. request.write(data)
  746. def start_response(status, response_headers, exc_info=None):
  747. if exc_info:
  748. try:
  749. if headers_sent:
  750. # Re-raise if too late
  751. raise exc_info[0], exc_info[1], exc_info[2]
  752. finally:
  753. exc_info = None # avoid dangling circular ref
  754. else:
  755. assert not headers_set, 'Headers already set!'
  756. assert type(status) is str, 'Status must be a string'
  757. assert len(status) >= 4, 'Status must be at least 4 characters'
  758. assert int(status[:3]), 'Status must begin with 3-digit code'
  759. assert status[3] == ' ', 'Status must have a space after code'
  760. assert type(response_headers) is list, 'Headers must be a list'
  761. if __debug__:
  762. for name,val in response_headers:
  763. assert type(name) is str, 'Header name "%s" must be a string' % name
  764. assert type(val) is str, 'Value of header "%s" must be a string' % name
  765. headers_set[:] = [status, response_headers]
  766. return write
  767. if not self.multithreaded:
  768. self._appLock.acquire()
  769. try:
  770. try:
  771. result = self.application(environ, start_response)
  772. try:
  773. for data in result:
  774. if data:
  775. write(data)
  776. if not headers_sent:
  777. write('') # in case body was empty
  778. finally:
  779. if hasattr(result, 'close'):
  780. result.close()
  781. except socket.error, e:
  782. if e[0] != errno.EPIPE:
  783. raise # Don't let EPIPE propagate beyond server
  784. finally:
  785. if not self.multithreaded:
  786. self._appLock.release()
  787. def _sanitizeEnv(self, environ):
  788. """Fill-in/deduce missing values in environ."""
  789. # Namely SCRIPT_NAME/PATH_INFO
  790. value = environ['REQUEST_URI']
  791. scriptName = environ.get('WSGI_SCRIPT_NAME', self.scriptName)
  792. if not value.startswith(scriptName):
  793. self.logger.warning('scriptName does not match request URI')
  794. environ['PATH_INFO'] = value[len(scriptName):]
  795. environ['SCRIPT_NAME'] = scriptName
  796. reqUri = None
  797. if environ.has_key('REQUEST_URI'):
  798. reqUri = environ['REQUEST_URI'].split('?', 1)
  799. if not environ.has_key('QUERY_STRING') or not environ['QUERY_STRING']:
  800. if reqUri is not None and len(reqUri) > 1:
  801. environ['QUERY_STRING'] = reqUri[1]
  802. else:
  803. environ['QUERY_STRING'] = ''
  804. def error(self, request):
  805. """
  806. Override to provide custom error handling. Ideally, however,
  807. all errors should be caught at the application level.
  808. """
  809. if self.debug:
  810. request.startResponse(200, 'OK', [('Content-Type', 'text/html')])
  811. import cgitb
  812. request.write(cgitb.html(sys.exc_info()))
  813. else:
  814. errorpage = """<!DOCTYPE HTML PUBLIC "-//IETF//DTD HTML 2.0//EN">
  815. <html><head>
  816. <title>Unhandled Exception</title>
  817. </head><body>
  818. <h1>Unhandled Exception</h1>
  819. <p>An unhandled exception was thrown by the application.</p>
  820. </body></html>
  821. """
  822. request.startResponse(200, 'OK', [('Content-Type', 'text/html')])
  823. request.write(errorpage)