def handle_one_request(self): self.raw_requestline = self.rfile.readline() if not self.raw_requestline: self.close_connection = 1 return if not self.parse_request(): # An error code has been sent, just exit return for prefix in self.server.wsgi_mods: if self.path.startswith(prefix): logger.debug('before wsgi SimpleHandler') try: handler = SimpleHandler(self.rfile, self.wfile, self.get_stderr(), self.get_environ(prefix)) handler.request_handler = self handler.run(self.server.wsgi_mods[prefix]) except Exception as e: logger.exception(e) logger.debug('after wsgi SimpleHandler') return mname = 'do_' + self.command if not hasattr(self, mname): self.send_error(501, "Unsupported method (%r)" % self.command) return method = getattr(self, mname) method()
def close(self): try: self.request_handler.log_request( self.status.split(' ',1)[0], self.bytes_sent ) finally: SimpleHandler.close(self)
def close(self): # M: # S.split([sep [,maxsplit]]) -> list of strings # >>> status # '200 OK' # >>> status.split(' ', 0) # ['200 OK'] # >>> status.split(' ', 1) # ['200', 'OK'] # >>> status.split(' ', 2) # ['200', 'OK'] # In WSGIRequestHandler.handle # handler.request_handler = self # WSGIRequestHandler.log_request # -> BaseHTTPRequestHandler.log_request # -> BaseHTTPRequestHandler.log_message # -> sys.stderr.write # SimpleHandler.close # -> BaseHandler.close try: self.request_handler.log_request( self.status.split(' ', 1)[0], self.bytes_sent) finally: SimpleHandler.close(self)
def finish_response(self): """ Completes the response and performs the following tasks: - Remove the `'ws4py.socket'` and `'ws4py.websocket'` environ keys. - Attach the returned websocket, if any, to the WSGI server using its ``link_websocket_to_server`` method. """ # force execution of the result iterator until first actual content rest = iter(self.result) first = list(itertools.islice(rest, 1)) self.result = itertools.chain(first, rest) # now it's safe to look if environ was modified ws = None if self.environ: self.environ.pop('ws4py.socket', None) ws = self.environ.pop('ws4py.websocket', None) try: SimpleHandler.finish_response(self) except: if ws: ws.close(1011, reason='Something broke') raise else: if ws: self.request_handler.server.link_websocket_to_server(ws)
def testPartialWrite(self): written = bytearray() class PartialWriter: def write(self, b): partial = b[:7] written.extend(partial) return len(partial) def flush(self): pass environ = {"SERVER_PROTOCOL": "HTTP/1.0"} h = SimpleHandler(BytesIO(), PartialWriter(), sys.stderr, environ) msg = "should not do partial writes" with self.assertWarnsRegex(DeprecationWarning, msg): h.run(hello_app) self.assertEqual( b"HTTP/1.0 200 OK\r\n" b"Content-Type: text/plain\r\n" b"Date: Mon, 05 Jun 2006 18:49:54 GMT\r\n" b"Content-Length: 13\r\n" b"\r\n" b"Hello, world!", written, )
def close(self): # M: # S.split([sep [,maxsplit]]) -> list of strings # >>> status # '200 OK' # >>> status.split(' ', 0) # ['200 OK'] # >>> status.split(' ', 1) # ['200', 'OK'] # >>> status.split(' ', 2) # ['200', 'OK'] # In WSGIRequestHandler.handle # handler.request_handler = self # WSGIRequestHandler.log_request # -> BaseHTTPRequestHandler.log_request # -> BaseHTTPRequestHandler.log_message # -> sys.stderr.write # SimpleHandler.close # -> BaseHandler.close try: self.request_handler.log_request( self.status.split(' ',1)[0], self.bytes_sent ) finally: SimpleHandler.close(self)
def setup_environ(self): """ Setup the environ dictionary and add the `'ws4py.socket'` key. Its associated value is the real socket underlying socket. """ SimpleHandler.setup_environ(self) self.environ['ws4py.socket'] = self.environ['wsgi.input']._sock
def setup_environ(self): """ Setup the environ dictionary and add the `'ws4py.socket'` key. Its associated value is the real socket underlying socket. """ SimpleHandler.setup_environ(self) self.environ['ws4py.socket'] = get_connection(self.environ['wsgi.input']) self.http_version = self.environ['SERVER_PROTOCOL'].rsplit('/')[-1]
def handle(self): self.raw_requestline = self.rfile.readline() if not self.parse_request(): # An error code has been sent, just exit return handler = SimpleHandler( self.rfile, self.wfile, self.get_stderr(), self.get_environ() ) handler.request_handler = self # backpointer for logging handler.run(self.server.get_app())
def get(self, path): """ get the content of a url as rendered by bottle """ handler = SimpleHandler(sys.stdin, sys.stdout, sys.stderr, {}) handler.setup_environ() env = handler.environ env.update({ "PATH_INFO": "{0}/{1}".format(self.base_url, path), "REQUEST_METHOD": "GET", }) out = b"".join(self.app(env, lambda *args: None)) return out
def close(self): try: #self.request_handler.log_request( # self.status.split(' ',1)[0], self.bytes_sent #) if self.status: self.request_handler.log_request( self.status.split(' ', 1)[0], self.bytes_sent) else: pass finally: SimpleHandler.close(self)
def do_request(env): handler = SimpleHandler(environ=env, stdin=env['wsgi.input'], stdout=env['wsgi.input'], stderr=BytesIO()) return app(env, handler.start_response)[0].decode().strip()
def testClientConnectionTerminations(self): environ = {"SERVER_PROTOCOL": "HTTP/1.0"} for exception in ( ConnectionAbortedError, BrokenPipeError, ConnectionResetError, ): with self.subTest(exception=exception): class AbortingWriter: def write(self, b): raise exception stderr = StringIO() h = SimpleHandler(BytesIO(), AbortingWriter(), stderr, environ) h.run(hello_app) self.assertFalse(stderr.getvalue())
def handle(self): """Handle a single HTTP request""" self.raw_requestline = self.rfile.readline(65537) if len(self.raw_requestline) > 65536: self.requestline = '' self.request_version = '' self.command = '' self.send_error(414) return if not self.parse_request(): # An error code has been sent, just exit return handler = SimpleHandler( self.rfile, self.wfile, self.get_stderr(), self.get_environ() ) handler.request_handler = self # backpointer for logging handler.run(self.server.get_app())
def finish_response(self): """ Completes the response and performs the following tasks: - Remove the `'ws4py.socket'` and `'ws4py.websocket'` environ keys. - Attach the returned websocket, if any, to the WSGI server using its ``link_websocket_to_server`` method. """ ws = None if self.environ: self.environ.pop('ws4py.socket', None) ws = self.environ.pop('ws4py.websocket', None) try: SimpleHandler.finish_response(self) except: if ws: ws.close(1011, reason='Something broke') raise else: if ws: self.request_handler.server.link_websocket_to_server(ws)
def testDontResetInternalStateOnException(self): class CustomException(ValueError): pass # We are raising CustomException here to trigger an exception # during the execution of SimpleHandler.finish_response(), so # we can easily test that the internal state of the handler is # preserved in case of an exception. class AbortingWriter: def write(self, b): raise CustomException stderr = StringIO() environ = {"SERVER_PROTOCOL": "HTTP/1.0"} h = SimpleHandler(BytesIO(), AbortingWriter(), stderr, environ) h.run(hello_app) self.assertIn("CustomException", stderr.getvalue()) # Test that the internal state of the handler is preserved. self.assertIsNotNone(h.result) self.assertIsNotNone(h.headers) self.assertIsNotNone(h.status) self.assertIsNotNone(h.environ)
def testPartialWrite(self): written = bytearray() class PartialWriter: def write(self, b): partial = b[:7] written.extend(partial) return len(partial) def flush(self): pass environ = {"SERVER_PROTOCOL": "HTTP/1.0"} h = SimpleHandler(BytesIO(), PartialWriter(), sys.stderr, environ) msg = "should not do partial writes" with self.assertWarnsRegex(DeprecationWarning, msg): h.run(hello_app) self.assertEqual( b"HTTP/1.0 200 OK\r\n" b"Content-Type: text/plain\r\n" b"Date: Mon, 05 Jun 2006 18:49:54 GMT\r\n" b"Content-Length: 13\r\n" b"\r\n" b"Hello, world!", written)
def cleanup_headers(self): SimpleHandler.cleanup_headers(self) self.headers["Connection"] = "Close"
def test(app, environ={}, form={}, **kw): """Print the output of a WSGI app (e.g. for use in doctests) Runs `app` as a WSGI application and prints its output. If an untrapped error occurs in `app`, it drops into the ``pdb`` debugger's post-mortem debug shell (using ``sys.__stdout__`` if ``sys.stdout`` has been replaced). Any keyword arguments are added to the environment used to run `app`. If a keyword argument begins with ``wsgi_``, the ``_`` is replaced with a ``.``, so that you can set e.g. ``wsgi.multithread`` using a ``wsgi_multithread`` keyword argument. If a non-empty `form` dictionary is provided, it is treated as a collection of fields for a form ``POST``. The ``REQUEST_METHOD`` will default to ``POST``, and the default ``CONTENT_LENGTH``, ``CONTENT_TYPE``, and ``wsgi.input`` values will be appropriately set (but can still be overridden by explicit keyword arguments or the `environ` argument). Any `form` values that are not instances of ``basestring`` are assumed to be *sequences* of values, and will result in multiple name/value pairs being added to the encoded data sent to the application. Any WSGI-required variables that are not specified by `environ`, `form`, or keyword arguments, are initialized to default values using the ``wsgiref.util.setup_testing_defaults()`` function. """ from wsgiref.handlers import SimpleHandler from StringIO import StringIO from urllib import quote_plus environ = environ.copy() for k, v in kw.items(): if k.startswith('wsgi_'): environ[k.replace('_', '.', 1)] = v else: environ[k] = v if form: encoded = [] for k, v in form.items(): if isinstance(v, basestring): v = [v] for v in v: encoded.append('%s=%s' % (quote_plus(k), quote_plus(v))) encoded = '&'.join(encoded) environ.setdefault('wsgi.input', StringIO(encoded)) environ.setdefault('CONTENT_LENGTH', str(len(encoded))) environ.setdefault('CONTENT_TYPE', 'application/x-www-form-urlencoded') environ.setdefault('REQUEST_METHOD', 'POST') setup_testing_defaults(environ) stdout = StringIO() stderr = environ['wsgi.errors'] def wrapper(env, start): try: return app(env, start) except: stdout = sys.stdout try: if stdout is not sys.__stdout__: sys.stdout = sys.__stdout__ import pdb pdb.post_mortem(sys.exc_info()[2]) finally: sys.stdout = stdout raise SimpleHandler(environ['wsgi.input'], stdout, stderr, environ, environ['wsgi.multithread'], environ['wsgi.multiprocess']).run(wrapper) print stdout.getvalue().replace('\r\n', '\n') if stderr.getvalue(): print "--- Log Output ---" print stderr.getvalue().replace('\r\n', '\n')
def cleanup_headers(self): SimpleHandler.cleanup_headers(self) self.headers['Connection'] = 'Close'
def __init__(self, stdin, stdout, stderr): base_environ = dict(os.environ.items()) base_environ['SERVER_PROTOCOL'] = 'HTTP/1.0' SimpleHandler.__init__(self, stdin, stdout, stderr, base_environ, multithread=False, multiprocess=True)
def handleWSGI(stdin, stderr, stdout, environ): handler = SimpleHandler(stdin, stderr, stdout, environ.vars, multithread, multiprocess) handler.run_once = run_once handler.server_software = environ.vars["SERVER_SOFTWARE"] handler.run(app)
def mongrel2_handler(application, conn, debug=False): """ Based on : https://github.com/berry/Mongrel2-WSGI-Handler/blob/master/wsgi-handler.py WSGI handler based on the Python wsgiref SimpleHandler. A WSGI application should return a iterable op StringTypes. Any encoding must be handled by the WSGI application itself. """ from wsgiref.handlers import SimpleHandler try: import cStringIO as StringIO except: import StringIO # TODO - this wsgi handler executes the application and renders a page # in memory completely before returning it as a response to the client. # Thus, it does not "stream" the result back to the client. It should be # possible though. The SimpleHandler accepts file-like stream objects. So, # it should be just a matter of connecting 0MQ requests/response streams to # the SimpleHandler requests and response streams. However, the Python API # for Mongrel2 doesn't seem to support file-like stream objects for requests # and responses. Unless I have missed something. while True: if debug: print "WAITING FOR REQUEST" # receive a request req = conn.recv() if debug: print "REQUEST BODY: %r\n" % req.body if req.is_disconnect(): if debug: print "DISCONNECT" continue # effectively ignore the disconnect from the client # Set a couple of environment attributes a.k.a. header attributes # that are a must according to PEP 333 environ = req.headers environ[ 'SERVER_PROTOCOL'] = 'HTTP/1.1' # SimpleHandler expects a server_protocol, lets assume it is HTTP 1.1 environ['REQUEST_METHOD'] = environ['METHOD'] if ':' in environ['Host']: environ['SERVER_NAME'] = environ['Host'].split(':')[0] environ['SERVER_PORT'] = environ['Host'].split(':')[1] else: environ['SERVER_NAME'] = environ['Host'] environ['SERVER_PORT'] = '' environ['SCRIPT_NAME'] = '' # empty for now environ['PATH_INFO'] = urllib.unquote(environ['PATH']) if '?' in environ['URI']: environ['QUERY_STRING'] = environ['URI'].split('?')[1] else: environ['QUERY_STRING'] = '' if 'Content-Length' in environ: environ['CONTENT_LENGTH'] = environ[ 'Content-Length'] # necessary for POST to work with Django environ['wsgi.input'] = req.body if debug: print "ENVIRON: %r\n" % environ # SimpleHandler needs file-like stream objects for # requests, errors and responses reqIO = StringIO.StringIO(req.body) errIO = StringIO.StringIO() respIO = StringIO.StringIO() # execute the application handler = SimpleHandler(reqIO, respIO, errIO, environ, multithread=False, multiprocess=False) handler.run(application) # Get the response and filter out the response (=data) itself, # the response headers, # the response status code and the response status description response = respIO.getvalue() response = response.split("\r\n") data = response[-1] headers = dict([r.split(": ") for r in response[1:-2]]) code = response[0][9:12] status = response[0][13:] # strip BOM's from response data # Especially the WSGI handler from Django seems to generate them (2 actually, huh?) # a BOM isn't really necessary and cause HTML parsing errors in Chrome and Safari # See also: http://www.xs4all.nl/~mechiel/projects/bomstrip/ # Although I still find this a ugly hack, it does work. data = data.replace('\xef\xbb\xbf', '') # Get the generated errors errors = errIO.getvalue() # return the response if debug: print "RESPONSE: %r\n" % response if errors: if debug: print "ERRORS: %r" % errors data = "%s\r\n\r\n%s" % (data, errors) conn.reply_http(req, data, code=code, status=status, headers=headers)
def wsgi_server(application, conn): '''WSGI handler based on the Python wsgiref SimpleHandler. A WSGI application should return a iterable op StringTypes. Any encoding must be handled by the WSGI application itself. ''' # TODO - this wsgi handler executes the application and renders a page in # memory completely before returning it as a response to the client. # Thus, it does not "stream" the result back to the client. It should be # possible though. The SimpleHandler accepts file-like stream objects. So, # it should be just a matter of connecting 0MQ requests/response streams # to the SimpleHandler requests and response streams. However, the Python # API for Mongrel2 doesn't seem to support file-like stream objects for # requests and responses. Unless I have missed something. # setup connection handler # sender_id is automatically generated # so that each handler instance is uniquely identified while True: debug("WAITING FOR REQUEST") # receive a request req = conn.recv() if DEBUG: debug("REQUEST HEADERS: %r\n" % req.headers) debug("REQUEST BODY: %r\n" % req.body) if req.is_disconnect(): print("DISCONNECT") continue # effectively ignore the disconnect from the client # json parsing gives us unicode instead of ascii. headers = dict((key.encode('ascii'), value.encode('ascii')) for (key, value) in req.headers.items()) env = {} add_request_metavariables(env, headers) add_http_variables(env, headers) debug("ENVIRON: %r\n" % env) # SimpleHandler needs file-like stream objects for # requests, errors and reponses reqIO = StringIO(req.body) errIO = StringIO() respIO = StringIO() # execute the application simple_handler = SimpleHandler(reqIO, respIO, errIO, env, multithread=False, multiprocess=False) simple_handler.run(application) response = respIO.getvalue() errors = errIO.getvalue() # return the response debug("RESPONSE: %r\n" % response) if errors: debug("ERRORS: %r" % errors) conn.reply(req, response) # Check if we should close the connection. respIO.seek(0) protocol = respIO.readline()[:8] msg = httplib.HTTPMessage(respIO, 0) http_1p1 = protocol == 'HTTP/1.1' conn_close = http_1p1 and msg.getheader("Connection") == "close" keep_alive = http_1p1 or msg.getheader("Connection") == "Keep-Alive" if conn_close or not keep_alive: debug("EXPLICITLY CLOSING CONNECTION") conn.reply(req, "")
def handle_wsgi_request(stdin, stdout, stderr, environ): handler = SimpleHandler(stdin.buffer, stdout.buffer, stderr, environ.vars, environ.server.multithread, environ.server.multiprocess) handler.run_once = not environ.server.multiconnection handler.server_software = environ.server.software handler.run(app)