Example #1
0
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
Example #3
0
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
Example #4
0
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