def test_html_quote(self): self.assertEqual(quoting.html_quote(1), '1') self.assertEqual(quoting.html_quote(None), '') self.assertEqual(quoting.html_quote('<hey!>'), '<hey!>') if six.PY3: self.assertEqual(quoting.html_quote(u'<\u1029>'), u'<\u1029>') else: self.assertEqual(quoting.html_quote(u'<\u1029>'), '<\xe1\x80\xa9>')
def connection_handler(self): """The main request processing loop.""" while True: # Read the first line of the HTTP request. try: line = yield self.read_line() # Ignore blank lines, as suggested by the RFC if not line: continue method, url, protocol = line.split(' ') except (tcp.ConnectionOverflowException, ValueError): yield self.send_error("400 Bad Request") self.close() return # Prepare WSGI environment. waiting_coro = [] environ = { 'chiral.http.connection': self, 'wsgi.version': (1, 0), 'wsgi.url_scheme': 'http', 'wsgi.input': '', 'wsgi.errors': sys.stderr, 'wsgi.file_wrapper': WSGIFileWrapper, 'wsgi.multithread': False, 'wsgi.multiprocess': True, 'wsgi.run_once': False, 'REQUEST_METHOD': method, 'SCRIPT_NAME': '', 'SERVER_NAME': self.server.bind_addr[0], 'SERVER_PORT': self.server.bind_addr[1], 'SERVER_PROTOCOL': protocol } # Split query string out of URL, if present url = url.split('?', 1) if len(url) > 1: url, environ["QUERY_STRING"] = url else: url, = url # Read the rest of the request header, updating the WSGI environ dict # with each key/value pair last_key = None while True: try: line = yield self.read_line() except tcp.ConnectionOverflowException: yield self.send_error("400 Bad Request") self.close() return if not line: break # Allow for headers split over multiple lines. if last_key and line[0] in (" ", "\t"): environ[last_key] += "," + line continue if ":" not in line: yield self.send_error("400 Bad Request") self.close() return name, value = line.split(":", 1) # Convert the field name to WSGI's format key = "HTTP_" + name.upper().replace("-", "_") if key in environ: # RFC2616 4.2: Multiple copies of a header should be # treated as though they were separated by commas. environ[key] += "," + value.strip() else: environ[key] = value.strip() # Accept absolute URLs, as required by HTTP 1.1 if url.startswith("http://"): url = url[7:] if "/" in url: environ["HTTP_HOST"], url = url.split("/", 1) else: environ["HTTP_HOST"], url = url, "" environ["PATH_INFO"] = url # HTTP/1.1 requires that requests without a Host: header result in 400 #if protocol == "HTTP/1.1" and "HTTP_HOST" not in environ: # yield self.send_error("400 Bad Request") # continue # WSGI requires these two headers environ["CONTENT_TYPE"] = environ.get("HTTP_CONTENT_TYPE", "") environ["CONTENT_LENGTH"] = environ.get("HTTP_CONTENT_LENGTH", "") # If a 100 Continue is expected, send it now. if protocol == "HTTP/1.1" and "100-continue" in environ.get("HTTP_EXPECT", ""): yield self.sendall("HTTP/1.1 100 Continue\r\n\r\n") # If this is a POST request with Content-Length, read its data if method == "POST" and environ["CONTENT_LENGTH"]: postdata = yield self.read_exactly(int(environ["CONTENT_LENGTH"])) environ["wsgi.input"] = StringIO(postdata) def set_coro(coro): """ Tell the HTTP response to not close until coro has completed. """ waiting_coro[:] = [ coro ] environ['chiral.http.set_coro'] = set_coro # Prepare the response object. response = HTTPResponse(self, environ) # Invoke the application. try: result = self.server.application(environ, response.start_response) except Exception: yield self.send_error( "500 Internal Server Error", response, "<pre>%s</pre>" % html_quote(traceback.format_exc()) ) # Close if necessary if response.should_keep_alive: continue else: self.close() break # If the iterable has length 1, then we can determine the length # of the whole result now. try: if len(result) == 1 and not waiting_coro: response.headers["Content-Length"] = len(result[0]) except TypeError: pass # Handle WSGIFileWrapper. if isinstance(result, WSGIFileWrapper): yield result.send_to_connection(self, response) # We're now done with this request. if response.should_keep_alive: continue else: self.close() break # Did they use write()? write_data = response.write_data_buffer.getvalue() if write_data: headers_sent = True yield self.sendall(response.render_headers() + write_data) # Iterate through the result chunks provided by the application. res_iter = iter(result) headers_sent = False while True: try: data = res_iter.next() except StopIteration: break except Exception: yield self.send_error( "500 Internal Server Error", response, "<pre>%s</pre>" % ( html_quote(traceback.format_exc()), ) ) self.close() return # Ignore empty chunks if not data: continue # Sending the headers is delayed until the first actual # data chunk comes back. if not headers_sent: headers_sent = True yield self.sendall(response.render_headers() + data) else: yield self.sendall(data) # If no data at all was returned, the headers won't have been sent yet. if not headers_sent and not waiting_coro: headers_sent = True yield self.sendall(response.render_headers(no_content=True)) # If set_coro has been called, waiting_coro is a Coroutine that will # complete once the response is done. if waiting_coro: try: delayed_data = yield waiting_coro[0] del waiting_coro[:] # See if we can set Content-Length try: if type(delayed_data) in (list, tuple) and len(delayed_data) == 1: response.headers["Content-Length"] = len(delayed_data[0]) except TypeError: pass # Iterate through and send any delayed data as well if delayed_data: for data in delayed_data: # Ignore empty chunks if not data: continue # Add the headers, if not already sent if not headers_sent: headers_sent = True data = response.render_headers() + data yield self.sendall(data) except Exception: exc_formatted = "<pre>%s</pre>" % html_quote(traceback.format_exc()) yield self.send_error("500 Internal Server Error", response, exc_formatted) # If the waiting coro didn't return any data at all, send the headers already. if not headers_sent: headers_sent = True yield self.sendall(response.render_headers(no_content=True)) # Call any close handler on the WSGI app's result if hasattr(result, 'close'): result.close() # Close if necessary if not response.should_keep_alive: self.close() break