#!/usr/bin/env python # Phusion Passenger - www.modrails.com/ # Copyright © 2010 Phusion # # “Phusion Passenger” is a trademark of Hongli Lai & Ninh Bui. # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the “Software”), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in # all copies or substantial portions of the Software. # # THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN # THE SOFTWARE.
import socket, os, random, sys, struct, select, imp import exceptions, traceback
from socket import _fileobject
class RequestHandler:
def __init__(self, socket_file, server, owner_pipe, app): self.socket_file = socket_file self.server = server self.owner_pipe = owner_pipe self.app = app def cleanup(self): self.server.close() try: os.remove(self.socket_file) except: pass def main_loop(self): done = False try: while not done: client, address = self.accept_connection() if not client: done = True break try: try: env, input_stream = self.parse_request(client) if env: if env['REQUEST_METHOD'] == 'ping': self.process_ping(env, input_stream, client) else: self.process_request(env, input_stream, client) else: done = True except KeyboardInterrupt: done = True except Exception, e: traceback.print_tb(sys.exc_info()[2]) sys.stderr.write(str(e.__class__) + ": " + e.message + "\n") finally: try: client.close() except: pass except KeyboardInterrupt: pass def accept_connection(self): result = select.select([self.owner_pipe, self.server.fileno()], [], [])[0] if self.server.fileno() in result: return self.server.accept() else: return (None, None) def parse_request(self, client): buf = '' while len(buf) < 4: tmp = client.recv(4 - len(buf)) if len(tmp) == 0: return (None, None) buf += tmp header_size = struct.unpack('>I', buf)[0] buf = '' while len(buf) < header_size: tmp = client.recv(header_size - len(buf)) if len(tmp) == 0: return (None, None) buf += tmp headers = buf.split("\00"") headers.pop() # Remove trailing "\0" env = {} i = 0 while i < len(headers): env[headers[i]] = headers[i + 1] i += 2 return (env, client) def process_request(self, env, input_stream, output_stream): # The WSGI speculation says that the input paramter object passed needs to # implement a few file-like methods. This is the reason why we "wrap" the socket._socket # into the _fileobject to solve this. # # Otherwise, the POST data won't be correctly retrieved by Django. # # See: http://www.python.org/dev/peps/pep-0333/#input-and-error-streams env['wsgi.input'] = _fileobject(input_stream,'r',512) env['wsgi.errors'] = sys.stderr env['wsgi.version'] = (1, 0) env['wsgi.multithread'] = False env['wsgi.multiprocess'] = True env['wsgi.run_once'] = True if env.get('HTTPS','off') in ('on', '1'): env['wsgi.url_scheme'] = 'https' else: env['wsgi.url_scheme'] = 'http' # The following environment variables are required by WSCI PEP #333 # see: http://www.python.org/dev/peps/pep-0333/#environ-variables if 'HTTP_CONTENT_LENGTH' in env: env['CONTENT_LENGTH'] = env.get('HTTP_CONTENT_LENGTH') headers_set = [] headers_sent = [] def write(data): if not headers_set: raise AssertionError("write() before start_response()") elif not headers_sent: # Before the first output, send the stored headers. status, response_headers = headers_sent[:] = headers_set output_stream.send('Status: %s\r\n' % status) for header in response_headers: output_stream.send('%s: %s\r\n' % header) output_stream.send('\r\n') output_stream.send(data) def start_response(status, response_headers, exc_info = None): if exc_info: try: if headers_sent: # Re-raise original exception if headers sent. raise exc_info[0], exc_info[1], exc_info[2] finally: # Avoid dangling circular ref. exc_info = None elif headers_set: raise AssertionError("Headers already set!") headers_set[:] = [status, response_headers] return write result = self.app(env, start_response) try: for data in result: # Don't send headers until body appears. if data: write(data) if not headers_sent: # Send headers now if body was empty. write('') finally: if hasattr(result, 'close'): result.close() def process_ping(self, env, input_stream, output_stream): output_stream.send("pong")
def import_error_handler(environ, start_response):
write = start_response('500 Import Error', [('Content-type', 'text/plain')]) write("An error occurred importing your passenger_wsgi.py") raise KeyboardInterrupt # oh WEIRD.
if __name__ == “__main__”:
socket_file = sys.argv[1] server = socket.fromfd(int(sys.argv[2]), socket.AF_UNIX, socket.SOCK_STREAM) owner_pipe = int(sys.argv[3]) try: app_module = imp.load_source('passenger_wsgi', 'passenger_wsgi.py') handler = RequestHandler(socket_file, server, owner_pipe, app_module.application) except: handler = RequestHandler(socket_file, server, owner_pipe, import_error_handler) try: handler.main_loop() finally: handler.cleanup()