class Response(object): """Response(sock, request) -> new Response object A Response object that holds the response to send back to the client. This ensure that the correct data is sent in the correct order. """ chunked = False body = Body() def __init__(self, sock, request): "initializes x; see x.__class__.__doc__ for signature" self.sock = sock self.request = request self.clear() def __repr__(self): return "<Response %s %s (%d)>" % ( self.status, self.headers["Content-Type"], (len(self.body) if type(self.body) == str else 0)) def __str__(self): self.prepare() protocol = self.protocol status = self.status headers = self.headers return "%s %s\r\n%s" % (protocol, status, headers) def clear(self): self.done = False self.close = False if self.request.server: server_version = self.request.server.version else: server_version = SERVER_VERSION self.headers = Headers([ ("Date", strftime("%a, %d %b %Y %H:%M:%S %Z")), ("X-Powered-By", server_version)]) if self.request.server is not None: self.headers.add_header("Server", server_version) self.cookie = self.request.cookie self.stream = False self._body = [] self.time = time() self.status = "200 OK" self.protocol = "HTTP/%d.%d" % self.request.server_protocol def prepare(self): if self.body and type(self.body) is ListType: if unicode in map(type, self.body): cLength = sum(map(lambda s: len(s.encode("utf-8")), self.body)) else: cLength = sum(map(len, self.body)) self.headers.setdefault("Content-Type", "text/html") self.headers["Content-Length"] = str(cLength) if self.stream: self.headers.setdefault("Content-Type", "application/octet-stream") for k, v in self.cookie.iteritems(): self.headers.add_header("Set-Cookie", v.OutputString()) status = int(self.status.split(" ", 1)[0]) if status == 413: self.close = True elif "Content-Length" not in self.headers: if status < 200 or status in (204, 205, 304): pass else: if self.protocol == "HTTP/1.1" \ and self.request.method != "HEAD" \ and self.request.server is not None: self.chunked = True self.headers.add_header("Transfer-Encoding", "chunked") else: self.close = True if self.request.server is not None and "Connection" not in self.headers: if self.protocol == "HTTP/1.1": if self.close: self.headers.add_header("Connection", "close") else: if not self.close: self.headers.add_header("Connection", "Keep-Alive") if self.headers.get("Transfer-Encoding", "") == "chunked": self.chunked = True
class WsgiWrapper: error_status = '500 NOK' error_headers = [('Content-Type', 'text/plain')] error_body = "A server error occurred. Please check input." def __init__(self, event, context): self.context = context self.event = event self.env = dict(os.environ.items()) self.status = None self.headers = None self.content_length_flag = True self.result = None if six.PY2: self.body = StringIO.StringIO() if six.PY3: self.body = io.StringIO() self.bytes_sent = 0 self.headers_sent = False self.ret_content = { 'statusCode': 200, 'headers': {}, 'body': '', } def gen_env(self): env = self.env env['function.event'] = self.event env['function.context'] = self.context request = self.event['detail'] env['REQUEST_METHOD'] = request.get('httpMethod', 'GET') env['SCRIPT_NAME'] = '' env['SERVER_NAME'] = '' env['SERVER_PORT'] = '' env['SERVER_PROTOCOL'] = '' env['PATH_INFO'] = request.get('path', '/') path_params = request.get('pathParameters') if six.PY2: if path_params is None: for k, v in path_params.items(): k = k.replace('-', '_').upper() v = v.strip() if k in env: continue # skip content length, type,etc. if 'HTTP_' + k in env: env['HTTP_' + k] += ',' + v # comma-separate multiple headers else: env['HTTP_' + k] = v if six.PY3: if path_params != None: for k, v in path_params.items(): k = k.replace('-', '_').upper() v = v.strip() if k in env: continue # skip content length, type,etc. if 'HTTP_' + k in env: env['HTTP_' + k] += ',' + v # comma-separate multiple headers else: env['HTTP_' + k] = v query_string = request.get('queryString', '') if query_string == '': query_params = request.get('queryParameters') if six.PY2: if query_params is None: for k, v in query_params.items(): k = k.strip() v = v.strip() if query_string != '': query_string = query_string + '&' query_string = query_string + k + '=' + v if six.PY3: if query_params != None: for k, v in query_params.items(): k = k.strip() v = v.strip() if query_string != '': query_string = query_string + '&' query_string = query_string + k + '=' + v env['QUERY_STRING'] = query_string headers = request.get('headers') content_type = headers.get('content-type', '') if content_type == '': content_type = headers.get('Content-type', '') if content_type == '': content_type = headers.get('Content-Type', '') if content_type == '': content_type = 'Content-type: text/plain' env['CONTENT_TYPE'] = content_type if six.PY2: length = headers.get('content-length', '') if length == '': length = headers.get('Content-length', '') if length == '': length = headers.get('Content-Length', '') if length != '': env['CONTENT_LENGTH'] = length else: env['CONTENT_LENGTH'] = '0' if six.PY3: length = headers.get('content-length', None) if length == None: length = headers.get('Content-length', None) if length == None: length = headers.get('Content-Length', None) if length: env['CONTENT_LENGTH'] = length else: env['CONTENT_LENGTH'] = '0' for k, v in headers.items(): k = k.replace('-', '_').upper() v = v.strip() if k in env: continue # skip content length, type,etc. if 'HTTP_' + k in env: env['HTTP_' + k] += ',' + v # comma-separate multiple headers else: env['HTTP_' + k] = v input_body = request.get('body') if six.PY2: buf = StringIO.StringIO(input_body) if six.PY3: buf = io.StringIO(input_body) env['wsgi.input'] = buf env['wsgi.version'] = (1, 0) env['wsgi.url_scheme'] = self.get_scheme() env['wsgi.errors'] = sys.stderr env['wsgi.multithread'] = True env['wsgi.multiprocess'] = True env['wsgi.run_once'] = False env['wsgi.file_wrapper'] = FileWrapper def get_scheme(self): """Return a guess for whether 'wsgi.url_scheme' should be 'http' or 'https' """ if self.env.get("HTTPS") in ('yes', 'on', '1'): return 'https' else: return 'http' def start_response(self, status, headers, exc_info=None): """'start_response()' callable as specified by PEP 333""" if exc_info: try: if self.headers_sent: # Re-raise original exception if headers sent raise exc_info[0](exc_info[1]).with_traceback(exc_info[2]) finally: exc_info = None # avoid dangling circular ref elif self.headers is not None: raise AssertionError("Headers already set!") assert type(status) is str, "Status must be a string" assert len(status) >= 4, "Status must be at least 4 characters" assert int(status[:3]), "Status message must begin w/3-digit code" assert status[3] == " ", "Status message must have a space after code" self.status = int(status[:3]) self.headers = Headers(headers) return self.write def write(self, data): """'write()' callable as specified by PEP 333""" assert type(data) is StringType, "write() argument must be string" if not self.status: raise AssertionError("write() before start_response()") elif not self.headers_sent: # Before the first output, send the stored headers self.bytes_sent = len(data) # make sure we know content-length self.send_headers() else: self.bytes_sent += len(data) if self.content_length_flag == False: self.ret_content['headers']['Content-Length'] = str( self.bytes_sent) # XXX check Content-Length and truncate if too many bytes written? self.body.write(data) self.body.flush() def send_headers(self): """Transmit headers to the client, via self._write()""" if 'Content-Length' not in self.headers: self.headers['Content-Length'] = str(self.bytes_sent) self.content_length_flag = False self.headers_sent = True self.ret_content['statusCode'] = self.status for item in self.headers.items(): for k, v in self.headers.items(): k = k.strip() v = v.strip() self.ret_content['headers'][k] = v def finish_response(self): for data in self.result: if six.PY2: str_data = str(data) self.write(str_data) if six.PY3: if isinstance(data, str): str_data = data elif isinstance(data, bytes): str_data = data.decode() else: str_data = str(data) self.write(str_data) self.finish_content() def finish_content(self): """Ensure headers and content have both been sent""" if not self.headers_sent: # Only zero Content-Length if not set by the application (so # that HEAD requests can be satisfied properly, see #3839) self.headers.setdefault('Content-Length', "0") self.send_headers() else: pass # XXX check if content-length was too short? def handle_error(self): """Log current error, and send error output to client if possible""" if not self.headers_sent: self.result = self.error_output(self.env, self.start_response) self.finish_response() def error_output(self, environ, start_response): start_response(self.error_status, self.error_headers[:], sys.exc_info()) return [self.error_body] def run(self, application): try: self.gen_env() self.result = application(self.env, self.start_response) self.finish_response() except Exception as e: print(str(e)) self.handle_error() self.ret_content['body'] = self.body.getvalue() return self.ret_content
class Response(object): """Response(sock, request) -> new Response object A Response object that holds the response to send back to the client. This ensure that the correct data is sent in the correct order. """ chunked = False def __init__(self, sock, request): "initializes x; see x.__class__.__doc__ for signature" self.sock = sock self.request = request self.clear() def __repr__(self): return "<Response %s %s (%d)>" % ( self.status, self.headers["Content-Type"], (len(self.body) if type(self.body) == str else 0)) def output(self): protocol = "HTTP/%d.%d" % self.request.server_protocol status = self.status headers = self.headers body = self.process() or "" yield "%s %s\r\n" % (protocol, status) yield str(headers) if body: if self.chunked: buf = [hex(len(body))[2:], "\r\n", body, "\r\n"] yield "".join(buf) else: yield body def clear(self): self.done = False self.close = False if self.request.server: server_version = self.request.server.version else: server_version = SERVER_VERSION self.headers = Headers([("Server", server_version), ("Date", strftime("%a, %d %b %Y %H:%M:%S %Z")), ("X-Powered-By", server_version)]) self.cookie = self.request.cookie self.stream = False self.body = None self.time = time() self.status = "200 OK" def process(self): for k, v in self.cookie.iteritems(): self.headers.add_header("Set-Cookie", v.OutputString()) status = int(self.status.split(" ", 1)[0]) if status == 413: self.close = True elif "Content-Length" not in self.headers: if status < 200 or status in (204, 205, 304): pass else: if self.protocol == "HTTP/1.1" \ and self.request.method != "HEAD": self.chunked = True self.headers.add_header("Transfer-Encoding", "chunked") else: self.close = True if "Connection" not in self.headers: if self.protocol == "HTTP/1.1": if self.close: self.headers.add_header("Connection", "close") else: if not self.close: self.headers.add_header("Connection", "Keep-Alive") if isinstance(self.body, basestring): self.headers["Content-Length"] = str(len(self.body)) self.headers.setdefault("Content-Type", "text/html") return self.body elif type(self.body) is types.GeneratorType: self.stream = True return self.body.next() elif type(self.body) is types.FileType: st = os.fstat(self.body.fileno()) self.headers.setdefault("Content-Length", str(st.st_size)) self.headers.setdefault("Content-Type", "application/octet-stream") self.stream = True self.file = self.body self.body = file_generator(self.body) return None else: return None
class Response(object): """Response(sock, request) -> new Response object A Response object that holds the response to send back to the client. This ensure that the correct data is sent in the correct order. """ chunked = False body = Body() def __init__(self, sock, request): "initializes x; see x.__class__.__doc__ for signature" self.sock = sock self.request = request self.clear() def __repr__(self): return "<Response %s %s (%d)>" % ( self.status, self.headers["Content-Type"], (len(self.body) if type(self.body) == str else 0)) def __str__(self): self.prepare() protocol = self.protocol status = self.status headers = self.headers return "%s %s\r\n%s" % (protocol, status, headers) def clear(self): self.done = False self.close = False if self.request.server: server_version = self.request.server.version else: server_version = SERVER_VERSION self.headers = Headers([("Date", strftime("%a, %d %b %Y %H:%M:%S %Z")), ("X-Powered-By", server_version)]) if self.request.server is not None: self.headers.add_header("Server", server_version) self.cookie = self.request.cookie self.stream = False self._body = [] self.time = time() self.status = "200 OK" self.protocol = "HTTP/%d.%d" % self.request.server_protocol def prepare(self): if self.body and type(self.body) is ListType: if unicode in map(type, self.body): cLength = sum(map(lambda s: len(s.encode("utf-8")), self.body)) else: cLength = sum(map(len, self.body)) self.headers.setdefault("Content-Type", "text/html") self.headers["Content-Length"] = str(cLength) if self.stream: self.headers.setdefault("Content-Type", "application/octet-stream") for k, v in self.cookie.iteritems(): self.headers.add_header("Set-Cookie", v.OutputString()) status = int(self.status.split(" ", 1)[0]) if status == 413: self.close = True elif "Content-Length" not in self.headers: if status < 200 or status in (204, 205, 304): pass else: if self.protocol == "HTTP/1.1" \ and self.request.method != "HEAD" \ and self.request.server is not None: self.chunked = True self.headers.add_header("Transfer-Encoding", "chunked") else: self.close = True if self.request.server is not None and "Connection" not in self.headers: if self.protocol == "HTTP/1.1": if self.close: self.headers.add_header("Connection", "close") else: if not self.close: self.headers.add_header("Connection", "Keep-Alive") if self.headers.get("Transfer-Encoding", "") == "chunked": self.chunked = True