jets 1.0.18 → 1.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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