startback-websocket 0.14.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (95) hide show
  1. checksums.yaml +7 -0
  2. data/Gemfile +3 -0
  3. data/README.md +13 -0
  4. data/Rakefile +18 -0
  5. data/lib/startback/audit/prometheus.rb +87 -0
  6. data/lib/startback/audit/shared.rb +17 -0
  7. data/lib/startback/audit/trailer.rb +129 -0
  8. data/lib/startback/audit.rb +3 -0
  9. data/lib/startback/caching/entity_cache.rb +157 -0
  10. data/lib/startback/caching/no_store.rb +28 -0
  11. data/lib/startback/caching/store.rb +34 -0
  12. data/lib/startback/context/h_factory.rb +43 -0
  13. data/lib/startback/context/middleware.rb +53 -0
  14. data/lib/startback/context.rb +122 -0
  15. data/lib/startback/errors.rb +197 -0
  16. data/lib/startback/event/agent.rb +84 -0
  17. data/lib/startback/event/bus/bunny/async.rb +162 -0
  18. data/lib/startback/event/bus/bunny.rb +1 -0
  19. data/lib/startback/event/bus/memory/async.rb +45 -0
  20. data/lib/startback/event/bus/memory/sync.rb +35 -0
  21. data/lib/startback/event/bus/memory.rb +2 -0
  22. data/lib/startback/event/bus.rb +100 -0
  23. data/lib/startback/event/engine.rb +94 -0
  24. data/lib/startback/event/ext/context.rb +5 -0
  25. data/lib/startback/event/ext/operation.rb +13 -0
  26. data/lib/startback/event.rb +47 -0
  27. data/lib/startback/ext/date_time.rb +9 -0
  28. data/lib/startback/ext/time.rb +9 -0
  29. data/lib/startback/ext.rb +2 -0
  30. data/lib/startback/model.rb +6 -0
  31. data/lib/startback/operation/error_operation.rb +19 -0
  32. data/lib/startback/operation/multi_operation.rb +28 -0
  33. data/lib/startback/operation.rb +78 -0
  34. data/lib/startback/services.rb +11 -0
  35. data/lib/startback/support/data_object.rb +71 -0
  36. data/lib/startback/support/env.rb +41 -0
  37. data/lib/startback/support/fake_logger.rb +18 -0
  38. data/lib/startback/support/hooks.rb +48 -0
  39. data/lib/startback/support/log_formatter.rb +34 -0
  40. data/lib/startback/support/logger.rb +34 -0
  41. data/lib/startback/support/operation_runner.rb +150 -0
  42. data/lib/startback/support/robustness.rb +157 -0
  43. data/lib/startback/support/transaction_manager.rb +25 -0
  44. data/lib/startback/support/transaction_policy.rb +33 -0
  45. data/lib/startback/support/world.rb +54 -0
  46. data/lib/startback/support.rb +26 -0
  47. data/lib/startback/version.rb +8 -0
  48. data/lib/startback/web/api.rb +99 -0
  49. data/lib/startback/web/auto_caching.rb +85 -0
  50. data/lib/startback/web/catch_all.rb +52 -0
  51. data/lib/startback/web/cors_headers.rb +80 -0
  52. data/lib/startback/web/health_check.rb +49 -0
  53. data/lib/startback/web/magic_assets/ng_html_transformer.rb +80 -0
  54. data/lib/startback/web/magic_assets/rake_tasks.rb +64 -0
  55. data/lib/startback/web/magic_assets.rb +98 -0
  56. data/lib/startback/web/middleware.rb +13 -0
  57. data/lib/startback/web/prometheus.rb +16 -0
  58. data/lib/startback/web/shield.rb +58 -0
  59. data/lib/startback.rb +43 -0
  60. data/spec/spec_helper.rb +49 -0
  61. data/spec/unit/audit/test_prometheus.rb +72 -0
  62. data/spec/unit/audit/test_trailer.rb +105 -0
  63. data/spec/unit/caching/test_entity_cache.rb +136 -0
  64. data/spec/unit/context/test_abstraction_factory.rb +64 -0
  65. data/spec/unit/context/test_dup.rb +42 -0
  66. data/spec/unit/context/test_fork.rb +37 -0
  67. data/spec/unit/context/test_h_factory.rb +31 -0
  68. data/spec/unit/context/test_middleware.rb +45 -0
  69. data/spec/unit/context/test_with_world.rb +20 -0
  70. data/spec/unit/context/test_world.rb +17 -0
  71. data/spec/unit/event/bus/memory/test_async.rb +43 -0
  72. data/spec/unit/event/bus/memory/test_sync.rb +43 -0
  73. data/spec/unit/support/hooks/test_after_hook.rb +54 -0
  74. data/spec/unit/support/hooks/test_before_hook.rb +54 -0
  75. data/spec/unit/support/operation_runner/test_around_run.rb +156 -0
  76. data/spec/unit/support/operation_runner/test_before_after_call.rb +48 -0
  77. data/spec/unit/support/test_data_object.rb +156 -0
  78. data/spec/unit/support/test_env.rb +75 -0
  79. data/spec/unit/support/test_robusteness.rb +229 -0
  80. data/spec/unit/support/test_transaction_manager.rb +64 -0
  81. data/spec/unit/support/test_world.rb +72 -0
  82. data/spec/unit/test_event.rb +62 -0
  83. data/spec/unit/test_operation.rb +55 -0
  84. data/spec/unit/test_support.rb +40 -0
  85. data/spec/unit/web/fixtures/assets/app/hello.es6 +4 -0
  86. data/spec/unit/web/fixtures/assets/app/hello.html +1 -0
  87. data/spec/unit/web/fixtures/assets/index.es6 +1 -0
  88. data/spec/unit/web/test_api.rb +82 -0
  89. data/spec/unit/web/test_auto_caching.rb +81 -0
  90. data/spec/unit/web/test_catch_all.rb +77 -0
  91. data/spec/unit/web/test_cors_headers.rb +88 -0
  92. data/spec/unit/web/test_healthcheck.rb +59 -0
  93. data/spec/unit/web/test_magic_assets.rb +82 -0
  94. data/tasks/test.rake +14 -0
  95. metadata +237 -0
@@ -0,0 +1,150 @@
1
+ module Startback
2
+ module Support
3
+ #
4
+ # Support module for high-level architectural components that
5
+ # execute operations as part of their logic, see e.g. Web::Api.
6
+ #
7
+ # This module contributes a `run` instance method that allows
8
+ # binding an operation with a world, and executing it while
9
+ # supporting around runners.
10
+ #
11
+ # Example:
12
+ #
13
+ # class HighLevelComponent
14
+ # include Startback::Support::OperationRunner
15
+ #
16
+ # def some_method
17
+ # # Runs the operation passed after some binding
18
+ # run SomeOperation.new
19
+ # end
20
+ #
21
+ # protected
22
+ #
23
+ # # Overriden to inject some extra world
24
+ # def operation_world(op)
25
+ # super(op).merge({ hello: "world" })
26
+ # end
27
+ #
28
+ # # Execute this around op
29
+ # around_run do |op, then_block|
30
+ # puts "About to run #{op.inspect}"
31
+ # then_block.call
32
+ # end
33
+ #
34
+ # # SomeClass#call will be called with the operation
35
+ # # as first parameter and a block as continuation
36
+ # around_run SomeClass.new
37
+ #
38
+ # end
39
+ #
40
+ module OperationRunner
41
+
42
+ # Contributes the hook DSL methods to classes that include
43
+ # the OperationRunner module
44
+ module ClassMethods
45
+
46
+ # Registers a callable to be executed around operation running.
47
+ #
48
+ # In its block form, the callable is `instance_exec`uted on the
49
+ # runner instance, with the operation passed as first parameter
50
+ # and a then_block callable as second parameter (continuation):
51
+ #
52
+ # around_run do |op,then_block|
53
+ # # do whatever you want with the op (already bounded)
54
+ # puts op.inspect
55
+ #
56
+ # # do not forget to call the continuation block
57
+ # then_block.call
58
+ # end
59
+ #
60
+ # With a parameter responding to `#call`, the latter is invoked
61
+ # with the operation as parameter and a block as continuation:
62
+ #
63
+ # class Arounder
64
+ #
65
+ # def call(op)
66
+ # # do whatever you want with the op (already bounded)
67
+ # puts op.inspect
68
+ #
69
+ # # do not forget to call the continuation block
70
+ # yield
71
+ # end
72
+ #
73
+ # end
74
+ #
75
+ def around_run(arounder = nil, &bl)
76
+ raise ArgumentError, "Arg or block required" unless arounder || bl
77
+ arounds(true) << [arounder || bl, arounder.nil?]
78
+ end
79
+
80
+ private
81
+
82
+ def arounds(create = false)
83
+ if create
84
+ @arounds ||= superclass.respond_to?(:arounds, true) \
85
+ ? superclass.send(:arounds, true).dup \
86
+ : []
87
+ end
88
+ @arounds || (superclass.respond_to?(:arounds, true) ? superclass.send(:arounds, true) : [])
89
+ end
90
+
91
+ end
92
+
93
+ # When included by a class/module, install the DSL methods
94
+ def self.included(by)
95
+ by.extend(ClassMethods)
96
+ end
97
+
98
+ # Runs `operation`, taking care of binding it and executing
99
+ # hooks.
100
+ #
101
+ # This method is NOT intended to be overriden. Use hooks and
102
+ # `operation_world` to impact default behavior.
103
+ def run(operation)
104
+ op_world = operation_world(operation)
105
+ op_bound = operation.bind(op_world)
106
+ _run_befores(op_bound)
107
+ r = _run_with_arounds(op_bound, self.class.send(:arounds))
108
+ _run_afters(op_bound)
109
+ r
110
+ end
111
+
112
+ protected
113
+
114
+ # Returns the world to use to bind an operation.
115
+ #
116
+ # The default implementation returns an empty hash. This is
117
+ # intended to be overriden by classes including this module.
118
+ def operation_world(op)
119
+ {}
120
+ end
121
+
122
+ private
123
+
124
+ def _run_befores(op_bound)
125
+ op_bound.before_call if op_bound.respond_to?(:before_call, true)
126
+ end
127
+
128
+ def _run_with_arounds(operation, arounds = [])
129
+ if arounds.empty?
130
+ operation.call
131
+ else
132
+ arounder, iexec = arounds.first
133
+ after_first = ->() {
134
+ _run_with_arounds(operation, arounds[1..-1])
135
+ }
136
+ if iexec
137
+ self.instance_exec(operation, after_first, &arounder)
138
+ else
139
+ arounder.call(self, operation, &after_first)
140
+ end
141
+ end
142
+ end
143
+
144
+ def _run_afters(op_bound)
145
+ op_bound.after_call if op_bound.respond_to?(:after_call, true)
146
+ end
147
+
148
+ end # module OperationRunner
149
+ end # module Support
150
+ end # module Startback
@@ -0,0 +1,157 @@
1
+ module Startback
2
+ module Support
3
+ #
4
+ # This module provides helper methods for robustness of a software design.
5
+ #
6
+ # It is included by main Startback abstractions, and can be included by
7
+ # specific software components who needs fine-tuning of monitoring, logging
8
+ # and error handling.
9
+ #
10
+ # All public methods here follow the following free args parameters:
11
+ #
12
+ # 1. First (& second) argument(s) form the log message.
13
+ #
14
+ # A full log message is a Hash having :op (required), :op_took (optional),
15
+ # and :op_data (optional) keys.
16
+ #
17
+ # If a String (or two) are used instead, a log message will be built taking
18
+ # the former as the executer (a class or instance) and the second as a method.
19
+ # `{ op: "executer#method" }`
20
+ #
21
+ # 2. The second (or third) argument should be a Logger instance, a Context,
22
+ # or an instance knowing its context. The best logger is extracted from it
23
+ # and used for actual logging.
24
+ #
25
+ # Examples:
26
+ #
27
+ # log(:info, op: "hello", op_data: {foo: 12}) => logged as such on STDOUT
28
+ # log(:info, "A simple message") => { op: "A simple message" } on STDOUT
29
+ # log(:info, Startback, "hello") => { op: "Startback#hello" } on STDOUT
30
+ # log(:info, Event.new, "hello") => { op: "Event#hello" } on STDOUT
31
+ # log(:info, Event.new, "hello", "hello world") => { op: "Event#hello", op_data: { message: "hello world" } } on STDOUT
32
+ # log(:info, self, context) => { op: "..." } on context's logger or STDOUT
33
+ # log(:info, self, event) => { op: "..." } on event context's logger or STDOUT
34
+ # ...
35
+ #
36
+ module Robustness
37
+
38
+ # Included to avoid poluting the space of the including
39
+ # classes.
40
+ module Tools
41
+
42
+ def default_logger
43
+ @@default_logger ||= begin
44
+ l = ::Logger.new(STDOUT)
45
+ l.formatter = LogFormatter.new
46
+ l.warn(op: "#{self}", op_data: { msg: "Using default logger to STDOUT" })
47
+ @@default_logger = l
48
+ end
49
+ @@default_logger
50
+ end
51
+ module_function :default_logger
52
+
53
+ def logger_for(arg)
54
+ return arg if arg.is_a?(::Logger)
55
+ return arg.logger if arg.is_a?(Context) && arg.logger
56
+ return logger_for(arg.context) if arg.respond_to?(:context, false)
57
+ default_logger
58
+ end
59
+ module_function :logger_for
60
+
61
+ def parse_args(log_msg, method = nil, context = nil, extra = nil)
62
+ method, context, extra = nil, method, context unless method.is_a?(String)
63
+ context, extra = nil, context if context.is_a?(Hash) || context.is_a?(String) && extra.nil?
64
+ extra = { op_data: { message: extra } } if extra.is_a?(String)
65
+ logger = logger_for(context) || logger_for(log_msg)
66
+ log_msg = if log_msg.is_a?(Hash)
67
+ log_msg.dup
68
+ elsif log_msg.is_a?(String)
69
+ log_msg = { op: "#{log_msg}#{method.nil? ? '' : '#'+method.to_s}" }
70
+ elsif log_msg.is_a?(Exception)
71
+ log_msg = { error: log_msg }
72
+ else
73
+ log_msg = log_msg.class unless log_msg.is_a?(Module)
74
+ log_msg = { op: "#{log_msg.name}##{method}" }
75
+ end
76
+ log_msg.merge!(extra) if extra
77
+ [ log_msg, logger ]
78
+ end
79
+ module_function :parse_args
80
+
81
+ [:debug, :info, :warn, :error, :fatal].each do |meth|
82
+ define_method(meth) do |args, extra = nil, &bl|
83
+ act_args = (args + [extra]).compact
84
+ log_msg, logger = parse_args(*act_args)
85
+ logger.send(meth, log_msg)
86
+ end
87
+ module_function(meth)
88
+ end
89
+
90
+ end # module Tools
91
+
92
+ # Logs a specific message with a given severity.
93
+ #
94
+ # Severity can be :debug, :info, :warn, :error or :fatal.
95
+ # The args must follow module's conventions, see above.
96
+ def log(severity, *args)
97
+ Tools.send(severity, args)
98
+ end
99
+
100
+ # Calls the block and monitors then log its execution time.
101
+ #
102
+ # The args must follow module's conventions, see above.
103
+ def monitor(*args, &bl)
104
+ result = nil
105
+ took = Benchmark.realtime {
106
+ result = bl.call
107
+ }
108
+ Tools.info(args, op_took: took)
109
+ result
110
+ end
111
+
112
+ # Executes the block without letting errors propagate.
113
+ # Errors are logged, though. Nothing is logged if everything
114
+ # goes fine.
115
+ #
116
+ # The args must follow module's conventions, see above.
117
+ def stop_errors(*args, &bl)
118
+ result = nil
119
+ took = Benchmark.realtime {
120
+ result = bl.call
121
+ }
122
+ result
123
+ rescue => ex
124
+ Tools.fatal(args, op_took: took, error: ex)
125
+ nil
126
+ end
127
+
128
+ # Tries executing the block up to `n` times, until an attempt
129
+ # succeeds (then returning the result). Logs the first and last
130
+ # fatal error, if any.
131
+ #
132
+ # The args must follow module's conventions, see above.
133
+ def try_max_times(n, *args, &bl)
134
+ retried = 0
135
+ took = 0
136
+ begin
137
+ result = nil
138
+ took += Benchmark.realtime {
139
+ result = bl.call
140
+ }
141
+ result
142
+ rescue => ex
143
+ Tools.error(args + [{op_took: took, error: ex}]) if retried == 0
144
+ retried += 1
145
+ if retried < n
146
+ sleep(retried)
147
+ retry
148
+ else
149
+ Tools.fatal(args + [{op_took: took, error: ex}])
150
+ raise
151
+ end
152
+ end
153
+ end
154
+
155
+ end # module Robustness
156
+ end # module Support
157
+ end # module Startback
@@ -0,0 +1,25 @@
1
+ module Startback
2
+ module Support
3
+ class TransactionManager
4
+
5
+ def initialize(db, method = :transaction)
6
+ @db = db
7
+ @method = method
8
+ end
9
+
10
+ def call(runner, op, &then_block)
11
+ raise ArgumentError, "A block is required" unless then_block
12
+
13
+ before = (op.class.transaction_policy == :before_call)
14
+ if before
15
+ @db.send(@method) do
16
+ then_block.call
17
+ end
18
+ else
19
+ then_block.call
20
+ end
21
+ end
22
+
23
+ end # class TransactionManager
24
+ end # module Support
25
+ end # module Startback
@@ -0,0 +1,33 @@
1
+ module Startback
2
+ module Support
3
+ module TransactionPolicy
4
+
5
+ # Returns the operation's transaction policy
6
+ def transaction_policy
7
+ @transaction_policy || :before_call
8
+ end
9
+
10
+ # Sets the transaction policy to use. Valid values are:
11
+ # - before_call : the transaction is started by the operation
12
+ # runner, right before calling the #call method on operation
13
+ # instance
14
+ # - within_call: the transaction is started by the operation
15
+ # itself, as part of its internal logic.
16
+ def transaction_policy=(policy)
17
+ unless [:before_call, :within_call].include?(policy)
18
+ raise ArgumentError, "Unknown policy `#{policy}`"
19
+ end
20
+ @transaction_policy = policy
21
+ end
22
+
23
+ def after_commit(&bl)
24
+ after_call do
25
+ db.after_commit do
26
+ instance_exec(&bl)
27
+ end
28
+ end
29
+ end
30
+
31
+ end # module TransactionPolicy
32
+ end # module Support
33
+ end # module Startback
@@ -0,0 +1,54 @@
1
+ module Startback
2
+ module Support
3
+ class World
4
+ include DataObject
5
+
6
+ attr_accessor :_factory
7
+ protected :_factory=
8
+
9
+ def factory(who, &block)
10
+ dup.tap do |x|
11
+ x._factory = (self._factory || {}).merge(who => block)
12
+ end
13
+ end
14
+
15
+ attr_accessor :_scope
16
+ protected :_scope=
17
+
18
+ def with_scope(scope)
19
+ dup.tap do |x|
20
+ x._scope = scope
21
+ end
22
+ end
23
+
24
+ def with(hash)
25
+ dup.tap do |x|
26
+ x._data = to_data.merge(hash)
27
+ end
28
+ end
29
+
30
+ private
31
+
32
+ def _data_allow_camelize
33
+ false
34
+ end
35
+
36
+ def _data_allow_query
37
+ false
38
+ end
39
+
40
+ def _data_key_not_found(key)
41
+ raise Startback::Error, "Scope must be defined" unless s = _scope
42
+
43
+ block = (_factory || {})[key]
44
+ if block
45
+ factored = s.instance_exec(&block)
46
+ @_data = @_data.dup.merge(key => factored).freeze
47
+ [key, false]
48
+ else
49
+ nil
50
+ end
51
+ end
52
+ end # class World
53
+ end # module Support
54
+ end # module Startback
@@ -0,0 +1,26 @@
1
+ module Startback
2
+ module Support
3
+
4
+ def logger
5
+ Startback::LOGGER
6
+ end
7
+
8
+ def deep_merge(h1, h2)
9
+ h1.merge(h2){|k,v1,v2|
10
+ v1.is_a?(Hash) && v2.is_a?(Hash) ? deep_merge(v1, v2) : v2
11
+ }
12
+ end
13
+ module_function :deep_merge
14
+
15
+ end # module Support
16
+ end # module Startback
17
+ require_relative 'support/env'
18
+ require_relative 'support/log_formatter'
19
+ require_relative 'support/logger'
20
+ require_relative 'support/robustness'
21
+ require_relative 'support/hooks'
22
+ require_relative 'support/operation_runner'
23
+ require_relative 'support/transaction_policy'
24
+ require_relative 'support/transaction_manager'
25
+ require_relative 'support/data_object'
26
+ require_relative 'support/world'
@@ -0,0 +1,8 @@
1
+ module Startback
2
+ module Version
3
+ MAJOR = 0
4
+ MINOR = 14
5
+ TINY = 0
6
+ end
7
+ VERSION = "#{Version::MAJOR}.#{Version::MINOR}.#{Version::TINY}"
8
+ end
@@ -0,0 +1,99 @@
1
+ module Startback
2
+ module Web
3
+ class Api < Sinatra::Base
4
+ include Support
5
+ include Errors
6
+
7
+ set :raise_errors, true
8
+ set :show_exceptions, false
9
+ set :dump_errors, false
10
+
11
+ protected
12
+
13
+ ###
14
+ ### Facade over context
15
+ ###
16
+
17
+ def context
18
+ env[Startback::Context::Middleware::RACK_ENV_KEY]
19
+ end
20
+
21
+ def with_context(ctx = nil)
22
+ old_context = self.context
23
+ new_context = ctx || self.context.dup
24
+ env[Startback::Context::Middleware::RACK_ENV_KEY] = new_context
25
+ result = ctx ? yield : yield(new_context)
26
+ env[Startback::Context::Middleware::RACK_ENV_KEY] = old_context
27
+ result
28
+ end
29
+
30
+ ###
31
+ ### Facade over third party tools
32
+ ###
33
+ include Support::OperationRunner
34
+
35
+ def operation_world(op)
36
+ { context: context }
37
+ end
38
+
39
+ ###
40
+ ### About the body / input
41
+ ###
42
+
43
+ def loaded_body
44
+ @loaded_body ||= case ctype = request.content_type
45
+ when /json/
46
+ json_body
47
+ when /multipart\/form-data/
48
+ file = params[:file]
49
+ file_body file, Path(file[:filename]).extname
50
+ else
51
+ unsupported_media_type_error!(ctype)
52
+ end
53
+ end
54
+
55
+ def json_body(body = request.body.read)
56
+ JSON.load(body)
57
+ end
58
+
59
+ def file_body(file, ctype)
60
+ raise UnsupportedMediaTypeError, "Unable to use `#{ctype}` as input data"
61
+ end
62
+
63
+ ###
64
+ ### Various reusable responses
65
+ ###
66
+
67
+ def serve_nothing
68
+ [ 204, {}, [] ]
69
+ end
70
+
71
+ def serve(entity_description, entity, ct = nil)
72
+ if entity.nil?
73
+ status 404
74
+ content_type :json
75
+ { description: "#{entity_description} not found" }.to_json
76
+ elsif entity.respond_to?(:to_dto)
77
+ ct, body = entity.to_dto(context).to(env['HTTP_ACCEPT'], ct)
78
+ content_type ct
79
+ _serve(body)
80
+ elsif entity.is_a?(Path)
81
+ _serve(entity)
82
+ else
83
+ content_type ct || "application/json"
84
+ entity.to_json
85
+ end
86
+ end
87
+
88
+ def _serve(body)
89
+ case body
90
+ when Path
91
+ send_file(body)
92
+ else
93
+ body
94
+ end
95
+ end
96
+
97
+ end # class Api
98
+ end # module Web
99
+ end # module Startback
@@ -0,0 +1,85 @@
1
+ module Startback
2
+ module Web
3
+ #
4
+ # This rack middleware automatically mark response as non being cacheable
5
+ # in development, and being cacheble in production.
6
+ #
7
+ # The headers to set in development and production can be passed at
8
+ # construction, as well as whether the development environment must be
9
+ # forced. This class may also be configured through environment variables:
10
+ #
11
+ # - RACK_ENV: when "production" use the production headers, otherwise use
12
+ # the development ones
13
+ # - STARTBACK_AUTOCACHING_DEVELOPMENT_CACHE_CONTROL: Cache-Control header
14
+ # to use in development mode
15
+ # - STARTBACK_AUTOCACHING_PRODUCTION_CACHE_CONTROL: Cache-Control header
16
+ # to use in production mode
17
+ #
18
+ # Example:
19
+ #
20
+ # # Default configuration
21
+ # use Autocaching
22
+ #
23
+ # # Force development mode
24
+ # use Autocaching, true
25
+ #
26
+ # # Force production mode
27
+ # use Autocaching, false
28
+ #
29
+ # # Set production headers manually
30
+ # use Autocaching, { :production => "public, no-cache, no-store" }
31
+ #
32
+ class AutoCaching
33
+
34
+ # Cache-Control header to use in development mode
35
+ DEVELOPMENT_CACHE_CONTROL = ENV['STARTBACK_AUTOCACHING_DEVELOPMENT_CACHE_CONTROL'] || \
36
+ "no-cache, no-store, max-age=0, must-revalidate"
37
+
38
+ # Cache-Control header to use in produdction mode
39
+ PRODUCTION_CACHE_CONTROL = ENV['STARTBACK_AUTOCACHING_PRODUCTION_CACHE_CONTROL'] ||\
40
+ "public, must-revalidate, max-age=3600, s-max-age=3600"
41
+
42
+ def initialize(app, development = nil, cache_headers = {})
43
+ development, cache_headers = nil, development if development.is_a?(Hash)
44
+ @app = app
45
+ @development = development.nil? ? infer_is_development : development
46
+ @cache_headers = default_headers.merge(normalize_headers(cache_headers))
47
+ end
48
+
49
+ def call(env)
50
+ status, headers, body = @app.call(env)
51
+ [status, patch_response_headers(headers), body]
52
+ end
53
+
54
+ protected
55
+
56
+ def patch_response_headers(hs)
57
+ (development? ? @cache_headers[:development] : @cache_headers[:production]).merge(hs)
58
+ end
59
+
60
+ def development?
61
+ !!@development
62
+ end
63
+
64
+ def infer_is_development
65
+ ENV['RACK_ENV'] != "production"
66
+ end
67
+
68
+ def default_headers
69
+ {
70
+ development: {
71
+ "Cache-Control" => DEVELOPMENT_CACHE_CONTROL
72
+ },
73
+ production: {
74
+ "Cache-Control" => PRODUCTION_CACHE_CONTROL
75
+ }
76
+ }
77
+ end
78
+
79
+ def normalize_headers(h)
80
+ Hash[h.map{|k,v| [k, v.is_a?(Hash) ? v : {"Cache-Control" => v} ] }]
81
+ end
82
+
83
+ end # class AutoCaching
84
+ end # module Web
85
+ end # module Startback
@@ -0,0 +1,52 @@
1
+ module Startback
2
+ module Web
3
+ #
4
+ # This Rack middleware catches all exceptions that are raised by sublayers
5
+ # in the Rack chain. It converts them to correct 500 Errors, with a generic
6
+ # exception message encoded in json.
7
+ #
8
+ # This class aims at being used as top level of a Rack chain. It is not
9
+ # aimed at being subclassed.
10
+ #
11
+ # Fatal error cached are also sent as a `fatal` messange, on the error
12
+ # handler provided on Context#error_handler.fatal, if any.
13
+ #
14
+ # Examples:
15
+ #
16
+ # Rack::Builder.new do
17
+ # use Startback::Web::CatchAll
18
+ # end
19
+ #
20
+ class CatchAll < Rack::Robustness
21
+ include Errors
22
+ include Support::Robustness
23
+
24
+ FATAL_ERROR = {
25
+ code: "Startback::Errors::InternalServerError",
26
+ description: "An error occured, sorry"
27
+ }.to_json
28
+
29
+ self.catch_all
30
+ self.on(Exception)
31
+ self.status 500
32
+ self.content_type 'application/json'
33
+ self.body FATAL_ERROR
34
+
35
+ self.ensure(true) do |ex|
36
+ context = env[Context::Middleware::RACK_ENV_KEY]
37
+ begin
38
+ if context && context.respond_to?(:error_handler, true) && context.error_handler
39
+ context.error_handler.fatal(ex)
40
+ else
41
+ log(:fatal, self, "ensure", context, error: ex)
42
+ end
43
+ rescue => ex2
44
+ STDERR.puts(ex2.message)
45
+ STDERR.puts(ex2.backtrace[0..10].join("\n"))
46
+ raise
47
+ end
48
+ end
49
+
50
+ end # class CatchAll
51
+ end # class Web
52
+ end # module Startback