Skip to content
Permalink
Browse files

First working version using proxy+. Works nicely, status codes are fi…

…xed woot - TODO - test binary uploads and responses :[
  • Loading branch information
Miserlou committed Sep 29, 2016
1 parent b9ce293 commit c8addf5d36c395a1f06badc1153c4c0359f70147
Showing with 126 additions and 120 deletions.
  1. +37 −32 zappa/handler.py
  2. +10 −10 zappa/middleware.py
  3. +32 −25 zappa/wsgi.py
  4. +47 −53 zappa/zappa.py
@@ -317,7 +317,7 @@ def handler(self, event, context):
elif event.get('raw_command', None):

raw_command = event['raw_command']
exec(raw_command)
exec(raw_command)
return

# This is a Django management command invocation.
@@ -360,13 +360,13 @@ def handler(self, event, context):
time_start = datetime.datetime.now()

# This is a normal HTTP request
if event.get('method', None):
if event.get('httpMethod', None):
# If we just want to inspect this,
# return this event instead of processing the request
# https://your_api.aws-api.com/?event_echo=true
event_echo = getattr(settings, "EVENT_ECHO", True)
if event_echo and 'event_echo' in event['params'].values():
return {'Content': str(event) + '\n' + str(context), 'Status': 200}
# event_echo = getattr(settings, "EVENT_ECHO", True)
# if event_echo and 'event_echo' in event['params'].values():
# return {'Content': str(event) + '\n' + str(context), 'Status': 200}

if settings.DOMAIN:
# If we're on a domain, we operate normally
@@ -395,36 +395,39 @@ def handler(self, event, context):

# This is the object we're going to return.
# Pack the WSGI response into our special dictionary.
zappa_returndict = dict(response.headers)
zappa_returndict = dict()

if 'Content' not in zappa_returndict and response.data:
zappa_returndict['Content'] = response.data
if response.data:
zappa_returndict['body'] = response.data

zappa_returndict['Status'] = response.status_code
zappa_returndict['statusCode'] = response.status_code
zappa_returndict['headers'] = {}
for key, value in response.headers:
zappa_returndict['headers'][key] = value

# To ensure correct status codes, we need to
# pack the response as a deterministic B64 string and raise it
# as an error to match our APIGW regex.
# The DOCTYPE ensures that the page still renders in the browser.
exception = None
if response.status_code in ERROR_CODES:
content = collections.OrderedDict()
content['http_status'] = response.status_code
content['content'] = base64.b64encode(response.data.encode('utf-8'))
exception = json.dumps(content)
# Internal are changed to become relative redirects
# so they still work for apps on raw APIGW and on a domain.
elif 300 <= response.status_code < 400 and hasattr(response, 'Location'):
# Location is by default relative on Flask. Location is by default
# absolute on Werkzeug. We can set autocorrect_location_header on
# the response to False, but it doesn't work. We have to manually
# remove the host part.
location = response.location
hostname = 'https://' + environ['HTTP_HOST']
if location.startswith(hostname):
exception = location[len(hostname):]
else:
exception = location
# if response.status_code in ERROR_CODES:
# content = collections.OrderedDict()
# content['http_status'] = response.status_code
# content['content'] = base64.b64encode(response.data.encode('utf-8'))
# exception = json.dumps(content)
# # Internal are changed to become relative redirects
# # so they still work for apps on raw APIGW and on a domain.
# elif 300 <= response.status_code < 400 and hasattr(response, 'Location'):
# # Location is by default relative on Flask. Location is by default
# # absolute on Werkzeug. We can set autocorrect_location_header on
# # the response to False, but it doesn't work. We have to manually
# # remove the host part.
# location = response.location
# hostname = 'https://' + environ['HTTP_HOST']
# if location.startswith(hostname):
# exception = location[len(hostname):]
# else:
# exception = location

# Calculate the total response time,
# and log it in the Common Log format.
@@ -434,11 +437,13 @@ def handler(self, event, context):
response.content = response.data
common_log(environ, response, response_time=response_time_ms)

# Finally, return the response to API Gateway.
if exception: # pragma: no cover
raise WSGIException(exception)
else:
return zappa_returndict
# # Finally, return the response to API Gateway.
# if exception: # pragma: no cover
# raise WSGIException(exception)
# else:
#

return zappa_returndict
except WSGIException as e: # pragma: no cover
raise e
except Exception as e: # pragma: no cover
@@ -134,16 +134,16 @@ def encode_response(self, status, headers, exc_info=None):

# If setting cookie on a 301/2,
# return 200 and replace the content with a javascript redirector
content_type_header_key = [k for k, v in enumerate(new_headers) if v[0] == 'Content-Type']
if len(content_type_header_key) > 0:
if "text/html" in new_headers[content_type_header_key[0]][1]:
if status != '200 OK':
for key, value in new_headers:
if key != 'Location':
continue
self.redirect_content = REDIRECT_HTML.replace('REDIRECT_ME', value)
status = '200 OK'
break
# content_type_header_key = [k for k, v in enumerate(new_headers) if v[0] == 'Content-Type']
# if len(content_type_header_key) > 0:
# if "text/html" in new_headers[content_type_header_key[0]][1]:
# if status != '200 OK':
# for key, value in new_headers:
# if key != 'Location':
# continue
# self.redirect_content = REDIRECT_HTML.replace('REDIRECT_ME', value)
# status = '200 OK'
# break

return self.start_response(status, new_headers, exc_info)

@@ -13,9 +13,11 @@ def create_wsgi_request(event_info, server_name='zappa', script_name=None,
create and return a valid WSGI request environ.
"""

method = event_info['method']
params = event_info['params']
query = event_info['query']
print("MAKING WSGI REQUEST!!!")

method = event_info['httpMethod']
params = event_info['pathParameters']
query = event_info['queryStringParameters']
headers = event_info['headers']

# Non-GET data is B64'd through the APIGW.
@@ -31,26 +33,28 @@ def create_wsgi_request(event_info, server_name='zappa', script_name=None,
if canonical != header:
headers[canonical] = headers.pop(header)

if 'url' in params:
# new style
path = '/' + params.get('url') + "/"
else:
# old style
path = "/"
for key in sorted(params.keys()):
path = path + params[key] + "/"

# This determines if we should return
# site.com/resource/ : site.com/resource
# site.com/resource : site.com/resource
# vs.
# site.com/resource/ : site.com/resource/
# site.com/resource : site.com/resource/
# If no params are present, keep the slash.
if not trailing_slash and params.keys():
path = path[:-1]

query_string = urlencode(query)
path = event_info['path']

# if 'url' in params:
# # new style
# path = '/' + params.get('url') + "/"
# else:
# # old style
# path = "/"
# for key in sorted(params.keys()):
# path = path + params[key] + "/"

# # This determines if we should return
# # site.com/resource/ : site.com/resource
# # site.com/resource : site.com/resource
# # vs.
# # site.com/resource/ : site.com/resource/
# # site.com/resource : site.com/resource/
# # If no params are present, keep the slash.
# if not trailing_slash and params.keys():
# path = path[:-1]

query_string = query #$urlencode(query)

x_forwarded_for = headers.get('X-Forwarded-For', '')
if ',' in x_forwarded_for:
@@ -87,8 +91,8 @@ def create_wsgi_request(event_info, server_name='zappa', script_name=None,
# if 'multipart/form-data;' in headers['Content-Type']:
#
# # Unfortunately, this only works for text data,
# # not binary data. Yet.
# # See:
# # not binary data. Yet.
# # See:
# # https://github.com/Miserlou/Zappa/issues/80
# # https://forums.aws.amazon.com/thread.jspa?threadID=231371&tstart=0
# body = base64.b64decode(body)
@@ -107,6 +111,9 @@ def create_wsgi_request(event_info, server_name='zappa', script_name=None,
if script_name in path_info:
environ['PATH_INFO'].replace(script_name, '')

print("ENVIRON!!!")
print environ

return environ


@@ -200,13 +200,7 @@ class Zappa(object):
##

http_methods = [
'DELETE',
'GET',
'HEAD',
'OPTIONS',
'PATCH',
'POST',
'PUT'
'ANY'
]
parameter_depth = 8
integration_response_codes = [200, 201, 301, 400, 401, 403, 404, 500]
@@ -699,7 +693,7 @@ def create_api_gateway_routes(self, lambda_arn, api_name=None, api_key_required=
self.cf_api_resources.append(resource.title)
resource.RestApiId = troposphere.Ref(restapi)
resource.ParentId = root_id
resource.PathPart = "{url+}"
resource.PathPart = "{proxy+}"
self.cf_template.add_resource(resource)

self.create_and_setup_methods(restapi, resource, lambda_arn, api_key_required,
@@ -726,16 +720,16 @@ def create_and_setup_methods(self, restapi, resource, lambda_arn, api_key_requir
self.cf_template.add_resource(method)
self.cf_api_resources.append(method.title)

content_mapping_templates = {
'application/json': self.cache_param(POST_TEMPLATE_MAPPING),
'application/x-www-form-urlencoded': self.cache_param(POST_TEMPLATE_MAPPING),
'multipart/form-data': self.cache_param(FORM_ENCODED_TEMPLATE_MAPPING)
}
if integration_content_type_aliases:
for content_type in content_mapping_templates.keys():
aliases = integration_content_type_aliases.get(content_type, [])
for alias in aliases:
content_mapping_templates[alias] = self.cache_param(content_mapping_templates[content_type])
# content_mapping_templates = {
# 'application/json': self.cache_param(POST_TEMPLATE_MAPPING),
# 'application/x-www-form-urlencoded': self.cache_param(POST_TEMPLATE_MAPPING),
# 'multipart/form-data': self.cache_param(FORM_ENCODED_TEMPLATE_MAPPING)
# }
# if integration_content_type_aliases:
# for content_type in content_mapping_templates.keys():
# aliases = integration_content_type_aliases.get(content_type, [])
# for alias in aliases:
# content_mapping_templates[alias] = self.cache_param(content_mapping_templates[content_type])

if not self.credentials_arn:
self.get_credentials_arn()
@@ -751,55 +745,55 @@ def create_and_setup_methods(self, restapi, resource, lambda_arn, api_key_requir
integration.IntegrationResponses = []
integration.PassthroughBehavior = 'NEVER'
# integration.RequestParameters = {}
integration.RequestTemplates = content_mapping_templates
integration.Type = 'AWS'
# integration.RequestTemplates = content_mapping_templates
integration.Type = 'AWS_PROXY'
integration.Uri = uri
method.Integration = integration

##
# Method Response
##

for response_code in self.method_response_codes:
status_code = str(response_code)
# for response_code in self.method_response_codes:
# status_code = str(response_code)

response_parameters = {"method.response.header." + header_type: False for header_type in self.method_header_types}
response_models = {content_type: 'Empty' for content_type in self.method_content_types}
# response_parameters = {"method.response.header." + header_type: False for header_type in self.method_header_types}
# response_models = {content_type: 'Empty' for content_type in self.method_content_types}

response = troposphere.apigateway.MethodResponse()
response.ResponseModels = response_models
response.ResponseParameters = response_parameters
response.StatusCode = status_code
method.MethodResponses.append(response)
# response = troposphere.apigateway.MethodResponse()
# response.ResponseModels = response_models
# response.ResponseParameters = response_parameters
# response.StatusCode = status_code
# method.MethodResponses.append(response)

##
# Integration Response
##

for response in self.integration_response_codes:
status_code = str(response)

response_parameters = {
"method.response.header." + header_type: self.cache_param("integration.response.body." + header_type)
for header_type in self.method_header_types}

# Error code matching RegEx
# Thanks to @KevinHornschemeier and @jayway
# for the discussion on this.
if status_code == '200':
response_templates = {content_type: self.cache_param(RESPONSE_TEMPLATE) for content_type in self.integration_content_types}
elif status_code in ['301', '302']:
response_templates = {content_type: REDIRECT_RESPONSE_TEMPLATE for content_type in self.integration_content_types}
response_parameters["method.response.header.Location"] = self.cache_param("integration.response.body.errorMessage")
else:
response_templates = {content_type: self.cache_param(ERROR_RESPONSE_TEMPLATE) for content_type in self.integration_content_types}

integration_response = troposphere.apigateway.IntegrationResponse()
integration_response.ResponseParameters = response_parameters
integration_response.ResponseTemplates = response_templates
integration_response.SelectionPattern = self.selection_pattern(status_code)
integration_response.StatusCode = status_code
integration.IntegrationResponses.append(integration_response)
# for response in self.integration_response_codes:
# status_code = str(response)

# response_parameters = {
# "method.response.header." + header_type: self.cache_param("integration.response.body." + header_type)
# for header_type in self.method_header_types}

# # Error code matching RegEx
# # Thanks to @KevinHornschemeier and @jayway
# # for the discussion on this.
# if status_code == '200':
# response_templates = {content_type: self.cache_param(RESPONSE_TEMPLATE) for content_type in self.integration_content_types}
# elif status_code in ['301', '302']:
# response_templates = {content_type: REDIRECT_RESPONSE_TEMPLATE for content_type in self.integration_content_types}
# response_parameters["method.response.header.Location"] = self.cache_param("integration.response.body.errorMessage")
# else:
# response_templates = {content_type: self.cache_param(ERROR_RESPONSE_TEMPLATE) for content_type in self.integration_content_types}

# integration_response = troposphere.apigateway.IntegrationResponse()
# integration_response.ResponseParameters = response_parameters
# integration_response.ResponseTemplates = response_templates
# integration_response.SelectionPattern = self.selection_pattern(status_code)
# integration_response.StatusCode = status_code
# integration.IntegrationResponses.append(integration_response)

def deploy_api_gateway(self, api_id, stage_name, stage_description="", description="", cache_cluster_enabled=False, cache_cluster_size='0.5', variables=None,
cloudwatch_log_level='OFF', cloudwatch_data_trace=False, cloudwatch_metrics_enabled=False):

0 comments on commit c8addf5

Please sign in to comment.
You can’t perform that action at this time.