def fail(self): """Still return a response in cases of extreme failure. """ log(90, "Critical error:\n%s" % traceback.format_exc()) # Build the response. # =================== status_line = "%s %d %s\r\n" % (self.server.http_version_string, 500, "Internal Server Error") if self.server.deploy_mode: body = "Internal Server Error" else: body = "Internal Server Error\r\n\r\n%s" % traceback.format_exc() headers = {} headers['Content-Length'] = len(body) headers['Content-Type'] = 'text/plain' # Send the response. # ================== self.channel.write(status_line) for header in sorted(headers.items()): self.channel.write('%s: %s\r\n' % header) self.channel.write('\r\n') self.channel.write(body)
def stop(self, signum=-1, frame=None): """Stop the child process, then exit ourselves. """ self.stop_child() if signum is not None: log(1, "parent caught %s" % STOP_SIGNALS[signum]) log(1, "parent shutting down...") raise SystemExit
def process(self): """Execute one transaction. The call to Transaction.process is complicated by the fact that in development mode we want to drop into the post-mortem debugger when there is an exception (other than Response, of course). Transaction.process is expected to raise a Response or other exception. """ config = TransactionConfig(self.app, self.server_config) # Do some late validation so we can use Response. # =============================================== resource_fs_path = uri_to_fs(config, self.request.path, raw=True) # Is the app still on the filesystem? if not os.path.isdir(config.app_fs_root): raise Response(404) if config.__: # Is the app's magic directory still on the filesystem? if not os.path.isdir(config.__): response = Response(500) response.body = ("The application's magic directory has " + "disappeared.") raise response # Protect the magic directory from direct access. if resource_fs_path.startswith(config.__): raise Response(404) # Get out of the way. # =================== transaction = self.app.module.Transaction(config) if not self.dev_mode: transaction.process(self.request) else: try: transaction.process(self.request) except Response: raise except: log(90, traceback.format_exc()) pdb.post_mortem(sys.exc_info()[2]) raise # You know something? No soup for you! # ==================================== response = Response(500) response.body = "%s.process did not raise anything." % str(transaction) raise response
def respond(self): """Execute one transaction. The call to Application.respond is complicated by the fact that in debugging mode we want to drop into the post-mortem debugger when there is an exception (other than Response, of course). Application.respond is expected to raise a Response or other exception. """ # Do some late validation so we can use Response. # =============================================== resource_fs_path = uri_to_fs(self.app.site_root, self.app.fs_root, self.app.uri_root, self.request.path, raw=True) # Is the app still on the filesystem? if not os.path.isdir(self.app.fs_root): raise Response(404) if self.app.__: # Is the app's magic directory still on the filesystem? if not os.path.isdir(self.app.__): raise Response( 500, "The application's magic directory has " + "disappeared.") # Protect the magic directory from direct access. if resource_fs_path.startswith(self.app.__): raise Response(404) # Get out of the way. # =================== if not self.server.debug_mode: self.app.respond(self.request) else: try: self.app.respond(self.request) except Response: raise except: log(90, traceback.format_exc()) pdb.post_mortem(sys.exc_info()[2]) raise # You know something? No soup for you! # ==================================== raise Response( 500, "%s.respond did not raise " % str(application) + "anything.")
def get_app(self): """Get an application with which to serve the requested URI. """ app = None for _app in self.server.config.apps: if self.request.path.startswith(_app.uri_root): app = _app break if app is None: # This catches, e.g., ../../../../../../../../../etc/master.passwd raise StandardError("Unable to find an application to serve " + "%s." % self.request.path) log(98, "Using %s for this request" % app) return app
def stop_child(self): """Stop the child server process. """ try: os.kill(self.pid, signal.SIGHUP) except OSError: log(90, "OSError killing pid %d" % self.pid) log(99, traceback.format_exc()) if 0: # this is for debugging the restart-while-debugging bug print "isatty: %s %s %s" % tuple( [x.isatty() for x in (sys.stdin, sys.stdout, sys.stderr)]) while 0: msg = os.read(0, 8192) if msg == 'quit\n': break os.write(1, msg)
def __init__(self, channel, request): """Takes an IServerChannel (httpy._zope) and an IRequest (httpy). """ try: # Set these early in case we fail. self.channel = channel self.server = self.channel.server # This is where we are likely to fail. self.request = Request(request) self.app = self.get_app() if int(os.environ.get('HTTPY_VERBOSITY', 0)) >= 99: request_lines = request.raw.splitlines() raw_request = os.linesep.join(request_lines) log(99, raw_request) except: self.fail()
def start(self, args=None): log(1, "starting child server ...") if args is None: args = [sys.executable] + sys.argv new_env = os.environ.copy() new_env['HTTPY_PLAIN_JANE'] = 'So plain.' while 1: try: try: self.pid = os.spawnve(os.P_NOWAIT, sys.executable, args, new_env) while 1: self.look_for_changes() time.sleep(1) finally: self.stop_child() except SystemExit: time.sleep(0.1) # Give stdout a chance to flush. raise except Restart: log(90, "Restarting child server ...") except: log( 90, "Exception while spawning child ...\n" + traceback.format_exc())
def start(self): self.accept_connections() try: addr, port = self.socket.getsockname() log(1, "httpy started on port %s" % port) log(99, "%s\n\n" % ("="*76)) asyncore.loop(timeout=5) except KeyboardInterrupt: log(1, "shutting down...") self.task_dispatcher.shutdown()
def stop(self, signum=None, frame=None): if signum is not None: log(1, "caught %s" % STOP_SIGNALS[signum]) log(1, "shutting down...") for app in self.config.apps: if hasattr(app, 'close') and inspect.ismethod(app.close): app.close() self.task_dispatcher.shutdown() asyncore.close_all() log(1, "httpy stopped")
def deliver(self, response): """Given an httpy.Response, write it out to the wire. """ log(96, "Attempting to send a response") log(99, "Response lineage as follows:\n%s" % traceback.format_exc()) # Output the Status-Line. # ======================= if response.code not in StatusCodes: raise StandardError("Bad response code: %r" % response.code) elif (response.code == 537) and self.server.deploy_mode: raise StandardError("Won't serve 537 in deployment mode.") reason_phrase, reason_message = StatusCodes.get(response.code) status_line = ' '.join((str(self.server.http_version_string), str(response.code), reason_phrase)) self.channel.write(status_line + '\r\n') # Generate the body. # ================== # We do this here so we can calculate the content-length. if (not response.body) and (response.code not in (200, 537)): response.body = reason_message if response.code == 537: if not isinstance(response.body, basestring): response.body = pprint.pformat(response.body) response.body = str(response.body) # Output the headers. # =================== # First we convert all headers to lower case so that we can index them # sanely. We then ensure a minimal set of headers, overriding any # existing content-length because we don't trust it. headers = {} for k, v in response.headers.iteritems(): header = k.lower() if header == 'content-length': continue headers[header] = v if 'content-length' not in headers: # always true headers['content-length'] = len(response.body) if 'content-type' not in headers: if str(response.code).startswith('2'): # Setting this header for successful requests is the # application's job. headers['content-type'] = 'application/octet-stream' else: # But error messages default to text/plain. headers['content-type'] = 'text/plain' if 'server' not in headers: headers['server'] = self.server.response_header for header in headers.iteritems(): self.channel.write("%s: %s\r\n" % header) self.channel.write('\r\n') # Output the body. # ================ # We don't output the body for 304s or HEAD requests, but we always do # for Request parsing errors. if (response.code != 304) and (self.request.method != 'HEAD'): self.channel.write(response.body) log( 94, "Responded to %s with %d %s" % (self.request.raw_line, response.code, reason_phrase)) log(99, "%s\n\n" % ("=" * 76))
def start(self): self.accept_connections() addr, port = self.socket.getsockname() log(1, "httpy started on port %s" % port) log(99, "%s\n\n" % ("=" * 76)) asyncore.loop(timeout=5)