Class: Takagi::MiddlewareStack

Inherits:
Object
  • Object
show all
Includes:
Singleton
Defined in:
lib/takagi/middleware_stack.rb,
sig/takagi/middleware_stack.rbs

Overview

Middleware stack for processing CoAP requests

Provides a Rack-style middleware chain for request/response processing. Middleware can be configured via Takagi.config.middleware or programmatically.

Examples:

Programmatic configuration

stack = MiddlewareStack.instance
stack.use(Takagi::Middleware::Logging.new)
stack.use(Takagi::Middleware::Caching.new)

YAML configuration (takagi.yml)

middleware:
  enabled: true
  stack:
    - Logging
    - name: Caching
      options:
        ttl: 300

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initializeMiddlewareStack

Returns a new instance of MiddlewareStack.



30
31
32
33
34
35
# File 'lib/takagi/middleware_stack.rb', line 30

def initialize
  @logger = Takagi.logger
  @middlewares = []
  @router = Takagi::Router.instance
  @config_loaded = false
end

Instance Attribute Details

#middlewaresObject (readonly)

Returns the value of attribute middlewares.

Returns:

  • (Object)


28
29
30
# File 'lib/takagi/middleware_stack.rb', line 28

def middlewares
  @middlewares
end

#routerObject (readonly)

Returns the value of attribute router.



28
29
30
# File 'lib/takagi/middleware_stack.rb', line 28

def router
  @router
end

Instance Method Details

#call(request) ⇒ Takagi::Message::Outbound

Processes the request through the middleware stack and routes it

Parameters:

Returns:



68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
# File 'lib/takagi/middleware_stack.rb', line 68

def call(request)
  # Lazily load configuration on first request
  load_from_config unless @config_loaded
  Takagi::Hooks.emit(:middleware_before_call, request: request)

  # Define the core application logic (routing)
  app = lambda do |req|
    block, params = @router.find_route(req.method.to_s, req.uri.path)
    if block
      block.call(req, params)
    else
      req.to_response('4.04 Not Found', { error: 'not found' })
    end
  end

  # Build middleware chain (reverse order for proper execution)
  response = @middlewares.reverse.reduce(app) do |next_middleware, middleware|
    ->(req) { middleware.call(req, &next_middleware) }
  end.call(request)

  Takagi::Hooks.emit(:middleware_after_call, request: request, response: response)
  response
end

#clearObject

Clear all middleware (useful for testing)

Returns:

  • (Object)


93
94
95
# File 'lib/takagi/middleware_stack.rb', line 93

def clear
  @middlewares.clear
end

#instantiate_middleware(klass, options) ⇒ Object

Instantiate middleware with or without options

Parameters:

  • klass (Class)

    Middleware class

  • options (Hash)

    Options to pass to initializer

Returns:

  • (Object)


147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
# File 'lib/takagi/middleware_stack.rb', line 147

def instantiate_middleware(klass, options)
  if options.empty?
    # Try to instantiate without arguments
    klass.new
  else
    # Pass options to initializer
    # Support both keyword arguments and hash argument
    begin
      klass.new(**options)
    rescue ArgumentError
      # Fall back to positional hash argument
      klass.new(options)
    end
  end
rescue ArgumentError => e
  @logger.warn "Failed to instantiate #{klass}: #{e.message}. Trying without options..."
  klass.new
end

#load_from_configObject

Load middleware from Takagi configuration This is called lazily on first request to ensure config is loaded

Returns:

  • (Object)


39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
# File 'lib/takagi/middleware_stack.rb', line 39

def load_from_config
  return if @config_loaded
  return unless defined?(Takagi.config)

  @config_loaded = true

  # Check if middleware is globally disabled
  return unless Takagi.config.middleware.enabled

  # Load middleware stack from configuration
  Takagi.config.middleware.stack.each do |middleware_config|
    load_middleware(middleware_config)
  end

  @logger.debug "Loaded #{@middlewares.size} middleware(s)" if @middlewares.any?
rescue StandardError => e
  @logger.error "Failed to load middleware configuration: #{e.message}"
  @logger.debug e.backtrace.join("\n")
end

#load_middleware(config) ⇒ Object

Load and instantiate a middleware from configuration

Parameters:

  • config (Hash)

    Middleware configuration with :name and :options

Returns:

  • (Object)


107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
# File 'lib/takagi/middleware_stack.rb', line 107

def load_middleware(config)
  middleware_name = config[:name]
  options = config[:options] || {}

  # Resolve middleware class
  klass = resolve_middleware_class(middleware_name)
  return unless klass

  # Instantiate middleware with options
  middleware = instantiate_middleware(klass, options)
  use(middleware) if middleware

  @logger.debug "Loaded middleware: #{middleware_name} with options: #{options.inspect}"
rescue StandardError => e
  @logger.warn "Failed to load middleware #{middleware_name}: #{e.message}"
  @logger.debug e.backtrace.join("\n")
end

#reset!Object

Reset configuration loaded flag (useful for testing)

Returns:

  • (Object)


98
99
100
101
# File 'lib/takagi/middleware_stack.rb', line 98

def reset!
  @middlewares.clear
  @config_loaded = false
end

#resolve_middleware_class(name) ⇒ Object

Resolve middleware class from name Supports both short names (e.g., "Logging") and full names (e.g., "MyApp::CustomMiddleware")

Parameters:

  • name (Object)

Returns:

  • (Object)


127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
# File 'lib/takagi/middleware_stack.rb', line 127

def resolve_middleware_class(name)
  # Try Takagi::Middleware namespace first
  begin
    return Object.const_get("Takagi::Middleware::#{name}")
  rescue NameError
    # Not in Takagi::Middleware namespace
  end

  # Try as a full class name
  begin
    Object.const_get(name)
  rescue NameError
    @logger.warn "Middleware class not found: #{name}"
    nil
  end
end

#use(middleware) ⇒ Object

Adds a new middleware to the stack

Parameters:

  • middleware (Object)

    Middleware instance that responds to call

Returns:

  • (Object)


61
62
63
# File 'lib/takagi/middleware_stack.rb', line 61

def use(middleware)
  @middlewares << middleware
end