-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathlambda_http.rb
140 lines (117 loc) · 4.32 KB
/
lambda_http.rb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
require 'json'
require 'rack'
require 'base64'
$app ||= Rack::Builder.parse_file("#{__dir__}/config.ru").first
RELATIVE_URL_ROOT = ENV['RAILS_RELATIVE_URL_ROOT']
def handler(event:, context:)
# Retrieve HTTP request parameters conforming to Lambda proxy integration input format 2.0 of AWS API Gateway HTTP API
# https://docs.aws.amazon.com/apigateway/latest/developerguide/http-api-develop-integrations-lambda.html#http-api-develop-integrations-lambda.proxy-format
requestContext = event.fetch('requestContext')
http = requestContext.fetch('http')
httpMethod = http.fetch('method')
protocol = http['protocol'] || 'HTTP/1.1'
path = http.fetch('path')
# In input format 2.0, the path always contains a stage
stage = requestContext['stage'] || '$default'
if stage != '$default'
path.sub!(/\A\/#{stage}/, '')
end
requestTimeEpoch = requestContext['timeEpoch']
rawQueryString = event['rawQueryString']
cookies = event['cookies'] || []
requestHeaders = event.fetch('headers')
host = requestHeaders['x-forwarded-host'] || requestHeaders.fetch('host')
port = requestHeaders.fetch('x-forwarded-port')
scheme = requestHeaders['cloudfront-forwarded-proto'] || requestHeaders.fetch('x-forwarded-proto')
requestBody = event['body'] || ''
if event['isBase64Encoded']
requestBody = Base64.decode64(requestBody)
end
requestBodyContent = StringIO.new(requestBody)
# Set environment for Rack application
# https://github.com/rack/rack/blob/main/SPEC.rdoc
env = {
Rack::REQUEST_METHOD => httpMethod,
Rack::SCRIPT_NAME => RELATIVE_URL_ROOT || '',
Rack::PATH_INFO => path,
Rack::QUERY_STRING => rawQueryString,
Rack::SERVER_NAME => host,
Rack::SERVER_PORT => port,
Rack::SERVER_PROTOCOL => protocol,
Rack::RACK_VERSION => Rack::VERSION,
Rack::RACK_URL_SCHEME => scheme,
Rack::RACK_INPUT => requestBodyContent,
Rack::RACK_ERRORS => $stderr,
# Escape hatch for access to the context and event of Lambda function
'lambda.context' => context,
'lambda.event' => event,
}
# Add request headers to environment based on Rack specification
requestHeaders.each_pair do |key, value|
name = key.upcase.gsub('-', '_')
header = case name
when 'CONTENT_TYPE', 'CONTENT_LENGTH'
name
else
"HTTP_#{name}"
end
env[header] = value
end
env['CONTENT_LENGTH'] ||= requestBodyContent.size.to_s
if cookies.any?
env['HTTP_COOKIE'] = cookies.join('; ')
end
env['HTTP_X_REQUEST_ID'] ||= context.aws_request_id
env['HTTP_X_REQUEST_START'] ||= "t=#{requestTimeEpoch}" if requestTimeEpoch
begin
# Execute Rack application and get response
status, responseHeaders, responseBody = $app.call(env)
# Build response body
responseBodyContent = ''
if responseBody.respond_to?(:each)
responseBody.each do |item|
responseBodyContent << item if item
end
end
if responseBody.respond_to?(:close)
responseBody.close
end
# Generate response conforming to HTTP API output format 2.0
# https://docs.aws.amazon.com/apigateway/latest/developerguide/http-api-develop-integrations-lambda.html#http-api-develop-integrations-lambda.v2
response = {
'statusCode' => status,
'body' => responseBodyContent
}
# If 'Set-Cookie' response header is present, split it and set as 'cookies' response field
responseCookies = []
if setCookie = responseHeaders&.delete('Set-Cookie')
if setCookie.is_a?(::Array)
responseCookies = setCookie
elsif setCookie.is_a?(::String)
responseCookies = setCookie.split("\n")
end
end
if !responseCookies.empty?
response['cookies'] = responseCookies
end
# Set response headers as 'headers' response field
unifiedResponseHeaders = {}
responseHeaders&.each { |key, value|
if value.is_a?(::Array)
unifiedResponseHeaders[key] = value.join(',')
elsif value.is_a?(::String)
unifiedResponseHeaders[key] = value.split("\n").join(',')
end
}
if !unifiedResponseHeaders.empty?
response['headers'] = unifiedResponseHeaders
end
return response
rescue Exception => exception
return {
'statusCode' => 500,
# For debug purpose only. It is not recommended to include error messages in the response body
'body' => exception.message
}
end
end