falcon 0.55.6 → 0.57.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 9efae16cb2a08be54e0b1099559fdb943d7ef5a85d709b57f4caa2590d08ff8f
4
- data.tar.gz: 4f03dd493732da1714b55ec9af8df3d1ec7e20ce0bd51e9ef48f5cee1e8141ed
3
+ metadata.gz: 4d899d227cc38f6f4b766bfcef947ec0bd7a2a1396e695e96454ed707395e6eb
4
+ data.tar.gz: b72457b92c692a02e23be49d05734cda178c86bf5f983c2f2dd1b8f9788de40a
5
5
  SHA512:
6
- metadata.gz: c0301ea50dea981115c09258abe18b55e8327bfcf2a74d018a4b1b7a55c1567521f8afb38b16b52a4559dc181df922af0408901bd7b79c6ac61a6cbc16b6c8b7
7
- data.tar.gz: 5270c593232e8d23d4157faa6b85176b5c95d220c4c0360871cb11e8990d04d1c2f508ef1681b1ff1e7c87fc408f5a68783d4fcf094812317ec8f241627d5a32
6
+ metadata.gz: 99243d8b4f61bc2280b502e664c291184c36968b8783048587ec9e1ea38f7e659b261262b73f3839873c145f7c772c849e404db0482a401be8af1bba84a9ab74
7
+ data.tar.gz: dcc6b26e79a764d8c522713b2b61cc1ff9d4c981a03955a0e0743a5761fa1f11b77b11b456eb0167486d32d9f0929a91afd40fbea0791286b232e02e8895464b
checksums.yaml.gz.sig CHANGED
Binary file
@@ -0,0 +1,203 @@
1
+ # Dynamic Clusters with Envoy
2
+
3
+ This guide explains how to run Falcon workers with independently bound endpoints, publish them dynamically using xDS, and balance requests according to their current load using ORCA.
4
+
5
+ ## When to Use a Cluster
6
+
7
+ A regular {ruby Falcon::Service::Server} binds one listener and shares it with every worker. This is the simplest design when Falcon accepts connections directly or sits behind a load balancer that targets one stable address.
8
+
9
+ {ruby Falcon::Service::Cluster} instead gives each worker its own listener. Use it when an external load balancer needs to address, monitor, and remove workers individually. Because workers may bind ephemeral ports or Unix-domain sockets, the load balancer needs a dynamic source of viable endpoints rather than a static address list.
10
+
11
+ | Service | Listener ownership | Upstream discovery |
12
+ | --- | --- | --- |
13
+ | `Falcon::Service::Server` | One listener shared by all workers | One stable address |
14
+ | `Falcon::Service::Cluster` | One listener per worker | Dynamic worker endpoints |
15
+
16
+ ## Architecture
17
+
18
+ Each cluster worker can bind to `localhost` with port `0`, allowing the operating system to assign an available port. Falcon describes the bound resource with a {ruby Falcon::Listener}, including its name, scheme, supported protocols, and concrete addresses.
19
+
20
+ The worker registers that listener with `async-service-supervisor-envoy`. The supervisor publishes the current workers and load-balancing policy through an xDS control plane. Envoy uses Cluster Discovery Service (CDS) and Endpoint Discovery Service (EDS) updates to maintain the upstream cluster.
21
+
22
+ The supervisor also samples each worker's CPU utilization and request counter. It exposes those measurements using out-of-band Open Request Cost Aggregation (ORCA), which lets Envoy's client-side weighted-round-robin policy direct more requests to workers with more available capacity. This avoids coupling connection acceptance to a process-local token limiter while still responding to CPU-heavy work.
23
+
24
+ Requests arrive at Envoy's stable listener. Envoy selects one of the discovered worker endpoints and forwards the request to it:
25
+
26
+ ```mermaid
27
+ flowchart LR
28
+ Client[Client] -->|HTTP on port 10000| Envoy
29
+
30
+ subgraph Network[Shared network namespace]
31
+ Envoy[Envoy]
32
+
33
+ subgraph Falcon[Falcon container]
34
+ Supervisor[Supervisor, xDS, and ORCA]
35
+ Worker1[Falcon worker 1]
36
+ Worker2[Falcon worker 2]
37
+ end
38
+
39
+ Worker1 -.->|Register endpoint| Supervisor
40
+ Worker2 -.->|Register endpoint| Supervisor
41
+ Supervisor -.->|Dedicated CDS and EDS streams| Envoy
42
+ Supervisor -.->|Per-worker ORCA reports| Envoy
43
+ Envoy -->|HTTP on dynamic port| Worker1
44
+ Envoy -->|HTTP on dynamic port| Worker2
45
+ end
46
+ ```
47
+
48
+ ## Configuration
49
+
50
+ Add Falcon and the Envoy supervisor integration to your `gems.rb`:
51
+
52
+ ```ruby
53
+ gem "falcon", "~> 0.56.0"
54
+ gem "async-service-supervisor-envoy", "~> 0.5"
55
+ ```
56
+
57
+ Define a Falcon cluster service and an accompanying supervisor in `falcon.rb`:
58
+
59
+ ```ruby
60
+ #!/usr/bin/env async-service
61
+ # frozen_string_literal: true
62
+
63
+ require "async/service/supervisor"
64
+ require "async/service/supervisor/envoy"
65
+ require "falcon/environment/cluster"
66
+
67
+ service "cluster" do
68
+ include Falcon::Environment::Cluster
69
+ include Async::Service::Supervisor::Envoy::Supervised
70
+
71
+ count 2
72
+
73
+ def url
74
+ "http://localhost:0"
75
+ end
76
+
77
+ middleware do
78
+ application = proc do |_env|
79
+ body = "Hello from worker #{Process.pid}!\n"
80
+
81
+ [200, {
82
+ "content-type" => "text/plain",
83
+ "content-length" => body.bytesize.to_s,
84
+ }, [body]]
85
+ end
86
+
87
+ Falcon::Server.rack_middleware(application, cache: false)
88
+ end
89
+ end
90
+
91
+ service "supervisor" do
92
+ include Async::Service::Supervisor::Environment
93
+
94
+ monitors do
95
+ utilization_monitor = Async::Service::Supervisor::UtilizationMonitor.new
96
+
97
+ [
98
+ utilization_monitor,
99
+ Async::Service::Supervisor::Envoy::Monitor.new(
100
+ bind: "http://[::]:18000",
101
+ orca: true,
102
+ utilization_monitor: utilization_monitor,
103
+ ),
104
+ ]
105
+ end
106
+ end
107
+ ```
108
+
109
+ The Falcon service name becomes the listener name, so the corresponding Envoy cluster uses `cluster` as its service name. Configure Envoy to receive cluster and endpoint updates from the supervisor:
110
+
111
+ ```yaml
112
+ node:
113
+ id: falcon-cluster
114
+ cluster: falcon-cluster
115
+
116
+ dynamic_resources:
117
+ cds_config:
118
+ resource_api_version: V3
119
+ api_config_source:
120
+ api_type: GRPC
121
+ transport_api_version: V3
122
+ grpc_services:
123
+ - envoy_grpc:
124
+ cluster_name: xds_cluster
125
+
126
+ static_resources:
127
+ listeners:
128
+ - name: listener_http
129
+ address:
130
+ socket_address:
131
+ address: 0.0.0.0
132
+ port_value: 10000
133
+ filter_chains:
134
+ - filters:
135
+ - name: envoy.filters.network.http_connection_manager
136
+ typed_config:
137
+ "@type": type.googleapis.com/envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager
138
+ stat_prefix: ingress_http
139
+ route_config:
140
+ name: local_route
141
+ validate_clusters: false
142
+ virtual_hosts:
143
+ - name: falcon
144
+ domains: ["*"]
145
+ routes:
146
+ - match:
147
+ prefix: "/"
148
+ route:
149
+ cluster: cluster
150
+ http_filters:
151
+ - name: envoy.filters.http.router
152
+ typed_config:
153
+ "@type": type.googleapis.com/envoy.extensions.filters.http.router.v3.Router
154
+
155
+ clusters:
156
+ - name: xds_cluster
157
+ connect_timeout: 1s
158
+ type: STATIC
159
+ load_assignment:
160
+ cluster_name: xds_cluster
161
+ endpoints:
162
+ - lb_endpoints:
163
+ - endpoint:
164
+ address:
165
+ socket_address:
166
+ address: "::1"
167
+ port_value: 18000
168
+ typed_extension_protocol_options:
169
+ envoy.extensions.upstreams.http.v3.HttpProtocolOptions:
170
+ "@type": type.googleapis.com/envoy.extensions.upstreams.http.v3.HttpProtocolOptions
171
+ explicit_http_config:
172
+ http2_protocol_options: {}
173
+ ```
174
+
175
+ The `xds_cluster` connection uses HTTP/2 because CDS and EDS are served over gRPC. The supervisor serves dedicated CDS and EDS streams together with ORCA on port `18000`; Envoy uses that as an alternative to each worker's HTTP port when opening ORCA streams. Envoy 1.39 or later is required for this alternative reporting-port configuration.
176
+
177
+ ## Worker Registration
178
+
179
+ When each worker starts:
180
+
181
+ 1. Falcon binds the worker to an available loopback port.
182
+ 2. The worker registers its concrete addresses and supported protocols with the supervisor.
183
+ 3. The supervisor's Envoy monitor publishes the cluster policy and current worker endpoints as CDS and EDS resources.
184
+ 4. Envoy receives the resources over dedicated CDS and EDS streams and updates its upstream cluster.
185
+ 5. The supervisor samples worker CPU time and request totals, then streams the current load reports to Envoy using ORCA.
186
+
187
+ The first processor and request samples establish baselines. Load-aware weights become available after the next sampling interval. If a report is temporarily unavailable, Envoy retains its normal policy fallback rather than making the worker unreachable.
188
+
189
+ The listener preserves all addresses returned by the bound endpoint. This allows the same interface to describe IP sockets, Unix-domain sockets, and endpoints with additional addresses.
190
+
191
+ ## Worker Restarts
192
+
193
+ If a worker exits, its supervisor connection closes and the monitor removes both its endpoint and ORCA report. Falcon restarts the worker, which binds a new available port and registers it. The monitor then publishes another update, and Envoy receives both changes over its existing EDS stream without polling or restarting.
194
+
195
+ This lifecycle is important when ports are ephemeral or a directory may contain stale Unix-domain socket paths: consumers should use the supervisor's current endpoint state as the source of truth.
196
+
197
+ ## Network Topology
198
+
199
+ Falcon and Envoy can run in the same network namespace, allowing workers to bind to loopback addresses while remaining reachable by Envoy. With Docker Compose, `network_mode: service:falcon` gives the Envoy service access to Falcon's network namespace, so loopback addresses refer to the same interface for both processes.
200
+
201
+ The configuration binds the supervisor endpoint to the IPv6 wildcard address because `localhost` worker endpoints use IPv6 in the container. Envoy connects to CDS and EDS through `::1`; for each ORCA stream it uses the worker's address with the configured supervisor port `18000`. The supervisor listener must therefore be reachable using the same address family as every published worker endpoint.
202
+
203
+ Without a shared network namespace, Envoy cannot connect to worker endpoints bound to Falcon's loopback interface. In a different deployment topology, bind workers to an interface that Envoy can reach and apply the appropriate network access controls.
@@ -44,6 +44,23 @@ Then run the application with:
44
44
  $ falcon serve
45
45
  ~~~
46
46
 
47
+ #### Rack Applications Defined in Ruby Files
48
+
49
+ Rack can load a Ruby file directly and infer the application constant from its filename. For example, `Rack::Builder.parse_file("app.rb")` requires the file and uses `::App` as the Rack application.
50
+
51
+ Falcon reserves `.rb` serve configurations for protocol HTTP middleware. Existing Rack applications can be exposed through `config/serve.rb` using {ruby Protocol::Rack::Adapter}:
52
+
53
+ ~~~ ruby
54
+ # config/serve.rb
55
+
56
+ require "protocol/rack"
57
+ require_relative "../app"
58
+
59
+ run Protocol::Rack::Adapter.new(App)
60
+ ~~~
61
+
62
+ Running `falcon serve` loads `config/serve.rb` as protocol HTTP middleware, while the adapter translates requests and responses for the existing Rack application. This replaces the older `falcon serve --config app.rb` convention without requiring changes to `App` itself.
63
+
47
64
  ## Running a Local Server
48
65
 
49
66
  For local application development, you can use the `falcon serve` command. This will start a local server on `https://localhost:9292`. Falcon generates self-signed certificates for `localhost`. This allows you to test your application with HTTPS locally.
data/context/index.yaml CHANGED
@@ -18,6 +18,11 @@ files:
18
18
  description: This guide explains how to deploy applications using the Falcon web
19
19
  server. It covers the recommended deployment methods, configuration options, and
20
20
  examples for different environments, including systemd and kubernetes.
21
+ - path: cluster-deployment.md
22
+ title: Dynamic Clusters with Envoy
23
+ description: This guide explains how to run Falcon workers with independently bound
24
+ endpoints, publish them dynamically using xDS, and balance requests according
25
+ to their current load using ORCA.
21
26
  - path: performance-tuning.md
22
27
  title: Performance Tuning
23
28
  description: This guide explains the performance characteristics of Falcon.
@@ -8,7 +8,7 @@ require_relative "../server"
8
8
  require_relative "../endpoint"
9
9
  require_relative "../service/server"
10
10
  require_relative "../environment/server"
11
- require_relative "../environment/rackup"
11
+ require_relative "../environment/serve"
12
12
 
13
13
  require "async/service/configuration"
14
14
  require "async/container"
@@ -32,7 +32,7 @@ module Falcon
32
32
  option "-h/--hostname <hostname>", "Specify the hostname which would be used for certificates, etc."
33
33
  option "-t/--timeout <duration>", "Specify the maximum time to wait for non-blocking operations.", type: Float, default: nil
34
34
 
35
- option "-c/--config <path>", "Rackup configuration file to load.", default: "config.ru"
35
+ option "-c/--config <path>", "Application configuration file to load."
36
36
  option "--preload <path>", "Preload the specified path before creating containers."
37
37
 
38
38
  option "--cache", "Enable the response cache."
@@ -72,7 +72,7 @@ module Falcon
72
72
  # @returns [Async::Service::Environment] The configured server environment.
73
73
  def environment
74
74
  Async::Service::Environment.new(Falcon::Environment::Server).with(
75
- Falcon::Environment::Rackup,
75
+ Falcon::Environment::Serve,
76
76
  root: Dir.pwd,
77
77
 
78
78
  verbose: self.parent&.verbose?,
@@ -81,7 +81,7 @@ module Falcon
81
81
  container_options: self.container_options,
82
82
  endpoint_options: self.endpoint_options,
83
83
 
84
- rackup_path: @options[:config],
84
+ configuration_path: @options[:config],
85
85
  preload: [@options[:preload]].compact,
86
86
  url: @options[:bind],
87
87
 
@@ -0,0 +1,27 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Released under the MIT License.
4
+ # Copyright, 2026, by Samuel Williams.
5
+
6
+ require_relative "server"
7
+ require_relative "../service/cluster"
8
+
9
+ module Falcon
10
+ module Environment
11
+ # Provides an environment for hosting a cluster of Falcon server workers, where each worker binds its own endpoint.
12
+ module Cluster
13
+ include Server
14
+
15
+ # The service class to use for the cluster.
16
+ # @returns [Class]
17
+ def service_class
18
+ Service::Cluster
19
+ end
20
+
21
+ # The host that this server will receive connections for.
22
+ def url
23
+ "http://[::]:0"
24
+ end
25
+ end
26
+ end
27
+ end
@@ -25,7 +25,7 @@ module Falcon
25
25
  # Build the middleware stack for the rack application.
26
26
  # @returns [Protocol::HTTP::Middleware] The middleware stack.
27
27
  def middleware
28
- ::Falcon::Server.middleware(rack_app, verbose: verbose, cache: cache)
28
+ ::Falcon::Server.rack_middleware(rack_app, verbose: verbose, cache: cache)
29
29
  end
30
30
  end
31
31
  end
@@ -0,0 +1,73 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Released under the MIT License.
4
+ # Copyright, 2026, by Samuel Williams.
5
+
6
+ require "protocol/http/middleware/builder"
7
+ require "protocol/rack"
8
+
9
+ require_relative "../server"
10
+
11
+ module Falcon
12
+ module Environment
13
+ # Provides application configuration discovery and loading for `falcon serve`.
14
+ #
15
+ # When {#configuration_path} is `nil`, {#resolved_configuration_path} looks for
16
+ # `config/serve.rb` first and falls back to `config.ru`. An explicit
17
+ # {#configuration_path} bypasses this discovery order.
18
+ #
19
+ # The file extension selects the application interface:
20
+ #
21
+ # - `.rb` files are evaluated by {Protocol::HTTP::Middleware.load} using the
22
+ # protocol HTTP middleware builder interface.
23
+ # - `.ru` files are parsed as Rack applications and wrapped with
24
+ # {Protocol::Rack::Adapter}.
25
+ #
26
+ # Both application interfaces respond to `call`, so the configuration file
27
+ # extension is the explicit contract rather than inspecting the loaded object.
28
+ module Serve
29
+ # The explicitly specified application configuration path, if any.
30
+ # @returns [String | Nil]
31
+ def configuration_path
32
+ nil
33
+ end
34
+
35
+ # Resolve the application configuration path.
36
+ # @returns [String] The absolute application configuration path.
37
+ def resolved_configuration_path
38
+ if configuration_path
39
+ return File.expand_path(configuration_path, root)
40
+ end
41
+
42
+ serve_path = File.expand_path("config/serve.rb", root)
43
+ if File.file?(serve_path)
44
+ return serve_path
45
+ end
46
+
47
+ rackup_path = File.expand_path("config.ru", root)
48
+ if File.file?(rackup_path)
49
+ return rackup_path
50
+ end
51
+
52
+ raise ArgumentError, "Could not find config/serve.rb or config.ru in #{root}!"
53
+ end
54
+
55
+ # Load and wrap the configured application.
56
+ # @returns [Protocol::HTTP::Middleware] The middleware stack.
57
+ def middleware
58
+ path = resolved_configuration_path
59
+
60
+ case File.extname(path)
61
+ when ".rb"
62
+ application = ::Protocol::HTTP::Middleware.load(path)
63
+ return ::Falcon::Server.protocol_middleware(application, verbose: verbose, cache: cache)
64
+ when ".ru"
65
+ application = ::Protocol::Rack::Adapter.parse_file(path)
66
+ return ::Falcon::Server.rack_middleware(application, verbose: verbose, cache: cache)
67
+ else
68
+ raise ArgumentError, "Unsupported application configuration: #{path}!"
69
+ end
70
+ end
71
+ end
72
+ end
73
+ end
@@ -67,6 +67,14 @@ module Falcon
67
67
  ::Async::HTTP::Endpoint.parse(url)
68
68
  end
69
69
 
70
+ # Prepare a server worker after its listener has been bound.
71
+ #
72
+ # @parameter instance [Object] The container instance.
73
+ # @parameter listener [Falcon::Listener] The worker's bound listener.
74
+ def prepare_worker!(instance, listener)
75
+ prepare!(instance)
76
+ end
77
+
70
78
  # Make a server instance using the given endpoint. The endpoint may be a bound endpoint, so we take care to specify the protocol and scheme as per the original endpoint.
71
79
  #
72
80
  # @parameter endpoint [IO::Endpoint] The endpoint to bind to.
@@ -0,0 +1,43 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Released under the MIT License.
4
+ # Copyright, 2026, by Samuel Williams.
5
+
6
+ module Falcon
7
+ # Describes a bound listener for a Falcon server.
8
+ class Listener
9
+ # Initialize a bound listener.
10
+ # @parameter name [String] The logical listener name.
11
+ # @parameter scheme [String] The application protocol scheme.
12
+ # @parameter protocols [Array(String)] The supported application protocol names.
13
+ # @parameter endpoint [IO::Endpoint::BoundEndpoint] The bound endpoint.
14
+ def initialize(name:, scheme:, protocols:, endpoint:)
15
+ @name = name
16
+ @scheme = scheme
17
+ @protocols = protocols.map(&:to_s).freeze
18
+ @endpoint = endpoint
19
+ @addresses = endpoint.sockets.map{|socket| socket.to_io.local_address}.freeze
20
+ freeze
21
+ end
22
+
23
+ # @attribute [String] The logical listener name.
24
+ attr_reader :name
25
+
26
+ # @attribute [String] The application protocol scheme.
27
+ attr_reader :scheme
28
+
29
+ # @attribute [Array(String)] The supported application protocol names.
30
+ attr_reader :protocols
31
+
32
+ # @attribute [IO::Endpoint::BoundEndpoint] The bound endpoint.
33
+ attr_reader :endpoint
34
+
35
+ # @attribute [Array(Addrinfo)] The bound addresses.
36
+ attr_reader :addresses
37
+
38
+ # Close the bound endpoint.
39
+ def close
40
+ @endpoint.close
41
+ end
42
+ end
43
+ end
data/lib/falcon/server.rb CHANGED
@@ -17,11 +17,26 @@ require "protocol/rack"
17
17
  module Falcon
18
18
  # A server listening on a specific endpoint, hosting a specific middleware.
19
19
  class Server < Async::HTTP::Server
20
- # Wrap a rack application into a middleware suitable the server.
20
+ # @deprecated Use {rack_middleware} instead.
21
+ def self.middleware(...)
22
+ warn("`Falcon::Server.middleware` is deprecated, use `.rack_middleware` instead.", uplevel: 1, category: :deprecated) if $VERBOSE
23
+
24
+ return self.rack_middleware(...)
25
+ end
26
+
27
+ # Wrap a Rack application with the standard server middleware.
21
28
  # @parameter rack_app [Proc | Object] A rack application/middleware.
22
29
  # @parameter verbose [Boolean] Whether to add the {Middleware::Verbose} middleware.
23
30
  # @parameter cache [Boolean] Whether to add the {Async::HTTP::Cache} middleware.
24
- def self.middleware(rack_app, verbose: false, cache: true)
31
+ def self.rack_middleware(rack_app, verbose: false, cache: true)
32
+ return self.protocol_middleware(::Protocol::Rack::Adapter.new(rack_app), verbose: verbose, cache: cache)
33
+ end
34
+
35
+ # Wrap a protocol application with the standard server middleware.
36
+ # @parameter application [Protocol::HTTP::Middleware] The protocol application/middleware.
37
+ # @parameter verbose [Boolean] Whether to add the {Middleware::Verbose} middleware.
38
+ # @parameter cache [Boolean] Whether to add the {Async::HTTP::Cache} middleware.
39
+ def self.protocol_middleware(application, verbose: false, cache: true)
25
40
  ::Protocol::HTTP::Middleware.build do
26
41
  if verbose
27
42
  use Middleware::Verbose
@@ -32,9 +47,7 @@ module Falcon
32
47
  end
33
48
 
34
49
  use ::Protocol::HTTP::ContentEncoding
35
-
36
- use ::Protocol::Rack::Adapter
37
- run rack_app
50
+ run application
38
51
  end
39
52
  end
40
53
 
@@ -0,0 +1,32 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Released under the MIT License.
4
+ # Copyright, 2026, by Samuel Williams.
5
+
6
+ require_relative "server"
7
+
8
+ module Falcon
9
+ # @namespace
10
+ module Service
11
+ # A managed service for running Falcon workers with independently bound endpoints.
12
+ class Cluster < Server
13
+ # Cluster workers bind independently in their own process.
14
+ def bind_endpoint
15
+ end
16
+
17
+ # Bind and yield a listener owned by a cluster worker.
18
+ # @parameter evaluator [Environment::Evaluator] The environment evaluator.
19
+ # @yields {|listener| ...} The listener owned by the worker.
20
+ # @parameter listener [Falcon::Listener] The bound listener.
21
+ def with_listener(evaluator)
22
+ endpoint = evaluator.endpoint
23
+ bound_endpoint = endpoint.bound
24
+ listener = make_listener(evaluator, endpoint, bound_endpoint)
25
+
26
+ yield listener
27
+ ensure
28
+ bound_endpoint&.close
29
+ end
30
+ end
31
+ end
32
+ end
@@ -7,6 +7,7 @@
7
7
  require "async/service/managed/service"
8
8
  require "async/http/endpoint"
9
9
 
10
+ require_relative "../listener"
10
11
  require_relative "../server"
11
12
 
12
13
  module Falcon
@@ -18,35 +19,99 @@ module Falcon
18
19
  def initialize(...)
19
20
  super
20
21
 
21
- @bound_endpoint = nil
22
+ @listener = nil
22
23
  end
23
24
 
24
- # Prepare the bound endpoint for the server.
25
- def start
25
+ # Build a listener from a configured and bound endpoint.
26
+ # @parameter evaluator [Environment::Evaluator] The environment evaluator.
27
+ # @parameter endpoint [Async::HTTP::Endpoint] The configured endpoint.
28
+ # @parameter bound_endpoint [IO::Endpoint::BoundEndpoint] The bound endpoint.
29
+ # @returns [Falcon::Listener] The bound listener.
30
+ def make_listener(evaluator, endpoint, bound_endpoint)
31
+ Listener.new(
32
+ name: evaluator.name,
33
+ scheme: endpoint.scheme,
34
+ protocols: endpoint.protocol.names,
35
+ endpoint: bound_endpoint,
36
+ )
37
+ end
38
+
39
+ # Bind the endpoint used by each server worker.
40
+ def bind_endpoint
26
41
  @endpoint = @evaluator.endpoint
27
42
 
28
43
  Sync do
29
- @bound_endpoint = @endpoint.bound
44
+ bound_endpoint = @endpoint.bound
45
+ @listener = make_listener(@evaluator, @endpoint, bound_endpoint)
30
46
  end
31
47
 
32
48
  Console.info(self){"Starting #{self.name} on #{@endpoint}"}
49
+ end
50
+
51
+ # Prepare the bound endpoint for the server.
52
+ def start
53
+ bind_endpoint
33
54
 
34
55
  super
35
56
  end
36
57
 
58
+ # Yield the listener used by a server worker.
59
+ # @parameter evaluator [Environment::Evaluator] The environment evaluator.
60
+ # @yields {|listener| ...} The listener used by the worker.
61
+ # @parameter listener [Falcon::Listener] The bound listener.
62
+ def with_listener(evaluator)
63
+ yield @listener
64
+ end
65
+
66
+ # Setup the service into the specified container.
67
+ # @parameter container [Async::Container] The container to configure.
68
+ def setup(container)
69
+ container_options = @evaluator.container_options
70
+ health_check_timeout = container_options[:health_check_timeout]
71
+
72
+ container.run(**container_options) do |instance|
73
+ clock = Async::Clock.start
74
+ evaluator = self.environment.evaluator
75
+
76
+ with_listener(evaluator) do |listener|
77
+ Async do
78
+ server = nil
79
+
80
+ health_checker(instance, health_check_timeout) do
81
+ if server
82
+ instance.name = format_title(evaluator, server)
83
+ end
84
+ end
85
+
86
+ instance.status!("Preparing...")
87
+ evaluator.prepare_worker!(instance, listener)
88
+ emit_prepared(instance, clock)
89
+
90
+ instance.status!("Running...")
91
+ server = run(instance, evaluator, listener)
92
+ instance.name = format_title(evaluator, server)
93
+ emit_running(instance, clock)
94
+
95
+ instance.ready!
96
+ end
97
+ end
98
+ end
99
+ end
100
+
37
101
  # Run the service logic.
38
102
  #
39
103
  # @parameter instance [Object] The container instance.
40
104
  # @parameter evaluator [Environment::Evaluator] The environment evaluator.
105
+ # @parameter listener [Falcon::Listener] The listener used by this worker.
41
106
  # @returns [Falcon::Server] The server instance.
42
- def run(instance, evaluator)
107
+ def run(instance, evaluator, listener = @listener)
43
108
  if evaluator.respond_to?(:make_supervised_worker)
44
109
  Console.warn(self, "Async::Container::Supervisor is replaced by Async::Service::Supervisor, please update your service definition.")
45
110
 
46
111
  evaluator.make_supervised_worker(instance).run
47
112
  end
48
113
 
49
- server = evaluator.make_server(@bound_endpoint)
114
+ server = evaluator.make_server(listener.endpoint)
50
115
 
51
116
  Async do |task|
52
117
  server.run
@@ -69,9 +134,9 @@ module Falcon
69
134
 
70
135
  # Close the bound endpoint.
71
136
  def stop(...)
72
- if @bound_endpoint
73
- @bound_endpoint.close
74
- @bound_endpoint = nil
137
+ if @listener
138
+ @listener.close
139
+ @listener = nil
75
140
  end
76
141
 
77
142
  @endpoint = nil
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  # Released under the MIT License.
4
- # Copyright, 2020-2024, by Samuel Williams.
4
+ # Copyright, 2020-2026, by Samuel Williams.
5
5
 
6
6
  require "async/service/generic"
7
7
  require "console"
@@ -5,5 +5,5 @@
5
5
 
6
6
  # @namespace
7
7
  module Falcon
8
- VERSION = "0.55.6"
8
+ VERSION = "0.57.0"
9
9
  end
data/lib/falcon.rb CHANGED
@@ -4,6 +4,7 @@
4
4
  # Copyright, 2017-2026, by Samuel Williams.
5
5
 
6
6
  require_relative "falcon/version"
7
+ require_relative "falcon/listener"
7
8
  require_relative "falcon/server"
8
9
  require_relative "falcon/composite_server"
9
10
 
data/readme.md CHANGED
@@ -35,6 +35,8 @@ Please see the [project documentation](https://socketry.github.io/falcon/) for m
35
35
 
36
36
  - [Deployment](https://socketry.github.io/falcon/guides/deployment/index) - This guide explains how to deploy applications using the Falcon web server. It covers the recommended deployment methods, configuration options, and examples for different environments, including systemd and kubernetes.
37
37
 
38
+ - [Dynamic Clusters with Envoy](https://socketry.github.io/falcon/guides/cluster-deployment/index) - This guide explains how to run Falcon workers with independently bound endpoints, publish them dynamically using xDS, and balance requests according to their current load using ORCA.
39
+
38
40
  - [Performance Tuning](https://socketry.github.io/falcon/guides/performance-tuning/index) - This guide explains the performance characteristics of Falcon.
39
41
 
40
42
  - [WebSockets](https://socketry.github.io/falcon/guides/websockets/index) - This guide explains how to use WebSockets with Falcon.
@@ -47,6 +49,16 @@ Please see the [project documentation](https://socketry.github.io/falcon/) for m
47
49
 
48
50
  Please see the [project releases](https://socketry.github.io/falcon/releases/index) for all releases.
49
51
 
52
+ ### v0.57.0
53
+
54
+ - Update the Envoy cluster example to use dedicated CDS and EDS services from `async-service-supervisor-envoy` v0.5.
55
+ - [Rack Compatibility](https://socketry.github.io/falcon/releases/index#rack-compatibility)
56
+
57
+ ### v0.56.0
58
+
59
+ - Add `Falcon::Environment::Cluster` and `Falcon::Service::Cluster` for running workers with independently bound endpoints.
60
+ - Add `Falcon::Listener` to describe bound listeners shared by regular server workers or owned by cluster workers.
61
+
50
62
  ### v0.55.6
51
63
 
52
64
  - Move Falcon middleware trace providers to `traces/provider/falcon/middleware`.
@@ -83,14 +95,6 @@ Please see the [project releases](https://socketry.github.io/falcon/releases/ind
83
95
 
84
96
  - Fix handling of old style supervisors from `Async::Container::Supervisor`.
85
97
 
86
- ### v0.54.0
87
-
88
- - Introduce `Falcon::CompositeServer` for hosting multiple server instances in a single worker.
89
-
90
- ### v0.52.4
91
-
92
- - Relax dependency on `async-container-supervisor` to allow `~> 0.6`.
93
-
94
98
  ## Contributing
95
99
 
96
100
  We welcome contributions to this project.
data/releases.md CHANGED
@@ -1,5 +1,20 @@
1
1
  # Releases
2
2
 
3
+ ## v0.57.0
4
+
5
+ - Update the Envoy cluster example to use dedicated CDS and EDS services from `async-service-supervisor-envoy` v0.5.
6
+
7
+ ### Rack Compatibility
8
+
9
+ Falcon is shifting its application boundary from a Rack-centric design to {ruby Protocol::HTTP::Middleware}. `falcon serve` now prefers protocol HTTP middleware configured by `config/serve.rb`, while continuing to discover and run Rack `config.ru` applications through {ruby Protocol::Rack::Adapter}.
10
+
11
+ Use {ruby Falcon::Server.protocol\_middleware} for protocol HTTP applications and {ruby Falcon::Server.rack\_middleware} for Rack applications. {ruby Falcon::Server.middleware} is deprecated. Explicit `.rb` serve configurations are now interpreted as {ruby Protocol::HTTP::Middleware}; Rack applications defined in Ruby files should use `config.ru` or wrap the application explicitly with {ruby Protocol::Rack::Adapter}.
12
+
13
+ ## v0.56.0
14
+
15
+ - Add `Falcon::Environment::Cluster` and `Falcon::Service::Cluster` for running workers with independently bound endpoints.
16
+ - Add `Falcon::Listener` to describe bound listeners shared by regular server workers or owned by cluster workers.
17
+
3
18
  ## v0.55.6
4
19
 
5
20
  - Move Falcon middleware trace providers to `traces/provider/falcon/middleware`.
data.tar.gz.sig CHANGED
Binary file
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: falcon
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.55.6
4
+ version: 0.57.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Samuel Williams
@@ -101,14 +101,14 @@ dependencies:
101
101
  requirements:
102
102
  - - "~>"
103
103
  - !ruby/object:Gem::Version
104
- version: '0.75'
104
+ version: '0.97'
105
105
  type: :runtime
106
106
  prerelease: false
107
107
  version_requirements: !ruby/object:Gem::Requirement
108
108
  requirements:
109
109
  - - "~>"
110
110
  - !ruby/object:Gem::Version
111
- version: '0.75'
111
+ version: '0.97'
112
112
  - !ruby/object:Gem::Dependency
113
113
  name: async-http-cache
114
114
  requirement: !ruby/object:Gem::Requirement
@@ -243,6 +243,7 @@ extra_rdoc_files: []
243
243
  files:
244
244
  - bin/falcon
245
245
  - bin/falcon-host
246
+ - context/cluster-deployment.md
246
247
  - context/deployment.md
247
248
  - context/extended-features.md
248
249
  - context/getting-started.md
@@ -267,6 +268,7 @@ files:
267
268
  - lib/falcon/endpoint.rb
268
269
  - lib/falcon/environment.rb
269
270
  - lib/falcon/environment/application.rb
271
+ - lib/falcon/environment/cluster.rb
270
272
  - lib/falcon/environment/configured.rb
271
273
  - lib/falcon/environment/lets_encrypt_tls.rb
272
274
  - lib/falcon/environment/proxy.rb
@@ -274,9 +276,11 @@ files:
274
276
  - lib/falcon/environment/rackup.rb
275
277
  - lib/falcon/environment/redirect.rb
276
278
  - lib/falcon/environment/self_signed_tls.rb
279
+ - lib/falcon/environment/serve.rb
277
280
  - lib/falcon/environment/server.rb
278
281
  - lib/falcon/environment/tls.rb
279
282
  - lib/falcon/environment/virtual.rb
283
+ - lib/falcon/listener.rb
280
284
  - lib/falcon/middleware/proxy.rb
281
285
  - lib/falcon/middleware/redirect.rb
282
286
  - lib/falcon/middleware/verbose.rb
@@ -284,6 +288,7 @@ files:
284
288
  - lib/falcon/rackup/handler.rb
285
289
  - lib/falcon/railtie.rb
286
290
  - lib/falcon/server.rb
291
+ - lib/falcon/service/cluster.rb
287
292
  - lib/falcon/service/server.rb
288
293
  - lib/falcon/service/virtual.rb
289
294
  - lib/falcon/tls.rb
metadata.gz.sig CHANGED
Binary file