Class: Tina4::Middleware

Inherits:
Object
  • Object
show all
Defined in:
lib/tina4/middleware.rb

Class Method Summary collapse

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_handlersObject



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_handlersObject



6
7
8
# File 'lib/tina4/middleware.rb', line 6

def before_handlers
  @before_handlers ||= []
end

.clear!Object



38
39
40
41
42
# File 'lib/tina4/middleware.rb', line 38

def clear!
  @before_handlers = []
  @after_handlers = []
  @global_middleware = []
end

.get_globalObject

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_middlewareObject

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.



143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
# File 'lib/tina4/middleware.rb', line 143

def middleware_500(response, label, error)
  begin
    Tina4::Log.error(
      "Middleware #{label} raised #{error.class.name}: #{error.message}"
    )
  rescue StandardError
    begin
      $stderr.puts("Middleware #{label} raised #{error.class.name}: #{error.message}")
      $stderr.flush
    rescue StandardError
      # never let logging break the worker
    end
  end
  response.json({ error: "Internal Server Error", status: 500 }, 500)
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.



111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
# File 'lib/tina4/middleware.rb', line 111

def run_after(middleware_classes, request, 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?(Array) && result.length == 2
        request, response = result
      end
    end
  end
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.

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. A before_* that sets status >= 400 also halts (the existing 4xx short-circuit). 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).



59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
# File 'lib/tina4/middleware.rb', line 59

def run_before(middleware_classes, request, response)
  # 1. Block-based before handlers (pattern-matched)
  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
      # Support returning [request, response] (Python convention) or false to halt
      if result == false
        return false
      elsif result.is_a?(Array) && result.length == 2
        request, response = result
        # If response already has a non-2xx status, halt processing
        return false if response.status_code >= 400
      end
    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