scgi_app.py 5.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176
  1. # Copyright (c) 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 select
  29. import struct
  30. import socket
  31. import errno
  32. __all__ = ['SCGIApp']
  33. def encodeNetstring(s):
  34. return ''.join([str(len(s)), ':', s, ','])
  35. class SCGIApp(object):
  36. def __init__(self, connect=None, host=None, port=None,
  37. filterEnviron=True):
  38. if host is not None:
  39. assert port is not None
  40. connect=(host, port)
  41. assert connect is not None
  42. self._connect = connect
  43. self._filterEnviron = filterEnviron
  44. def __call__(self, environ, start_response):
  45. sock = self._getConnection()
  46. outfile = sock.makefile('w')
  47. infile = sock.makefile('r')
  48. sock.close()
  49. # Filter WSGI environ and send as request headers
  50. if self._filterEnviron:
  51. headers = self._defaultFilterEnviron(environ)
  52. else:
  53. headers = self._lightFilterEnviron(environ)
  54. # TODO: Anything not from environ that needs to be sent also?
  55. content_length = int(environ.get('CONTENT_LENGTH') or 0)
  56. if headers.has_key('CONTENT_LENGTH'):
  57. del headers['CONTENT_LENGTH']
  58. headers_out = ['CONTENT_LENGTH', str(content_length), 'SCGI', '1']
  59. for k,v in headers.items():
  60. headers_out.append(k)
  61. headers_out.append(v)
  62. headers_out.append('') # For trailing NUL
  63. outfile.write(encodeNetstring('\x00'.join(headers_out)))
  64. # Transfer wsgi.input to outfile
  65. while True:
  66. chunk_size = min(content_length, 4096)
  67. s = environ['wsgi.input'].read(chunk_size)
  68. content_length -= len(s)
  69. outfile.write(s)
  70. if not s: break
  71. outfile.close()
  72. # Read result from SCGI server
  73. result = []
  74. while True:
  75. buf = infile.read(4096)
  76. if not buf: break
  77. result.append(buf)
  78. infile.close()
  79. result = ''.join(result)
  80. # Parse response headers
  81. status = '200 OK'
  82. headers = []
  83. pos = 0
  84. while True:
  85. eolpos = result.find('\n', pos)
  86. if eolpos < 0: break
  87. line = result[pos:eolpos-1]
  88. pos = eolpos + 1
  89. # strip in case of CR. NB: This will also strip other
  90. # whitespace...
  91. line = line.strip()
  92. # Empty line signifies end of headers
  93. if not line: break
  94. # TODO: Better error handling
  95. header, value = line.split(':', 1)
  96. header = header.strip().lower()
  97. value = value.strip()
  98. if header == 'status':
  99. # Special handling of Status header
  100. status = value
  101. if status.find(' ') < 0:
  102. # Append a dummy reason phrase if one was not provided
  103. status += ' SCGIApp'
  104. else:
  105. headers.append((header, value))
  106. result = result[pos:]
  107. # Set WSGI status, headers, and return result.
  108. start_response(status, headers)
  109. return [result]
  110. def _getConnection(self):
  111. if type(self._connect) is str:
  112. sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
  113. else:
  114. sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
  115. sock.connect(self._connect)
  116. return sock
  117. _environPrefixes = ['SERVER_', 'HTTP_', 'REQUEST_', 'REMOTE_', 'PATH_',
  118. 'CONTENT_']
  119. _environCopies = ['SCRIPT_NAME', 'QUERY_STRING', 'AUTH_TYPE']
  120. _environRenames = {}
  121. def _defaultFilterEnviron(self, environ):
  122. result = {}
  123. for n in environ.keys():
  124. for p in self._environPrefixes:
  125. if n.startswith(p):
  126. result[n] = environ[n]
  127. if n in self._environCopies:
  128. result[n] = environ[n]
  129. if n in self._environRenames:
  130. result[self._environRenames[n]] = environ[n]
  131. return result
  132. def _lightFilterEnviron(self, environ):
  133. result = {}
  134. for n in environ.keys():
  135. if n.upper() == n:
  136. result[n] = environ[n]
  137. return result
  138. if __name__ == '__main__':
  139. from flup.server.ajp import WSGIServer
  140. app = SCGIApp(connect=('localhost', 4000))
  141. #import paste.lint
  142. #app = paste.lint.middleware(app)
  143. WSGIServer(app).run()