Class: Jets::Server::LambdaAwsProxy

Inherits:
Object
  • Object
show all
Defined in:
lib/jets/server/lambda_aws_proxy.rb

Overview

This doesnt really need to be middleware

Constant Summary collapse

CASING_MAP =

Annoying. The headers part of the AWS Lambda proxy structure does not consisently use the same casing scheme for the header keys. Sometimes it looks like this:

Accept-Encoding

and sometimes it looks like this:

cache-control

Map for special cases when the casing doesn’t match.

{
  "Cache-Control" => "cache-control",
  "Content-Type" => "content-type",
  "Origin" => "origin",
  "Upgrade-Insecure-Requests" => "upgrade-insecure-requests",
}

Instance Method Summary collapse

Constructor Details

#initialize(route, env) ⇒ LambdaAwsProxy

Returns a new instance of LambdaAwsProxy.



7
8
9
10
11
12
13
14
# File 'lib/jets/server/lambda_aws_proxy.rb', line 7

def initialize(route, env)
  @route = route
  @env = env
  puts "Rack env:".colorize(:yellow)
  @env.each do |k,v|
    puts "#{k}: #{v}"
  end
end

Instance Method Details

#build_eventObject



35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
# File 'lib/jets/server/lambda_aws_proxy.rb', line 35

def build_event
  resource = @route.path(:api_gateway) # posts/{id}/edit
  path = @env['PATH_INFO'].sub('/','') # remove beginning slash
  {
    "resource" => "/#{resource}", # "/posts/{id}/edit"
    "path" => @env['PATH_INFO'],  # /posts/tung/edit
    "httpMethod" => @env['REQUEST_METHOD'], # GET
    "headers" => request_headers,
    "queryStringParameters" => query_string_parameters,
    "pathParameters" => @route.extract_parameters(path),
    "stageVariables" => nil,
    "requestContext" => {},
    "body" => get_body,
    "isBase64Encoded" => false,
  }
end

#find_controller_actionObject



130
131
132
# File 'lib/jets/server/lambda_aws_proxy.rb', line 130

def find_controller_action
  @route.action_name
end

#find_controller_classObject



125
126
127
128
# File 'lib/jets/server/lambda_aws_proxy.rb', line 125

def find_controller_class
  # posts#edit => PostsController
  @route.controller_name.constantize
end

#get_bodyObject

To get the post body:

rack.input: #<StringIO:0x007f8ccf8db9a0>


115
116
117
118
119
120
121
122
123
# File 'lib/jets/server/lambda_aws_proxy.rb', line 115

def get_body
  # @env["rack.input"] is always provided by rack and we should make
  # the test data always have rack.input to mimic rack, but but handling
  # it this way because it's simpler.
  input = @env["rack.input"] || StringIO.new
  body = input.read
  # return nil for blank string, because thats what Lambda AWS_PROXY does
  body unless body.empty?
end

#query_string_parametersObject



109
110
111
# File 'lib/jets/server/lambda_aws_proxy.rb', line 109

def query_string_parameters
  Rack::Utils.parse_nested_query(@env['QUERY_STRING'])
end

#request_headersObject

Map rack env headers to Api Gateway event headers. Most rack env headers are prepended by HTTP_.

Some API Gateway Lambda Proxy are also in the rack env headers. Example:

"X-Amz-Cf-Id": "W8DF6J-lx1bkV00eCiBwIq5dldTSGGiG4BinJlxvN_4o8fCZtbsVjw==",
"X-Amzn-Trace-Id": "Root=1-5a0dc1ac-58a7db712a57d6aa4186c2ac",
"X-Forwarded-For": "88.88.88.88, 54.239.203.117",
"X-Forwarded-Port": "443",
"X-Forwarded-Proto": "https",

For sample dump of the event headers, check out:

spec/fixtures/samples/event-headers-form-post.json

We generally do add those API Gateway Lambda specific headers because they would be fake anyway and by not adding them we can distinguish a local request from a remote request on API Gateway.



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
# File 'lib/jets/server/lambda_aws_proxy.rb', line 83

def request_headers
  headers = @env.select { |k,v| k =~ /^HTTP_/ }.inject({}) do |h,(k,v)|
      # map things like HTTP_USER_AGENT to "User-Agent"
      key = k.sub('HTTP_','').split('_').map(&:capitalize).join('-')
      h[key] = v
      h
    end
  # Content type is not prepended with HTTP_ but is part of Lambda's event headers thankfully
  headers["Content-Type"] = @env["CONTENT_TYPE"] if @env["CONTENT_TYPE"]

  # Adjust the casing so it matches the Lambda AWS Proxy's structure
  CASING_MAP.each do |nice_casing, bad_casing|
    if headers.key?(nice_casing)
      headers[bad_casing] = headers.delete(nice_casing)
    end
  end

  # Way to fake X-Amzn-Trace-Id which on_aws? helper checks.
  # This is how we distinguish a request from API gateway vs local.
  if ENV['JETS_ON_AWS']
    headers["X-Amzn-Trace-Id"] = "Root=fake-trace-id"
  end

  headers
end

#responseObject



16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
# File 'lib/jets/server/lambda_aws_proxy.rb', line 16

def response
  event = build_event
  context = {}

  controller_class = find_controller_class
  controller_action = find_controller_action

  fun = Jets::PolyFun.new(controller_class, controller_action)
  resp = fun.run(event, context) # check the logs for polymorphic function errors

  # Map lambda proxy response format to rack format
  status = resp["statusCode"]
  headers = resp["headers"] || {}
  headers = {'Content-Type' => 'text/html'}.merge(headers)
  body = resp["body"]

  [status, headers, [body]]
end