jets 1.0.18 → 1.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (59) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +4 -0
  3. data/Gemfile.lock +10 -10
  4. data/README/testing.md +5 -2
  5. data/lib/jets.rb +2 -2
  6. data/lib/jets/application.rb +69 -40
  7. data/lib/jets/booter.rb +17 -20
  8. data/lib/jets/builders/code_builder.rb +7 -8
  9. data/lib/jets/cfn/ship.rb +0 -6
  10. data/lib/jets/commands/build.rb +0 -5
  11. data/lib/jets/commands/deploy.rb +0 -4
  12. data/lib/jets/commands/main.rb +31 -4
  13. data/lib/jets/commands/templates/skeleton/{.env → .env.tt} +1 -0
  14. data/lib/jets/commands/templates/skeleton/config.ru +1 -0
  15. data/lib/jets/commands/upgrade/v1.rb +12 -0
  16. data/lib/jets/controller.rb +5 -0
  17. data/lib/jets/controller/base.rb +43 -21
  18. data/lib/jets/controller/cookies.rb +40 -0
  19. data/lib/jets/controller/cookies/jar.rb +269 -0
  20. data/lib/jets/controller/middleware.rb +4 -0
  21. data/lib/jets/controller/middleware/local.rb +119 -0
  22. data/lib/jets/{server/lambda_aws_proxy.rb → controller/middleware/local/api_gateway.rb} +11 -49
  23. data/lib/jets/controller/middleware/local/mimic_aws_call.rb +38 -0
  24. data/lib/jets/{server → controller/middleware/local}/route_matcher.rb +4 -4
  25. data/lib/jets/controller/middleware/main.rb +46 -0
  26. data/lib/jets/{server → controller/middleware}/webpacker_setup.rb +0 -1
  27. data/lib/jets/controller/params.rb +2 -1
  28. data/lib/jets/controller/rack.rb +5 -0
  29. data/lib/jets/controller/rack/adapter.rb +60 -0
  30. data/lib/jets/controller/rack/env.rb +96 -0
  31. data/lib/jets/controller/redirection.rb +1 -1
  32. data/lib/jets/controller/renderers.rb +1 -1
  33. data/lib/jets/controller/renderers/base_renderer.rb +0 -4
  34. data/lib/jets/controller/renderers/{aws_proxy_renderer.rb → rack_renderer.rb} +7 -19
  35. data/lib/jets/controller/renderers/template_renderer.rb +1 -1
  36. data/lib/jets/controller/request.rb +14 -44
  37. data/lib/jets/controller/response.rb +55 -7
  38. data/lib/jets/internal/app/controllers/jets/rack_controller.rb +13 -3
  39. data/lib/jets/mega.rb +7 -0
  40. data/lib/jets/{rack → mega}/hash_converter.rb +1 -1
  41. data/lib/jets/{rack → mega}/request.rb +17 -4
  42. data/lib/jets/middleware.rb +38 -0
  43. data/lib/jets/middleware/configurator.rb +84 -0
  44. data/lib/jets/middleware/default_stack.rb +44 -0
  45. data/lib/jets/middleware/layer.rb +34 -0
  46. data/lib/jets/middleware/stack.rb +77 -0
  47. data/lib/jets/resource/function.rb +1 -1
  48. data/lib/jets/ruby_server.rb +1 -1
  49. data/lib/jets/server.rb +48 -13
  50. data/lib/jets/version.rb +1 -1
  51. metadata +24 -17
  52. data/lib/jets/application/middleware.rb +0 -23
  53. data/lib/jets/default/application.rb +0 -23
  54. data/lib/jets/rack.rb +0 -7
  55. data/lib/jets/rack/server.rb +0 -47
  56. data/lib/jets/server/api_gateway.rb +0 -39
  57. data/lib/jets/server/timing_middleware.rb +0 -33
  58. data/lib/jets/timing.rb +0 -65
  59. data/lib/jets/timing/report.rb +0 -82
@@ -19,7 +19,7 @@ class Jets::Controller
19
19
 
20
20
  redirect_url = ensure_protocol(redirect_url)
21
21
 
22
- aws_proxy = Renderers::AwsProxyRenderer.new(self,
22
+ aws_proxy = Renderers::RackRenderer.new(self,
23
23
  status: options[:status] || 302,
24
24
  headers: { "Location" => redirect_url },
25
25
  body: "",
@@ -1,5 +1,5 @@
1
1
  module Jets::Controller::Renderers
2
- autoload :AwsProxyRenderer, "jets/controller/renderers/aws_proxy_renderer"
3
2
  autoload :BaseRenderer, "jets/controller/renderers/base_renderer"
3
+ autoload :RackRenderer, "jets/controller/renderers/rack_renderer"
4
4
  autoload :TemplateRenderer, "jets/controller/renderers/template_renderer"
5
5
  end
@@ -8,9 +8,5 @@ module Jets::Controller::Renderers
8
8
  @controller = controller
9
9
  @options = options
10
10
  end
11
-
12
- def render_aws_proxy(options)
13
- AwsProxyRenderer.new(@controller, options).render
14
- end
15
11
  end
16
12
  end
@@ -2,17 +2,10 @@ require "rack/utils"
2
2
 
3
3
  # Special renderer. All the other renderers lead here
4
4
  module Jets::Controller::Renderers
5
- class AwsProxyRenderer < BaseRenderer
6
- # Transform the structure to AWS_PROXY compatiable structure
7
- # http://docs.aws.amazon.com/apigateway/latest/developerguide/set-up-lambda-proxy-integrations.html#api-gateway-simple-proxy-for-lambda-output-format
5
+ class RackRenderer < BaseRenderer
8
6
  # Example response:
9
7
  #
10
- # {
11
- # "statusCode" => status,
12
- # "headers" => headers,
13
- # "body" => body,
14
- # "isBase64Encoded" => base64,
15
- # }
8
+ # [200, {"my-header" = > "value" }, "my body" ]
16
9
  def render
17
10
  # we do some normalization here
18
11
  status = map_status_code(@options[:status]) || 200
@@ -22,16 +15,11 @@ module Jets::Controller::Renderers
22
15
 
23
16
  headers = @options[:headers] || {}
24
17
  headers = cors_headers.merge(headers)
25
- headers["Content-Type"] ||= @options[:content_type] || "text/html; charset=utf-8"
26
-
27
- # Compatiable Lambda Proxy response Hash.
28
- # Additional extra keys results in compatiability. Explictly assign keys.
29
- {
30
- "statusCode" => status,
31
- "headers" => headers,
32
- "body" => body,
33
- "isBase64Encoded" => base64,
34
- }
18
+ headers["Content-Type"] ||= @options[:content_type] || Jets::Controller::DEFAULT_CONTENT_TYPE
19
+ # x-jets-base64 to convert this Rack triplet to a API Gateway hash structure later
20
+ headers["x-jets-base64"] = base64 ? "true" : "false"
21
+ body = StringIO.new(body)
22
+ [status, headers, body] # triplet
35
23
  end
36
24
 
37
25
  # maps:
@@ -16,7 +16,7 @@ module Jets::Controller::Renderers
16
16
  body = renderer.render(render_options)
17
17
  @options[:body] = body # important to set as it was originally nil
18
18
 
19
- render_aws_proxy(@options)
19
+ RackRenderer.new(@controller, @options).render
20
20
  end
21
21
 
22
22
  # Example: posts/index
@@ -1,42 +1,21 @@
1
- # Somewhat based off of: https://github.com/rails/rails/blob/master/actionpack/lib/action_dispatch/http/request.rb
1
+ require 'rack/request'
2
+
2
3
  class Jets::Controller
3
- class Request
4
- def initialize(event)
5
- @event = event
4
+ class Request < ::Rack::Request
5
+ def initialize(event, context)
6
+ @event, @context = event, context
7
+ super(env)
6
8
  end
7
9
 
8
- # lambda integration proxy headers
9
- HEADER_METHODS = %w[
10
- Accept
11
- Accept-Encoding
12
- Accept-Language
13
- cache-control
14
- CloudFront-Forwarded-Proto
15
- CloudFront-Is-Desktop-Viewer
16
- CloudFront-Is-Mobile-Viewer
17
- CloudFront-Is-SmartTV-Viewer
18
- CloudFront-Is-Tablet-Viewer
19
- CloudFront-Viewer-Country
20
- content-type
21
- Host
22
- origin
23
- Referer
24
- upgrade-insecure-requests
25
- User-Agent
26
- Via
27
- X-Amz-Cf-Id
28
- X-Amzn-Trace-Id
29
- X-Forwarded-For
30
- X-Forwarded-Port
31
- X-Forwarded-Proto
32
- ].freeze
10
+ def env
11
+ @env ||= Jets::Controller::Rack::Env.new(@event, @context).convert # convert to Rack env
12
+ end
33
13
 
34
- HEADER_METHODS.each do |meth|
35
- class_eval <<-METHOD, __FILE__, __LINE__ + 1
36
- def #{meth.downcase.underscore} # def content_type
37
- headers["#{meth.downcase}"].freeze # headers["content-type"]
38
- end # end
39
- METHOD
14
+ # When request hits the middleware Controller::Rack::Middleware::Main endpoint
15
+ # We set the it with the updated env since it could had been mutated down the
16
+ # middleware stack.
17
+ def set_env!(env)
18
+ @env = env
40
19
  end
41
20
 
42
21
  # API Gateway is inconsistent about how it cases it keys.
@@ -46,14 +25,5 @@ class Jets::Controller
46
25
  headers = @event["headers"] || {}
47
26
  headers.transform_keys { |key| key.downcase }
48
27
  end
49
-
50
- def xhr?
51
- headers["x-requested-with"] == "XMLHttpRequest"
52
- end
53
-
54
- def path
55
- @event["path"]
56
- end
57
-
58
28
  end
59
29
  end
@@ -1,13 +1,61 @@
1
+ require 'rack/response'
2
+
1
3
  class Jets::Controller
2
- class Response
3
- attr_reader :headers
4
- def initialize(event)
5
- @event = event
6
- @headers = {}
4
+ # The response object. See Rack::Response and Rack::Response::Helpers for
5
+ # more info:
6
+ # http://rubydoc.info/github/rack/rack/master/Rack/Response
7
+ # http://rubydoc.info/github/rack/rack/master/Rack/Response/Helpers
8
+ class Response < ::Rack::Response
9
+ DROP_BODY_RESPONSES = [204, 304]
10
+ def initialize(*)
11
+ super
12
+ # headers['Content-Type'] ||= 'text/html'
13
+ end
14
+
15
+ # TODO: unsure if we should even have these methods. We dont really use them.
16
+ def body=(value)
17
+ value = value.body while Rack::Response === value
18
+ @body = String === value ? [value.to_str] : value
19
+ end
20
+
21
+ def each
22
+ block_given? ? super : enum_for(:each)
23
+ end
24
+
25
+ def finish
26
+ result = body
27
+
28
+ if drop_content_info?
29
+ headers.delete "Content-Length"
30
+ headers.delete "Content-Type"
31
+ end
32
+
33
+ if drop_body?
34
+ close
35
+ result = []
36
+ end
37
+
38
+ if calculate_content_length?
39
+ # if some other code has already set Content-Length, don't muck with it
40
+ # currently, this would be the static file-handler
41
+ headers["Content-Length"] = body.inject(0) { |l, p| l + p.bytesize }.to_s
42
+ end
43
+
44
+ [status.to_i, headers, result]
45
+ end
46
+
47
+ private
48
+
49
+ def calculate_content_length?
50
+ headers["Content-Type"] and not headers["Content-Length"] and Array === body
51
+ end
52
+
53
+ def drop_content_info?
54
+ status.to_i / 100 == 1 or drop_body?
7
55
  end
8
56
 
9
- def set_header(k,v)
10
- @headers[k] = v
57
+ def drop_body?
58
+ DROP_BODY_RESPONSES.include?(status.to_i)
11
59
  end
12
60
  end
13
61
  end
@@ -4,12 +4,22 @@ class Jets::RackController < Jets::Controller::Base
4
4
 
5
5
  # Megamode
6
6
  def process
7
- resp = rack_request
7
+ resp = mega_request
8
8
  render(resp)
9
9
  end
10
10
 
11
11
  private
12
- def rack_request
13
- Jets::Rack::Request.new(event, self).process
12
+ # Override process! so it doesnt go through middleware adapter and hits
13
+ # process logic directly. This handles the case for AWS Lambda.
14
+ # For local server, we adjust the Middleware::Local logic.
15
+ def process!
16
+ status, headers, body = dispatch!
17
+ # Use the adapter only to convert the Rack triplet to a API Gateway hash structure
18
+ adapter = Jets::Controller::Rack::Adapter.new(event, context, meth)
19
+ adapter.convert_to_api_gateway(status, headers, body)
20
+ end
21
+
22
+ def mega_request
23
+ Jets::Mega::Request.new(event, self).proxy
14
24
  end
15
25
  end
@@ -0,0 +1,7 @@
1
+ module Jets
2
+ module Mega
3
+ autoload :Request, 'jets/mega/request'
4
+ autoload :Server, 'jets/mega/server'
5
+ autoload :HashConverter, 'jets/mega/hash_converter'
6
+ end
7
+ end
@@ -1,5 +1,5 @@
1
1
  # Thanks https://mensfeld.pl/2012/01/converting-nested-hash-into-http-url-params-hash-version-in-ruby/
2
- module Jets::Rack
2
+ module Jets::Mega
3
3
  module HashConverter
4
4
  def self.encode(value, key = nil, out_hash = {})
5
5
  case value
@@ -1,18 +1,21 @@
1
1
  require 'net/http'
2
+ require 'rack'
2
3
 
3
- module Jets::Rack
4
+ module Jets::Mega
4
5
  class Request
5
6
  def initialize(event, controller)
6
7
  @event = event
7
8
  @controller = controller # Jets::Controller instance
8
9
  end
9
10
 
10
- def process
11
+ def proxy
11
12
  http_method = @event['httpMethod'] # GET, POST, PUT, DELETE, etc
12
13
  params = @controller.params(raw: true, path_parameters: false)
13
14
 
14
15
  uri = URI("http://localhost:9292#{@controller.request.path}") # local rack server
15
16
  http = Net::HTTP.new(uri.host, uri.port)
17
+ http.open_timeout = 60
18
+ http.read_timeout = 60
16
19
 
17
20
  # Rails sets _method=patch or _method=put as workaround
18
21
  # Falls back to GET when testing in lambda console
@@ -20,7 +23,7 @@ module Jets::Rack
20
23
  http_class.capitalize!
21
24
 
22
25
  request_class = "Net::HTTP::#{http_class}".constantize # IE: Net::HTTP::Get
23
- request = request_class.new(@controller.request.path)
26
+ request = request_class.new(uri.path)
24
27
  if %w[Post Patch Put].include?(http_class)
25
28
  params = HashConverter.encode(params)
26
29
  request.set_form_data(params)
@@ -28,8 +31,18 @@ module Jets::Rack
28
31
 
29
32
  request = set_headers!(request)
30
33
 
31
- # TODO: handle binary
34
+ # Setup body
35
+ env = Jets::Controller::Rack::Env.new(@event, {}).convert # convert to Rack env
36
+ source_request = Rack::Request.new(env)
37
+ if source_request.body.respond_to?(:read)
38
+ request.body = source_request.body.read
39
+ request.content_length = source_request.content_length.to_i
40
+ source_request.body.rewind
41
+ end
42
+
32
43
  response = http.request(request)
44
+
45
+ # TODO: handle binary
33
46
  {
34
47
  status: response.code.to_i,
35
48
  headers: response.each_header.to_h,
@@ -0,0 +1,38 @@
1
+ module Jets
2
+ module Middleware
3
+ extend Memoist
4
+
5
+ autoload :Configurator, 'jets/middleware/configurator'
6
+ autoload :DefaultStack, 'jets/middleware/default_stack'
7
+ autoload :Layer, 'jets/middleware/layer'
8
+ autoload :Stack, 'jets/middleware/stack'
9
+
10
+ def call(env)
11
+ stack = middlewares.build(endpoint)
12
+ stack.call(env)
13
+ end
14
+
15
+ # Final middleware in the stack
16
+ def endpoint
17
+ Jets::Controller::Middleware::Main
18
+ end
19
+
20
+ # Called in Jets::Booter to build middleware stack only once during bootup
21
+ def build_stack
22
+ middlewares
23
+ end
24
+
25
+ def middlewares
26
+ config_middleware.merge_into(default_stack) # returns Jets::Middleware::Stack
27
+ end
28
+ memoize :middlewares
29
+
30
+ def default_stack
31
+ Jets::Middleware::DefaultStack.new(Jets.config, Jets.application).build_stack # returns Jets::Middleware::Stack
32
+ end
33
+
34
+ def config_middleware
35
+ Jets.config.middleware # returns Jets::Middleware::Configurator
36
+ end
37
+ end
38
+ end
@@ -0,0 +1,84 @@
1
+ # Based on Rails MiddlewareStackProxy
2
+ #
3
+ # Configurator is a proxy for the Jets middleware stack that allows
4
+ # you to configure middlewares in your application. It works basically as a
5
+ # command recorder, saving each command to be applied after initialization
6
+ # over the default middleware stack, so you can add, swap, or remove any
7
+ # middleware in Jets.
8
+ #
9
+ # You can add your own middlewares by using the +config.middleware.use+ method:
10
+ #
11
+ # config.middleware.use Magical::Unicorns
12
+ #
13
+ # This will put the <tt>Magical::Unicorns</tt> middleware on the end of the stack.
14
+ # You can use +insert_before+ if you wish to add a middleware before another:
15
+ #
16
+ # config.middleware.insert_before Rack::Head, Magical::Unicorns
17
+ #
18
+ # There's also +insert_after+ which will insert a middleware after another:
19
+ #
20
+ # config.middleware.insert_after Rack::Head, Magical::Unicorns
21
+ #
22
+ # Middlewares can also be completely swapped out and replaced with others:
23
+ #
24
+ # config.middleware.swap ActionDispatch::Flash, Magical::Unicorns
25
+ #
26
+ # And finally they can also be removed from the stack completely:
27
+ #
28
+ # config.middleware.delete ActionDispatch::Flash
29
+ #
30
+ module Jets::Middleware
31
+ class Configurator
32
+ def initialize(operations = [], delete_operations = [])
33
+ @operations = operations
34
+ @delete_operations = delete_operations
35
+ end
36
+
37
+ def insert_before(*args, &block)
38
+ @operations << [__method__, args, block]
39
+ end
40
+
41
+ alias :insert :insert_before
42
+
43
+ def insert_after(*args, &block)
44
+ @operations << [__method__, args, block]
45
+ end
46
+
47
+ def swap(*args, &block)
48
+ @operations << [__method__, args, block]
49
+ end
50
+
51
+ def use(*args, &block)
52
+ @operations << [__method__, args, block]
53
+ end
54
+
55
+ def delete(*args, &block)
56
+ @delete_operations << [__method__, args, block]
57
+ end
58
+
59
+ def unshift(*args, &block)
60
+ @operations << [__method__, args, block]
61
+ end
62
+
63
+ def merge_into(other) #:nodoc:
64
+ (@operations + @delete_operations).each do |operation, args, block|
65
+ other.send(operation, *args, &block)
66
+ end
67
+
68
+ other
69
+ end
70
+
71
+ def +(other) # :nodoc:
72
+ Configurator.new(@operations + other.operations, @delete_operations + other.delete_operations)
73
+ end
74
+
75
+ protected
76
+ def operations
77
+ @operations
78
+ end
79
+
80
+ def delete_operations
81
+ @delete_operations
82
+ end
83
+ end
84
+ end
@@ -0,0 +1,44 @@
1
+ module Jets::Middleware
2
+ class DefaultStack
3
+ attr_reader :config, :app
4
+ def initialize(app, config)
5
+ @app = app
6
+ @config = config
7
+ end
8
+
9
+ def build_stack
10
+ Stack.new do |middleware|
11
+ middleware.use Jets::Controller::Middleware::Local # mimics AWS Lambda for local server only
12
+ middleware.use Rack::Runtime
13
+ middleware.use Rack::MethodOverride
14
+ middleware.use session_store, session_options # use session_store, session_options
15
+ middleware.use Rack::Head
16
+ middleware.use Rack::ConditionalGet
17
+ middleware.use Rack::ETag
18
+ use_webpacker(middleware)
19
+ end
20
+ end
21
+
22
+ private
23
+ # Written as method to easily not include webpacker for case when either
24
+ # webpacker not installed at all or disabled upon `jets deploy`.
25
+ def use_webpacker(middleware)
26
+ return unless Jets.webpacker? # checks for local development if webpacker installed
27
+ # Different check for middleware because we need webpacker helpers for url helpers.
28
+ # But we dont want to actually serve via webpacker middleware when running on AWS.
29
+ # By this time the url helpers are serving assets out of s3.
30
+ return if File.exist?("#{Jets.root}config/disable-webpacker-middleware.txt") # created as part of `jets deploy`
31
+ require "jets/controller/middleware/webpacker_setup"
32
+ middleware.use Webpacker::DevServerProxy
33
+ end
34
+
35
+ def session_store
36
+ Jets.config.session[:store] # do not use dot notation. session.store is a method on ActiveSupport::OrderedOptions.new
37
+ end
38
+
39
+ def session_options
40
+ defaults = { secret: ENV['SECRET_KEY_BASE'] }
41
+ defaults.merge(Jets.config.session.options)
42
+ end
43
+ end
44
+ end