Пример #1
0
    def now_handler(event, context):
        payload = json.loads(event['body'])

        headers = Headers(payload.get('headers', {}))

        body = payload.get('body', '')
        if body != '':
            if payload.get('encoding') == 'base64':
                body = base64.b64decode(body)
        if isinstance(body, string_types):
            body = to_bytes(body, charset='utf-8')

        path = unquote(payload['path'])
        query = urlparse(path).query

        environ = {
            'CONTENT_LENGTH': str(len(body)),
            'CONTENT_TYPE': headers.get('content-type', ''),
            'PATH_INFO': path,
            'QUERY_STRING': query,
            'REMOTE_ADDR': headers.get(
                'x-forwarded-for', headers.get(
                    'x-real-ip', payload.get(
                        'true-client-ip', ''))),
            'REQUEST_METHOD': payload['method'],
            'SERVER_NAME': headers.get('host', 'lambda'),
            'SERVER_PORT': headers.get('x-forwarded-port', '80'),
            'SERVER_PROTOCOL': 'HTTP/1.1',
            'event': event,
            'context': context,
            'wsgi.errors': sys.stderr,
            'wsgi.input': BytesIO(body),
            'wsgi.multiprocess': False,
            'wsgi.multithread': False,
            'wsgi.run_once': False,
            'wsgi.url_scheme': headers.get('x-forwarded-proto', 'http'),
            'wsgi.version': (1, 0),
        }

        for key, value in environ.items():
            if isinstance(value, string_types) and key != 'QUERY_STRING':
                environ[key] = wsgi_encoding_dance(value)

        for key, value in headers.items():
            key = 'HTTP_' + key.upper().replace('-', '_')
            if key not in ('HTTP_CONTENT_TYPE', 'HTTP_CONTENT_LENGTH'):
                environ[key] = value

        response = Response.from_app(__NOW_HANDLER_FILENAME.app, environ)

        return_dict = {
            'statusCode': response.status_code,
            'headers': dict(response.headers)
        }

        if response.data:
            return_dict['body'] = base64.b64encode(response.data).decode('utf-8')
            return_dict['encoding'] = 'base64'

        return return_dict
Пример #2
0
 def _create_headers_dictionary_flask(
         self, headers: Headers) -> Mapping[str, List[str]]:
     headers_dict: Mapping[str, List[str]] = defaultdict(list)
     for (key, value) in headers.items():
         headers_dict[key].append(value)
     return dict(headers_dict)
Пример #3
0
def handle_request(application, event, context):

    if u"multiValueHeaders" in event:
        headers = Headers(event["multiValueHeaders"])
    else:
        headers = Headers(event["headers"])

    strip_stage_path = os.environ.get("STRIP_STAGE_PATH",
                                      "").lower().strip() in [
                                          "yes",
                                          "y",
                                          "true",
                                          "t",
                                          "1",
                                      ]
    if u"apigw.tencentcs.com" in headers.get(u"Host",
                                             u"") and not strip_stage_path:
        script_name = "/{}".format(event["requestContext"].get(u"stage", ""))
    else:
        script_name = ""

    path_info = event["path"]
    base_path = os.environ.get("API_GATEWAY_BASE_PATH")
    if base_path:
        script_name = "/" + base_path

        if path_info.startswith(script_name):
            path_info = path_info[len(script_name):] or "/"

    if u"body" in event:
        body = event[u"body"] or ""
    else:
        body = ""

    if event.get("isBase64Encoded", False):
        body = base64.b64decode(body)
    if isinstance(body, string_types):
        body = to_bytes(body, charset="utf-8")

    environ = {
        "CONTENT_LENGTH":
        str(len(body)),
        "CONTENT_TYPE":
        headers.get(u"Content-Type", ""),
        "PATH_INFO":
        url_unquote(path_info),
        "QUERY_STRING":
        encode_query_string(event),
        "REMOTE_ADDR":
        event["requestContext"].get(u"identity", {}).get(u"sourceIp", ""),
        "REMOTE_USER":
        event["requestContext"].get(u"authorizer", {}).get(u"principalId", ""),
        "REQUEST_METHOD":
        event["httpMethod"],
        "SCRIPT_NAME":
        script_name,
        "SERVER_NAME":
        headers.get(u"Host", "lambda"),
        "SERVER_PORT":
        headers.get(u"X-Forwarded-Port", "80"),
        "SERVER_PROTOCOL":
        "HTTP/1.1",
        "wsgi.errors":
        sys.stderr,
        "wsgi.input":
        BytesIO(body),
        "wsgi.multiprocess":
        False,
        "wsgi.multithread":
        False,
        "wsgi.run_once":
        False,
        "wsgi.url_scheme":
        headers.get(u"X-Forwarded-Proto", "http"),
        "wsgi.version": (1, 0),
        "serverless.authorizer":
        event["requestContext"].get(u"authorizer"),
        "serverless.event":
        event,
        "serverless.context":
        context,
        # TODO: Deprecate the following entries, as they do not comply with the WSGI
        # spec. For custom variables, the spec says:
        #
        #   Finally, the environ dictionary may also contain server-defined variables.
        #   These variables should be named using only lower-case letters, numbers, dots,
        #   and underscores, and should be prefixed with a name that is unique to the
        #   defining server or gateway.
        "API_GATEWAY_AUTHORIZER":
        event["requestContext"].get(u"authorizer"),
        "event":
        event,
        "context":
        context,
    }

    for key, value in environ.items():
        if isinstance(value, string_types):
            environ[key] = wsgi_encoding_dance(value)

    for key, value in headers.items():
        key = "HTTP_" + key.upper().replace("-", "_")
        if key not in ("HTTP_CONTENT_TYPE", "HTTP_CONTENT_LENGTH"):
            environ[key] = value

    response = Response.from_app(application, environ)

    returndict = {u"statusCode": response.status_code}

    if u"multiValueHeaders" in event:
        returndict["multiValueHeaders"] = group_headers(response.headers)
    else:
        returndict["headers"] = split_headers(response.headers)

    if event.get("requestContext").get("elb"):
        # If the request comes from ALB we need to add a status description
        returndict["statusDescription"] = u"%d %s" % (
            response.status_code,
            HTTP_STATUS_CODES[response.status_code],
        )

    if response.data:
        mimetype = response.mimetype or "text/plain"
        if (mimetype.startswith("text/")
                or mimetype in TEXT_MIME_TYPES) and not response.headers.get(
                    "Content-Encoding", ""):
            returndict["body"] = response.get_data(as_text=True)
            returndict["isBase64Encoded"] = False
        else:
            returndict["body"] = base64.b64encode(
                response.data).decode("utf-8")
            returndict["isBase64Encoded"] = True

    return returndict
Пример #4
0
def encode_headers(headers: Headers) -> List[Tuple[bytes, bytes]]:
    return [(key.lower().encode(), value.encode())
            for key, value in headers.items()]
def handle_request(app, event, context):
    if event.get("source") in ["aws.events", "serverless-plugin-warmup"]:
        return {}

    if u"multiValueHeaders" in event:
        headers = Headers(event[u"multiValueHeaders"])
    else:
        headers = Headers(event[u"headers"])

    if u"amazonaws.com" in headers.get(u"Host", u""):
        script_name = "/{}".format(event[u"requestContext"].get(u"stage", ""))
    else:
        script_name = ""

    # If a user is using a custom domain on API Gateway, they may have a base
    # path in their URL. This allows us to strip it out via an optional
    # environment variable.
    path_info = event[u"path"]
    base_path = os.environ.get("API_GATEWAY_BASE_PATH", "")
    if base_path:
        script_name = "/" + base_path

        if path_info.startswith(script_name):
            path_info = path_info[len(script_name):]

    body = event[u"body"] or ""
    if event.get("isBase64Encoded", False):
        body = base64.b64decode(body)
    if isinstance(body, string_types):
        body = to_bytes(body, charset="utf-8")

    environ = {
        "CONTENT_LENGTH":
        str(len(body)),
        "CONTENT_TYPE":
        headers.get(u"Content-Type", ""),
        "PATH_INFO":
        path_info,
        "QUERY_STRING":
        encode_query_string(event),
        "REMOTE_ADDR":
        event[u"requestContext"].get(u"identity", {}).get(u"sourceIp", ""),
        "REMOTE_USER":
        event[u"requestContext"].get(u"authorizer",
                                     {}).get(u"principalId", ""),
        "REQUEST_METHOD":
        event[u"httpMethod"],
        "SCRIPT_NAME":
        script_name,
        "SERVER_NAME":
        headers.get(u"Host", "lambda"),
        "SERVER_PORT":
        headers.get(u"X-Forwarded-Port", "80"),
        "SERVER_PROTOCOL":
        "HTTP/1.1",
        "wsgi.errors":
        sys.stderr,
        "wsgi.input":
        BytesIO(body),
        "wsgi.multiprocess":
        False,
        "wsgi.multithread":
        False,
        "wsgi.run_once":
        False,
        "wsgi.url_scheme":
        headers.get(u"X-Forwarded-Proto", "http"),
        "wsgi.version": (1, 0),
        "API_GATEWAY_AUTHORIZER":
        event[u"requestContext"].get(u"authorizer"),
        "event":
        event,
        "context":
        context,
    }

    for key, value in environ.items():
        if isinstance(value, string_types):
            environ[key] = wsgi_encoding_dance(value)

    for key, value in headers.items():
        key = "HTTP_" + key.upper().replace("-", "_")
        if key not in ("HTTP_CONTENT_TYPE", "HTTP_CONTENT_LENGTH"):
            environ[key] = value

    response = Response.from_app(app, environ)

    returndict = {u"statusCode": response.status_code}

    if u"multiValueHeaders" in event:
        returndict[u"multiValueHeaders"] = group_headers(response.headers)
    else:
        returndict[u"headers"] = split_headers(response.headers)

    if event.get("requestContext").get("elb"):
        # If the request comes from ALB we need to add a status description
        returndict["statusDescription"] = u"%d %s" % (
            response.status_code,
            HTTP_STATUS_CODES[response.status_code],
        )

    if response.data:
        mimetype = response.mimetype or "text/plain"
        if (mimetype.startswith("text/")
                or mimetype in TEXT_MIME_TYPES) and not response.headers.get(
                    "Content-Encoding", ""):
            returndict["body"] = response.get_data(as_text=True)
            returndict["isBase64Encoded"] = False
        else:
            returndict["body"] = base64.b64encode(
                response.data).decode("utf-8")
            returndict["isBase64Encoded"] = True

    return returndict
Пример #6
0
def handler(event, context):
    headers = Headers(event[u'headers'])

    if headers.get(u'Host', u'').endswith(u'.amazonaws.com'):
        script_name = '/{}'.format(event[u'requestContext'].get(u'stage', ''))
    else:
        script_name = ''

    # If a user is using a custom domain on API Gateway, they may have a base
    # path in their URL. This allows us to strip it out via an optional
    # environment variable.
    path_info = event[u'path']
    base_path = os.environ.get('API_GATEWAY_BASE_PATH', '')
    if base_path:
        script_name = '/' + base_path

        if path_info.startswith(script_name):
            path_info = path_info[len(script_name):]

    body = event[u'body'] or ''
    if event.get('isBase64Encoded', False):
        body = base64.b64decode(body)
    if isinstance(body, string_types):
        body = to_bytes(wsgi_encoding_dance(body))

    environ = {
        'API_GATEWAY_AUTHORIZER':
        event[u'requestContext'].get(u'authorizer', None),
        'CONTENT_LENGTH':
        str(len(body)),
        'CONTENT_TYPE':
        headers.get(u'Content-Type', ''),
        'PATH_INFO':
        path_info,
        'QUERY_STRING':
        url_encode(event.get(u'queryStringParameters', None) or {}),
        'REMOTE_ADDR':
        event[u'requestContext'].get(u'identity', {}).get(u'sourceIp', ''),
        'REMOTE_USER':
        event[u'requestContext'].get(u'authorizer',
                                     {}).get(u'principalId', ''),
        'REQUEST_METHOD':
        event[u'httpMethod'],
        'SCRIPT_NAME':
        script_name,
        'SERVER_NAME':
        headers.get(u'Host', 'lambda'),
        'SERVER_PORT':
        headers.get(u'X-Forwarded-Port', '80'),
        'SERVER_PROTOCOL':
        'HTTP/1.1',
        'event':
        event,
        'context':
        context,
        'wsgi.errors':
        sys.stderr,
        'wsgi.input':
        BytesIO(body),
        'wsgi.multiprocess':
        False,
        'wsgi.multithread':
        False,
        'wsgi.run_once':
        False,
        'wsgi.url_scheme':
        headers.get(u'X-Forwarded-Proto', 'http'),
        'wsgi.version': (1, 0),
    }

    for key, value in environ.items():
        if isinstance(value, string_types):
            environ[key] = wsgi_encoding_dance(value)

    for key, value in headers.items():
        key = 'HTTP_' + key.upper().replace('-', '_')
        if key not in ('HTTP_CONTENT_TYPE', 'HTTP_CONTENT_LENGTH'):
            environ[key] = value

    response = Response.from_app(wsgi_app, environ)

    # If there are multiple Set-Cookie headers, create case-mutated variations
    # in order to pass them through APIGW. This is a hack that's currently
    # needed. See: https://github.com/logandk/serverless-wsgi/issues/11
    # Source: https://github.com/Miserlou/Zappa/blob/master/zappa/middleware.py
    new_headers = [x for x in response.headers if x[0] != 'Set-Cookie']
    cookie_headers = [x for x in response.headers if x[0] == 'Set-Cookie']
    if len(cookie_headers) > 1:
        for header, new_name in zip(cookie_headers, all_casings('Set-Cookie')):
            new_headers.append((new_name, header[1]))
    elif len(cookie_headers) == 1:
        new_headers.extend(cookie_headers)

    returndict = {
        u'statusCode': response.status_code,
        u'headers': dict(new_headers)
    }

    if response.data:
        mimetype = response.mimetype or 'text/plain'
        if ((mimetype.startswith('text/') or mimetype in TEXT_MIME_TYPES)
                and not response.headers.get('Content-Encoding', '')):
            returndict['body'] = response.get_data(as_text=True)
        else:
            returndict['body'] = base64.b64encode(
                response.data).decode('utf-8')
            returndict['isBase64Encoded'] = 'true'

    return returndict
Пример #7
0
class Response(object):
    """
    A response is responsible (no pun intended) for delivering data to the client, again.

    The method :meth:`to_bytes` transforms this into a bytes response.

    :ivar code: The response code.
    :ivar cookies: The cookies to send down with the response.
    :ivar body: The string or bytes body of the request.
    :ivar headers: A dict of headers for the response.
    :ivar request: The request object this Response is handling.
            This is automatically set inside Kyoukai.
    """
    def __init__(self,
                 code: int,
                 body: typing.Union[str, bytes],
                 headers: dict = None):
        """
        Create a new response.
        """
        self.code = code
        self.cookies = SimpleCookie()
        self.body = body
        self.headers = Headers(headers) if headers else Headers()

        self._should_gzip = False
        self._is_head = False

        self.request = None

    @property
    def gzip(self):
        """
        :return: If the request is to be gzip compressed.

        Note: This will always return False on a newly created Response, unless the ``request`` instance variable is
        set on the Response.
        """
        if self.request:
            return ('gzip' in self.request.headers.get(
                "Accept-Encoding", "")) and self._should_gzip
        else:
            return False
            # return self._should_gzip

    @gzip.setter
    def gzip(self, value):
        self._should_gzip = value

    # Utility functions.

    def get_compressed_body(self, body: bytes) -> bytes:
        """
        Returns the compressed body, if gzip is enabled.

        Otherwise, returns the normal body.
        :param body: The body to compress.
        :return: The compressed body, or just the body.
        """
        if self.gzip:
            return gzip.compress(body, 5)
        else:
            return body

    def get_response_http_version(self):
        """
        Gets what HTTP version the response should use.
        """
        if self.request:
            return self.request.version
        else:
            return "1.0"

    def _mimetype(self, body):
        """
        Calculates the mime type of the response, using libmagic.

        This is an **internal method**.
        """
        if _has_magic:
            # libmagic is unreliable when the body is str.
            # so encode it to make sure it returns correctly
            if isinstance(self.body, str):
                mime = magic.from_buffer(body.encode(), mime=True)
            else:
                mime = magic.from_buffer(body, mime=True)
            if mime:
                return mime.decode() if isinstance(mime, bytes) else mime
            else:
                return "empty"
        else:
            return "text/plain"

    def _recalculate_headers(self):
        """
        Override certain headers, like Content-Size.

        This is an **internal method**.
        """
        if not self._is_head:
            # The +2 is for the \r\n at the end.
            self.headers["Content-Length"] = len(self.body) + 2
        if 'Content-Type' not in self.headers:
            self.headers["Content-Type"] = self._mimetype(
                self.body) or "text/plain"

        # If it's gzip enabled, add the gzip header.
        if self.gzip:
            self.headers["Content-Encoding"] = "gzip"

        # Set cookies.
        self.headers["Date"] = formatdate()
        self.headers[
            "Server"] = "Kyoukai/{} (see https://github.com/SunDwarf/Kyoukai)".format(
                util.VERSION)
        self.headers["X-Powered-By"] = "Kyoukai"

    def to_bytes(self):
        """
        Serialize a Response into :class:`bytes` to return and send to the client.

        :return: The encoded data for the response.
        """
        if self.request:
            if self.request.method.lower() == "head":
                self._is_head = True

        version = self.get_response_http_version()

        if isinstance(self.body, str):
            self.body = self.body.encode()
        elif isinstance(self.body, bytes):
            pass
        else:
            self.body = str(self.body).encode()

        if self.gzip:
            if 'Content-Type' not in self.headers:
                self.headers["Content-Type"] = self._mimetype(self.body)
            self.body = self.get_compressed_body(self.body)

        # Re-calculate headers to update everything as appropriate.
        self._recalculate_headers()

        fmt = "HTTP/{version} {code} {msg}\r\n{headers}{cookies}\r\n"
        headers_fmt = ""
        # Calculate headers
        for name, val in self.headers.items():
            headers_fmt += "{}: {}\r\n".format(name, val)

        # Get the HTTP code.
        code = http.HTTPStatus(self.code).name.replace("_", " ")

        built = fmt.format(code=self.code,
                           msg=code,
                           headers=headers_fmt,
                           cookies=(self.cookies.output() +
                                    "\r\n") if len(self.cookies) else "",
                           version=version)

        # Encode the built string so far.
        built = built.encode()

        # Append the body, plus the terminator.
        built += self.body + b"\r\n"

        return built

    @classmethod
    def redirect(cls, location, code=302):
        """
        Creates a new Response that redirects to a specific location.

        :param location: The location to redirect to.
        :param code: The code (usually a 301 or 302) to add to the response.
        :return: A new :class:`Response` for the redirect.
        """
        # https://github.com/pallets/werkzeug/blob/master/werkzeug/utils.py#L373
        # response body used from Werkzeug

        res = cls(
            code=code,
            body='<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2 Final//EN">\n'
            '<title>Redirecting...</title>\n'
            '<h1>Redirecting...</h1>\n'
            '<p>You should be redirected automatically to target URL: '
            '<a href="{location}">{location}</a>.  If not click the link.'.
            format(location=location),
            headers={"Location": location})
        return res
Пример #8
0
def handler(app, lambda_event, context):
    event = json.loads(lambda_event['body'])
    headers = Headers(event.get('headers', None))
    parsed_url = urlparse(event['path'])

    body = event.get('body', '')
    if event.get('isBase64Encoded', False):
        body = base64.b64decode(body)
    if isinstance(body, string_types):
        body = to_bytes(body, charset='utf-8')

    environ = {
        'CONTENT_LENGTH': str(len(body)),
        'CONTENT_TYPE': headers.get('Content-Type', ''),
        'PATH_INFO': parsed_url.path,
        'QUERY_STRING': parsed_url.query,
        'REMOTE_ADDR': event.get('x-real-ip', ''),
        'REQUEST_METHOD': event['method'],
        'SCRIPT_NAME': '',
        'SERVER_NAME': headers.get('Host', 'lambda'),
        'SERVER_PORT': headers.get('X-Forwarded-Port', '80'),
        'SERVER_PROTOCOL': 'HTTP/1.1',
        'event': lambda_event['body'],
        'wsgi.errors': sys.stderr,
        'wsgi.input': BytesIO(body),
        'wsgi.multiprocess': False,
        'wsgi.multithread': False,
        'wsgi.run_once': False,
        'wsgi.url_scheme': headers.get('X-Forwarded-Proto', 'http'),
        'wsgi.version': (1, 0),
    }

    for key, value in environ.items():
        if isinstance(value, string_types):
            environ[key] = wsgi_encoding_dance(value)

    for key, value in headers.items():
        key = 'HTTP_' + key.upper().replace('-', '_')
        if key not in ('HTTP_CONTENT_TYPE', 'HTTP_CONTENT_LENGTH'):
            environ[key] = value

    response = Response.from_app(app, environ)

    # If there are multiple Set-Cookie headers, create case-mutated variations
    # in order to pass them through APIGW. This is a hack that's currently
    # needed. See: https://github.com/logandk/serverless-wsgi/issues/11
    # Source: https://github.com/Miserlou/Zappa/blob/master/zappa/middleware.py
    new_headers = [x for x in response.headers if x[0] != 'Set-Cookie']
    cookie_headers = [x for x in response.headers if x[0] == 'Set-Cookie']
    if len(cookie_headers) > 1:
        for header, new_name in zip(cookie_headers, all_casings('Set-Cookie')):
            new_headers.append((new_name, header[1]))
    elif len(cookie_headers) == 1:
        new_headers.extend(cookie_headers)

    returndict = {
        'statusCode': response.status_code,
        'headers': dict(new_headers),
        'body': '',
    }

    if response.data:
        mimetype = response.mimetype or 'text/plain'
        if (mimetype.startswith('text/')
                or mimetype in TEXT_MIME_TYPES) and not response.headers.get(
                    'Content-Encoding', ''):
            returndict['body'] = response.get_data(as_text=True)
            returndict['isBase64Encoded'] = False
        else:
            returndict['body'] = base64.b64encode(response.data)\
                                       .decode('utf-8')
            returndict['isBase64Encoded'] = True

    return returndict
Пример #9
0
def handler(app, lambda_event, context):

    event = json.loads(lambda_event['body'])
    headers = Headers(event.get('headers', None))
    parsed_url = urlparse(event['path'])

    body = event.get('body', '')
    encoding = event.get('encoding', None)

    if encoding == 'base64':
        body = base64.b64decode(body)
    else:
        body = to_bytes(body, charset='utf-8')

    environ = {
        'CONTENT_LENGTH': str(len(body)),
        'CONTENT_TYPE': headers.get('Content-Type', ''),
        'PATH_INFO': parsed_url.path,
        'QUERY_STRING': unquote(parsed_url.query),
        'REMOTE_ADDR': event.get('x-real-ip', ''),
        'REQUEST_METHOD': event['method'],
        'SCRIPT_NAME': '',
        'SERVER_NAME': headers.get('Host', 'lambda'),
        'SERVER_PORT': headers.get('X-Forwarded-Port', '80'),
        'SERVER_PROTOCOL': 'HTTP/1.1',
        'event': lambda_event['body'],
        'wsgi.errors': sys.stderr,
        'wsgi.input': BytesIO(body),
        'wsgi.multiprocess': False,
        'wsgi.multithread': False,
        'wsgi.run_once': False,
        'wsgi.url_scheme': headers.get('X-Forwarded-Proto', 'http'),
        'wsgi.version': (1, 0),
    }

    for key, value in environ.items():
        if isinstance(value, string_types):
            environ[key] = wsgi_encoding_dance(value)

    for key, value in headers.items():
        key = 'HTTP_' + key.upper().replace('-', '_')
        if key not in ('HTTP_CONTENT_TYPE', 'HTTP_CONTENT_LENGTH'):
            environ[key] = value

    response = Response.from_app(app, environ)

    # Handle multi-value headers
    headers = {}
    for key, value in response.headers:
        if key in headers:
            current_value = headers[key]
            if isinstance(current_value, list):
                headers[key] += [value]
            else:
                headers[key] = [current_value, value]
        else:
            headers[key] = value

    returndict = {
        'statusCode': response.status_code,
        'headers': headers,
        'body': '',
    }

    if response.data:
        mimetype = response.mimetype or 'text/plain'
        if (mimetype.startswith('text/')
                or mimetype in TEXT_MIME_TYPES) and not response.headers.get(
                    'Content-Encoding', ''):
            returndict['body'] = response.get_data(as_text=True)
        else:
            returndict['body'] = base64.b64encode(response.data)\
                                       .decode('utf-8')
            returndict['encoding'] = 'base64'

    return returndict
Пример #10
0
def handler(event, context):
    if event.get("source") in ["aws.events", "serverless-plugin-warmup"]:
        return {}

    headers = Headers(event[u"headers"])

    if u"amazonaws.com" in headers.get(u"Host", u""):
        script_name = "/{}".format(event[u"requestContext"].get(u"stage", ""))
    else:
        script_name = ""

    # If a user is using a custom domain on API Gateway, they may have a base
    # path in their URL. This allows us to strip it out via an optional
    # environment variable.
    path_info = event[u"path"]
    base_path = os.environ.get("API_GATEWAY_BASE_PATH", "")
    if base_path:
        script_name = "/" + base_path

        if path_info.startswith(script_name):
            path_info = path_info[len(script_name):]

    body = event[u"body"] or ""
    if event.get("isBase64Encoded", False):
        body = base64.b64decode(body)
    if isinstance(body, string_types):
        body = to_bytes(body, charset="utf-8")

    environ = {
        "API_GATEWAY_AUTHORIZER":
        event[u"requestContext"].get(u"authorizer"),
        "CONTENT_LENGTH":
        str(len(body)),
        "CONTENT_TYPE":
        headers.get(u"Content-Type", ""),
        "PATH_INFO":
        path_info,
        "QUERY_STRING":
        url_encode(event.get(u"queryStringParameters") or {}),
        "REMOTE_ADDR":
        event[u"requestContext"].get(u"identity", {}).get(u"sourceIp", ""),
        "REMOTE_USER":
        event[u"requestContext"].get(u"authorizer",
                                     {}).get(u"principalId", ""),
        "REQUEST_METHOD":
        event[u"httpMethod"],
        "SCRIPT_NAME":
        script_name,
        "SERVER_NAME":
        headers.get(u"Host", "lambda"),
        "SERVER_PORT":
        headers.get(u"X-Forwarded-Port", "80"),
        "SERVER_PROTOCOL":
        "HTTP/1.1",
        "event":
        event,
        "context":
        context,
        "wsgi.errors":
        sys.stderr,
        "wsgi.input":
        BytesIO(body),
        "wsgi.multiprocess":
        False,
        "wsgi.multithread":
        False,
        "wsgi.run_once":
        False,
        "wsgi.url_scheme":
        headers.get(u"X-Forwarded-Proto", "http"),
        "wsgi.version": (1, 0),
    }

    for key, value in environ.items():
        if isinstance(value, string_types):
            environ[key] = wsgi_encoding_dance(value)

    for key, value in headers.items():
        key = "HTTP_" + key.upper().replace("-", "_")
        if key not in ("HTTP_CONTENT_TYPE", "HTTP_CONTENT_LENGTH"):
            environ[key] = value

    response = Response.from_app(wsgi_app, environ)

    # If there are multiple Set-Cookie headers, create case-mutated variations
    # in order to pass them through APIGW. This is a hack that's currently
    # needed. See: https://github.com/logandk/serverless-wsgi/issues/11
    # Source: https://github.com/Miserlou/Zappa/blob/master/zappa/middleware.py
    new_headers = [x for x in response.headers if x[0] != "Set-Cookie"]
    cookie_headers = [x for x in response.headers if x[0] == "Set-Cookie"]
    if len(cookie_headers) > 1:
        for header, new_name in zip(cookie_headers, all_casings("Set-Cookie")):
            new_headers.append((new_name, header[1]))
    elif len(cookie_headers) == 1:
        new_headers.extend(cookie_headers)

    returndict = {
        u"statusCode": response.status_code,
        u"headers": dict(new_headers)
    }

    if response.data:
        mimetype = response.mimetype or "text/plain"
        if (mimetype.startswith("text/")
                or mimetype in TEXT_MIME_TYPES) and not response.headers.get(
                    "Content-Encoding", ""):
            returndict["body"] = response.get_data(as_text=True)
        else:
            returndict["body"] = base64.b64encode(
                response.data).decode("utf-8")
            returndict["isBase64Encoded"] = "true"

    return returndict
Пример #11
0
def handler(event, context):
    headers = Headers(event[u'headers'])

    if headers.get(u'Host', u'').endswith(u'.amazonaws.com'):
        script_name = '/{}'.format(event[u'requestContext'].get(u'stage', ''))
    else:
        script_name = ''

    environ = {
        'CONTENT_LENGTH':
        headers.get(u'Content-Length', str(len(event[u'body'] or ''))),
        'CONTENT_TYPE':
        headers.get(u'Content-Type', ''),
        'PATH_INFO':
        event[u'path'],
        'QUERY_STRING':
        url_encode(event.get(u'queryStringParameters', None) or {}),
        'REMOTE_ADDR':
        headers.get(u'X-Forwarded-For', '').split(', ')[0],
        'REMOTE_USER':
        event[u'requestContext'].get(u'authorizer',
                                     {}).get(u'principalId', ''),
        'REQUEST_METHOD':
        event[u'httpMethod'],
        'SCRIPT_NAME':
        script_name,
        'SERVER_NAME':
        headers.get(u'Host', 'lambda'),
        'SERVER_PORT':
        headers.get(u'X-Forwarded-Port', '80'),
        'SERVER_PROTOCOL':
        'HTTP/1.1',
        'wsgi.errors':
        StringIO(),
        'wsgi.input':
        StringIO(wsgi_encoding_dance(event[u'body'] or '')),
        'wsgi.multiprocess':
        False,
        'wsgi.multithread':
        False,
        'wsgi.run_once':
        False,
        'wsgi.url_scheme':
        headers.get(u'X-Forwarded-Proto', 'http'),
        'wsgi.version': (1, 0),
    }

    for key, value in environ.items():
        if isinstance(value, basestring):
            environ[key] = wsgi_encoding_dance(value)

    for key, value in headers.items():
        key = 'HTTP_' + key.upper().replace('-', '_')
        if key not in ('HTTP_CONTENT_TYPE', 'HTTP_CONTENT_LENGTH'):
            environ[key] = value

    response = Response.from_app(wsgi_app, environ)

    errors = environ['wsgi.errors'].getvalue()
    if errors:
        print errors

    return {
        u'statusCode': response.status_code,
        u'headers': dict(response.headers),
        u'body': response.data
    }
Пример #12
0
class HeadersParser(object):
    """Parse request headers
    """

    NGINX_ADDED_HEADERS = [
        'x-remote-addr',
        'x-remote-port',
        'x-server-addr',
        'x-host',
        'x-scheme',
        'x-is-secure',
        'x-server-protocol',
        'x-server-port',
        'x-tcp-rtt',
        'x-tcp-rttvar',
        'x-tcp-snd-cwd',
        'x-tcp-rcv-space',
    ]

    def __init__(self, headers_obj):
        """
        headers_obj: request.headers object
        """
        self.headers = Headers(headers_obj)

    def remove_extra_headers(self):
        extra_headers = ['content-type', 'content-length']
        for header in self.headers.items():
            h_name = header[0]
            h_value = header[1]
            if (h_name.lower() in extra_headers and h_value == '')\
                or (h_name.lower in extra_headers and h_value != ''):
                del self.headers[h_name]
        return True

    def extract_nginx_headers_data(self, remove_data=True):
        """method to extract data from nginx added headers
        """
        data = {}

        # if header name in NGINX_ADDED_HEADERS extract data

        for header in self.headers:
            h_name = header[0].lower()
            h_value = header[1]
            if h_name in self.NGINX_ADDED_HEADERS:
                data[h_name] = h_value

        if remove_data:
            self.remove_nginx_headers()

        # if header is missing set it's value to 0
        for header in self.NGINX_ADDED_HEADERS:
            if header not in data:
                data[header] = '0'

        return data

    def remove_nginx_headers(self):
        """ method to delete all Nginx added headers from request headers
        """
        for header in self.headers.items():
            h_name = header[0]
            if h_name.lower() in self.NGINX_ADDED_HEADERS:
                del self.headers[h_name]

        return True

    def headers_to_json(self):
        """Converts list of headers to list of header name: value pairs"""
        return {header[0]: header[1] for header in self.headers}
Пример #13
0
def handler(event, context):
    headers = Headers(event[u'headers'])

    if headers.get(u'Host', u'').endswith(u'.amazonaws.com'):
        script_name = '/{}'.format(event[u'requestContext'].get(u'stage', ''))
    else:
        script_name = ''

    environ = {
        'API_GATEWAY_AUTHORIZER':
            event[u'requestContext'].get(u'authorizer', None),
        'CONTENT_LENGTH':
            headers.get(u'Content-Length', str(len(event[u'body'] or ''))),
        'CONTENT_TYPE':
            headers.get(u'Content-Type', ''),
        'PATH_INFO':
            event[u'path'],
        'QUERY_STRING':
            url_encode(event.get(u'queryStringParameters', None) or {}),
        'REMOTE_ADDR':
            headers.get(u'X-Forwarded-For', '').split(', ')[0],
        'REMOTE_USER':
            event[u'requestContext'].get(u'authorizer', {}).get(
                u'principalId', ''),
        'REQUEST_METHOD':
            event[u'httpMethod'],
        'SCRIPT_NAME':
            script_name,
        'SERVER_NAME':
            headers.get(u'Host', 'lambda'),
        'SERVER_PORT':
            headers.get(u'X-Forwarded-Port', '80'),
        'SERVER_PROTOCOL':
            'HTTP/1.1',
        'context':
            context,
        'wsgi.errors':
            # Per PEP 333, this should be a "text mode" stream.
            StringIO(),
        'wsgi.input':
            BytesIO(),
        'wsgi.multiprocess':
            False,
        'wsgi.multithread':
            False,
        'wsgi.run_once':
            False,
        'wsgi.url_scheme':
            headers.get(u'X-Forwarded-Proto', 'http'),
        'wsgi.version':
            (1, 0),
    }

    body = event[u'body'] or ''
    encoded = wsgi_encoding_dance(body)
    if not PY2:
        encoded = bytes(encoded, 'utf-8', 'replace')

    # XXX: why do we have to supply an encoding here?
    # Doesn't wsgi_encoding_dance already do that?
    environ['wsgi.input'] = BytesIO(encoded)

    for key, value in environ.items():
        if PY2:
            if isinstance(value, basestring):  # noqa: F821
                environ[key] = wsgi_encoding_dance(value)
        else:
            if isinstance(value, str):
                environ[key] = wsgi_encoding_dance(value)

    for key, value in headers.items():
        key = 'HTTP_' + key.upper().replace('-', '_')
        if key not in ('HTTP_CONTENT_TYPE', 'HTTP_CONTENT_LENGTH'):
            environ[key] = value

    response = Response.from_app(wsgi_app, environ)

    errors = environ['wsgi.errors'].getvalue()
    if errors:
        print(errors)

    # If there are multiple Set-Cookie headers, create case-mutated variations
    # in order to pass them through APIGW. This is a hack that's currently
    # needed. See: https://github.com/logandk/serverless-wsgi/issues/11
    # Source: https://github.com/Miserlou/Zappa/blob/master/zappa/middleware.py
    new_headers = [x for x in response.headers if x[0] != 'Set-Cookie']
    cookie_headers = [x for x in response.headers if x[0] == 'Set-Cookie']
    if len(cookie_headers) > 1:
        for header, new_name in zip(cookie_headers, all_casings('Set-Cookie')):
            new_headers.append((new_name, header[1]))
    elif len(cookie_headers) == 1:
        new_headers.extend(cookie_headers)

    returndict = {
        u'statusCode': response.status_code,
        u'headers': dict(new_headers)
    }

    if response.data:
        mimetype = response.mimetype or 'text/plain'
        if mimetype.startswith('text/') or mimetype in TEXT_MIME_TYPES:
            returndict['body'] = response.get_data(as_text=True)
        else:
            returndict['body'] = base64.b64encode(response.data).decode(
                'utf-8')
            returndict["isBase64Encoded"] = "true"

    return returndict