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,166 @@
|
|
|
1
|
+
module Takagi
|
|
2
|
+
class Router
|
|
3
|
+
@routes: untyped
|
|
4
|
+
|
|
5
|
+
@routes_mutex: untyped
|
|
6
|
+
|
|
7
|
+
@logger: untyped
|
|
8
|
+
|
|
9
|
+
@route_matcher: untyped
|
|
10
|
+
|
|
11
|
+
@metadata_extractor: untyped
|
|
12
|
+
|
|
13
|
+
DEFAULT_CONTENT_FORMAT: 50
|
|
14
|
+
|
|
15
|
+
# Global singleton instance for backward compatibility
|
|
16
|
+
def self.instance: () -> Router
|
|
17
|
+
|
|
18
|
+
# Reset the global instance (for testing)
|
|
19
|
+
def self.reset!: () -> void
|
|
20
|
+
|
|
21
|
+
# Represents a registered route with its handler and CoRE Link Format metadata
|
|
22
|
+
class RouteEntry
|
|
23
|
+
@method: untyped
|
|
24
|
+
|
|
25
|
+
@path: untyped
|
|
26
|
+
|
|
27
|
+
@block: untyped
|
|
28
|
+
|
|
29
|
+
@receiver: untyped
|
|
30
|
+
|
|
31
|
+
@attribute_set: untyped
|
|
32
|
+
|
|
33
|
+
attr_reader method: untyped
|
|
34
|
+
|
|
35
|
+
attr_reader path: untyped
|
|
36
|
+
|
|
37
|
+
attr_reader block: untyped
|
|
38
|
+
|
|
39
|
+
attr_reader receiver: untyped
|
|
40
|
+
|
|
41
|
+
attr_reader attribute_set: untyped
|
|
42
|
+
|
|
43
|
+
def initialize: (method: untyped, path: untyped, block: untyped, ?metadata: ::Hash[untyped, untyped], ?receiver: untyped?) -> void
|
|
44
|
+
|
|
45
|
+
# Returns the underlying metadata hash for backward compatibility
|
|
46
|
+
def metadata: () -> untyped
|
|
47
|
+
|
|
48
|
+
# Configure CoRE Link Format attributes using DSL block
|
|
49
|
+
#
|
|
50
|
+
# @example
|
|
51
|
+
# entry.configure_attributes do
|
|
52
|
+
# rt 'sensor'
|
|
53
|
+
# obs true
|
|
54
|
+
# ct 'application/json'
|
|
55
|
+
# end
|
|
56
|
+
def configure_attributes: () { (?) -> untyped } -> untyped
|
|
57
|
+
|
|
58
|
+
# Support for dup operation (used in discovery)
|
|
59
|
+
def initialize_copy: (untyped original) -> untyped
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
# Provides the execution context for route handlers, exposing helper
|
|
63
|
+
# methods for configuring CoRE Link Format attributes via a small DSL.
|
|
64
|
+
class RouteContext
|
|
65
|
+
@entry: untyped
|
|
66
|
+
|
|
67
|
+
@request: untyped
|
|
68
|
+
|
|
69
|
+
@params: untyped
|
|
70
|
+
|
|
71
|
+
@receiver: untyped
|
|
72
|
+
|
|
73
|
+
# Create a fresh AttributeSet for this request to avoid cross-request state sharing
|
|
74
|
+
# Initialize it with a copy of the entry's current metadata
|
|
75
|
+
@core_attributes: untyped
|
|
76
|
+
|
|
77
|
+
extend Forwardable
|
|
78
|
+
|
|
79
|
+
include Takagi::Helpers
|
|
80
|
+
|
|
81
|
+
attr_reader request: untyped
|
|
82
|
+
|
|
83
|
+
attr_reader params: untyped
|
|
84
|
+
|
|
85
|
+
# Aliases for common methods
|
|
86
|
+
alias content_format ct
|
|
87
|
+
|
|
88
|
+
alias observable obs
|
|
89
|
+
|
|
90
|
+
alias if_ interface
|
|
91
|
+
|
|
92
|
+
def initialize: (untyped entry, untyped request, untyped params, untyped receiver) -> void
|
|
93
|
+
|
|
94
|
+
def run: (untyped block) -> untyped
|
|
95
|
+
|
|
96
|
+
private
|
|
97
|
+
|
|
98
|
+
# Delegates method calls to the receiver (application instance)
|
|
99
|
+
# This allows route handlers to call application methods within their blocks
|
|
100
|
+
# Example: get '/users' do; fetch_users; end - calls application's fetch_users method
|
|
101
|
+
def method_missing: (untyped name, *untyped) ?{ (?) -> untyped } -> untyped
|
|
102
|
+
|
|
103
|
+
# Required pair for method_missing to properly support respond_to?
|
|
104
|
+
def respond_to_missing?: (untyped name, ?bool include_private) -> untyped
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
def initialize: () -> void
|
|
108
|
+
|
|
109
|
+
# Registers a new route for a given HTTP method and path
|
|
110
|
+
# @param method [String] The HTTP method (GET, POST, etc.)
|
|
111
|
+
# @param path [String] The URL path, can include dynamic segments like `:id`
|
|
112
|
+
# @param block [Proc] The handler to be executed when the route is matched
|
|
113
|
+
def add_route: (untyped method, untyped path, ?metadata: ::Hash[untyped, untyped]) { (?) -> untyped } -> untyped
|
|
114
|
+
|
|
115
|
+
# Registers a OBSERVE route
|
|
116
|
+
# @param path [String] The URL path
|
|
117
|
+
# @param block [Proc] The handler function
|
|
118
|
+
def observable: (untyped path, ?metadata: ::Hash[untyped, untyped]) { (?) -> untyped } -> untyped
|
|
119
|
+
|
|
120
|
+
def all_routes: () -> untyped
|
|
121
|
+
|
|
122
|
+
def find_observable: (untyped path) -> untyped
|
|
123
|
+
|
|
124
|
+
# Finds a registered route for a given method and path
|
|
125
|
+
# @param method [String] HTTP method
|
|
126
|
+
# @param path [String] URL path
|
|
127
|
+
# @return [Proc, Hash] The matching handler and extracted parameters
|
|
128
|
+
def find_route: (untyped method, untyped path) -> untyped
|
|
129
|
+
|
|
130
|
+
def link_format_entries: () -> untyped
|
|
131
|
+
|
|
132
|
+
# Applies CoRE metadata outside the request cycle. Useful for boot time
|
|
133
|
+
# configuration where the DSL block does not have a live request object.
|
|
134
|
+
def configure_core: (untyped method, untyped path) ?{ (?) -> untyped } -> (nil | untyped)
|
|
135
|
+
|
|
136
|
+
private
|
|
137
|
+
|
|
138
|
+
def wrap_block: (untyped entry) -> (nil | untyped)
|
|
139
|
+
|
|
140
|
+
# Matches dynamic routes that contain parameters (e.g., `/users/:id`)
|
|
141
|
+
# Delegates to RouteMatcher for the actual matching logic
|
|
142
|
+
# @param method [String] HTTP method
|
|
143
|
+
# @param path [String] Request path
|
|
144
|
+
# @return [Array(RouteEntry, Hash)] Matched route entry and extracted parameters
|
|
145
|
+
def match_dynamic_route: (untyped method, untyped path) -> untyped
|
|
146
|
+
|
|
147
|
+
def build_route_entry: (untyped method, untyped path, untyped metadata, untyped block) -> untyped
|
|
148
|
+
|
|
149
|
+
# Normalizes route metadata with sensible defaults for CoRE Link Format
|
|
150
|
+
#
|
|
151
|
+
# @param method [String] HTTP-like method (GET, POST, OBSERVE, etc.)
|
|
152
|
+
# @param path [String] Route path
|
|
153
|
+
# @param metadata [Hash, nil] User-provided metadata
|
|
154
|
+
# @return [Hash] Normalized metadata with defaults applied
|
|
155
|
+
def normalize_metadata: (untyped method, untyped path, untyped metadata) -> untyped
|
|
156
|
+
|
|
157
|
+
def default_resource_type: (untyped method) -> ("core#observable" | "core#endpoint")
|
|
158
|
+
|
|
159
|
+
def default_interface: (untyped method) -> ("takagi.observe" | ::String)
|
|
160
|
+
|
|
161
|
+
# Executes route handler in metadata extraction mode to capture core block attributes
|
|
162
|
+
# Delegates to MetadataExtractor for the actual extraction logic
|
|
163
|
+
# @param entry [RouteEntry] The route entry to extract metadata from
|
|
164
|
+
def extract_metadata_from_handler: (untyped entry) -> untyped
|
|
165
|
+
end
|
|
166
|
+
end
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
# Takagi::Serialization - Content-format serialization system
|
|
2
|
+
module Takagi
|
|
3
|
+
module Serialization
|
|
4
|
+
# Custom error classes
|
|
5
|
+
class Error < StandardError
|
|
6
|
+
end
|
|
7
|
+
|
|
8
|
+
class UnknownFormatError < Error
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
class EncodeError < Error
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
class DecodeError < Error
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
class InvalidSerializerError < Error
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
# Register default serializers
|
|
21
|
+
def self.register_defaults!: () -> void
|
|
22
|
+
|
|
23
|
+
# Encode data with format
|
|
24
|
+
def self.encode: (untyped data, Integer format) -> String
|
|
25
|
+
|
|
26
|
+
# Decode bytes with format
|
|
27
|
+
def self.decode: (String bytes, Integer format) -> untyped
|
|
28
|
+
|
|
29
|
+
# Check if format is supported
|
|
30
|
+
def self.supports?: (Integer format) -> bool
|
|
31
|
+
end
|
|
32
|
+
end
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
module Takagi
|
|
2
|
+
module Server
|
|
3
|
+
# Helper class to run multiple servers concurrently
|
|
4
|
+
class Multi
|
|
5
|
+
@servers: untyped
|
|
6
|
+
|
|
7
|
+
@threads: untyped
|
|
8
|
+
|
|
9
|
+
def initialize: (untyped servers) -> void
|
|
10
|
+
|
|
11
|
+
def run!: () -> untyped
|
|
12
|
+
|
|
13
|
+
def shutdown!: () -> untyped
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
end
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
module Takagi
|
|
2
|
+
module Server
|
|
3
|
+
# TCP server implementation for CoAP over TCP
|
|
4
|
+
class Tcp
|
|
5
|
+
@port: untyped
|
|
6
|
+
|
|
7
|
+
@worker_threads: untyped
|
|
8
|
+
|
|
9
|
+
@middleware_stack: untyped
|
|
10
|
+
|
|
11
|
+
@router: untyped
|
|
12
|
+
|
|
13
|
+
@logger: untyped
|
|
14
|
+
|
|
15
|
+
@watcher: untyped
|
|
16
|
+
|
|
17
|
+
@server: untyped
|
|
18
|
+
|
|
19
|
+
@sender: untyped
|
|
20
|
+
|
|
21
|
+
@workers: untyped
|
|
22
|
+
|
|
23
|
+
@shutdown_called: untyped
|
|
24
|
+
|
|
25
|
+
def initialize: (?port: ::Integer, ?worker_threads: ::Integer, ?middleware_stack: untyped?, ?router: untyped?, ?logger: untyped?, ?watcher: untyped?, ?sender: untyped?) -> void
|
|
26
|
+
|
|
27
|
+
def run!: () -> untyped
|
|
28
|
+
|
|
29
|
+
def shutdown!: () -> (nil | untyped)
|
|
30
|
+
|
|
31
|
+
private
|
|
32
|
+
|
|
33
|
+
def handle_connection: (untyped sock) -> untyped
|
|
34
|
+
|
|
35
|
+
def read_request: (untyped sock) -> (nil | untyped)
|
|
36
|
+
|
|
37
|
+
def build_response: (untyped inbound_request) -> untyped
|
|
38
|
+
|
|
39
|
+
def transmit_response: (untyped sock, untyped response) -> untyped
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
end
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
module Takagi
|
|
2
|
+
module Server
|
|
3
|
+
# UDP server for handling CoAP messages
|
|
4
|
+
class Udp
|
|
5
|
+
@port: untyped
|
|
6
|
+
|
|
7
|
+
@worker_processes: untyped
|
|
8
|
+
|
|
9
|
+
@worker_threads: untyped
|
|
10
|
+
|
|
11
|
+
@middleware_stack: untyped
|
|
12
|
+
|
|
13
|
+
@router: untyped
|
|
14
|
+
|
|
15
|
+
@logger: untyped
|
|
16
|
+
|
|
17
|
+
@watcher: untyped
|
|
18
|
+
|
|
19
|
+
@socket: untyped
|
|
20
|
+
|
|
21
|
+
@sender: untyped
|
|
22
|
+
|
|
23
|
+
@shutdown_called: untyped
|
|
24
|
+
|
|
25
|
+
@worker_pids: untyped
|
|
26
|
+
|
|
27
|
+
def initialize: (?port: ::Integer, ?worker_processes: ::Integer, ?worker_threads: ::Integer, ?middleware_stack: untyped?, ?router: untyped?, ?logger: untyped?, ?watcher: untyped?) -> void
|
|
28
|
+
|
|
29
|
+
# Starts the server with multiple worker processes
|
|
30
|
+
def run!: () -> untyped
|
|
31
|
+
|
|
32
|
+
# Gracefully shuts down all workers
|
|
33
|
+
def shutdown!: () -> (nil | untyped)
|
|
34
|
+
|
|
35
|
+
private
|
|
36
|
+
|
|
37
|
+
def log_boot_details: () -> untyped
|
|
38
|
+
|
|
39
|
+
def spawn_workers: () -> untyped
|
|
40
|
+
|
|
41
|
+
def fork_worker: () -> untyped
|
|
42
|
+
|
|
43
|
+
def worker_configuration: () -> { port: untyped, socket: untyped, middleware_stack: untyped, sender: untyped, logger: untyped, threads: untyped }
|
|
44
|
+
|
|
45
|
+
def close_socket: () -> untyped
|
|
46
|
+
|
|
47
|
+
def terminate_workers: () -> (nil | untyped)
|
|
48
|
+
|
|
49
|
+
def test_environment?: () -> untyped
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
end
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
module Takagi
|
|
2
|
+
module Server
|
|
3
|
+
# Handles incoming UDP messages on behalf of the master Udp server.
|
|
4
|
+
class UdpWorker
|
|
5
|
+
@socket: untyped
|
|
6
|
+
|
|
7
|
+
@middleware_stack: untyped
|
|
8
|
+
|
|
9
|
+
@sender: untyped
|
|
10
|
+
|
|
11
|
+
@logger: untyped
|
|
12
|
+
|
|
13
|
+
@port: untyped
|
|
14
|
+
|
|
15
|
+
@threads: untyped
|
|
16
|
+
|
|
17
|
+
@dedup_cache: untyped
|
|
18
|
+
|
|
19
|
+
def initialize: (socket: untyped, middleware_stack: untyped, **untyped options) -> void
|
|
20
|
+
|
|
21
|
+
def run: () -> untyped
|
|
22
|
+
|
|
23
|
+
private
|
|
24
|
+
|
|
25
|
+
def process_loop: (untyped queue) -> untyped
|
|
26
|
+
|
|
27
|
+
def spawn_thread: (untyped queue) -> untyped
|
|
28
|
+
|
|
29
|
+
def handle_request: (untyped request, untyped addr) -> untyped
|
|
30
|
+
|
|
31
|
+
def log_inbound_request: (untyped inbound_request) -> untyped
|
|
32
|
+
|
|
33
|
+
def immediate_response: (untyped inbound_request) -> (untyped | nil)
|
|
34
|
+
|
|
35
|
+
def log_middleware_result: (untyped result) -> (nil | untyped)
|
|
36
|
+
|
|
37
|
+
def build_response: (untyped inbound_request, untyped result) -> untyped
|
|
38
|
+
|
|
39
|
+
def transmit: (untyped response, untyped addr) -> untyped
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
end
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
module Takagi
|
|
2
|
+
# Registry for CoAP server implementations
|
|
3
|
+
#
|
|
4
|
+
# Allows registering different protocol implementations (UDP, TCP, DTLS, QUIC, etc.)
|
|
5
|
+
# without modifying core code. Follows the Open/Closed Principle.
|
|
6
|
+
#
|
|
7
|
+
# @example Registering a server
|
|
8
|
+
# ServerRegistry.register(:udp, Server::Udp)
|
|
9
|
+
# ServerRegistry.register(:tcp, Server::Tcp)
|
|
10
|
+
#
|
|
11
|
+
# @example Building a server
|
|
12
|
+
# server = ServerRegistry.build(:tcp, port: 5683, worker_threads: 4)
|
|
13
|
+
#
|
|
14
|
+
# @example Adding a custom protocol
|
|
15
|
+
# class MyCustomServer
|
|
16
|
+
# def initialize(port:, **options)
|
|
17
|
+
# # ...
|
|
18
|
+
# end
|
|
19
|
+
# end
|
|
20
|
+
# ServerRegistry.register(:custom, MyCustomServer)
|
|
21
|
+
class ServerRegistry
|
|
22
|
+
self.@servers: untyped
|
|
23
|
+
|
|
24
|
+
self.@mutex: untyped
|
|
25
|
+
|
|
26
|
+
class ProtocolNotFoundError < StandardError
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
# Register a server implementation for a protocol
|
|
30
|
+
#
|
|
31
|
+
# @param protocol [Symbol] Protocol identifier (:udp, :tcp, :dtls, etc.)
|
|
32
|
+
# @param klass [Class] Server class that responds to .new
|
|
33
|
+
# @param options [Hash] Optional metadata (description, rfc, etc.)
|
|
34
|
+
#
|
|
35
|
+
# @example
|
|
36
|
+
# ServerRegistry.register(:udp, Server::Udp, rfc: 'RFC 7252')
|
|
37
|
+
# ServerRegistry.register(:tcp, Server::Tcp, rfc: 'RFC 8323')
|
|
38
|
+
def self.register: (untyped protocol, untyped klass, **untyped options) -> untyped
|
|
39
|
+
|
|
40
|
+
# Build a server instance for the given protocol
|
|
41
|
+
#
|
|
42
|
+
# @param protocol [Symbol] Protocol identifier
|
|
43
|
+
# @param options [Hash] Options to pass to server constructor
|
|
44
|
+
# @return [Object] Server instance
|
|
45
|
+
# @raise [ProtocolNotFoundError] If protocol is not registered
|
|
46
|
+
#
|
|
47
|
+
# @example
|
|
48
|
+
# server = ServerRegistry.build(:tcp, port: 5683, worker_threads: 4)
|
|
49
|
+
def self.build: (untyped protocol, **untyped options) -> untyped
|
|
50
|
+
|
|
51
|
+
# Check if a protocol is registered
|
|
52
|
+
#
|
|
53
|
+
# @param protocol [Symbol] Protocol identifier
|
|
54
|
+
# @return [Boolean] true if registered
|
|
55
|
+
def self.registered?: (untyped protocol) -> untyped
|
|
56
|
+
|
|
57
|
+
# Get all registered protocols
|
|
58
|
+
#
|
|
59
|
+
# @return [Array<Symbol>] List of protocol identifiers
|
|
60
|
+
def self.protocols: () -> untyped
|
|
61
|
+
|
|
62
|
+
# Get metadata for a protocol
|
|
63
|
+
#
|
|
64
|
+
# @param protocol [Symbol] Protocol identifier
|
|
65
|
+
# @return [Hash, nil] Metadata hash or nil if not found
|
|
66
|
+
def self.metadata_for: (untyped protocol) -> untyped
|
|
67
|
+
|
|
68
|
+
# Clear all registrations (useful for testing)
|
|
69
|
+
def self.clear!: () -> untyped
|
|
70
|
+
end
|
|
71
|
+
end
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
module Takagi
|
|
2
|
+
# CoAP-over-TCP client for testing Takagi servers with TCP transport.
|
|
3
|
+
#
|
|
4
|
+
# This client implements CoAP over TCP (RFC 8323) with automatic length framing.
|
|
5
|
+
# Unlike the UDP client, TCP provides reliable delivery so no retransmission
|
|
6
|
+
# manager is needed.
|
|
7
|
+
#
|
|
8
|
+
# @example Basic usage with auto-close (recommended)
|
|
9
|
+
# Takagi::TcpClient.open('coap+tcp://localhost:5683') do |client|
|
|
10
|
+
# client.get('/temperature')
|
|
11
|
+
# end
|
|
12
|
+
#
|
|
13
|
+
# @example Manual lifecycle management
|
|
14
|
+
# client = Takagi::TcpClient.new('coap+tcp://localhost:5683')
|
|
15
|
+
# begin
|
|
16
|
+
# client.get('/temperature')
|
|
17
|
+
# ensure
|
|
18
|
+
# client.close
|
|
19
|
+
# end
|
|
20
|
+
class TcpClient < ClientBase
|
|
21
|
+
def request: (untyped method, untyped path, ?untyped? payload) { (?) -> untyped } -> untyped
|
|
22
|
+
end
|
|
23
|
+
end
|
data/takagi.gemspec
CHANGED
|
@@ -1,44 +1,46 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
require_relative
|
|
3
|
+
require_relative 'lib/takagi/version'
|
|
4
|
+
|
|
5
|
+
# Compute files at the top level so the Gem::Specification block can
|
|
6
|
+
# reference it (the block runs in instance context, not top-level, so
|
|
7
|
+
# a top-level `def gem_files` is not visible inside `spec.files =`).
|
|
8
|
+
FILES = Dir.chdir(__dir__) do
|
|
9
|
+
`git ls-files -z`.split("\x0").reject do |file|
|
|
10
|
+
(File.expand_path(file) == __FILE__) ||
|
|
11
|
+
file.start_with?(*%w[bin/ test/ spec/ features/ .git .github appveyor Gemfile])
|
|
12
|
+
end
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
HOMEPAGE = 'https://github.com/takagi-works/takagi'
|
|
4
16
|
|
|
5
17
|
Gem::Specification.new do |spec|
|
|
6
|
-
spec.name =
|
|
18
|
+
spec.name = 'takagi'
|
|
7
19
|
spec.version = Takagi::VERSION
|
|
8
|
-
spec.authors = [
|
|
9
|
-
spec.email = [
|
|
10
|
-
|
|
11
|
-
spec.summary =
|
|
12
|
-
spec.description =
|
|
13
|
-
spec.homepage =
|
|
14
|
-
spec.license =
|
|
15
|
-
spec.required_ruby_version =
|
|
16
|
-
|
|
17
|
-
spec.metadata[
|
|
18
|
-
|
|
19
|
-
spec.metadata[
|
|
20
|
-
spec.metadata[
|
|
21
|
-
spec.metadata[
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
spec.
|
|
26
|
-
|
|
27
|
-
(File.expand_path(f) == __FILE__) ||
|
|
28
|
-
f.start_with?(*%w[bin/ test/ spec/ features/ .git .github appveyor Gemfile])
|
|
29
|
-
end
|
|
30
|
-
end
|
|
31
|
-
spec.bindir = "exe"
|
|
32
|
-
spec.executables = spec.files.grep(%r{\Aexe/}) { |f| File.basename(f) }
|
|
33
|
-
spec.require_paths = ["lib"]
|
|
20
|
+
spec.authors = ['Dominik Matoulek']
|
|
21
|
+
spec.email = ['domitea@gmail.com']
|
|
22
|
+
|
|
23
|
+
spec.summary = 'Lightweight CoAP framework for Ruby'
|
|
24
|
+
spec.description = 'Sinatra-like framework for CoAP and IoT messaging.'
|
|
25
|
+
spec.homepage = HOMEPAGE
|
|
26
|
+
spec.license = 'MIT'
|
|
27
|
+
spec.required_ruby_version = '>= 3.0.0'
|
|
28
|
+
|
|
29
|
+
spec.metadata['allowed_push_host'] ||= 'https://rubygems.org'
|
|
30
|
+
|
|
31
|
+
spec.metadata['homepage_uri'] = HOMEPAGE
|
|
32
|
+
spec.metadata['source_code_uri'] = "#{HOMEPAGE}.git"
|
|
33
|
+
spec.metadata['changelog_uri'] = "#{HOMEPAGE}/blob/master/CHANGELOG.md"
|
|
34
|
+
|
|
35
|
+
spec.files = FILES
|
|
36
|
+
spec.bindir = 'bin'
|
|
37
|
+
spec.executables = ['takagi-dev']
|
|
38
|
+
spec.require_paths = ['lib']
|
|
34
39
|
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
spec.add_dependency "rack"
|
|
38
|
-
spec.add_dependency "sequel"
|
|
40
|
+
spec.add_dependency 'rack', '~> 2.0'
|
|
41
|
+
spec.add_dependency 'zeitwerk', '~> 2.0'
|
|
39
42
|
|
|
40
|
-
spec.add_development_dependency
|
|
43
|
+
spec.add_development_dependency 'rspec', '~> 3.0'
|
|
41
44
|
|
|
42
|
-
|
|
43
|
-
# guide at: https://bundler.io/guides/creating_gem.html
|
|
45
|
+
spec.metadata['rubygems_mfa_required'] = 'true'
|
|
44
46
|
end
|