Class: Tina4::Middleware
- Inherits:
-
Object
- Object
- Tina4::Middleware
- Defined in:
- lib/tina4/middleware.rb
Class Method Summary collapse
- .after(pattern = nil, &block) ⇒ Object
- .after_handlers ⇒ Object
- .before(pattern = nil, &block) ⇒ Object
- .before_handlers ⇒ Object
- .clear! ⇒ Object
-
.get_global ⇒ Object
Parity alias matching Python/PHP/Node orchestrators.
-
.global_middleware ⇒ Object
Registry of class-based middleware (registered via Router.use).
-
.middleware_500(response, label, error) ⇒ Object
Deterministic clean 500 for a middleware that threw.
-
.post_match_middleware ⇒ Object
Global middleware that runs after matching, once the matched route's metadata is readable.
-
.pre_match_middleware ⇒ Object
Global middleware that runs BEFORE route matching.
-
.refuse(response) ⇒ Object
The
falserow of the return-value table, on its own. -
.run_after(middleware_classes, request, response) ⇒ Object
Run all "after" hooks: block-based handlers, then class-based after_* methods (in definition order).
-
.run_before(middleware_classes, request, response) ⇒ Object
Run all "before" hooks: block-based handlers, then class-based before_* methods (in definition order).
-
.use(klass) ⇒ Object
Register a class-based middleware globally.
Class Method Details
.after(pattern = nil, &block) ⇒ Object
28 29 30 |
# File 'lib/tina4/middleware.rb', line 28 def after(pattern = nil, &block) after_handlers << { pattern: pattern, handler: block } end |
.after_handlers ⇒ Object
10 11 12 |
# File 'lib/tina4/middleware.rb', line 10 def after_handlers @after_handlers ||= [] end |
.before(pattern = nil, &block) ⇒ Object
24 25 26 |
# File 'lib/tina4/middleware.rb', line 24 def before(pattern = nil, &block) before_handlers << { pattern: pattern, handler: block } end |
.before_handlers ⇒ Object
6 7 8 |
# File 'lib/tina4/middleware.rb', line 6 def before_handlers @before_handlers ||= [] end |
.clear! ⇒ Object
64 65 66 67 68 |
# File 'lib/tina4/middleware.rb', line 64 def clear! @before_handlers = [] @after_handlers = [] @global_middleware = [] end |
.get_global ⇒ Object
Parity alias matching Python/PHP/Node orchestrators.
20 21 22 |
# File 'lib/tina4/middleware.rb', line 20 def get_global global_middleware.dup end |
.global_middleware ⇒ Object
Registry of class-based middleware (registered via Router.use)
15 16 17 |
# File 'lib/tina4/middleware.rb', line 15 def global_middleware @global_middleware ||= [] end |
.middleware_500(response, label, error) ⇒ Object
Deterministic clean 500 for a middleware that threw. Logs the cause (NEVER silent) then sets the response to the canonical error shape — byte-identical to the Python master (Server Error", "status":500 + status 500). Returns the response for chaining.
198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 |
# File 'lib/tina4/middleware.rb', line 198 def middleware_500(response, label, error) begin Tina4::Log.error( "Middleware #{label} raised #{error.class.name}: #{error.}" ) rescue StandardError begin $stderr.puts("Middleware #{label} raised #{error.class.name}: #{error.}") $stderr.flush rescue StandardError # never let logging break the worker end end response.json({ error: "Internal Server Error", status: 500 }, 500) end |
.post_match_middleware ⇒ Object
Global middleware that runs after matching, once the matched route's metadata is readable. This is the default.
60 61 62 |
# File 'lib/tina4/middleware.rb', line 60 def post_match_middleware global_middleware.reject { |k| k.respond_to?(:pre_match?) && k.pre_match? } end |
.pre_match_middleware ⇒ Object
Global middleware that runs BEFORE route matching.
A middleware opts in by declaring def self.pre_match?; true; end.
NOT before_match? - the hook discovery treats every before_* method
as a middleware hook and calls it with (request, response), so that name
made the flag itself run as middleware and 500 the request.
Everything else stays where it has always run - after matching - so
this is additive and no existing middleware changes behaviour.
The split exists because the two groups need opposite things. CORS must run before matching so its headers survive a short-circuited 401/403; a browser that gets a 401 without them reports a CORS error and the real status is invisible. CSRF must run AFTER, because it reads the matched route's metadata to honour a route marked no_auth - PHP shipped exactly that bypass as dead code once, because the metadata was not set yet.
54 55 56 |
# File 'lib/tina4/middleware.rb', line 54 def pre_match_middleware global_middleware.select { |k| k.respond_to?(:pre_match?) && k.pre_match? } end |
.refuse(response) ⇒ Object
The false row of the return-value table, on its own.
A middleware that halts by returning false keeps the response it set; only a response still left default/empty becomes a 403. Public because per-route "filter" middleware (a 2-arg callable returning false, see Tina4::Route#run_middleware) must obey the SAME row as a before_* hook, and the rule should exist exactly once.
221 222 223 224 |
# File 'lib/tina4/middleware.rb', line 221 def refuse(response) forbid(response) if default_response?(response) response end |
.run_after(middleware_classes, request, response) ⇒ Object
Run all "after" hooks: block-based handlers, then class-based after_* methods (in definition order).
Signature matches Python/PHP/Node orchestrators: pass the list of middleware classes explicitly.
AFTER-ON-4xx RULE (M2, documented + consistent across all 4 frameworks): after_* ALWAYS run even when a before_* short-circuited with status >= 400 and the handler was skipped — so they can still add headers / logging. The dispatcher calls #run_after unconditionally after the before/handler block (including on the 4xx / throw halt path).
M2 — every after_* call is wrapped: a THROW is LOGGED and turns the response into a clean 500, then the REMAINING after_* still run (they may add headers/logging). Never an unhandled crash.
RETURN VALUES: an after_* hook shapes the response the same way a
before_* one does — a returned Tina4::Response BECOMES the response, a
returned [request, response] pair rebinds both. What it CANNOT do is
halt: the handler has already run, so there is nothing left to skip,
and stopping the remaining after_* would contradict the AFTER-ON-4xx
resilience rule above (they exist to add headers/logging on every path).
So false and a >= 400 status are inert here by design.
159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 |
# File 'lib/tina4/middleware.rb', line 159 def run_after(middleware_classes, request, response) origin = response # 1. Block-based after handlers (pattern-matched) after_handlers.each do |entry| next unless matches_pattern?(request.path, entry[:pattern]) begin entry[:handler].call(request, response) rescue StandardError, ScriptError => error middleware_500(response, "after handler", error) end end # 2. Class-based middleware: call every after_* method (definition order) middleware_classes.each do |klass| after_methods_for(klass).each do |method_name| begin result = klass.send(method_name, request, response) rescue StandardError, ScriptError => error middleware_500(response, "#{class_label(klass)}.#{method_name}", error) next end if result.is_a?(Tina4::Response) response = result elsif result.is_a?(Array) && result.length == 2 request, response = result end end end adopt_response(origin, response) unless response.equal?(origin) response end |
.run_before(middleware_classes, request, response) ⇒ Object
Run all "before" hooks: block-based handlers, then class-based before_* methods (in definition order).
Signature matches Python/PHP/Node orchestrators: pass the list of middleware classes explicitly.
THE RETURN-VALUE CONTRACT is #apply_before_result below — one table, applied to EVERY before_* hook at EVERY scope. Per-route middleware comes through this same method (Tina4::Route#run_middleware), so there is exactly one implementation of the table, not two.
M2 — visible-but-resilient: every before_* call is wrapped so a THROW never crashes the worker. On a throw the error is LOGGED and the response becomes a clean 500 (Server Error", "status":500), then processing halts (handler skipped) — deterministic, never an unhandled exception. after_* still run on either halt path (see the dispatcher / #run_after docstring).
Returns true on success, or false to halt the request (handler skipped).
89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 |
# File 'lib/tina4/middleware.rb', line 89 def run_before(middleware_classes, request, response) # The response object the CALLER holds. A hook may hand back a # different Response; on a halt that object IS the answer, so its state # is adopted onto this one before returning — otherwise the dispatcher # would serve the object it still has a reference to and the # short-circuit would silently vanish. origin = response # 1. Block-based before handlers (pattern-matched). These are a # Ruby-only surface (Python/PHP/Node have no block form) and keep # their historical "false halts" contract: a block's value is its # last expression, so reading a returned Response as a # short-circuit would fire on any block ending in a chainable # response call. before_handlers.each do |entry| next unless matches_pattern?(request.path, entry[:pattern]) begin result = entry[:handler].call(request, response) rescue StandardError, ScriptError => error middleware_500(response, "before handler", error) return false end return false if result == false end # 2. Class-based middleware: call every before_* method (definition order) middleware_classes.each do |klass| before_methods_for(klass).each do |method_name| begin result = klass.send(method_name, request, response) rescue StandardError, ScriptError => error middleware_500(response, "#{class_label(klass)}.#{method_name}", error) return false end halt, request, response = apply_before_result(result, request, response) next unless halt adopt_response(origin, response) unless response.equal?(origin) return false end end true end |
.use(klass) ⇒ Object
Register a class-based middleware globally. The class should define static before_* and/or after_* methods.
34 35 36 |
# File 'lib/tina4/middleware.rb', line 34 def use(klass) global_middleware << klass unless global_middleware.include?(klass) end |