Skip to content

Commit 3c6b4cf

Browse files
authored
Merge pull request #3 from Moesif/fix-url-and-cleanup
Fix: URL parsing. Lot's of cleanup
2 parents 38fd15d + 238e52d commit 3c6b4cf

File tree

4 files changed

+81
-150
lines changed

4 files changed

+81
-150
lines changed

lambda_function.py

-31
Original file line numberDiff line numberDiff line change
@@ -1,40 +1,9 @@
11
from moesif_aws_lambda.middleware import MoesifLogger
22
import os
33

4-
# Moesif Application Id
5-
os.environ["MOESIF_APPLICATION_ID"] = "Your Moesif Application Id"
6-
7-
def identify_user(event, context):
8-
return 'my_user_id'
9-
10-
def identify_company(event, context):
11-
return 'my_company_id'
12-
13-
def get_api_version(event, context):
14-
return '1.0.0'
15-
16-
def get_session_token(event, context):
17-
return '23jdf0owekfmcn4u3qypxg09w4d8ayrcdx8nu2ng]s98y18cx98q3yhwmnhcfx43f'
18-
19-
def get_metadata(event, context):
20-
return { 'foo' : 'aws lambda', 'bar' : 'aws lambda metadata', }
21-
22-
def mask_event(eventmodel):
23-
return eventmodel
24-
25-
def should_skip(event, context):
26-
return "/" in event['path']
27-
284
moesif_options = {
29-
'GET_METADATA': get_metadata,
30-
'IDENTIFY_USER': identify_user,
31-
'IDENTIFY_COMPANY': identify_company,
32-
'GET_SESSION_TOKEN': get_session_token,
33-
'GET_API_VERSION': get_api_version,
34-
'MASK_EVENT_MODEL': mask_event,
355
'LOG_BODY': True,
366
'DEBUG': True,
37-
'SKIP': should_skip,
387
}
398

409
@MoesifLogger(moesif_options)

moesif_aws_lambda/middleware.py

+76-116
Original file line numberDiff line numberDiff line change
@@ -6,10 +6,15 @@
66
from moesifapi.models import *
77
from .client_ip import ClientIp
88
from datetime import *
9-
from urllib.parse import urlencode
109
import base64
1110
import json
1211
import os
12+
from pprint import pprint
13+
import base64
14+
try:
15+
from urllib import urlencode
16+
except ImportError:
17+
from urllib.parse import urlencode
1318

1419
def MoesifLogger(moesif_options):
1520
class log_data(LambdaDecorator):
@@ -26,8 +31,10 @@ def __init__(self, handler):
2631
self.DEBUG = self.moesif_options.get('DEBUG', False)
2732
self.event = None
2833
self.context = None
34+
self.start_time = datetime.utcnow()
35+
2936
# Intialized the client
30-
if os.environ["MOESIF_APPLICATION_ID"]:
37+
if os.environ.get("MOESIF_APPLICATION_ID"):
3138
self.api_client = MoesifAPIClient(os.environ["MOESIF_APPLICATION_ID"]).api
3239
else:
3340
raise Exception('Moesif Application ID is required in settings')
@@ -47,10 +54,10 @@ def get_user_id(self, event, context):
4754
username = rc_identity_id
4855
except:
4956
if self.DEBUG:
50-
print("can not fetch apiKey from cognitoIdentityId event, setting userId to None.")
57+
print("MOESIF can not fetch apiKey from cognitoIdentityId event, setting userId to None.")
5158
except Exception as e:
5259
if self.DEBUG:
53-
print("can not execute identify_user function, please check moesif settings.")
60+
print("MOESIF can not execute identify_user function, please check moesif settings.")
5461
print(e)
5562
return username
5663

@@ -63,89 +70,70 @@ def get_company_id(self, event, context):
6370
company_id = identify_company(event, context)
6471
except Exception as e:
6572
if self.DEBUG:
66-
print("can not execute identify_company function, please check moesif settings.")
73+
print("MOESIF can not execute identify_company function, please check moesif settings.")
6774
print(e)
6875
return company_id
6976

70-
def process_request_body(self, raw_request_body):
71-
"""Function to process Request body"""
72-
req_body = None
73-
req_transfer_encoding = None
74-
try:
75-
if 'isBase64Encoded' in raw_request_body and 'body' in raw_request_body and raw_request_body['isBase64Encoded'] and raw_request_body['body']:
76-
req_body = raw_request_body['body']
77-
req_transfer_encoding = 'base64'
78-
else:
79-
if 'body' in raw_request_body and raw_request_body['body'] and isinstance(raw_request_body['body'], str):
80-
req_body = json.loads(raw_request_body['body'])
81-
else:
82-
req_body = raw_request_body['body']
83-
req_transfer_encoding = 'json'
84-
except Exception as e:
85-
if self.DEBUG:
86-
print('Error while parsing request body')
87-
print(e)
88-
return req_body, req_transfer_encoding
77+
def build_uri(self, event):
78+
79+
uri = event['headers'].get('X-Forwarded-Proto', event['headers'].get('x-forwarded-proto', 'http://')) + event['headers'].get('Host', event['headers'].get('host', 'localhost')) + event.get('path', '/')
80+
81+
if event.get('multiValueQueryStringParameters', {}):
82+
uri = uri + '?' + urlencode(event['multiValueQueryStringParameters'], doseq=True)
83+
elif event.get('queryStringParameters', {}):
84+
uri = uri + '?' + urlencode(event['queryStringParameters'])
85+
return uri
8986

90-
def process_response_body(self, raw_response_body):
91-
"""Function to process Response body"""
92-
resp_body = None
93-
resp_transfer_encoding = None
87+
def process_body(self, body_wrapper):
88+
"""Function to process body"""
89+
if not (self.LOG_BODY and body_wrapper.get('body')):
90+
return None, 'json'
91+
92+
body = None
93+
transfer_encoding = None
9494
try:
95-
if 'isBase64Encoded' in raw_response_body and 'body' in raw_response_body and raw_response_body['isBase64Encoded'] and raw_response_body['body']:
96-
resp_body = raw_response_body['body']
97-
resp_transfer_encoding = 'base64'
95+
if body_wrapper.get('isBase64Encoded', False):
96+
body = body_wrapper.get('body')
97+
transfer_encoding = 'base64'
9898
else:
99-
if 'body' in raw_response_body and raw_response_body['body'] and isinstance(raw_response_body['body'], str):
100-
resp_body = json.loads(raw_response_body['body'])
99+
if isinstance(body_wrapper['body'], str):
100+
body = json.loads(body_wrapper.get('body'))
101101
else:
102-
resp_body = raw_response_body['body']
103-
resp_transfer_encoding = 'json'
102+
body = body_wrapper.get('body')
103+
transfer_encoding = 'json'
104104
except Exception as e:
105-
if self.DEBUG:
106-
print('Error while parsing response body')
107-
print(e)
108-
return resp_body, resp_transfer_encoding
105+
body = base64.b64encode(body_wrapper['body'].encode("utf-8"))
106+
return str(encodedBytes, "utf-8"), 'json'
107+
return body, transfer_encoding
109108

110109
def before(self, event, context):
111110
"""This function runs before the handler is invoked, is passed the event & context and must return an event & context too."""
111+
112+
# Request Method
113+
request_verb = event.get('httpMethod')
114+
if request_verb is None:
115+
print('MOESIF: [before] AWS Lambda trigger must be a Load Balancer or API Gateway See https://docs.aws.amazon.com/lambda/latest/dg/services-alb.html or https://docs.aws.amazon.com/lambda/latest/dg/with-on-demand-https.html.')
116+
return event, context
117+
112118
# Request headers
113119
req_headers = {}
114120
try:
115121
if 'headers' in event:
116122
req_headers = APIHelper.json_deserialize(event['headers'])
117123
except Exception as e:
118124
if self.DEBUG:
119-
print('Error while fetching request headers')
125+
print('MOESIF Error while fetching request headers')
120126
print(e)
121127

122128
# Request Time
123-
request_time = datetime.utcnow().strftime("%Y-%m-%dT%H:%M:%S.%f")[:-3]
124-
125-
# Request URI
126-
try:
127-
request_uri = event['headers']['X-Forwarded-Proto'] + '://' + event['headers']['Host'] + event['path'] + '?' + urlencode(event['queryStringParameters'])
128-
except:
129-
request_uri = '/'
130-
131-
# Request Method
132-
try:
133-
request_verb = event['httpMethod']
134-
except:
135-
if self.DEBUG:
136-
print('Request method should not be empty.')
129+
epoch = event and event.get('request_context', {}).get('requestTimeEpoch')
130+
if epoch is not None:
131+
request_time =datetime.utcfromtimestamp(epoch)
132+
else:
133+
request_time = self.start_time
137134

138135
# Request Body
139-
req_body = None
140-
req_transfer_encoding = None
141-
try:
142-
if self.LOG_BODY and 'body' in event:
143-
if event['body']:
144-
req_body, req_transfer_encoding = self.process_request_body(event)
145-
except Exception as e:
146-
if self.DEBUG:
147-
print('Error while fetching request body')
148-
print(e)
136+
req_body, req_transfer_encoding = self.process_body(event)
149137

150138
# Metadata
151139
try:
@@ -158,14 +146,15 @@ def before(self, event, context):
158146
self.metadata = {
159147
'trace_id': str(context.aws_request_id),
160148
'function_name': context.function_name,
161-
'request_context': event['requestContext']
149+
'request_context': event['requestContext'],
150+
'context': context
162151
}
163152
except:
164153
if self.DEBUG:
165-
print("can not fetch default function_name and request_context from aws context, setting metadata to None.")
154+
print("MOESIF can not fetch default function_name and request_context from aws context, setting metadata to None.")
166155
except Exception as e:
167156
if self.DEBUG:
168-
print("can not execute GET_METADATA function, please check moesif settings.")
157+
print("MOESIF can not execute GET_METADATA function, please check moesif settings.")
169158
print(e)
170159

171160
# User Id
@@ -187,10 +176,10 @@ def before(self, event, context):
187176
self.session_token = rc_api_key
188177
except KeyError:
189178
if self.DEBUG:
190-
print("can not fetch apiKey from aws event, setting session_token to None.")
179+
print("MOESIF can not fetch apiKey from aws event, setting session_token to None.")
191180
except Exception as e:
192181
if self.DEBUG:
193-
print("can not execute GET_SESSION_TOKEN function, please check moesif settings.")
182+
print("MOESIF can not execute GET_SESSION_TOKEN function, please check moesif settings.")
194183
print(e)
195184

196185
# Api Version
@@ -205,74 +194,41 @@ def before(self, event, context):
205194
api_version = context.function_version
206195
except KeyError:
207196
if self.DEBUG:
208-
print("can not fetch default function_version from aws context, setting api_version to None.")
197+
print("MOESIF can not fetch default function_version from aws context, setting api_version to None.")
209198
except Exception as e:
210199
if self.DEBUG:
211-
print("can not execute GET_API_VERSION function, please check moesif settings.")
200+
print("MOESIF can not execute GET_API_VERSION function, please check moesif settings.")
212201
print(e)
213202

214203
# IpAddress
215-
ip_address = None
216-
try:
217-
if 'headers' in event and 'requestContext' in event and 'identity' in event['requestContext'] and 'sourceIp' in event['requestContext']['identity'] and event['requestContext']['identity']['sourceIp']:
218-
ip_address = self.client_ip.get_client_address(event['headers'], event['requestContext']['identity']['sourceIp'])
219-
except Exception as e:
220-
if self.DEBUG:
221-
print("Error while fetching Client Ip address, setting Request Ip address to None.")
222-
print(e)
204+
ip_address = event.get('requestContext', {}).get('identity', {}).get('sourceIp', None)
223205

224206
# Event Request Object
225-
self.event_req = EventRequestModel(time = request_time,
226-
uri = request_uri,
207+
self.event_req = EventRequestModel(time = request_time.strftime("%Y-%m-%dT%H:%M:%S.%f")[:-3],
208+
uri = self.build_uri(event),
227209
verb = request_verb,
228210
api_version = api_version,
229-
ip_address = ip_address,
211+
ip_address = self.client_ip.get_client_address(event['headers'], ip_address),
230212
headers = req_headers,
231213
body = req_body,
232214
transfer_encoding = req_transfer_encoding)
233215

234216
# Set/Save event and context for use Skip Event function
235217
self.event = event
236218
self.context = context
237-
219+
238220
# Return event, context
239221
return event, context
240222

241223
def after(self, retval):
242224
"""This function runs after the handler is invoked, is passed the response and must return an response too."""
243225
# Response body
244-
resp_body = None
245-
resp_transfer_encoding = None
246-
try:
247-
if self.LOG_BODY and 'body' in retval:
248-
if retval['body']:
249-
resp_body, resp_transfer_encoding = self.process_response_body(retval)
250-
except Exception as e:
251-
if self.DEBUG:
252-
print('Error while fetching response body')
253-
print(e)
254-
255-
# Response headers
256-
resp_headers = {}
257-
try:
258-
if 'headers' in retval:
259-
resp_headers = retval['headers']
260-
except Exception as e:
261-
if self.DEBUG:
262-
print('Error while fetching response headers')
263-
print(e)
264-
265-
# Response status code
266-
try:
267-
status_code = retval['statusCode']
268-
except:
269-
if self.DEBUG:
270-
print('Response status code should not be empty')
226+
resp_body, resp_transfer_encoding = self.process_body(retval)
271227

272228
# Event Response object
273229
event_rsp = EventResponseModel(time = datetime.utcnow().strftime("%Y-%m-%dT%H:%M:%S.%f")[:-3],
274-
status = status_code,
275-
headers = resp_headers,
230+
status = retval.get('statusCode', 599),
231+
headers = retval.get('headers', {}),
276232
body = resp_body,
277233
transfer_encoding = resp_transfer_encoding)
278234

@@ -291,24 +247,28 @@ def after(self, retval):
291247
event_model = mask_event_model(event_model)
292248
except:
293249
if self.DEBUG:
294-
print("Can not execute MASK_EVENT_MODEL function. Please check moesif settings.")
250+
print("MOESIF Can not execute MASK_EVENT_MODEL function. Please check moesif settings.")
295251

296252
# Skip Event
297253
try:
298254
skip_event = self.moesif_options.get('SKIP', None)
299255
if skip_event is not None:
300256
if skip_event(self.event, self.context):
301257
if self.DEBUG:
302-
print('Skip sending event to Moesif')
258+
print('MOESIF Skip sending event to Moesif')
303259
return retval
304260
except:
305261
if self.DEBUG:
306-
print("Having difficulty executing skip_event function. Please check moesif settings.")
262+
print("MOESIF Having difficulty executing skip_event function. Please check moesif settings.")
307263

308264
# Send event to Moesif
265+
if self.DEBUG:
266+
print('Moesif Event Model:')
267+
pprint(self.event)
268+
309269
event_send = self.api_client.create_event(event_model)
310270
if self.DEBUG:
311-
print('Event Sent successfully')
271+
print('MOESIF ' + str(event_send))
312272

313273
# Send response
314274
return retval

package.sh

+4-2
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
#!/usr/bin/env bash
2-
pip3 install --target ./package moesif_aws_lambda
2+
rm -rf package build dist
3+
pip3 uninstall moesif_aws_lambda
4+
pip3 install --target ./package -r requirements.txt
35
cd package
46
zip -r9 ${OLDPWD}/function.zip .
57
cd $OLDPWD
6-
zip -g function.zip lambda_function.py
8+
zip -g function.zip lambda_function.py moesif_aws_lambda/*

setup.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@
2828
# Versions should comply with PEP440. For a discussion on single-sourcing
2929
# the version across setup.py and the project code, see
3030
# https://packaging.python.org/en/latest/single_source_version.html
31-
version='1.0.2',
31+
version='1.0.3',
3232

3333
description='Moesif Middleware to automatically log API calls from AWS Lambda functions',
3434
long_description=long_description,

0 commit comments

Comments
 (0)