Top Level Namespace
Constant Summary collapse
- TEXT_MIME_TYPES =
[ 'application/json', 'application/javascript', 'application/xml', 'application/vnd.api+json', 'image/svg+xml' ].freeze
Instance Method Summary collapse
- #all_casings(input_string) ⇒ Object
- #build_environ(event:, context:, headers:, body:) ⇒ Object
- #format_body(body:, headers:, text_mime_types:) ⇒ Object
- #format_grouped_headers(headers:) ⇒ Object
- #format_response(event:, status:, headers:, body:, text_mime_types:) ⇒ Object
- #format_split_headers(headers:) ⇒ Object
- #format_status_description(event:, status:) ⇒ Object
- #handle_request(app:, event:, context:, config: {}) ⇒ Object
- #keepalive_event?(event) ⇒ Boolean
- #parse_body(event) ⇒ Object
- #parse_headers(event) ⇒ Object
- #parse_http_headers(headers) ⇒ Object
- #parse_path_info(event) ⇒ Object
- #parse_query_string(event) ⇒ Object
- #parse_script_name(event, headers) ⇒ Object
- #text_mime_type?(headers:, text_mime_types:) ⇒ Boolean
Instance Method Details
#all_casings(input_string) ⇒ Object
148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 |
# File 'lib/serverless_rack.rb', line 148 def all_casings(input_string) # Permute all casings of a given string. # A pretty algoritm, via @Amber # http://stackoverflow.com/questions/6792803/finding-all-possible-case-permutations-in-python if input_string.empty? yield '' else first = input_string[0] if first.downcase == first.upcase all_casings(input_string[1..-1]) do |sub_casing| yield first + sub_casing end else all_casings(input_string[1..-1]) do |sub_casing| yield first.downcase + sub_casing yield first.upcase + sub_casing end end end end |
#build_environ(event:, context:, headers:, body:) ⇒ Object
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 |
# File 'lib/serverless_rack.rb', line 84 def build_environ(event:, context:, headers:, body:) { 'REQUEST_METHOD' => event['httpMethod'], 'SCRIPT_NAME' => parse_script_name(event, headers), 'PATH_INFO' => parse_path_info(event), 'QUERY_STRING' => parse_query_string(event), 'SERVER_NAME' => headers['Host'] || 'lambda', 'SERVER_PORT' => headers['X-Forwarded-Port'] || '80', 'CONTENT_LENGTH' => body.bytesize.to_s, 'CONTENT_TYPE' => headers['Content-Type'] || '', 'SERVER_PROTOCOL' => 'HTTP/1.1', 'REMOTE_ADDR' => (event['requestContext']['identity'] || {})['sourceIp'] || '', 'REMOTE_USER' => (event['requestContext']['authorizer'] || {})['principalId'] || '', 'rack.version' => Rack::VERSION, 'rack.url_scheme' => headers['X-Forwarded-Proto'] || 'http', 'rack.input' => StringIO.new(body), 'rack.errors' => $stderr, 'rack.multithread' => false, 'rack.multiprocess' => false, 'rack.run_once' => false, 'serverless.event' => event, 'serverless.context' => context, 'serverless.authorizer' => event['requestContext']['authorizer'] }.merge(parse_http_headers(headers)) end |
#format_body(body:, headers:, text_mime_types:) ⇒ Object
129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 |
# File 'lib/serverless_rack.rb', line 129 def format_body(body:, headers:, text_mime_types:) response_data = '' body.each { |part| response_data += part } return {} if response_data.empty? if text_mime_type?(headers: headers, text_mime_types: text_mime_types) { 'body' => response_data, 'isBase64Encoded' => false } else { 'body' => Base64.strict_encode64(response_data), 'isBase64Encoded' => true } end end |
#format_grouped_headers(headers:) ⇒ Object
192 193 194 195 196 |
# File 'lib/serverless_rack.rb', line 192 def format_grouped_headers(headers:) { 'multiValueHeaders' => headers.transform_values do |value| value.split("\n") end } end |
#format_response(event:, status:, headers:, body:, text_mime_types:) ⇒ Object
198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 |
# File 'lib/serverless_rack.rb', line 198 def format_response(event:, status:, headers:, body:, text_mime_types:) response = { 'statusCode' => status } if event.include? 'multiValueHeaders' response.merge!(format_grouped_headers(headers: headers)) else response.merge!(format_split_headers(headers: headers)) end response.merge!( format_status_description(event: event, status: status) ) response.merge!( format_body( body: body, headers: headers, text_mime_types: text_mime_types ) ) response end |
#format_split_headers(headers:) ⇒ Object
169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 |
# File 'lib/serverless_rack.rb', line 169 def format_split_headers(headers:) headers = headers.to_hash keys = headers.keys # If there are headers multiple occurrences, e.g. Set-Cookie, create # case-mutated variations in order to pass them through APIGW. # This is a hack that's currently needed. keys.each do |key| values = headers[key].split("\n") next if values.size < 2 headers.delete(key) all_casings(key) do |casing| headers[casing] = values.shift break if values.empty? end end { 'headers' => headers } end |
#format_status_description(event:, status:) ⇒ Object
110 111 112 113 114 115 116 117 |
# File 'lib/serverless_rack.rb', line 110 def format_status_description(event:, status:) return {} unless event['requestContext']['elb'] # If the request comes from ALB we need to add a status description description = Rack::Utils::HTTP_STATUS_CODES[status] { 'statusDescription' => "#{status} #{description}" } end |
#handle_request(app:, event:, context:, config: {}) ⇒ Object
222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 |
# File 'lib/serverless_rack.rb', line 222 def handle_request(app:, event:, context:, config: {}) return {} if keepalive_event?(event) status, headers, body = app.call( build_environ( event: event, context: context, headers: parse_headers(event), body: parse_body(event) ) ) format_response( event: event, status: status, headers: headers, body: body, text_mime_types: TEXT_MIME_TYPES + config['text_mime_types'].to_a ) end |
#keepalive_event?(event) ⇒ Boolean
18 19 20 |
# File 'lib/serverless_rack.rb', line 18 def keepalive_event?(event) ['aws.events', 'serverless-plugin-warmup'].include?(event['source']) end |
#parse_body(event) ⇒ Object
52 53 54 55 56 57 58 |
# File 'lib/serverless_rack.rb', line 52 def parse_body(event) if event['isBase64Encoded'] Base64.decode64(event['body']) else event['body'] || '' end end |
#parse_headers(event) ⇒ Object
60 61 62 63 64 65 66 67 68 69 70 |
# File 'lib/serverless_rack.rb', line 60 def parse_headers(event) if event.include? 'multiValueHeaders' Rack::Utils::HeaderHash.new( (event['multiValueHeaders'] || {}).transform_values do |value| value.join("\n") end ) else Rack::Utils::HeaderHash.new(event['headers'] || {}) end end |
#parse_http_headers(headers) ⇒ Object
72 73 74 75 76 77 78 79 80 81 82 |
# File 'lib/serverless_rack.rb', line 72 def parse_http_headers(headers) headers = headers.map do |key, value| ["HTTP_#{key.upcase.tr('-', '_')}", value] end headers = headers.reject do |key, _value| %w[HTTP_CONTENT_TYPE HTTP_CONTENT_LENGTH].include?(key) end headers.to_h end |
#parse_path_info(event) ⇒ Object
32 33 34 35 36 37 38 39 40 41 42 |
# File 'lib/serverless_rack.rb', line 32 def parse_path_info(event) # 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. if ENV['API_GATEWAY_BASE_PATH'] base_path = "/#{ENV['API_GATEWAY_BASE_PATH']}" return event['path'][base_path.length..-1] if event['path'].start_with?(base_path) end event['path'] end |
#parse_query_string(event) ⇒ Object
44 45 46 47 48 49 50 |
# File 'lib/serverless_rack.rb', line 44 def parse_query_string(event) if event.include? 'multiValueQueryStringParameters' Rack::Utils.build_query(event['multiValueQueryStringParameters'] || {}) else Rack::Utils.build_query(event['queryStringParameters'] || {}) end end |
#parse_script_name(event, headers) ⇒ Object
22 23 24 25 26 27 28 29 30 |
# File 'lib/serverless_rack.rb', line 22 def parse_script_name(event, headers) if ENV['API_GATEWAY_BASE_PATH'] "/#{ENV['API_GATEWAY_BASE_PATH']}" elsif (headers['Host'] || '').include?('amazonaws.com') "/#{event['requestContext']['stage']}" else '' end end |
#text_mime_type?(headers:, text_mime_types:) ⇒ Boolean
119 120 121 122 123 124 125 126 127 |
# File 'lib/serverless_rack.rb', line 119 def text_mime_type?(headers:, text_mime_types:) mime_type = headers['Content-Type'] || 'text/plain' return false if headers['Content-Encoding'] return true if mime_type.start_with?('text/') return true if text_mime_types.include?(mime_type) false end |