Class: Otto::RouteHandlers::BaseHandler

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

Overview

Base class for all route handlers Provides common functionality and interface

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(route_definition, otto_instance = nil) ⇒ BaseHandler

Returns a new instance of BaseHandler.



17
18
19
20
# File 'lib/otto/route_handlers/base.rb', line 17

def initialize(route_definition, otto_instance = nil)
  @route_definition = route_definition
  @otto_instance    = otto_instance
end

Instance Attribute Details

#otto_instanceObject (readonly)

Returns the value of attribute otto_instance.



15
16
17
# File 'lib/otto/route_handlers/base.rb', line 15

def otto_instance
  @otto_instance
end

#route_definitionObject (readonly)

Returns the value of attribute route_definition.



15
16
17
# File 'lib/otto/route_handlers/base.rb', line 15

def route_definition
  @route_definition
end

Instance Method Details

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

Execute the route handler

Parameters:

  • env (Hash)

    Rack environment

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

    Additional parameters

Returns:

  • (Array)

    Rack response array



26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
# File 'lib/otto/route_handlers/base.rb', line 26

def call(env, extra_params = {})
  @start_time = Otto::Utils.now_in_μs
  req = otto_instance ? otto_instance.request_class.new(env) : Otto::Request.new(env)
  res = otto_instance ? otto_instance.response_class.new : Otto::Response.new

  begin
    setup_request_response(req, res, env, extra_params)
    result, context = invoke_target(req, res)

    handle_response(result, res, context) if route_definition.response_type != 'default'
  rescue StandardError => e
    handle_execution_error(e, env, req, res, @start_time)
  end

  finalize_response(res)
end

#finalize_response(res) ⇒ Array (protected)

Finalize response with the same processing as Route#call

Parameters:

  • res (Rack::Response)

    Response object

Returns:

  • (Array)

    Rack response array



169
170
171
172
# File 'lib/otto/route_handlers/base.rb', line 169

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

#handle_execution_error(error, env, _req, res, start_time) ⇒ Object (protected)

Handle errors during route execution

Parameters:

  • error (StandardError)

    The error that occurred

  • env (Hash)

    Rack environment

  • req (Rack::Request)

    Request object

  • res (Rack::Response)

    Response object

  • start_time (Integer)

    Start time in microseconds



65
66
67
68
69
70
71
72
73
74
75
# File 'lib/otto/route_handlers/base.rb', line 65

def handle_execution_error(error, env, _req, res, start_time)
  if otto_instance
    # Integrated context - let centralized error handler manage
    env['otto.handler'] = handler_name
    env['otto.handler_duration'] = Otto::Utils.now_in_μs - start_time
    raise error
  else
    # Direct testing context - handle locally
    handle_local_error(error, env, res)
  end
end

#handle_local_error(error, env, res) ⇒ Object (protected)

Handle errors locally for testing context

Parameters:

  • error (StandardError)

    The error that occurred

  • env (Hash)

    Rack environment

  • res (Rack::Response)

    Response object



81
82
83
84
85
86
87
88
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
# File 'lib/otto/route_handlers/base.rb', line 81

def handle_local_error(error, env, res)
  error_id = SecureRandom.hex(8)
  Otto.logger.error "[#{error_id}] #{error.class}: #{error.message}"
  Otto.logger.debug "[#{error_id}] Backtrace: #{error.backtrace.join("\n")}" if Otto.debug

  res.status = 500

  # Content negotiation for error response
  # Route's response_type takes precedence over Accept header
  route_def = env['otto.route_definition']
  wants_json = (route_def&.response_type == 'json') ||
               env['HTTP_ACCEPT'].to_s.include?('application/json')

  if wants_json
    res.headers['content-type'] = 'application/json'
    error_data = {
         error: 'Internal Server Error',
       message: 'Server error occurred. Check logs for details.',
      error_id: error_id,
    }
    res.write JSON.generate(error_data)
  else
    res.headers['content-type'] = 'text/plain'
    if Otto.env?(:dev, :development)
      res.write "Server error (ID: #{error_id}). Check logs for details."
    else
      res.write 'An error occurred. Please try again later.'
    end
  end

  # Security headers are not available without an otto_instance
  # (testing/local context). The RouteAuthWrapper handles security
  # headers when otto_instance is present.
end

#handle_response(result, response, context = {}) ⇒ Object (protected)

Handle response using appropriate response handler

Parameters:

  • result (Object)

    Result from route execution

  • response (Rack::Response)

    Response object

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

    Additional context for response handling



178
179
180
181
182
183
184
185
186
187
188
189
190
191
# File 'lib/otto/route_handlers/base.rb', line 178

def handle_response(result, response, context = {})
  response_type = route_definition.response_type

  # Get the appropriate response handler
  handler_class = case response_type
                  in 'json' then Otto::ResponseHandlers::JSONHandler
                  in 'redirect' then Otto::ResponseHandlers::RedirectHandler
                  in 'view' then Otto::ResponseHandlers::ViewHandler
                  in 'auto' then Otto::ResponseHandlers::AutoHandler
                  else Otto::ResponseHandlers::DefaultHandler
                  end

  handler_class.handle(result, response, context)
end

#handler_nameString (protected)

Format the handler name for logging

Returns:

  • (String)

    Handler name in format “ClassName#method_name”



118
119
120
# File 'lib/otto/route_handlers/base.rb', line 118

def handler_name
  "#{target_class.name}##{route_definition.method_name}"
end

#invoke_target(req, res) ⇒ Array (protected)

Template method for subclasses to implement their invocation logic

Parameters:

  • req (Rack::Request)

    Request object

  • res (Rack::Response)

    Response object

Returns:

  • (Array)

    [result, context] where context is a hash for handle_response

Raises:

  • (NotImplementedError)


55
56
57
# File 'lib/otto/route_handlers/base.rb', line 55

def invoke_target(req, res)
  raise NotImplementedError, 'Subclasses must implement #invoke_target'
end

#setup_request_response(req, res, env, extra_params) ⇒ Object (protected)

Setup request and response with the same extensions and processing as Route#call

Parameters:

  • req (Otto::Request)

    Request object

  • res (Otto::Response)

    Response object

  • env (Hash)

    Rack environment

  • extra_params (Hash)

    Additional parameters



127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
# File 'lib/otto/route_handlers/base.rb', line 127

def setup_request_response(req, res, env, extra_params)
  # Set request reference (helpers are already included in class)
  res.request = req

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

  # 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_instance.respond_to?(:security_config) && otto_instance.security_config
    otto_instance.security_config.security_headers.each do |header, value|
      res.headers[header] = value
    end
  end

  # Setup class extensions if target_class is available
  if target_class
    target_class.extend Otto::Route::ClassMethods
    target_class.otto = otto_instance if otto_instance
  end

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

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

#target_classClass (protected)

Get the target class, loading it safely

Returns:

  • (Class)

    The target class



47
48
49
# File 'lib/otto/route_handlers/base.rb', line 47

def target_class
  @target_class ||= Otto::Security::ConstantResolver.safe_const_get(route_definition.klass_name)
end