otto 2.9.0 → 2.10.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.github/dependabot.yml +5 -0
- data/.github/workflows/ci.yml +11 -8
- data/.github/workflows/claude-code-review.yml +38 -13
- data/.github/workflows/claude.yml +10 -8
- data/.github/workflows/code-smells.yml +5 -5
- data/.github/workflows/release-gem.yml +2 -2
- data/.github/workflows/ruby-lint.yml +3 -3
- data/.github/workflows/yardoc.yml +5 -5
- data/.gitignore +1 -5
- data/.rubocop_todo.yml +7 -5
- data/AGENTS.md +22 -1
- data/CHANGELOG.rst +203 -0
- data/Gemfile +3 -3
- data/Gemfile.lock +11 -15
- data/README.md +75 -37
- data/docs/README.md +166 -0
- data/docs/adr/README.md +16 -0
- data/docs/adr/adr-001-route-authentication-at-handler-boundary.md +38 -0
- data/docs/adr/adr-002-multi-strategy-authentication-and-authorization.md +50 -0
- data/docs/adr/adr-003-caddy-tls-route-based-integration.md +48 -0
- data/docs/adr/adr-004-separate-compatibility-from-security-maintenance.md +58 -0
- data/docs/guides/authentication.md +377 -0
- data/docs/guides/caddy-tls.md +205 -0
- data/docs/guides/configuration_freezing.md +146 -0
- data/docs/guides/enrichment.md +161 -0
- data/docs/guides/forwarded-authority.md +249 -0
- data/docs/guides/geo-country.md +168 -0
- data/docs/guides/ip_privacy.md +39 -0
- data/docs/guides/ipaddr-encoding-quirk.md +56 -0
- data/docs/guides/mcp.md +282 -0
- data/docs/guides/privacy.md +193 -0
- data/docs/guides/routing.md +181 -0
- data/docs/guides/structured_logging.md +281 -0
- data/docs/guides/testing-guide.md +391 -0
- data/docs/maintainers/github-actions.md +41 -0
- data/docs/maintainers/investigations/.gitignore +2 -0
- data/docs/migrating/v2.0.0.md +337 -0
- data/docs/reference/authentication.md +290 -0
- data/docs/reference/route-syntax.md +181 -0
- data/docs/reference/runtime-and-dependency-security.md +86 -0
- data/examples/advanced_routes/README.md +43 -57
- data/examples/authentication_strategies/README.md +37 -196
- data/examples/basic/README.md +24 -39
- data/examples/basic/config.ru +0 -1
- data/examples/caddy_tls_demo/README.md +8 -2
- data/examples/lambda_handlers/README.md +11 -2
- data/examples/mcp_demo/README.md +75 -161
- data/examples/mcp_demo/config.ru +1 -0
- data/examples/security_features/README.md +36 -234
- data/lib/otto/caddy_tls/localhost_guard.rb +22 -24
- data/lib/otto/core/configuration.rb +36 -22
- data/lib/otto/core/file_safety.rb +89 -31
- data/lib/otto/core/middleware_stack.rb +36 -36
- data/lib/otto/core/router.rb +62 -19
- data/lib/otto/env_keys.rb +20 -2
- data/lib/otto/mcp/auth/token.rb +10 -4
- data/lib/otto/mcp/core.rb +23 -5
- data/lib/otto/mcp/endpoint.rb +41 -0
- data/lib/otto/mcp/errors.rb +15 -0
- data/lib/otto/mcp/options.rb +292 -0
- data/lib/otto/mcp/protocol.rb +52 -22
- data/lib/otto/mcp/rate_limiting.rb +175 -97
- data/lib/otto/mcp/registry.rb +14 -14
- data/lib/otto/mcp/schema_validation.rb +20 -12
- data/lib/otto/mcp/server.rb +131 -32
- data/lib/otto/optional_dependency.rb +57 -0
- data/lib/otto/privacy/config.rb +10 -8
- data/lib/otto/security/authentication/route_auth_wrapper/role_authorization.rb +22 -5
- data/lib/otto/security/authentication/strategies/api_key_strategy.rb +181 -18
- data/lib/otto/security/authentication/strategies/permission_strategy.rb +1 -0
- data/lib/otto/security/authentication/strategies/role_strategy.rb +1 -0
- data/lib/otto/security/authentication/strategies/session_strategy.rb +1 -0
- data/lib/otto/security/authentication/strategy_result.rb +58 -28
- data/lib/otto/security/config.rb +328 -14
- data/lib/otto/security/configurator.rb +36 -6
- data/lib/otto/security/core.rb +19 -1
- data/lib/otto/security/middleware/ip_privacy_middleware.rb +105 -4
- data/lib/otto/security/middleware/rate_limit_middleware.rb +1 -6
- data/lib/otto/security/rate_limiter.rb +81 -46
- data/lib/otto/utils.rb +50 -0
- data/lib/otto/version.rb +1 -1
- data/lib/otto.rb +9 -11
- data/otto.gemspec +0 -2
- metadata +31 -41
- data/docs/.gitignore +0 -10
- data/docs/1108-STREAMING_ARCHITECTURE_ANALYSIS.md +0 -1105
- data/docs/1108-STREAMING_SUPPORT_SUMMARY.md +0 -376
- data/docs/enrichment.md +0 -128
- data/docs/geo-country.md +0 -181
- data/docs/ipaddr-encoding-quirk.md +0 -34
- data/docs/migrating/v2.0.0-pre1.md +0 -276
- data/docs/migrating/v2.0.0-pre2.md +0 -338
- data/docs/modern-authentication-authorization-landscape.md +0 -558
- data/docs/multi-strategy-authentication-design.md +0 -1401
- data/docs/reverse-proxy-network-services.md +0 -371
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
# Route syntax
|
|
2
|
+
|
|
3
|
+
This page is the compact contract for Otto's plain-text route definitions. It
|
|
4
|
+
covers the syntax needed to choose a route target and attach route options. For
|
|
5
|
+
application examples, start with the [routing guide](../guides/routing.md).
|
|
6
|
+
|
|
7
|
+
## Route line
|
|
8
|
+
|
|
9
|
+
A route line has this shape:
|
|
10
|
+
|
|
11
|
+
```text
|
|
12
|
+
VERB /path-pattern Target option=value option=value
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
For example:
|
|
16
|
+
|
|
17
|
+
```text
|
|
18
|
+
GET / App#index
|
|
19
|
+
GET /products/:id App#show_product response=view
|
|
20
|
+
POST /api/products ProductCreate response=json csrf=exempt
|
|
21
|
+
GET /admin Admin::Dashboard auth=session role=admin
|
|
22
|
+
GET /health &health_check
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
Whitespace separates the verb, path, target, and each option token. Option
|
|
26
|
+
values cannot contain unquoted whitespace. Values may contain `=` characters;
|
|
27
|
+
Otto splits each option at its first `=`.
|
|
28
|
+
|
|
29
|
+
## HTTP verbs and paths
|
|
30
|
+
|
|
31
|
+
The verb is normalized to uppercase. A path can contain named segments and a
|
|
32
|
+
splat:
|
|
33
|
+
|
|
34
|
+
```text
|
|
35
|
+
GET /products/:id Products#show
|
|
36
|
+
GET /assets/*path Assets#show
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
Named segments become route parameters. The splat is available under the
|
|
40
|
+
`"splat"` parameter name. Route matching is anchored to the complete path, and
|
|
41
|
+
Otto normalizes paths before matching, including trailing slashes and URL
|
|
42
|
+
encoding according to the router's path-normalization rules.
|
|
43
|
+
|
|
44
|
+
## Targets
|
|
45
|
+
|
|
46
|
+
The target determines how Otto invokes application code:
|
|
47
|
+
|
|
48
|
+
| Syntax | Kind | Invocation |
|
|
49
|
+
| --- | --- | --- |
|
|
50
|
+
| `App.index` | Class method | Calls `App.index(req, res)`. |
|
|
51
|
+
| `App#index` | Instance method | Builds `App.new(req, res)`, then calls `index`. |
|
|
52
|
+
| `Admin::Dashboard` | Logic class | Builds `Admin::Dashboard.new(strategy_result, params, locale)`, then runs its Logic lifecycle. |
|
|
53
|
+
| `&health_check` | Registered lambda | Looks up `"health_check"` in the boot-time `lambda_handlers` registry and calls it with `(req, res, extra_params)`. |
|
|
54
|
+
|
|
55
|
+
Class and instance targets must resolve to safe Ruby constants. Lambda targets
|
|
56
|
+
are registry keys, not Ruby constants: Otto does not evaluate route text or
|
|
57
|
+
resolve a lambda name dynamically.
|
|
58
|
+
|
|
59
|
+
### Logic classes
|
|
60
|
+
|
|
61
|
+
A bare class target is a Logic route. Logic classes receive a constrained
|
|
62
|
+
application context rather than the Rack request and environment:
|
|
63
|
+
|
|
64
|
+
```ruby
|
|
65
|
+
class ProductShow
|
|
66
|
+
def initialize(strategy_result, params, locale)
|
|
67
|
+
@context = strategy_result
|
|
68
|
+
@params = params
|
|
69
|
+
@locale = locale
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
def raise_concerns
|
|
73
|
+
# Load resources and perform resource-level authorization here.
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
def process
|
|
77
|
+
{ id: @params[:id], locale: @locale }
|
|
78
|
+
end
|
|
79
|
+
end
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
The route handler supplies the authenticated `StrategyResult` even for a public
|
|
83
|
+
route; public routes receive an anonymous result. Use a class or instance
|
|
84
|
+
handler when code needs direct access to cookies, headers, or the Rack request.
|
|
85
|
+
|
|
86
|
+
### Registered lambda handlers
|
|
87
|
+
|
|
88
|
+
Register lambdas before the first request:
|
|
89
|
+
|
|
90
|
+
```ruby
|
|
91
|
+
otto = Otto.new('routes', lambda_handlers: {
|
|
92
|
+
health_check: lambda do |req, res, _extra_params|
|
|
93
|
+
res['content-type'] = 'text/plain'
|
|
94
|
+
res.body = 'ok'
|
|
95
|
+
end,
|
|
96
|
+
})
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
The callable must accept three positional arguments. Registration validates the
|
|
100
|
+
name and callable arity at boot; a route referring to an unregistered handler
|
|
101
|
+
fails when it is invoked rather than evaluating arbitrary route text.
|
|
102
|
+
|
|
103
|
+
## Route options
|
|
104
|
+
|
|
105
|
+
Options are whitespace-delimited `key=value` tokens after the target.
|
|
106
|
+
Unknown well-formed options are retained for handlers and middleware that use
|
|
107
|
+
them. Malformed non-security options are ignored and logged.
|
|
108
|
+
|
|
109
|
+
### Response type
|
|
110
|
+
|
|
111
|
+
`response=` selects the response handler for non-default handler results:
|
|
112
|
+
|
|
113
|
+
| Option | Behavior |
|
|
114
|
+
| --- | --- |
|
|
115
|
+
| `response=default` | Default response behavior. This is the default. |
|
|
116
|
+
| `response=json` | JSON response handling. |
|
|
117
|
+
| `response=view` | View response handling. |
|
|
118
|
+
| `response=redirect` | Redirect response handling. |
|
|
119
|
+
| `response=auto` | Content-negotiated response handling. |
|
|
120
|
+
|
|
121
|
+
An unknown response type falls back to the default handler silently. A typo in
|
|
122
|
+
`response=` therefore changes behavior with no diagnostic; check the value here
|
|
123
|
+
rather than expecting a log line.
|
|
124
|
+
|
|
125
|
+
### CSRF
|
|
126
|
+
|
|
127
|
+
When CSRF protection is enabled, use the explicit exemption only for routes
|
|
128
|
+
whose request contract does not use browser credentials and therefore has an
|
|
129
|
+
independent protection model:
|
|
130
|
+
|
|
131
|
+
```text
|
|
132
|
+
POST /webhooks/provider Webhooks#receive csrf=exempt
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
`csrf=exempt` is the supported exemption token. A bare `csrf` or an empty CSRF
|
|
136
|
+
value is rejected as a malformed security option.
|
|
137
|
+
|
|
138
|
+
### Authentication and roles
|
|
139
|
+
|
|
140
|
+
Use `auth=` for one or more named authentication strategies and `role=` for
|
|
141
|
+
route-level role authorization:
|
|
142
|
+
|
|
143
|
+
```text
|
|
144
|
+
GET /account Account#show auth=session
|
|
145
|
+
GET /admin Admin::Dashboard auth=session role=admin
|
|
146
|
+
GET /editorial Editorial#show auth=session role=admin,editor
|
|
147
|
+
GET /api/data Api#show auth=session,api_key response=json
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
`auth=session,api_key` is OR logic: strategies run left to right, an
|
|
151
|
+
authenticated success stops the chain, and a later strategy can be tried after
|
|
152
|
+
a plain failure. `role=admin,editor` also uses OR logic: any listed role is
|
|
153
|
+
enough. See the [authentication guide](../guides/authentication.md).
|
|
154
|
+
|
|
155
|
+
Authentication and role options are security-gating options. They must use
|
|
156
|
+
lowercase `auth=`, `role=`, or `csrf=` with a non-empty value. Otto raises
|
|
157
|
+
`Otto::RouteDefinitionError` instead of silently treating a malformed token as
|
|
158
|
+
an unprotected route:
|
|
159
|
+
|
|
160
|
+
```text
|
|
161
|
+
# Rejected during route definition parsing:
|
|
162
|
+
GET /admin Admin#show auth
|
|
163
|
+
GET /admin Admin#show role=
|
|
164
|
+
POST /submit Form#save CSRF=exempt
|
|
165
|
+
```
|
|
166
|
+
|
|
167
|
+
## Configuration timing
|
|
168
|
+
|
|
169
|
+
Routes are loaded during `Otto.new`. Configuration remains available for
|
|
170
|
+
multi-step setup, but Otto freezes configuration on the first request in normal
|
|
171
|
+
operation. Register authentication strategies, lambda handlers, middleware,
|
|
172
|
+
helpers, and security settings before serving traffic.
|
|
173
|
+
|
|
174
|
+
## Related contracts
|
|
175
|
+
|
|
176
|
+
- [Routing guide](../guides/routing.md) — application-oriented examples.
|
|
177
|
+
- [Authentication guide](../guides/authentication.md) — strategy and
|
|
178
|
+
authorization behavior.
|
|
179
|
+
- [Configuration and lifecycle code](../../lib/otto/core/configuration.rb) and
|
|
180
|
+
[`RouteDefinition`](../../lib/otto/route_definition.rb) — implementation
|
|
181
|
+
source for the exact contract.
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
# Runtime and dependency security policy
|
|
2
|
+
|
|
3
|
+
This policy explains what Otto's Ruby and gem version declarations guarantee,
|
|
4
|
+
what they do not guarantee, and what applications using Otto must do to keep a
|
|
5
|
+
deployed bundle patched.
|
|
6
|
+
|
|
7
|
+
## Ruby compatibility and security maintenance
|
|
8
|
+
|
|
9
|
+
Otto separates **runtime compatibility** from **interpreter security
|
|
10
|
+
maintenance**. A blocking compatibility target must pass Otto's test suite;
|
|
11
|
+
provisional targets are exercised without blocking releases. Only the Ruby
|
|
12
|
+
project can provide security maintenance for the interpreter.
|
|
13
|
+
|
|
14
|
+
| Ruby version | Otto compatibility policy | CI status |
|
|
15
|
+
| --- | --- | --- |
|
|
16
|
+
| 3.2 | Retained as a compatibility target for now, despite upstream end of life | Blocking, with locked and freshly resolved dependencies |
|
|
17
|
+
| 3.3–3.4 | Supported compatibility targets | Blocking, with locked and freshly resolved dependencies |
|
|
18
|
+
| 3.5–4.0 | Accepted by the gem's Ruby version range, but compatibility remains provisional | Experimental and non-blocking, with locked and freshly resolved dependencies |
|
|
19
|
+
|
|
20
|
+
Ruby 3.2 reached upstream end of life on April 1, 2026. It receives no
|
|
21
|
+
interpreter security maintenance. Otto's continued compatibility testing cannot
|
|
22
|
+
correct vulnerabilities in Ruby 3.2 itself. Applications with a security
|
|
23
|
+
maintenance requirement should run Otto on a Ruby version that the Ruby project
|
|
24
|
+
currently maintains. Check the [Ruby branch maintenance
|
|
25
|
+
status](https://www.ruby-lang.org/en/downloads/branches/) when selecting a
|
|
26
|
+
runtime.
|
|
27
|
+
|
|
28
|
+
The `required_ruby_version` range in `otto.gemspec` controls whether RubyGems
|
|
29
|
+
may install Otto. It does not mean every version in that range has the same CI
|
|
30
|
+
or upstream security status. The table above is the support policy for the
|
|
31
|
+
current matrix.
|
|
32
|
+
|
|
33
|
+
## What dependency ranges mean
|
|
34
|
+
|
|
35
|
+
The runtime dependency ranges in `otto.gemspec` express the versions Otto
|
|
36
|
+
expects to be API-compatible with. Their lower bounds are compatibility
|
|
37
|
+
baselines, not a promise that every allowed version is free of known
|
|
38
|
+
vulnerabilities or still receives security fixes.
|
|
39
|
+
|
|
40
|
+
Otto may exclude a known-bad dependency release or raise a lower bound when a
|
|
41
|
+
vulnerability affects Otto users. Such a constraint is a targeted response, not
|
|
42
|
+
a substitute for auditing the complete resolved dependency graph. A future
|
|
43
|
+
advisory can make any previously acceptable version unsafe while it still
|
|
44
|
+
satisfies the gemspec.
|
|
45
|
+
|
|
46
|
+
Otto's committed `Gemfile.lock` makes maintainer development and CI resolution
|
|
47
|
+
reproducible. Bundler does not use that lockfile when Otto is installed as a
|
|
48
|
+
dependency of another application. Otto's locked and freshly resolved CI jobs
|
|
49
|
+
test compatibility; neither job selects or certifies a secure dependency set
|
|
50
|
+
for consumer applications.
|
|
51
|
+
|
|
52
|
+
## Consumer lockfile policy
|
|
53
|
+
|
|
54
|
+
The deployable application is responsible for the final dependency graph. An
|
|
55
|
+
application using Otto should:
|
|
56
|
+
|
|
57
|
+
1. Commit its `Gemfile.lock` and deploy that exact resolution.
|
|
58
|
+
2. Make [`bundler-audit`](https://github.com/rubysec/bundler-audit) available
|
|
59
|
+
in application CI, then run it with an updated advisory database on every
|
|
60
|
+
lockfile change, on a scheduled weekly job, and before each production
|
|
61
|
+
deployment:
|
|
62
|
+
|
|
63
|
+
```sh
|
|
64
|
+
bundle-audit check --update
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
3. Treat a relevant advisory as a release blocker. Update the affected gem and
|
|
68
|
+
re-run the application's tests and audit. For a conservative targeted
|
|
69
|
+
update, replace `GEM_NAME` with the affected gem:
|
|
70
|
+
|
|
71
|
+
```sh
|
|
72
|
+
bundle update --conservative GEM_NAME
|
|
73
|
+
bundle-audit check --update
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
4. Enable an automated dependency updater, or review `bundle outdated`
|
|
77
|
+
manually at least weekly, so patched releases reach the application
|
|
78
|
+
lockfile.
|
|
79
|
+
5. Audit the Ruby interpreter, operating system packages, and native libraries
|
|
80
|
+
separately. `bundler-audit` checks Ruby gems; it does not make an
|
|
81
|
+
end-of-life Ruby secure.
|
|
82
|
+
|
|
83
|
+
If the patched dependency version falls outside Otto's declared range,
|
|
84
|
+
applications should upgrade Otto when a compatible release is available and
|
|
85
|
+
report the blocked update to the maintainers. Do not assume that a successful
|
|
86
|
+
`bundle install` means the resolved bundle has no known vulnerabilities.
|
|
@@ -20,50 +20,54 @@ The example is organized to separate concerns:
|
|
|
20
20
|
- `app.rb`: Loader that requires all controller and logic files
|
|
21
21
|
- `app/controllers/`: Handler classes (`RoutesApp`, namespaced controllers)
|
|
22
22
|
- `app/logic/`: Business logic classes (simple, nested, namespaced)
|
|
23
|
-
|
|
23
|
+
|
|
24
24
|
|
|
25
25
|
## Key Features Demonstrated
|
|
26
26
|
|
|
27
27
|
### Response Types
|
|
28
28
|
Define how responses are formatted directly in routes:
|
|
29
29
|
```
|
|
30
|
-
GET /api/users
|
|
31
|
-
GET /
|
|
32
|
-
GET /
|
|
30
|
+
GET /api/users RoutesApp#list_users response=json
|
|
31
|
+
GET /dashboard RoutesApp#dashboard response=view
|
|
32
|
+
GET /login RoutesApp#login_redirect response=redirect
|
|
33
33
|
```
|
|
34
34
|
|
|
35
35
|
### Logic Classes
|
|
36
36
|
Route to specialized classes that encapsulate business logic:
|
|
37
37
|
```
|
|
38
|
-
GET /
|
|
39
|
-
GET /
|
|
38
|
+
GET /logic/simple SimpleLogic
|
|
39
|
+
GET /logic/data DataLogic response=json
|
|
40
40
|
```
|
|
41
41
|
|
|
42
|
+
The classes in `app/logic/` expose the logic used by these routes.
|
|
43
|
+
|
|
42
44
|
### CSRF Exemption
|
|
43
45
|
Mark routes that don't need CSRF tokens (APIs, webhooks):
|
|
44
46
|
```
|
|
45
|
-
POST /api/webhook
|
|
47
|
+
POST /api/webhook RoutesApp#webhook_handler csrf=exempt
|
|
46
48
|
```
|
|
47
49
|
|
|
48
50
|
### Namespaced Routing
|
|
49
51
|
Handle complex class hierarchies naturally:
|
|
50
52
|
```
|
|
51
|
-
GET /v2/dashboard
|
|
52
|
-
GET /admin
|
|
53
|
+
GET /logic/v2/dashboard V2::Logic::Dashboard response=view
|
|
54
|
+
GET /logic/admin Admin::Panel
|
|
53
55
|
```
|
|
54
56
|
|
|
55
57
|
### Custom Parameters
|
|
56
58
|
Add arbitrary key-value pairs for flexible routing:
|
|
57
59
|
```
|
|
58
|
-
GET /
|
|
60
|
+
GET /feature/flags RoutesApp#feature_flags feature=advanced mode=enabled
|
|
59
61
|
```
|
|
60
62
|
|
|
63
|
+
These are route configuration values, not query-string parameters.
|
|
64
|
+
|
|
61
65
|
### Lambda / Inline Route Handlers (Issue #41)
|
|
62
66
|
Route to a proc that you **pre-register** by name, using the `&` prefix:
|
|
63
67
|
```
|
|
64
68
|
GET /ping &health_check response=json
|
|
65
|
-
POST /
|
|
66
|
-
GET /go
|
|
69
|
+
POST /hooks/receive &receive_webhook response=json csrf=exempt
|
|
70
|
+
GET /go/dashboard &to_dashboard response=redirect
|
|
67
71
|
```
|
|
68
72
|
|
|
69
73
|
The `&name` token is a plain string key looked up (O(1)) in a registry you
|
|
@@ -104,67 +108,49 @@ loading**. A route naming an unregistered handler fails with a clear
|
|
|
104
108
|
instead of executing anything. The registered procs are, of course, trusted
|
|
105
109
|
code that you wrote.
|
|
106
110
|
|
|
107
|
-
|
|
108
|
-
|
|
111
|
+
This example enables CSRF protection in `config.rb`; the `routes` file marks its API and webhook examples with `csrf=exempt`.
|
|
112
|
+
|
|
113
|
+
## Run it
|
|
109
114
|
|
|
110
|
-
|
|
115
|
+
### Prerequisites
|
|
111
116
|
|
|
112
|
-
|
|
117
|
+
Run this example from an Otto source checkout with Ruby 3.2 through 4.0 and Bundler. `rackup` is a development dependency in the root `Gemfile`, so enable that optional group before installing the bundle.
|
|
113
118
|
|
|
114
119
|
```sh
|
|
120
|
+
git clone https://github.com/delano/otto.git
|
|
121
|
+
cd otto
|
|
122
|
+
bundle config set with development
|
|
123
|
+
bundle install
|
|
115
124
|
cd examples/advanced_routes
|
|
116
|
-
rackup config.ru
|
|
125
|
+
bundle exec rackup config.ru
|
|
117
126
|
```
|
|
118
127
|
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
```sh
|
|
122
|
-
ruby run.rb # Basic rackup
|
|
123
|
-
ruby puma.rb # Using puma server
|
|
124
|
-
ruby test.rb # For testing
|
|
125
|
-
```
|
|
128
|
+
The server listens on `http://localhost:9292`.
|
|
126
129
|
|
|
127
|
-
|
|
130
|
+
> **Current limitation:** This checked-in example does not finish booting: `app/logic/complex/business/handler.rb` attempts to declare `Complex` as a module, which conflicts with Ruby's built-in `Complex` class. Until that source issue is fixed, `rackup` exits before listening and the checks below cannot be run. They document the routes and expected responses configured by this example.
|
|
128
131
|
|
|
129
|
-
|
|
132
|
+
### Verify routes
|
|
130
133
|
|
|
131
|
-
|
|
134
|
+
In another terminal, run these checks against routes defined in `routes`:
|
|
132
135
|
|
|
133
136
|
```sh
|
|
134
137
|
# JSON response
|
|
135
|
-
curl http://localhost:9292/
|
|
138
|
+
curl -i http://localhost:9292/api/health
|
|
136
139
|
|
|
137
|
-
#
|
|
138
|
-
curl http://localhost:9292/
|
|
140
|
+
# HTML view response
|
|
141
|
+
curl -i http://localhost:9292/dashboard
|
|
139
142
|
|
|
140
|
-
#
|
|
141
|
-
curl http://localhost:9292/
|
|
143
|
+
# Registered lambda handler
|
|
144
|
+
curl -i http://localhost:9292/ping
|
|
142
145
|
|
|
143
|
-
#
|
|
144
|
-
curl http://localhost:9292/
|
|
146
|
+
# Redirect response; inspect the Location header
|
|
147
|
+
curl -i http://localhost:9292/go/dashboard
|
|
145
148
|
|
|
146
|
-
#
|
|
147
|
-
curl
|
|
149
|
+
# Route configuration values
|
|
150
|
+
curl -i http://localhost:9292/feature/flags
|
|
148
151
|
```
|
|
149
152
|
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
```
|
|
153
|
-
Listening on 127.0.0.1:9292, CTRL+C to stop
|
|
154
|
-
|
|
155
|
-
[JSON response]
|
|
156
|
-
GET /json/test 200 OK
|
|
157
|
-
Content-Type: application/json
|
|
158
|
-
{"status": "success", "data": {...}}
|
|
159
|
-
|
|
160
|
-
[Logic class routing]
|
|
161
|
-
GET /logic/simple 200 OK
|
|
162
|
-
{"processed": true, "input": "test"}
|
|
163
|
-
|
|
164
|
-
[Namespaced routing]
|
|
165
|
-
GET /logic/v2/dashboard 200 OK
|
|
166
|
-
{"version": "2.0", "dashboard": {...}}
|
|
167
|
-
```
|
|
153
|
+
Expect `200` responses for the first three and last commands. `/api/health` returns JSON containing `healthy`, `/dashboard` returns HTML containing `Dashboard`, `/ping` returns JSON containing `ok`, and `/feature/flags` returns JSON containing `advanced`. `/go/dashboard` returns `302` with `Location: /dashboard`.
|
|
168
154
|
|
|
169
155
|
## File Structure Details
|
|
170
156
|
|
|
@@ -191,9 +177,9 @@ The `routes` file is extensively commented to explain each feature:
|
|
|
191
177
|
- Review the `routes` file for syntax reference
|
|
192
178
|
- Examine handler methods to see request/response patterns
|
|
193
179
|
- Check logic classes for business logic encapsulation patterns
|
|
194
|
-
- Explore [Authentication](../authentication_strategies/) for protecting routes
|
|
195
|
-
- See [Security Features](../security_features/) for CSRF, validation, file uploads
|
|
180
|
+
- Explore [Authentication](../authentication_strategies/README.md) for protecting routes
|
|
181
|
+
- See [Security Features](../security_features/README.md) for CSRF, validation, and file uploads
|
|
196
182
|
|
|
197
183
|
## Further Reading
|
|
198
184
|
|
|
199
|
-
- [
|
|
185
|
+
- [Project README](../../README.md) - Installation and framework overview
|