startback-websocket 0.14.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 (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