From bb47caf7effd3ff50ef4630c9537f71c39540562 Mon Sep 17 00:00:00 2001 From: Samuel Williams Date: Sun, 9 Aug 2026 11:31:45 +1200 Subject: [PATCH 01/12] Support protocol applications in falcon serve. --- lib/falcon/command/serve.rb | 8 ++-- lib/falcon/environment/serve.rb | 59 ++++++++++++++++++++++++ lib/falcon/server.rb | 12 +++-- releases.md | 1 + test/falcon/command/.serve/config.rb | 3 ++ test/falcon/command/serve.rb | 11 ++++- test/falcon/environment/serve.rb | 69 ++++++++++++++++++++++++++++ 7 files changed, 155 insertions(+), 8 deletions(-) create mode 100644 lib/falcon/environment/serve.rb create mode 100644 test/falcon/command/.serve/config.rb create mode 100644 test/falcon/environment/serve.rb diff --git a/lib/falcon/command/serve.rb b/lib/falcon/command/serve.rb index 06f320ca..7b3730a2 100644 --- a/lib/falcon/command/serve.rb +++ b/lib/falcon/command/serve.rb @@ -8,7 +8,7 @@ require_relative "../endpoint" require_relative "../service/server" require_relative "../environment/server" -require_relative "../environment/rackup" +require_relative "../environment/serve" require "async/service/configuration" require "async/container" @@ -32,7 +32,7 @@ class Serve < Samovar::Command option "-h/--hostname ", "Specify the hostname which would be used for certificates, etc." option "-t/--timeout ", "Specify the maximum time to wait for non-blocking operations.", type: Float, default: nil - option "-c/--config ", "Rackup configuration file to load.", default: "config.ru" + option "-c/--config ", "Application configuration file to load." option "--preload ", "Preload the specified path before creating containers." option "--cache", "Enable the response cache." @@ -72,7 +72,7 @@ def name # @returns [Async::Service::Environment] The configured server environment. def environment Async::Service::Environment.new(Falcon::Environment::Server).with( - Falcon::Environment::Rackup, + Falcon::Environment::Serve, root: Dir.pwd, verbose: self.parent&.verbose?, @@ -81,7 +81,7 @@ def environment container_options: self.container_options, endpoint_options: self.endpoint_options, - rackup_path: @options[:config], + configuration_path: @options[:config], preload: [@options[:preload]].compact, url: @options[:bind], diff --git a/lib/falcon/environment/serve.rb b/lib/falcon/environment/serve.rb new file mode 100644 index 00000000..2d869b53 --- /dev/null +++ b/lib/falcon/environment/serve.rb @@ -0,0 +1,59 @@ +# frozen_string_literal: true + +# Released under the MIT License. +# Copyright, 2026, by Samuel Williams. + +require "protocol/http/middleware/builder" +require "protocol/rack" + +require_relative "../server" + +module Falcon + module Environment + # Provides configuration discovery and loading for `falcon serve`. + module Serve + # The explicitly specified application configuration path, if any. + # @returns [String | Nil] + def configuration_path + nil + end + + # Resolve the application configuration path. + # @returns [String] The absolute application configuration path. + def resolved_configuration_path + if configuration_path + return File.expand_path(configuration_path, root) + end + + serve_path = File.expand_path("config/serve.rb", root) + if File.file?(serve_path) + return serve_path + end + + rackup_path = File.expand_path("config.ru", root) + if File.file?(rackup_path) + return rackup_path + end + + raise ArgumentError, "Could not find config/serve.rb or config.ru in #{root}!" + end + + # Load and wrap the configured application. + # @returns [Protocol::HTTP::Middleware] The middleware stack. + def middleware + path = resolved_configuration_path + + case File.extname(path) + when ".rb" + application = ::Protocol::HTTP::Middleware.load(path) + return ::Falcon::Server.protocol_middleware(application, verbose: verbose, cache: cache) + when ".ru" + application = ::Protocol::Rack::Adapter.parse_file(path) + return ::Falcon::Server.middleware(application, verbose: verbose, cache: cache) + else + raise ArgumentError, "Unsupported application configuration: #{path}!" + end + end + end + end +end diff --git a/lib/falcon/server.rb b/lib/falcon/server.rb index 5dca1040..aa8df3f8 100644 --- a/lib/falcon/server.rb +++ b/lib/falcon/server.rb @@ -22,6 +22,14 @@ class Server < Async::HTTP::Server # @parameter verbose [Boolean] Whether to add the {Middleware::Verbose} middleware. # @parameter cache [Boolean] Whether to add the {Async::HTTP::Cache} middleware. def self.middleware(rack_app, verbose: false, cache: true) + return self.protocol_middleware(::Protocol::Rack::Adapter.new(rack_app), verbose: verbose, cache: cache) + end + + # Wrap a protocol application with the standard server middleware. + # @parameter application [Protocol::HTTP::Middleware] The protocol application/middleware. + # @parameter verbose [Boolean] Whether to add the {Middleware::Verbose} middleware. + # @parameter cache [Boolean] Whether to add the {Async::HTTP::Cache} middleware. + def self.protocol_middleware(application, verbose: false, cache: true) ::Protocol::HTTP::Middleware.build do if verbose use Middleware::Verbose @@ -32,9 +40,7 @@ def self.middleware(rack_app, verbose: false, cache: true) end use ::Protocol::HTTP::ContentEncoding - - use ::Protocol::Rack::Adapter - run rack_app + run application end end diff --git a/releases.md b/releases.md index fcd17e91..756b3fe2 100644 --- a/releases.md +++ b/releases.md @@ -3,6 +3,7 @@ ## Unreleased - Update the Envoy cluster example to use dedicated CDS and EDS services from `async-service-supervisor-envoy` v0.5. + - Add protocol-native `config/serve.rb` support to `falcon serve`, with fallback to Rack `config.ru` applications. ## v0.56.0 diff --git a/test/falcon/command/.serve/config.rb b/test/falcon/command/.serve/config.rb new file mode 100644 index 00000000..1959c154 --- /dev/null +++ b/test/falcon/command/.serve/config.rb @@ -0,0 +1,3 @@ +# frozen_string_literal: true + +run Protocol::HTTP::Middleware::Okay diff --git a/test/falcon/command/serve.rb b/test/falcon/command/serve.rb index f1185fcb..a0926b01 100644 --- a/test/falcon/command/serve.rb +++ b/test/falcon/command/serve.rb @@ -14,10 +14,12 @@ let(:command) do subject[ "--port", port, - "--config", File.expand_path("config.ru", __dir__), *options + "--config", File.expand_path(configuration, __dir__), *options ] end + let(:configuration) {"config.ru"} + it "can listen on specified port" do configuration = command.configuration controller = configuration.make_controller @@ -56,6 +58,13 @@ it_behaves_like ServeCommand end + with "a protocol application" do + let(:port) {8096} + let(:configuration) {".serve/config.rb"} + + it_behaves_like ServeCommand + end + with "threaded container" do let(:port) {8092} let(:options) {["--count", 4, "--threaded"]} diff --git a/test/falcon/environment/serve.rb b/test/falcon/environment/serve.rb new file mode 100644 index 00000000..54f84a31 --- /dev/null +++ b/test/falcon/environment/serve.rb @@ -0,0 +1,69 @@ +# frozen_string_literal: true + +# Released under the MIT License. +# Copyright, 2026, by Samuel Williams. + +require "falcon/environment/serve" +require "falcon/environment/server" +require "async/service/environment" +require "sus/fixtures/temporary_directory_context" +require "fileutils" + +describe Falcon::Environment::Serve do + include Sus::Fixtures::TemporaryDirectoryContext + + let(:evaluator) do + Async::Service::Environment.build( + Falcon::Environment::Server, + subject, + root: root, + name: "localhost", + ).evaluator + end + + it "prefers config/serve.rb" do + FileUtils.mkdir_p(File.join(root, "config")) + File.write(File.join(root, "config.ru"), "run ->(env) {[200, {}, []]}\n") + File.write(File.join(root, "config/serve.rb"), "run Protocol::HTTP::Middleware::Okay\n") + + expect(evaluator.resolved_configuration_path).to be == File.join(root, "config/serve.rb") + end + + it "falls back to config.ru" do + File.write(File.join(root, "config.ru"), "run ->(env) {[200, {}, []]}\n") + + expect(evaluator.resolved_configuration_path).to be == File.join(root, "config.ru") + end + + it "uses an explicit configuration path" do + evaluator = Async::Service::Environment.build( + Falcon::Environment::Server, + subject, + root: root, + name: "localhost", + configuration_path: "application.rb", + ).evaluator + + expect(evaluator.resolved_configuration_path).to be == File.join(root, "application.rb") + end + + it "rejects unsupported configuration extensions" do + evaluator = Async::Service::Environment.build( + Falcon::Environment::Server, + subject, + root: root, + name: "localhost", + configuration_path: "application.txt", + ).evaluator + + expect do + evaluator.middleware + end.to raise_exception(ArgumentError, message: be(:include?, "Unsupported application configuration")) + end + + it "fails when no configuration exists" do + expect do + evaluator.resolved_configuration_path + end.to raise_exception(ArgumentError, message: be(:include?, "Could not find")) + end +end From bc930c74c606c5218609b7dec404d28f57dfac58 Mon Sep 17 00:00:00 2001 From: Samuel Williams Date: Sun, 9 Aug 2026 11:40:28 +1200 Subject: [PATCH 02/12] Cover protocol serve configuration loading. --- test/falcon/environment/serve.rb | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/test/falcon/environment/serve.rb b/test/falcon/environment/serve.rb index 54f84a31..ed1412cb 100644 --- a/test/falcon/environment/serve.rb +++ b/test/falcon/environment/serve.rb @@ -29,6 +29,13 @@ expect(evaluator.resolved_configuration_path).to be == File.join(root, "config/serve.rb") end + it "loads config/serve.rb as protocol middleware" do + FileUtils.mkdir_p(File.join(root, "config")) + File.write(File.join(root, "config/serve.rb"), "run Protocol::HTTP::Middleware::Okay\n") + + expect(evaluator.middleware).to be_a(Protocol::HTTP::Middleware) + end + it "falls back to config.ru" do File.write(File.join(root, "config.ru"), "run ->(env) {[200, {}, []]}\n") From ffa7045c19115bd2a2ea407fadabe6db1dbbdaca Mon Sep 17 00:00:00 2001 From: Samuel Williams Date: Sun, 9 Aug 2026 11:49:23 +1200 Subject: [PATCH 03/12] Simplify serve configuration path resolution. --- lib/falcon/command/serve.rb | 15 +++++++++++---- lib/falcon/environment/serve.rb | 16 +++------------- test/falcon/environment/serve.rb | 12 +++++++----- 3 files changed, 21 insertions(+), 22 deletions(-) diff --git a/lib/falcon/command/serve.rb b/lib/falcon/command/serve.rb index 7b3730a2..0ab64cb6 100644 --- a/lib/falcon/command/serve.rb +++ b/lib/falcon/command/serve.rb @@ -71,8 +71,7 @@ def name # Create the environment for the serve command. # @returns [Async::Service::Environment] The configured server environment. def environment - Async::Service::Environment.new(Falcon::Environment::Server).with( - Falcon::Environment::Serve, + environment_options = { root: Dir.pwd, verbose: self.parent&.verbose?, @@ -81,13 +80,21 @@ def environment container_options: self.container_options, endpoint_options: self.endpoint_options, - configuration_path: @options[:config], preload: [@options[:preload]].compact, url: @options[:bind], name: self.name, - endpoint: ->{Endpoint.parse(url, **endpoint_options)} + endpoint: ->{Endpoint.parse(url, **endpoint_options)}, + } + + if configuration_path = @options[:config] + environment_options[:configuration_path] = File.expand_path(configuration_path, Dir.pwd) + end + + return Async::Service::Environment.new(Falcon::Environment::Server).with( + Falcon::Environment::Serve, + **environment_options, ) end diff --git a/lib/falcon/environment/serve.rb b/lib/falcon/environment/serve.rb index 2d869b53..7a164db6 100644 --- a/lib/falcon/environment/serve.rb +++ b/lib/falcon/environment/serve.rb @@ -12,19 +12,9 @@ module Falcon module Environment # Provides configuration discovery and loading for `falcon serve`. module Serve - # The explicitly specified application configuration path, if any. - # @returns [String | Nil] - def configuration_path - nil - end - - # Resolve the application configuration path. + # Discover the application configuration path. # @returns [String] The absolute application configuration path. - def resolved_configuration_path - if configuration_path - return File.expand_path(configuration_path, root) - end - + def configuration_path serve_path = File.expand_path("config/serve.rb", root) if File.file?(serve_path) return serve_path @@ -41,7 +31,7 @@ def resolved_configuration_path # Load and wrap the configured application. # @returns [Protocol::HTTP::Middleware] The middleware stack. def middleware - path = resolved_configuration_path + path = configuration_path case File.extname(path) when ".rb" diff --git a/test/falcon/environment/serve.rb b/test/falcon/environment/serve.rb index ed1412cb..2139de58 100644 --- a/test/falcon/environment/serve.rb +++ b/test/falcon/environment/serve.rb @@ -26,7 +26,7 @@ File.write(File.join(root, "config.ru"), "run ->(env) {[200, {}, []]}\n") File.write(File.join(root, "config/serve.rb"), "run Protocol::HTTP::Middleware::Okay\n") - expect(evaluator.resolved_configuration_path).to be == File.join(root, "config/serve.rb") + expect(evaluator.configuration_path).to be == File.join(root, "config/serve.rb") end it "loads config/serve.rb as protocol middleware" do @@ -39,19 +39,21 @@ it "falls back to config.ru" do File.write(File.join(root, "config.ru"), "run ->(env) {[200, {}, []]}\n") - expect(evaluator.resolved_configuration_path).to be == File.join(root, "config.ru") + expect(evaluator.configuration_path).to be == File.join(root, "config.ru") end it "uses an explicit configuration path" do + configuration_path = File.join(root, "application.rb") + evaluator = Async::Service::Environment.build( Falcon::Environment::Server, subject, root: root, name: "localhost", - configuration_path: "application.rb", + configuration_path: configuration_path, ).evaluator - expect(evaluator.resolved_configuration_path).to be == File.join(root, "application.rb") + expect(evaluator.configuration_path).to be == configuration_path end it "rejects unsupported configuration extensions" do @@ -70,7 +72,7 @@ it "fails when no configuration exists" do expect do - evaluator.resolved_configuration_path + evaluator.configuration_path end.to raise_exception(ArgumentError, message: be(:include?, "Could not find")) end end From f4bb534c9c05b90e3a982ff0db9e2fe7b29d17eb Mon Sep 17 00:00:00 2001 From: Samuel Williams Date: Sun, 9 Aug 2026 12:01:40 +1200 Subject: [PATCH 04/12] Restore resolved serve configuration paths. --- lib/falcon/command/serve.rb | 15 ++++----------- lib/falcon/environment/serve.rb | 16 +++++++++++++--- test/falcon/environment/serve.rb | 24 ++++++++++++++++++------ 3 files changed, 35 insertions(+), 20 deletions(-) diff --git a/lib/falcon/command/serve.rb b/lib/falcon/command/serve.rb index 0ab64cb6..93107188 100644 --- a/lib/falcon/command/serve.rb +++ b/lib/falcon/command/serve.rb @@ -71,7 +71,8 @@ def name # Create the environment for the serve command. # @returns [Async::Service::Environment] The configured server environment. def environment - environment_options = { + return Async::Service::Environment.new(Falcon::Environment::Server).with( + Falcon::Environment::Serve, root: Dir.pwd, verbose: self.parent&.verbose?, @@ -80,21 +81,13 @@ def environment container_options: self.container_options, endpoint_options: self.endpoint_options, + configuration_path: @options[:config], preload: [@options[:preload]].compact, url: @options[:bind], name: self.name, - endpoint: ->{Endpoint.parse(url, **endpoint_options)}, - } - - if configuration_path = @options[:config] - environment_options[:configuration_path] = File.expand_path(configuration_path, Dir.pwd) - end - - return Async::Service::Environment.new(Falcon::Environment::Server).with( - Falcon::Environment::Serve, - **environment_options, + endpoint: ->{Endpoint.parse(url, **endpoint_options)} ) end diff --git a/lib/falcon/environment/serve.rb b/lib/falcon/environment/serve.rb index 7a164db6..2d869b53 100644 --- a/lib/falcon/environment/serve.rb +++ b/lib/falcon/environment/serve.rb @@ -12,9 +12,19 @@ module Falcon module Environment # Provides configuration discovery and loading for `falcon serve`. module Serve - # Discover the application configuration path. - # @returns [String] The absolute application configuration path. + # The explicitly specified application configuration path, if any. + # @returns [String | Nil] def configuration_path + nil + end + + # Resolve the application configuration path. + # @returns [String] The absolute application configuration path. + def resolved_configuration_path + if configuration_path + return File.expand_path(configuration_path, root) + end + serve_path = File.expand_path("config/serve.rb", root) if File.file?(serve_path) return serve_path @@ -31,7 +41,7 @@ def configuration_path # Load and wrap the configured application. # @returns [Protocol::HTTP::Middleware] The middleware stack. def middleware - path = configuration_path + path = resolved_configuration_path case File.extname(path) when ".rb" diff --git a/test/falcon/environment/serve.rb b/test/falcon/environment/serve.rb index 2139de58..082edc6f 100644 --- a/test/falcon/environment/serve.rb +++ b/test/falcon/environment/serve.rb @@ -26,7 +26,7 @@ File.write(File.join(root, "config.ru"), "run ->(env) {[200, {}, []]}\n") File.write(File.join(root, "config/serve.rb"), "run Protocol::HTTP::Middleware::Okay\n") - expect(evaluator.configuration_path).to be == File.join(root, "config/serve.rb") + expect(evaluator.resolved_configuration_path).to be == File.join(root, "config/serve.rb") end it "loads config/serve.rb as protocol middleware" do @@ -39,21 +39,33 @@ it "falls back to config.ru" do File.write(File.join(root, "config.ru"), "run ->(env) {[200, {}, []]}\n") - expect(evaluator.configuration_path).to be == File.join(root, "config.ru") + expect(evaluator.resolved_configuration_path).to be == File.join(root, "config.ru") end it "uses an explicit configuration path" do - configuration_path = File.join(root, "application.rb") + evaluator = Async::Service::Environment.build( + Falcon::Environment::Server, + subject, + root: root, + name: "localhost", + configuration_path: "application.rb", + ).evaluator + + expect(evaluator.resolved_configuration_path).to be == File.join(root, "application.rb") + end + + it "discovers a configuration when the explicit path is nil" do + File.write(File.join(root, "config.ru"), "run ->(env) {[200, {}, []]}\n") evaluator = Async::Service::Environment.build( Falcon::Environment::Server, subject, root: root, name: "localhost", - configuration_path: configuration_path, + configuration_path: nil, ).evaluator - expect(evaluator.configuration_path).to be == configuration_path + expect(evaluator.resolved_configuration_path).to be == File.join(root, "config.ru") end it "rejects unsupported configuration extensions" do @@ -72,7 +84,7 @@ it "fails when no configuration exists" do expect do - evaluator.configuration_path + evaluator.resolved_configuration_path end.to raise_exception(ArgumentError, message: be(:include?, "Could not find")) end end From 02a5bef8fd98f1b5859eb42ed0f91a934261b65c Mon Sep 17 00:00:00 2001 From: Samuel Williams Date: Sun, 9 Aug 2026 12:22:11 +1200 Subject: [PATCH 05/12] Document serve configuration multiplexing. --- lib/falcon/environment/serve.rb | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/lib/falcon/environment/serve.rb b/lib/falcon/environment/serve.rb index 2d869b53..4097c410 100644 --- a/lib/falcon/environment/serve.rb +++ b/lib/falcon/environment/serve.rb @@ -10,7 +10,21 @@ module Falcon module Environment - # Provides configuration discovery and loading for `falcon serve`. + # Provides application configuration discovery and loading for `falcon serve`. + # + # When {#configuration_path} is `nil`, {#resolved_configuration_path} looks for + # `config/serve.rb` first and falls back to `config.ru`. An explicit + # {#configuration_path} bypasses this discovery order. + # + # The file extension selects the application interface: + # + # - `.rb` files are evaluated by {Protocol::HTTP::Middleware.load} using the + # protocol middleware builder interface. + # - `.ru` files are parsed as Rack applications and wrapped with + # {Protocol::Rack::Adapter}. + # + # Both application interfaces respond to `call`, so the configuration file + # extension is the explicit contract rather than inspecting the loaded object. module Serve # The explicitly specified application configuration path, if any. # @returns [String | Nil] From ffcdbb11e314efb13c16315c40e4af8432288c0b Mon Sep 17 00:00:00 2001 From: Samuel Williams Date: Sun, 9 Aug 2026 12:43:10 +1200 Subject: [PATCH 06/12] Document Rack applications defined in Ruby files. --- context/getting-started.md | 17 +++++++++++++++++ guides/getting-started/readme.md | 17 +++++++++++++++++ 2 files changed, 34 insertions(+) diff --git a/context/getting-started.md b/context/getting-started.md index ee200a32..f0c86fda 100644 --- a/context/getting-started.md +++ b/context/getting-started.md @@ -44,6 +44,23 @@ Then run the application with: $ falcon serve ~~~ +#### Rack Applications Defined in Ruby Files + +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. + +Falcon reserves `.rb` serve configurations for protocol middleware. Existing Rack applications can be exposed through `config/serve.rb` using {ruby Protocol::Rack::Adapter}: + +~~~ ruby +# config/serve.rb + +require "protocol/rack" +require_relative "../app" + +run Protocol::Rack::Adapter.new(App) +~~~ + +Running `falcon serve` loads `config/serve.rb` as protocol 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. + ## Running a Local Server 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. diff --git a/guides/getting-started/readme.md b/guides/getting-started/readme.md index ee200a32..f0c86fda 100644 --- a/guides/getting-started/readme.md +++ b/guides/getting-started/readme.md @@ -44,6 +44,23 @@ Then run the application with: $ falcon serve ~~~ +#### Rack Applications Defined in Ruby Files + +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. + +Falcon reserves `.rb` serve configurations for protocol middleware. Existing Rack applications can be exposed through `config/serve.rb` using {ruby Protocol::Rack::Adapter}: + +~~~ ruby +# config/serve.rb + +require "protocol/rack" +require_relative "../app" + +run Protocol::Rack::Adapter.new(App) +~~~ + +Running `falcon serve` loads `config/serve.rb` as protocol 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. + ## Running a Local Server 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. From 290d7db43d499fc11d20d834b366a6628f1eba31 Mon Sep 17 00:00:00 2001 From: Samuel Williams Date: Sun, 9 Aug 2026 12:50:24 +1200 Subject: [PATCH 07/12] Add explicit Rack middleware construction. --- context/cluster-deployment.md | 2 +- examples/cluster/falcon.rb | 2 +- examples/server/standalone.rb | 2 +- examples/utilization/falcon.rb | 2 +- guides/cluster-deployment/readme.md | 2 +- lib/falcon/environment/rackup.rb | 2 +- lib/falcon/environment/serve.rb | 2 +- lib/falcon/server.rb | 9 +++++++-- releases.md | 1 + test/falcon/server.rb | 8 ++++++++ 10 files changed, 23 insertions(+), 9 deletions(-) diff --git a/context/cluster-deployment.md b/context/cluster-deployment.md index 6952c244..fad58aff 100644 --- a/context/cluster-deployment.md +++ b/context/cluster-deployment.md @@ -84,7 +84,7 @@ service "cluster" do }, [body]] end - Falcon::Server.middleware(application, cache: false) + Falcon::Server.rack_middleware(application, cache: false) end end diff --git a/examples/cluster/falcon.rb b/examples/cluster/falcon.rb index 57e17a98..dc650311 100644 --- a/examples/cluster/falcon.rb +++ b/examples/cluster/falcon.rb @@ -34,7 +34,7 @@ def url ] end - Falcon::Server.middleware(rack_application, cache: false) + Falcon::Server.rack_middleware(rack_application, cache: false) end end diff --git a/examples/server/standalone.rb b/examples/server/standalone.rb index 95b4d1e0..71546bf8 100644 --- a/examples/server/standalone.rb +++ b/examples/server/standalone.rb @@ -23,7 +23,7 @@ def self.call(env) Async do websocket_endpoint = Async::HTTP::Endpoint.parse("http://127.0.0.1:3000") - app = Falcon::Server.middleware(WebSocketApp) + app = Falcon::Server.rack_middleware(WebSocketApp) server = Falcon::Server.new(app, websocket_endpoint) diff --git a/examples/utilization/falcon.rb b/examples/utilization/falcon.rb index 69c1ab22..78ae81d0 100644 --- a/examples/utilization/falcon.rb +++ b/examples/utilization/falcon.rb @@ -30,7 +30,7 @@ def call(env) # Define the middleware stack for this server middleware do - Falcon::Server.middleware(SimpleApp.new, verbose: false, cache: false) + Falcon::Server.rack_middleware(SimpleApp.new, verbose: false, cache: false) end # Define the utilization schema for this service diff --git a/guides/cluster-deployment/readme.md b/guides/cluster-deployment/readme.md index 6952c244..fad58aff 100644 --- a/guides/cluster-deployment/readme.md +++ b/guides/cluster-deployment/readme.md @@ -84,7 +84,7 @@ service "cluster" do }, [body]] end - Falcon::Server.middleware(application, cache: false) + Falcon::Server.rack_middleware(application, cache: false) end end diff --git a/lib/falcon/environment/rackup.rb b/lib/falcon/environment/rackup.rb index 1a767315..184cad60 100644 --- a/lib/falcon/environment/rackup.rb +++ b/lib/falcon/environment/rackup.rb @@ -25,7 +25,7 @@ def rack_app # Build the middleware stack for the rack application. # @returns [Protocol::HTTP::Middleware] The middleware stack. def middleware - ::Falcon::Server.middleware(rack_app, verbose: verbose, cache: cache) + ::Falcon::Server.rack_middleware(rack_app, verbose: verbose, cache: cache) end end end diff --git a/lib/falcon/environment/serve.rb b/lib/falcon/environment/serve.rb index 4097c410..0a1dd010 100644 --- a/lib/falcon/environment/serve.rb +++ b/lib/falcon/environment/serve.rb @@ -63,7 +63,7 @@ def middleware return ::Falcon::Server.protocol_middleware(application, verbose: verbose, cache: cache) when ".ru" application = ::Protocol::Rack::Adapter.parse_file(path) - return ::Falcon::Server.middleware(application, verbose: verbose, cache: cache) + return ::Falcon::Server.rack_middleware(application, verbose: verbose, cache: cache) else raise ArgumentError, "Unsupported application configuration: #{path}!" end diff --git a/lib/falcon/server.rb b/lib/falcon/server.rb index aa8df3f8..f1878732 100644 --- a/lib/falcon/server.rb +++ b/lib/falcon/server.rb @@ -17,11 +17,16 @@ module Falcon # A server listening on a specific endpoint, hosting a specific middleware. class Server < Async::HTTP::Server - # Wrap a rack application into a middleware suitable the server. + # @deprecated Use {rack_middleware} instead. + def self.middleware(...) + return self.rack_middleware(...) + end + + # Wrap a Rack application with the standard server middleware. # @parameter rack_app [Proc | Object] A rack application/middleware. # @parameter verbose [Boolean] Whether to add the {Middleware::Verbose} middleware. # @parameter cache [Boolean] Whether to add the {Async::HTTP::Cache} middleware. - def self.middleware(rack_app, verbose: false, cache: true) + def self.rack_middleware(rack_app, verbose: false, cache: true) return self.protocol_middleware(::Protocol::Rack::Adapter.new(rack_app), verbose: verbose, cache: cache) end diff --git a/releases.md b/releases.md index 756b3fe2..182fe111 100644 --- a/releases.md +++ b/releases.md @@ -4,6 +4,7 @@ - Update the Envoy cluster example to use dedicated CDS and EDS services from `async-service-supervisor-envoy` v0.5. - Add protocol-native `config/serve.rb` support to `falcon serve`, with fallback to Rack `config.ru` applications. + - Add explicit `Falcon::Server.rack_middleware` and `Falcon::Server.protocol_middleware` constructors; deprecate `Falcon::Server.middleware`. ## v0.56.0 diff --git a/test/falcon/server.rb b/test/falcon/server.rb index 35f87a0a..af536d56 100644 --- a/test/falcon/server.rb +++ b/test/falcon/server.rb @@ -13,6 +13,14 @@ [200, {}, ["OK"]] end + expect(subject.rack_middleware(app, cache: true)).to be_a(Async::HTTP::Cache::General) + end + + it "provides the deprecated middleware wrapper" do + app = lambda do |env| + [200, {}, ["OK"]] + end + expect(subject.middleware(app, cache: true)).to be_a(Async::HTTP::Cache::General) end From 4f6d2098473b737abda61247f8e24d25e0b6920c Mon Sep 17 00:00:00 2001 From: Samuel Williams Date: Sun, 9 Aug 2026 13:06:40 +1200 Subject: [PATCH 08/12] Clarify protocol HTTP middleware documentation. --- context/getting-started.md | 4 ++-- guides/getting-started/readme.md | 4 ++-- lib/falcon/environment/serve.rb | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/context/getting-started.md b/context/getting-started.md index f0c86fda..abbb6d4f 100644 --- a/context/getting-started.md +++ b/context/getting-started.md @@ -48,7 +48,7 @@ $ falcon serve 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. -Falcon reserves `.rb` serve configurations for protocol middleware. Existing Rack applications can be exposed through `config/serve.rb` using {ruby Protocol::Rack::Adapter}: +Falcon reserves `.rb` serve configurations for protocol HTTP middleware. Existing Rack applications can be exposed through `config/serve.rb` using {ruby Protocol::Rack::Adapter}: ~~~ ruby # config/serve.rb @@ -59,7 +59,7 @@ require_relative "../app" run Protocol::Rack::Adapter.new(App) ~~~ -Running `falcon serve` loads `config/serve.rb` as protocol 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. +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. ## Running a Local Server diff --git a/guides/getting-started/readme.md b/guides/getting-started/readme.md index f0c86fda..abbb6d4f 100644 --- a/guides/getting-started/readme.md +++ b/guides/getting-started/readme.md @@ -48,7 +48,7 @@ $ falcon serve 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. -Falcon reserves `.rb` serve configurations for protocol middleware. Existing Rack applications can be exposed through `config/serve.rb` using {ruby Protocol::Rack::Adapter}: +Falcon reserves `.rb` serve configurations for protocol HTTP middleware. Existing Rack applications can be exposed through `config/serve.rb` using {ruby Protocol::Rack::Adapter}: ~~~ ruby # config/serve.rb @@ -59,7 +59,7 @@ require_relative "../app" run Protocol::Rack::Adapter.new(App) ~~~ -Running `falcon serve` loads `config/serve.rb` as protocol 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. +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. ## Running a Local Server diff --git a/lib/falcon/environment/serve.rb b/lib/falcon/environment/serve.rb index 0a1dd010..962cf481 100644 --- a/lib/falcon/environment/serve.rb +++ b/lib/falcon/environment/serve.rb @@ -19,7 +19,7 @@ module Environment # The file extension selects the application interface: # # - `.rb` files are evaluated by {Protocol::HTTP::Middleware.load} using the - # protocol middleware builder interface. + # protocol HTTP middleware builder interface. # - `.ru` files are parsed as Rack applications and wrapped with # {Protocol::Rack::Adapter}. # From 02fbb421795fecf4660eb8731b8ccc4ab896ef49 Mon Sep 17 00:00:00 2001 From: Samuel Williams Date: Sun, 9 Aug 2026 13:24:13 +1200 Subject: [PATCH 09/12] Simplify serve environment construction. --- lib/falcon/command/serve.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/falcon/command/serve.rb b/lib/falcon/command/serve.rb index 93107188..7b3730a2 100644 --- a/lib/falcon/command/serve.rb +++ b/lib/falcon/command/serve.rb @@ -71,7 +71,7 @@ def name # Create the environment for the serve command. # @returns [Async::Service::Environment] The configured server environment. def environment - return Async::Service::Environment.new(Falcon::Environment::Server).with( + Async::Service::Environment.new(Falcon::Environment::Server).with( Falcon::Environment::Serve, root: Dir.pwd, From 94264b4927f9d30551a6e3afde63386c1f8a251f Mon Sep 17 00:00:00 2001 From: Samuel Williams Date: Sun, 9 Aug 2026 14:17:08 +1200 Subject: [PATCH 10/12] Warn when using deprecated server middleware. --- lib/falcon/server.rb | 2 ++ test/falcon/server.rb | 8 +++++++- 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/lib/falcon/server.rb b/lib/falcon/server.rb index f1878732..b67dc2f9 100644 --- a/lib/falcon/server.rb +++ b/lib/falcon/server.rb @@ -19,6 +19,8 @@ module Falcon class Server < Async::HTTP::Server # @deprecated Use {rack_middleware} instead. def self.middleware(...) + warn("`Falcon::Server.middleware` is deprecated, use `.rack_middleware` instead.", uplevel: 1, category: :deprecated) if $VERBOSE + return self.rack_middleware(...) end diff --git a/test/falcon/server.rb b/test/falcon/server.rb index af536d56..9a78b1cf 100644 --- a/test/falcon/server.rb +++ b/test/falcon/server.rb @@ -16,12 +16,18 @@ expect(subject.rack_middleware(app, cache: true)).to be_a(Async::HTTP::Cache::General) end - it "provides the deprecated middleware wrapper" do + it "warns when using the deprecated middleware wrapper" do app = lambda do |env| [200, {}, ["OK"]] end + verbose = $VERBOSE + $VERBOSE = true + + expect(subject).to receive(:warn).and_return(nil) expect(subject.middleware(app, cache: true)).to be_a(Async::HTTP::Cache::General) + ensure + $VERBOSE = verbose end it "formats large statistics counts" do From 12711166a929dc82fd065ada1dfcaea334181f6d Mon Sep 17 00:00:00 2001 From: Samuel Williams Date: Sun, 9 Aug 2026 14:24:17 +1200 Subject: [PATCH 11/12] Document Rack compatibility changes. --- releases.md | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/releases.md b/releases.md index 182fe111..3c169499 100644 --- a/releases.md +++ b/releases.md @@ -3,8 +3,12 @@ ## Unreleased - Update the Envoy cluster example to use dedicated CDS and EDS services from `async-service-supervisor-envoy` v0.5. - - Add protocol-native `config/serve.rb` support to `falcon serve`, with fallback to Rack `config.ru` applications. - - Add explicit `Falcon::Server.rack_middleware` and `Falcon::Server.protocol_middleware` constructors; deprecate `Falcon::Server.middleware`. + +### Rack Compatibility + +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}. + +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 protocol HTTP middleware; Rack applications defined in Ruby files should use `config.ru` or wrap the application explicitly with {ruby Protocol::Rack::Adapter}. ## v0.56.0 From 5b5f7bd44fea585ed061a8cf17ecfd3e4670f854 Mon Sep 17 00:00:00 2001 From: Samuel Williams Date: Sun, 9 Aug 2026 14:25:20 +1200 Subject: [PATCH 12/12] Clarify protocol middleware interpretation. --- releases.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/releases.md b/releases.md index 3c169499..7bc86423 100644 --- a/releases.md +++ b/releases.md @@ -8,7 +8,7 @@ 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}. -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 protocol HTTP middleware; Rack applications defined in Ruby files should use `config.ru` or wrap the application explicitly with {ruby Protocol::Rack::Adapter}. +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}. ## v0.56.0