takagi 0.1.0 → 1.1.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/.rubocop.yml +70 -7
- data/.yard/templates/default/layout/html/layout.erb +34 -0
- data/AGENTS.md +16 -0
- data/CHANGELOG.md +158 -1
- data/CODE_OF_CONDUCT.md +1 -1
- data/README.md +590 -23
- data/ROADMAP.md +55 -0
- data/Rakefile +4 -4
- data/Steepfile +39 -0
- data/bin/takagi-dev +159 -0
- data/docs/FIRST_PLUGIN_GUIDE.md +224 -0
- data/docs/HOOKS.md +31 -0
- data/examples/client_lifecycle_example.rb +118 -0
- data/examples/cloud_gateway_app.rb +217 -0
- data/examples/nested_api_app.rb +258 -0
- data/examples/simple_device_app.rb +71 -0
- data/examples/takagi.yml +138 -0
- data/lib/takagi/application.rb +256 -0
- data/lib/takagi/base/middleware_management.rb +39 -0
- data/lib/takagi/base/plugin_management.rb +75 -0
- data/lib/takagi/base/reactor_management.rb +104 -0
- data/lib/takagi/base/server_lifecycle.rb +156 -0
- data/lib/takagi/base.rb +103 -11
- data/lib/takagi/branding.rb +88 -0
- data/lib/takagi/cbor/decoder.rb +385 -0
- data/lib/takagi/cbor/encoder.rb +260 -0
- data/lib/takagi/cbor/error.rb +17 -0
- data/lib/takagi/cbor/version.rb +9 -0
- data/lib/takagi/client/response.rb +236 -0
- data/lib/takagi/client.rb +265 -0
- data/lib/takagi/client_base.rb +204 -0
- data/lib/takagi/coap/code_helpers.rb +190 -0
- data/lib/takagi/coap/registries/base.rb +165 -0
- data/lib/takagi/coap/registries/content_format.rb +71 -0
- data/lib/takagi/coap/registries/message_type.rb +69 -0
- data/lib/takagi/coap/registries/method.rb +38 -0
- data/lib/takagi/coap/registries/option.rb +71 -0
- data/lib/takagi/coap/registries/response.rb +93 -0
- data/lib/takagi/coap/registries/signaling.rb +34 -0
- data/lib/takagi/coap/signaling.rb +10 -0
- data/lib/takagi/coap.rb +37 -0
- data/lib/takagi/composite_router.rb +186 -0
- data/lib/takagi/config.rb +337 -0
- data/lib/takagi/controller/resource_allocator.rb +164 -0
- data/lib/takagi/controller/thread_pool.rb +144 -0
- data/lib/takagi/controller.rb +319 -0
- data/lib/takagi/core/attribute_set.rb +128 -0
- data/lib/takagi/discovery/core_link_format.rb +137 -0
- data/lib/takagi/errors.rb +536 -0
- data/lib/takagi/event_bus/address_prefix.rb +142 -0
- data/lib/takagi/event_bus/async_executor.rb +235 -0
- data/lib/takagi/event_bus/coap_bridge.rb +208 -0
- data/lib/takagi/event_bus/future.rb +153 -0
- data/lib/takagi/event_bus/lru_cache.rb +157 -0
- data/lib/takagi/event_bus/message_buffer.rb +237 -0
- data/lib/takagi/event_bus/observer_cleanup.rb +110 -0
- data/lib/takagi/event_bus/scope.rb +74 -0
- data/lib/takagi/event_bus.rb +594 -0
- data/lib/takagi/helpers.rb +88 -0
- data/lib/takagi/hooks.rb +82 -0
- data/lib/takagi/initializer.rb +18 -0
- data/lib/takagi/logger.rb +15 -6
- data/lib/takagi/message/base.rb +155 -0
- data/lib/takagi/message/deduplication_cache.rb +84 -0
- data/lib/takagi/message/inbound.rb +147 -0
- data/lib/takagi/message/outbound.rb +223 -0
- data/lib/takagi/message/request.rb +158 -0
- data/lib/takagi/message/retransmission_manager.rb +193 -0
- data/lib/takagi/middleware/authentication.rb +19 -0
- data/lib/takagi/middleware/caching.rb +23 -0
- data/lib/takagi/middleware/debugging.rb +16 -0
- data/lib/takagi/middleware/logging.rb +14 -0
- data/lib/takagi/middleware/metrics.rb +440 -0
- data/lib/takagi/middleware/rate_limiting.rb +24 -0
- data/lib/takagi/middleware_stack.rb +166 -0
- data/lib/takagi/network/base.rb +76 -0
- data/lib/takagi/network/framing/tcp.rb +222 -0
- data/lib/takagi/network/framing/udp.rb +110 -0
- data/lib/takagi/network/registry.rb +72 -0
- data/lib/takagi/network/tcp.rb +60 -0
- data/lib/takagi/network/tcp_sender.rb +21 -0
- data/lib/takagi/network/udp.rb +61 -0
- data/lib/takagi/network/udp_sender.rb +20 -0
- data/lib/takagi/observable/emitter.rb +62 -0
- data/lib/takagi/observable/reactor.rb +488 -0
- data/lib/takagi/observable/registry.rb +122 -0
- data/lib/takagi/observe_registry.rb +10 -0
- data/lib/takagi/observer/client.rb +68 -0
- data/lib/takagi/observer/registry.rb +137 -0
- data/lib/takagi/observer/sender.rb +39 -0
- data/lib/takagi/observer/watcher.rb +43 -0
- data/lib/takagi/plugin.rb +313 -0
- data/lib/takagi/profiles.rb +176 -0
- data/lib/takagi/reactor.rb +23 -0
- data/lib/takagi/reactor_registry.rb +64 -0
- data/lib/takagi/registry/base.rb +268 -0
- data/lib/takagi/response_builder.rb +141 -0
- data/lib/takagi/router/metadata_extractor.rb +133 -0
- data/lib/takagi/router/route_matcher.rb +83 -0
- data/lib/takagi/router.rb +284 -25
- data/lib/takagi/serialization/base.rb +102 -0
- data/lib/takagi/serialization/cbor_serializer.rb +92 -0
- data/lib/takagi/serialization/json_serializer.rb +96 -0
- data/lib/takagi/serialization/octet_stream_serializer.rb +82 -0
- data/lib/takagi/serialization/registry.rb +187 -0
- data/lib/takagi/serialization/text_serializer.rb +87 -0
- data/lib/takagi/serialization.rb +117 -0
- data/lib/takagi/server/multi.rb +41 -0
- data/lib/takagi/server/registry.rb +71 -0
- data/lib/takagi/server/tcp.rb +249 -0
- data/lib/takagi/server/udp.rb +139 -0
- data/lib/takagi/server/udp_worker.rb +174 -0
- data/lib/takagi/server.rb +1 -31
- data/lib/takagi/server_registry.rb +10 -0
- data/lib/takagi/tcp_client.rb +142 -0
- data/lib/takagi/version.rb +2 -1
- data/lib/takagi.rb +24 -3
- data/sig/takagi/application.rbs +48 -0
- data/sig/takagi/base/middleware_management.rbs +33 -0
- data/sig/takagi/base/reactor_management.rbs +52 -0
- data/sig/takagi/base/server_lifecycle.rbs +54 -0
- data/sig/takagi/base.rbs +48 -0
- data/sig/takagi/cbor/decoder.rbs +171 -0
- data/sig/takagi/cbor/encoder.rbs +146 -0
- data/sig/takagi/cbor/error.rbs +19 -0
- data/sig/takagi/cbor/version.rbs +7 -0
- data/sig/takagi/client/response.rbs +148 -0
- data/sig/takagi/client.rbs +119 -0
- data/sig/takagi/client_base.rbs +135 -0
- data/sig/takagi/coap/code_helpers.rbs +91 -0
- data/sig/takagi/coap/registries/base.rbs +95 -0
- data/sig/takagi/coap/registries/content_format.rbs +47 -0
- data/sig/takagi/coap/registries/message_type.rbs +53 -0
- data/sig/takagi/coap/registries/method.rbs +27 -0
- data/sig/takagi/coap/registries/option.rbs +43 -0
- data/sig/takagi/coap/registries/response.rbs +52 -0
- data/sig/takagi/coap.rbs +24 -0
- data/sig/takagi/composite_router.rbs +46 -0
- data/sig/takagi/config.rbs +134 -0
- data/sig/takagi/controller.rbs +73 -0
- data/sig/takagi/core/attribute_set.rbs +57 -0
- data/sig/takagi/discovery/core_link_format.rbs +50 -0
- data/sig/takagi/event_bus/address_prefix.rbs +78 -0
- data/sig/takagi/event_bus/async_executor.rbs +88 -0
- data/sig/takagi/event_bus/coap_bridge.rbs +93 -0
- data/sig/takagi/event_bus/future.rbs +78 -0
- data/sig/takagi/event_bus/lru_cache.rbs +86 -0
- data/sig/takagi/event_bus/message_buffer.rbs +133 -0
- data/sig/takagi/event_bus/observer_cleanup.rbs +62 -0
- data/sig/takagi/event_bus.rbs +320 -0
- data/sig/takagi/helpers.rbs +34 -0
- data/sig/takagi/initializer.rbs +9 -0
- data/sig/takagi/logger.rbs +17 -0
- data/sig/takagi/message/base.rbs +64 -0
- data/sig/takagi/message/deduplication_cache.rbs +49 -0
- data/sig/takagi/message/inbound.rbs +76 -0
- data/sig/takagi/message/outbound.rbs +48 -0
- data/sig/takagi/message/request.rbs +32 -0
- data/sig/takagi/message/retransmission_manager.rbs +76 -0
- data/sig/takagi/middleware/authentication.rbs +11 -0
- data/sig/takagi/middleware/caching.rbs +13 -0
- data/sig/takagi/middleware/debugging.rbs +9 -0
- data/sig/takagi/middleware/logging.rbs +7 -0
- data/sig/takagi/middleware/metrics.rbs +15 -0
- data/sig/takagi/middleware/rate_limiting.rbs +13 -0
- data/sig/takagi/middleware_stack.rbs +69 -0
- data/sig/takagi/network/tcp_sender.rbs +10 -0
- data/sig/takagi/network/udp_sender.rbs +14 -0
- data/sig/takagi/observe_registry.rbs +36 -0
- data/sig/takagi/observer/client.rbs +36 -0
- data/sig/takagi/observer/sender.rbs +12 -0
- data/sig/takagi/observer/watcher.rbs +18 -0
- data/sig/takagi/profiles.rbs +33 -0
- data/sig/takagi/reactor.rbs +20 -0
- data/sig/takagi/reactor_registry.rbs +14 -0
- data/sig/takagi/response_builder.rbs +12 -0
- data/sig/takagi/router/metadata_extractor.rbs +71 -0
- data/sig/takagi/router/route_matcher.rbs +43 -0
- data/sig/takagi/router.rbs +166 -0
- data/sig/takagi/serialization.rbs +32 -0
- data/sig/takagi/server/multi.rbs +16 -0
- data/sig/takagi/server/tcp.rbs +42 -0
- data/sig/takagi/server/udp.rbs +52 -0
- data/sig/takagi/server/udp_worker.rbs +42 -0
- data/sig/takagi/server.rbs +4 -0
- data/sig/takagi/server_registry.rbs +71 -0
- data/sig/takagi/tcp_client.rbs +23 -0
- data/sig/takagi/version.rbs +5 -0
- data/takagi.gemspec +37 -35
- metadata +204 -31
- data/.idea/.gitignore +0 -8
- data/.idea/misc.xml +0 -4
- data/.idea/modules.xml +0 -8
- data/.idea/takagi.iml +0 -81
- data/.idea/vcs.xml +0 -6
- data/lib/takagi/message.rb +0 -75
|
@@ -0,0 +1,256 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Takagi
|
|
4
|
+
# Application class for modular controller-based apps
|
|
5
|
+
#
|
|
6
|
+
# Application provides a centralized way to mount and manage multiple
|
|
7
|
+
# controllers, auto-load controller files, and run the server.
|
|
8
|
+
#
|
|
9
|
+
# @example Simple application
|
|
10
|
+
# class MyApp < Takagi::Application
|
|
11
|
+
# configure do
|
|
12
|
+
# load_controllers TelemetryController, ConfigController
|
|
13
|
+
# end
|
|
14
|
+
# end
|
|
15
|
+
#
|
|
16
|
+
# MyApp.run!(port: 5683)
|
|
17
|
+
#
|
|
18
|
+
# @example With auto-loading
|
|
19
|
+
# class MyApp < Takagi::Application
|
|
20
|
+
# configure do
|
|
21
|
+
# auto_load 'app/controllers/**/*_controller.rb'
|
|
22
|
+
# end
|
|
23
|
+
# end
|
|
24
|
+
#
|
|
25
|
+
# MyApp.run!
|
|
26
|
+
class Application
|
|
27
|
+
# Internal controller for CoRE Link Format discovery endpoint
|
|
28
|
+
# Automatically mounted by Application
|
|
29
|
+
class DiscoveryController < Controller
|
|
30
|
+
@@app_router = nil
|
|
31
|
+
|
|
32
|
+
def self.app_router=(router)
|
|
33
|
+
@@app_router = router
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def self.app_router
|
|
37
|
+
@@app_router
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
configure do
|
|
41
|
+
mount '/.well-known'
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
get '/core', metadata: {
|
|
45
|
+
rt: 'core.discovery',
|
|
46
|
+
if: 'core.rd',
|
|
47
|
+
ct: Discovery::CoreLinkFormat::CONTENT_FORMAT,
|
|
48
|
+
discovery: true,
|
|
49
|
+
title: 'Resource Discovery'
|
|
50
|
+
} do |req|
|
|
51
|
+
# Get the composite router from the application
|
|
52
|
+
app_router = DiscoveryController.app_router
|
|
53
|
+
payload = Discovery::CoreLinkFormat.generate(router: app_router, request: req)
|
|
54
|
+
req.to_response(
|
|
55
|
+
'2.05 Content',
|
|
56
|
+
payload,
|
|
57
|
+
options: { CoAP::Registries::Option::CONTENT_FORMAT => Discovery::CoreLinkFormat::CONTENT_FORMAT }
|
|
58
|
+
)
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
class << self
|
|
63
|
+
# Get the application's composite router
|
|
64
|
+
#
|
|
65
|
+
# @return [CompositeRouter] The application's router
|
|
66
|
+
def router
|
|
67
|
+
@router ||= CompositeRouter.new
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
# Get the application's configuration
|
|
71
|
+
#
|
|
72
|
+
# @return [Hash] Configuration hash
|
|
73
|
+
def config
|
|
74
|
+
@config ||= {
|
|
75
|
+
controllers: [],
|
|
76
|
+
auto_load_patterns: [],
|
|
77
|
+
allocation_mode: :automatic, # :manual or :automatic
|
|
78
|
+
total_threads: nil # For automatic mode
|
|
79
|
+
}
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
# Configure the application
|
|
83
|
+
#
|
|
84
|
+
# @yield Block for configuration DSL
|
|
85
|
+
#
|
|
86
|
+
# @example
|
|
87
|
+
# configure do
|
|
88
|
+
# load_controllers TelemetryController, ConfigController
|
|
89
|
+
# auto_load 'app/controllers/**/*_controller.rb'
|
|
90
|
+
# end
|
|
91
|
+
def configure(&block)
|
|
92
|
+
ConfigContext.new(self).instance_eval(&block) if block
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
# Load and mount all registered controllers
|
|
96
|
+
#
|
|
97
|
+
# @return [void]
|
|
98
|
+
def load_controllers!
|
|
99
|
+
# Load auto-discovered controllers
|
|
100
|
+
auto_load_controllers! if config[:auto_load_patterns].any?
|
|
101
|
+
|
|
102
|
+
# Mount discovery controller first (so it's available at /.well-known/core)
|
|
103
|
+
# Store reference to composite router for discovery endpoint
|
|
104
|
+
DiscoveryController.app_router = router
|
|
105
|
+
router.mount(DiscoveryController)
|
|
106
|
+
|
|
107
|
+
# Mount all registered controllers
|
|
108
|
+
config[:controllers].each do |controller_class|
|
|
109
|
+
router.mount(controller_class)
|
|
110
|
+
end
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
# Start worker thread pools for all controllers
|
|
114
|
+
#
|
|
115
|
+
# Allocates thread pool resources based on allocation_mode:
|
|
116
|
+
# - :manual - Controllers use their explicit thread_count or profile settings
|
|
117
|
+
# - :automatic - Divides total_threads proportionally by profile weights
|
|
118
|
+
#
|
|
119
|
+
# @return [void]
|
|
120
|
+
def start_all_workers!
|
|
121
|
+
# Use app config first, fall back to global config
|
|
122
|
+
mode = config[:allocation_mode] || Takagi.config.allocation.mode
|
|
123
|
+
total_threads = config[:total_threads] || Takagi.config.allocation.total_threads
|
|
124
|
+
controllers_to_allocate = config[:controllers] + [DiscoveryController]
|
|
125
|
+
|
|
126
|
+
# Calculate allocations based on mode
|
|
127
|
+
allocations = Controller::ResourceAllocator.allocate(
|
|
128
|
+
controllers: controllers_to_allocate,
|
|
129
|
+
mode: mode,
|
|
130
|
+
total_threads: total_threads,
|
|
131
|
+
protocol: :tcp # TCP uses threads, UDP uses processes
|
|
132
|
+
)
|
|
133
|
+
|
|
134
|
+
# Validate allocations if automatic mode with total_threads specified
|
|
135
|
+
if mode == :automatic && total_threads
|
|
136
|
+
Controller::ResourceAllocator.validate!(allocations, total_threads: total_threads)
|
|
137
|
+
end
|
|
138
|
+
|
|
139
|
+
# Start each controller's thread pool
|
|
140
|
+
allocations.each do |controller_class, allocation|
|
|
141
|
+
threads = allocation[:threads]
|
|
142
|
+
next unless threads && threads > 0
|
|
143
|
+
|
|
144
|
+
controller_class.start_workers!(
|
|
145
|
+
threads: threads,
|
|
146
|
+
name: controller_class.name.split('::').last
|
|
147
|
+
)
|
|
148
|
+
|
|
149
|
+
Takagi.logger.info "Allocated #{threads} threads to #{controller_class.name} (mode: #{mode})"
|
|
150
|
+
end
|
|
151
|
+
end
|
|
152
|
+
|
|
153
|
+
# Shutdown all controller worker thread pools
|
|
154
|
+
#
|
|
155
|
+
# @return [void]
|
|
156
|
+
def shutdown_all_workers!
|
|
157
|
+
all_controllers = config[:controllers] + [DiscoveryController]
|
|
158
|
+
|
|
159
|
+
all_controllers.each do |controller_class|
|
|
160
|
+
controller_class.shutdown_workers! if controller_class.workers_running?
|
|
161
|
+
end
|
|
162
|
+
end
|
|
163
|
+
|
|
164
|
+
# Run the application server
|
|
165
|
+
#
|
|
166
|
+
# @param options [Hash] Server options (port, protocols, etc.)
|
|
167
|
+
# @return [void]
|
|
168
|
+
#
|
|
169
|
+
# @example
|
|
170
|
+
# MyApp.run!(port: 5683, protocols: [:udp, :tcp])
|
|
171
|
+
def run!(**options)
|
|
172
|
+
# Load all controllers
|
|
173
|
+
load_controllers!
|
|
174
|
+
|
|
175
|
+
# Start controller worker pools
|
|
176
|
+
start_all_workers!
|
|
177
|
+
|
|
178
|
+
# Use the composite router instead of global singleton
|
|
179
|
+
options[:router] = router
|
|
180
|
+
|
|
181
|
+
# Delegate to server lifecycle (same as Takagi::Base)
|
|
182
|
+
Base::ServerLifecycle.run!(**options)
|
|
183
|
+
end
|
|
184
|
+
|
|
185
|
+
# Get all loaded controller classes
|
|
186
|
+
#
|
|
187
|
+
# @return [Array<Class>] List of controller classes
|
|
188
|
+
def controllers
|
|
189
|
+
config[:controllers]
|
|
190
|
+
end
|
|
191
|
+
|
|
192
|
+
private
|
|
193
|
+
|
|
194
|
+
# Auto-load controllers from file patterns
|
|
195
|
+
#
|
|
196
|
+
# @return [void]
|
|
197
|
+
def auto_load_controllers!
|
|
198
|
+
config[:auto_load_patterns].each do |pattern|
|
|
199
|
+
Dir.glob(pattern).each do |file|
|
|
200
|
+
require_relative file
|
|
201
|
+
end
|
|
202
|
+
end
|
|
203
|
+
end
|
|
204
|
+
end
|
|
205
|
+
|
|
206
|
+
# Configuration DSL context for Application
|
|
207
|
+
class ConfigContext
|
|
208
|
+
def initialize(app_class)
|
|
209
|
+
@app = app_class
|
|
210
|
+
end
|
|
211
|
+
|
|
212
|
+
# Load specific controller classes
|
|
213
|
+
#
|
|
214
|
+
# @param controllers [Array<Class>] Controller classes to load
|
|
215
|
+
#
|
|
216
|
+
# @example
|
|
217
|
+
# load_controllers TelemetryController, ConfigController
|
|
218
|
+
def load_controllers(*controllers)
|
|
219
|
+
@app.config[:controllers].concat(controllers)
|
|
220
|
+
end
|
|
221
|
+
|
|
222
|
+
# Auto-load controllers from file pattern
|
|
223
|
+
#
|
|
224
|
+
# @param pattern [String] Glob pattern for controller files
|
|
225
|
+
#
|
|
226
|
+
# @example
|
|
227
|
+
# auto_load 'app/controllers/**/*_controller.rb'
|
|
228
|
+
def auto_load(pattern)
|
|
229
|
+
@app.config[:auto_load_patterns] << pattern
|
|
230
|
+
end
|
|
231
|
+
|
|
232
|
+
# Configure worker allocation mode
|
|
233
|
+
#
|
|
234
|
+
# @param mode [Symbol] :manual or :automatic
|
|
235
|
+
# @param threads [Integer] Total threads for automatic mode
|
|
236
|
+
#
|
|
237
|
+
# @example Manual allocation (controllers specify their own thread counts)
|
|
238
|
+
# allocation :manual
|
|
239
|
+
#
|
|
240
|
+
# @example Automatic allocation (divide 40 threads among controllers)
|
|
241
|
+
# allocation :automatic, threads: 40
|
|
242
|
+
def allocation(mode, threads: nil)
|
|
243
|
+
unless [:manual, :automatic].include?(mode)
|
|
244
|
+
raise ArgumentError, "Invalid allocation mode: #{mode}. Use :manual or :automatic"
|
|
245
|
+
end
|
|
246
|
+
|
|
247
|
+
if mode == :automatic && threads.nil?
|
|
248
|
+
raise ArgumentError, "Automatic allocation requires threads: parameter"
|
|
249
|
+
end
|
|
250
|
+
|
|
251
|
+
@app.config[:allocation_mode] = mode
|
|
252
|
+
@app.config[:total_threads] = threads
|
|
253
|
+
end
|
|
254
|
+
end
|
|
255
|
+
end
|
|
256
|
+
end
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Takagi
|
|
4
|
+
class Base < Router
|
|
5
|
+
# Manages middleware stack configuration and request processing.
|
|
6
|
+
#
|
|
7
|
+
# Extracted from Base class to follow Single Responsibility Principle.
|
|
8
|
+
# Handles middleware registration and stack initialization.
|
|
9
|
+
module MiddlewareManagement
|
|
10
|
+
# Processes a request through the middleware stack
|
|
11
|
+
#
|
|
12
|
+
# @param request [Message::Inbound] Incoming CoAP request
|
|
13
|
+
# @return [Message::Outbound] Response after middleware processing
|
|
14
|
+
def call(request)
|
|
15
|
+
middleware_stack.call(request)
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
# Returns the middleware stack instance
|
|
19
|
+
#
|
|
20
|
+
# Lazily initializes the middleware stack with default configuration
|
|
21
|
+
#
|
|
22
|
+
# @return [MiddlewareStack] The middleware stack instance
|
|
23
|
+
def middleware_stack
|
|
24
|
+
@middleware_stack ||= Takagi::MiddlewareStack.load_from_config('', router)
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
# Adds a middleware to the stack
|
|
28
|
+
#
|
|
29
|
+
# @param middleware [Object] Middleware instance responding to #call
|
|
30
|
+
#
|
|
31
|
+
# @example
|
|
32
|
+
# use CustomMiddleware.new
|
|
33
|
+
# use Takagi::Middleware::Caching.new(ttl: 300)
|
|
34
|
+
def use(middleware)
|
|
35
|
+
middleware_stack.use(middleware)
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
end
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative '../plugin'
|
|
4
|
+
require_relative '../hooks'
|
|
5
|
+
|
|
6
|
+
module Takagi
|
|
7
|
+
class Base < Router
|
|
8
|
+
# Provides a simple DSL for declaring and enabling plugins on boot.
|
|
9
|
+
module PluginManagement
|
|
10
|
+
def plugin(plugin_ref, order: 0, **options)
|
|
11
|
+
plugins_config << { ref: plugin_ref, options: options, order: order }
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def enable_plugins!(app: self)
|
|
15
|
+
load_config_plugins
|
|
16
|
+
Takagi::Plugin.auto_discover! if Takagi.config.plugins.auto_discover
|
|
17
|
+
|
|
18
|
+
merged = merged_plugins
|
|
19
|
+
merged.each do |entry|
|
|
20
|
+
mod = resolve_plugin(entry[:ref])
|
|
21
|
+
info = Takagi::Plugin.register(mod)
|
|
22
|
+
Takagi::Plugin.enable(info.name, app: app, options: entry[:options])
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
private
|
|
27
|
+
|
|
28
|
+
def plugins_config
|
|
29
|
+
@plugins_config ||= []
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def load_config_plugins
|
|
33
|
+
config_entries = Array(Takagi.config.plugins.enabled)
|
|
34
|
+
config_entries.each do |entry|
|
|
35
|
+
if entry.is_a?(Hash)
|
|
36
|
+
plugins_config << {
|
|
37
|
+
ref: entry[:name] || entry['name'],
|
|
38
|
+
options: entry[:options] || entry['options'] || {},
|
|
39
|
+
order: entry[:order] || entry['order'] || 0
|
|
40
|
+
}
|
|
41
|
+
else
|
|
42
|
+
plugins_config << { ref: entry, options: {}, order: 0 }
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
def merged_plugins
|
|
48
|
+
seen = {}
|
|
49
|
+
plugins_config.reverse_each do |entry|
|
|
50
|
+
key = entry[:ref].to_s
|
|
51
|
+
seen[key] ||= entry
|
|
52
|
+
end
|
|
53
|
+
seen.values.reverse.sort_by { |entry| entry[:order] || 0 }
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
def resolve_plugin(ref)
|
|
57
|
+
return ref if ref.is_a?(Module)
|
|
58
|
+
|
|
59
|
+
# Try Takagi::Plugins::<Name>
|
|
60
|
+
if ref.is_a?(Symbol) || ref.is_a?(String)
|
|
61
|
+
const_name = ref.to_s.split('_').map(&:capitalize).join
|
|
62
|
+
begin
|
|
63
|
+
return Takagi::Plugins.const_get(const_name)
|
|
64
|
+
rescue NameError
|
|
65
|
+
# fall through
|
|
66
|
+
end
|
|
67
|
+
# Try fully qualified
|
|
68
|
+
return Object.const_get(ref.to_s)
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
raise ArgumentError, "Unknown plugin reference: #{ref.inspect}"
|
|
72
|
+
end
|
|
73
|
+
end
|
|
74
|
+
end
|
|
75
|
+
end
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Takagi
|
|
4
|
+
class Base < Router
|
|
5
|
+
# Manages reactor registration and lifecycle for observable/observer patterns.
|
|
6
|
+
#
|
|
7
|
+
# Supports both inline reactor definitions and file-based reactor classes.
|
|
8
|
+
# Provides DSL for defining observables and observers.
|
|
9
|
+
module ReactorManagement
|
|
10
|
+
# Defines a reactor using a DSL block (inline definition)
|
|
11
|
+
#
|
|
12
|
+
# The block is evaluated in the context of a new Reactor instance,
|
|
13
|
+
# allowing you to define observables and observers inline.
|
|
14
|
+
#
|
|
15
|
+
# @param threads [Integer] Number of threads for the reactor (default: 2)
|
|
16
|
+
# @param name [String] Optional name for the reactor
|
|
17
|
+
# @param interval [Float] Default polling interval in seconds (default: 1.0)
|
|
18
|
+
# @yield Block defining reactor configuration
|
|
19
|
+
#
|
|
20
|
+
# @example Event-driven observable
|
|
21
|
+
# reactor threads: 4 do
|
|
22
|
+
# observable '/temperature' do |emitter|
|
|
23
|
+
# emitter.on_event('sensor.temp.changed')
|
|
24
|
+
# end
|
|
25
|
+
# end
|
|
26
|
+
#
|
|
27
|
+
# @example Polling observable
|
|
28
|
+
# reactor do
|
|
29
|
+
# observable_polling '/status', interval: 5.0 do
|
|
30
|
+
# { status: check_status, uptime: uptime }
|
|
31
|
+
# end
|
|
32
|
+
# end
|
|
33
|
+
#
|
|
34
|
+
# @example Remote observation
|
|
35
|
+
# reactor do
|
|
36
|
+
# observe 'coap://sensor:5683/temp' do |data|
|
|
37
|
+
# process_temperature(data)
|
|
38
|
+
# end
|
|
39
|
+
# end
|
|
40
|
+
def reactor(threads: 2, name: nil, interval: 1.0, &block)
|
|
41
|
+
reactor_name = name || "#{self.name.split('::').last.downcase}-reactor"
|
|
42
|
+
reactor_instance = Takagi::Observable::Reactor.new(
|
|
43
|
+
threads: threads,
|
|
44
|
+
name: reactor_name,
|
|
45
|
+
interval: interval
|
|
46
|
+
)
|
|
47
|
+
reactor_instance.instance_eval(&block)
|
|
48
|
+
|
|
49
|
+
# Register with Observable::Registry
|
|
50
|
+
Takagi::Observable::Registry.register(reactor_name.to_sym, reactor_instance)
|
|
51
|
+
|
|
52
|
+
reactor_instance
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
# Registers a file-based reactor class instance
|
|
56
|
+
#
|
|
57
|
+
# Use this to register reactors defined in separate files (e.g., app/reactors/).
|
|
58
|
+
# The reactor class should inherit from Takagi::Observable::Reactor.
|
|
59
|
+
#
|
|
60
|
+
# @param name [Symbol] Unique identifier for the reactor
|
|
61
|
+
# @param klass_or_instance [Class, Reactor] Reactor class or instance
|
|
62
|
+
#
|
|
63
|
+
# @example Register reactor class
|
|
64
|
+
# class IotReactor < Takagi::Observable::Reactor
|
|
65
|
+
# def initialize
|
|
66
|
+
# super(threads: 8, name: 'iot')
|
|
67
|
+
# setup_device_observables
|
|
68
|
+
# end
|
|
69
|
+
# end
|
|
70
|
+
#
|
|
71
|
+
# use_reactor :iot, IotReactor
|
|
72
|
+
#
|
|
73
|
+
# @example Register reactor instance
|
|
74
|
+
# use_reactor :sensors, SensorReactor.new
|
|
75
|
+
def use_reactor(name, klass_or_instance)
|
|
76
|
+
reactor_instance = if klass_or_instance.is_a?(Class)
|
|
77
|
+
klass_or_instance.new
|
|
78
|
+
else
|
|
79
|
+
klass_or_instance
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
Takagi::Observable::Registry.register(name.to_sym, reactor_instance)
|
|
83
|
+
reactor_instance
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
# Starts all registered reactors
|
|
87
|
+
#
|
|
88
|
+
# Called automatically by server lifecycle management during boot.
|
|
89
|
+
# Can be called manually if needed.
|
|
90
|
+
#
|
|
91
|
+
# @return [void]
|
|
92
|
+
def start_reactors
|
|
93
|
+
Takagi::Observable::Registry.start_all
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
# Stops all registered reactors
|
|
97
|
+
#
|
|
98
|
+
# @return [void]
|
|
99
|
+
def stop_reactors
|
|
100
|
+
Takagi::Observable::Registry.stop_all
|
|
101
|
+
end
|
|
102
|
+
end
|
|
103
|
+
end
|
|
104
|
+
end
|
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative '../branding'
|
|
4
|
+
|
|
5
|
+
module Takagi
|
|
6
|
+
class Base < Router
|
|
7
|
+
# Manages server lifecycle operations: booting, running, and spawning servers.
|
|
8
|
+
#
|
|
9
|
+
# Extracted from Base class to follow Single Responsibility Principle.
|
|
10
|
+
# Handles configuration loading, server instantiation, and process management.
|
|
11
|
+
module ServerLifecycle
|
|
12
|
+
# Boots the application by loading configuration and running initializers
|
|
13
|
+
#
|
|
14
|
+
# @param config_path [String] Path to configuration file
|
|
15
|
+
def boot!(config_path: 'config/takagi.yml')
|
|
16
|
+
Takagi.config.load_file(config_path) if File.exist?(config_path)
|
|
17
|
+
Takagi::Initializer.run!
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
# Runs the server in the foreground (blocking)
|
|
21
|
+
#
|
|
22
|
+
# @param port [Integer, nil] Port to bind to (uses config if nil)
|
|
23
|
+
# @param config_path [String] Path to configuration file
|
|
24
|
+
# @param protocols [Array<Symbol>, nil] Protocols to enable (uses config if nil)
|
|
25
|
+
# @param router [Router, CompositeRouter, nil] Custom router (uses global if nil)
|
|
26
|
+
# @param banner [Boolean] Show startup banner (default: true)
|
|
27
|
+
def run!(port: nil, config_path: 'config/takagi.yml', protocols: nil, router: nil, banner: true)
|
|
28
|
+
boot!(config_path: config_path)
|
|
29
|
+
enable_plugins!(app: self)
|
|
30
|
+
|
|
31
|
+
# Show startup banner
|
|
32
|
+
if banner
|
|
33
|
+
print_startup_banner(
|
|
34
|
+
port: port || Takagi.config.port,
|
|
35
|
+
protocols: protocols || Takagi.config.protocols,
|
|
36
|
+
router: router || self.router # Pass router to banner
|
|
37
|
+
)
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
selected_port = port || Takagi.config.port
|
|
41
|
+
servers = build_servers(protocols || Takagi.config.protocols, selected_port, router: router)
|
|
42
|
+
run_servers(servers)
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
# Spawns servers in background threads
|
|
46
|
+
#
|
|
47
|
+
# @param port [Integer] Port to bind to
|
|
48
|
+
# @param protocols [Array<Symbol>, nil] Protocols to enable (uses config if nil)
|
|
49
|
+
# @return [Server, Multi] The spawned server instance
|
|
50
|
+
def spawn!(port: 5683, protocols: nil)
|
|
51
|
+
protos = if protocols
|
|
52
|
+
Array(protocols)
|
|
53
|
+
else
|
|
54
|
+
Takagi.config.protocols
|
|
55
|
+
end.map(&:to_sym)
|
|
56
|
+
|
|
57
|
+
servers = protos.map do |proto|
|
|
58
|
+
# Temporary backward compatibility - will use registry after transition
|
|
59
|
+
proto == :tcp ? Takagi::Server::Tcp.new(port: port) : Takagi::Server::Udp.new(port: port)
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
if servers.length == 1
|
|
63
|
+
server = servers.first
|
|
64
|
+
thread = Thread.new { server.run! }
|
|
65
|
+
# Store thread reference on the server instance for cleanup
|
|
66
|
+
server.instance_variable_set(:@server_thread, thread)
|
|
67
|
+
server
|
|
68
|
+
else
|
|
69
|
+
multi = Takagi::Server::Multi.new(servers)
|
|
70
|
+
thread = Thread.new { multi.run! }
|
|
71
|
+
# Store thread reference on the multi instance for cleanup
|
|
72
|
+
multi.instance_variable_set(:@server_thread, thread)
|
|
73
|
+
multi
|
|
74
|
+
end
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
private
|
|
78
|
+
|
|
79
|
+
# Prints startup banner with Takagi branding
|
|
80
|
+
#
|
|
81
|
+
# @param port [Integer] Port server will bind to
|
|
82
|
+
# @param protocols [Array<Symbol>] Protocols being enabled
|
|
83
|
+
# @param router [Router] Router instance to count routes from
|
|
84
|
+
def print_startup_banner(port:, protocols:, router:)
|
|
85
|
+
# Print banner
|
|
86
|
+
Branding.print_banner(compact: false)
|
|
87
|
+
|
|
88
|
+
# Version info
|
|
89
|
+
version = defined?(Takagi::VERSION) ? Takagi::VERSION : '0.1.0'
|
|
90
|
+
Branding.print_version(version)
|
|
91
|
+
|
|
92
|
+
puts
|
|
93
|
+
puts Branding.section('Starting Server')
|
|
94
|
+
|
|
95
|
+
# Server info
|
|
96
|
+
puts Branding.log("Port: #{port}")
|
|
97
|
+
puts Branding.log("Protocols: #{protocols.map(&:to_s).map(&:upcase).join(', ')}")
|
|
98
|
+
puts Branding.log("Threads: #{Takagi.config.threads}")
|
|
99
|
+
puts Branding.log("Processes: #{Takagi.config.processes}") if Takagi.config.processes > 0
|
|
100
|
+
|
|
101
|
+
# Route count
|
|
102
|
+
route_count = router.instance_variable_get(:@routes)&.size || 0
|
|
103
|
+
puts Branding.log("Routes registered: #{route_count}")
|
|
104
|
+
|
|
105
|
+
puts
|
|
106
|
+
puts Branding.success('Server starting...')
|
|
107
|
+
puts Branding.info('Press Ctrl+C to shutdown')
|
|
108
|
+
puts
|
|
109
|
+
puts Branding::WAVE_LINE
|
|
110
|
+
puts
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
# Builds server instances for the given protocols
|
|
114
|
+
#
|
|
115
|
+
# @param protocols [Array<Symbol>] Protocol identifiers
|
|
116
|
+
# @param port [Integer] Port to bind to
|
|
117
|
+
# @param router [Router, CompositeRouter, nil] Custom router (uses global if nil)
|
|
118
|
+
# @return [Array<Server>] Array of server instances
|
|
119
|
+
def build_servers(protocols, port, router: nil)
|
|
120
|
+
threads = Takagi.config.threads
|
|
121
|
+
processes = Takagi.config.processes
|
|
122
|
+
Array(protocols).map(&:to_sym).map do |protocol|
|
|
123
|
+
instantiate_server(protocol, port, threads: threads, processes: processes, router: router)
|
|
124
|
+
end
|
|
125
|
+
end
|
|
126
|
+
|
|
127
|
+
# Instantiates a server for the given protocol using ServerRegistry
|
|
128
|
+
#
|
|
129
|
+
# @param protocol [Symbol] Protocol identifier (:udp, :tcp, etc.)
|
|
130
|
+
# @param port [Integer] Port to bind to
|
|
131
|
+
# @param threads [Integer] Number of worker threads
|
|
132
|
+
# @param processes [Integer] Number of worker processes (UDP only)
|
|
133
|
+
# @param router [Router, CompositeRouter, nil] Custom router (uses global if nil)
|
|
134
|
+
# @return [Server] Server instance
|
|
135
|
+
def instantiate_server(protocol, port, threads:, processes:, router: nil)
|
|
136
|
+
options = { port: port }
|
|
137
|
+
options[:worker_threads] = threads
|
|
138
|
+
options[:router] = router if router
|
|
139
|
+
|
|
140
|
+
# UDP requires worker_processes, TCP doesn't use it
|
|
141
|
+
options[:worker_processes] = processes if protocol == :udp
|
|
142
|
+
Takagi.logger.debug("Instantiating server for #{protocol} on port #{port} with #{options}")
|
|
143
|
+
Server::Registry.build(protocol, **options)
|
|
144
|
+
end
|
|
145
|
+
|
|
146
|
+
# Runs servers (single or multi-protocol)
|
|
147
|
+
#
|
|
148
|
+
# @param servers [Array<Server>] Server instances to run
|
|
149
|
+
def run_servers(servers)
|
|
150
|
+
return servers.first.run! if servers.length == 1
|
|
151
|
+
|
|
152
|
+
Takagi::Server::Multi.new(servers).run!
|
|
153
|
+
end
|
|
154
|
+
end
|
|
155
|
+
end
|
|
156
|
+
end
|