Class: Otto::Route

Inherits:
Object
  • Object
show all
Defined in:
lib/otto/route.rb

Overview

Otto::Route

A Route is a definition of a URL path and the method to call when that path is requested. Each route represents a single line in a routes file.

Routes include built-in security features: - Class name validation to prevent code injection - Automatic security header injection - CSRF protection when enabled - Input validation and sanitization

e.g.

 GET   /uri/path      YourApp.method
 GET   /uri/path2     YourApp#method

Defined Under Namespace

Modules: ClassMethods

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(verb, path, definition) ⇒ Route

Initialize a new route with security validations

Parameters:

  • verb (String)

    HTTP verb (GET, POST, PUT, DELETE, etc.)

  • path (String)

    URL path pattern with optional parameters

  • definition (String)

    Class and method definition with optional key-value parameters Examples: “Class.method” (traditional) “Class#method” (traditional) “V2::Logic::AuthSession auth=authenticated response=redirect” (enhanced)

Raises:

  • (ArgumentError)

    if definition format is invalid or class name is unsafe



86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
# File 'lib/otto/route.rb', line 86

def initialize(verb, path, definition)
  pattern, keys = *compile(path)

  # Create immutable route definition
  @route_definition = Otto::RouteDefinition.new(verb, path, definition, pattern: pattern, keys: keys)

  # Resolve the class.
  # Lambda routes carry a registry KEY in klass_name, not a Ruby constant.
  # Skip constant resolution (it would raise on a lowercase/unregistered key
  # and the loader would silently drop the route).
  @klass = if @route_definition.kind == :lambda
             nil
           else
             Otto::Security::ConstantResolver.safe_const_get(@route_definition.klass_name)
           end
end

Instance Attribute Details

#klassClass (readonly)

Returns The resolved class object.

Returns:

  • (Class)

    The resolved class object



72
73
74
# File 'lib/otto/route.rb', line 72

def klass
  @klass
end

#ottoObject

Returns the value of attribute otto.



74
75
76
# File 'lib/otto/route.rb', line 74

def otto
  @otto
end

#route_definitionOtto::RouteDefinition (readonly)

Returns The immutable route definition.

Returns:



69
70
71
# File 'lib/otto/route.rb', line 69

def route_definition
  @route_definition
end

Instance Method Details

#call(env, extra_params = {}) ⇒ Array

Execute the route by calling the associated class method

This method handles the complete request/response cycle with built-in security: - Processes parameters through the security layer - Adds configured security headers to the response - Extends request/response with security helpers when enabled - Provides CSRF and validation helpers to the target class

Parameters:

  • env (Hash)

    Rack environment hash

  • extra_params (Hash) (defaults to: {})

    Additional parameters to merge (default: {})

Returns:

  • (Array)

    Rack response array [status, headers, body]



147
148
149
150
151
152
153
154
155
156
157
158
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
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
# File 'lib/otto/route.rb', line 147

def call(env, extra_params = {})
  extra_params ||= {}

  # Pluggable route handler factory (Phase 4). The handler owns
  # request/response construction and decoration — param merging,
  # indifferent access, security headers, CSRF/validation helpers all
  # happen once in BaseHandler#setup_request_response. Building them here
  # too would duplicate that work on objects that get discarded (issue #189).
  if otto&.route_handler_factory
    # Make security config, route definition, and options available to
    # middleware and handlers before delegating, so wrappers that run
    # ahead of the handler's own setup (RouteAuthWrapper, the centralized
    # error handler) can see them.
    env['otto.security_config'] = otto.security_config if otto.respond_to?(:security_config) && otto.security_config
    env['otto.route_definition'] = @route_definition
    env['otto.route_options'] = @route_definition.options

    handler = otto.route_handler_factory.create_handler(@route_definition, otto)
    return handler.call(env, extra_params)
  end

  # Fallback to legacy behavior for backward compatibility. Build req/res
  # before touching env, preserving the exact ordering this path always
  # had — a custom request_class/response_class#initialize that reads env
  # must keep seeing it unpopulated, same as before #189 (review follow-up).
  req         = otto.request_class.new(env)
  res         = otto.response_class.new
  res.request = req

  # Make security config available to response helpers
  env['otto.security_config'] = otto.security_config if otto.respond_to?(:security_config) && otto.security_config

  # Make route definition and options available to middleware and handlers
  env['otto.route_definition'] = @route_definition
  env['otto.route_options'] = @route_definition.options

  # Process parameters through security layer
  req.params.merge! extra_params
  req.params.replace Otto::Static.indifferent_params(req.params)

  # Add security headers
  if otto.respond_to?(:security_config) && otto.security_config
    otto.security_config.security_headers.each do |header, value|
      res.headers[header] = value
    end
  end

  # No target class for lambda routes (klass is nil); skip class extension.
  if klass
    klass.extend Otto::Route::ClassMethods
    klass.otto = otto
  end

  # Add security helpers if CSRF is enabled
  if otto.respond_to?(:security_config) && otto.security_config&.csrf_enabled?
    res.extend Otto::Security::CSRFHelpers
  end

  # Add validation helpers
  res.extend Otto::Security::ValidationHelpers

  inst = nil
  result = case kind
           when :instance
             inst = klass.new req, res
             inst.send(name)
           when :class
             klass.send(name, req, res)
           else
             raise "Unsupported kind for #{definition}: #{kind}"
           end

  # Handle response based on route options
  response_type = @route_definition.response_type
  if response_type != 'default'
    context = {
      logic_instance: (kind == :instance ? inst : nil),
         status_code: nil,
       redirect_path: nil,
    }

    Otto::ResponseHandlers::HandlerFactory.handle_response(result, res, response_type, context)
  end

  res.body = [res.body] unless res.body.respond_to?(:each)
  res.finish
end

#definitionObject



112
113
114
# File 'lib/otto/route.rb', line 112

def definition
  @route_definition.definition
end

#keysObject



120
121
122
# File 'lib/otto/route.rb', line 120

def keys
  @route_definition.keys
end

#kindObject



128
129
130
# File 'lib/otto/route.rb', line 128

def kind
  @route_definition.kind
end

#nameObject



124
125
126
# File 'lib/otto/route.rb', line 124

def name
  @route_definition.method_name
end

#pathObject



108
109
110
# File 'lib/otto/route.rb', line 108

def path
  @route_definition.path
end

#patternObject



116
117
118
# File 'lib/otto/route.rb', line 116

def pattern
  @route_definition.pattern
end

#route_optionsObject



132
133
134
# File 'lib/otto/route.rb', line 132

def route_options
  @route_definition.options
end

#verbObject

Delegate common methods to route_definition for backward compatibility



104
105
106
# File 'lib/otto/route.rb', line 104

def verb
  @route_definition.verb
end