From df3f6bd3bb2bbfc1c4ccc49bf7365f14cfb1d074 Mon Sep 17 00:00:00 2001 From: Samuel Williams Date: Mon, 11 Mar 2024 12:54:51 +1300 Subject: [PATCH 01/21] Move `Configuration` logic to `Async::Service`. --- falcon.gemspec | 2 +- gems.rb | 1 + lib/falcon/configuration.rb | 150 +++++--------------- lib/falcon/environments.rb | 3 +- lib/falcon/environments/application.rb | 103 ++++++++------ lib/falcon/environments/lets_encrypt_tls.rb | 50 ++++--- lib/falcon/environments/proxy.rb | 34 +++-- lib/falcon/environments/rack.rb | 60 ++++---- lib/falcon/environments/self_signed_tls.rb | 59 ++++---- lib/falcon/environments/supervisor.rb | 56 ++++---- lib/falcon/environments/tls.rb | 150 +++++++++++--------- lib/falcon/service/generic.rb | 2 +- lib/falcon/services.rb | 2 +- test/falcon/middleware/proxy.rb | 3 +- 14 files changed, 327 insertions(+), 348 deletions(-) diff --git a/falcon.gemspec b/falcon.gemspec index 07ce94bd..6ff8609c 100644 --- a/falcon.gemspec +++ b/falcon.gemspec @@ -30,7 +30,7 @@ Gem::Specification.new do |spec| spec.add_dependency "async-http", "~> 0.57" spec.add_dependency "async-http-cache", "~> 0.4.0" spec.add_dependency "async-io", "~> 1.22" - spec.add_dependency "build-environment", "~> 1.13" + spec.add_dependency "async-service" spec.add_dependency "bundler" spec.add_dependency "localhost", "~> 1.1" spec.add_dependency "openssl", "~> 3.0" diff --git a/gems.rb b/gems.rb index 6f5584e0..735ea16c 100644 --- a/gems.rb +++ b/gems.rb @@ -15,6 +15,7 @@ # gem "protocol-http1", path: "../protocol-http1" # gem "utopia-project", path: "../utopia-project" # gem "protocol-rack", path: "../protocol-rack" +# gem "async-service", path: "../async-service" group :maintenance, optional: true do gem "bake-modernize" diff --git a/lib/falcon/configuration.rb b/lib/falcon/configuration.rb index a03745f3..a0b778c7 100644 --- a/lib/falcon/configuration.rb +++ b/lib/falcon/configuration.rb @@ -4,7 +4,7 @@ # Copyright, 2019-2023, by Samuel Williams. # Copyright, 2019, by Sho Ito. -require 'build/environment' +require 'async/service' module Falcon # Manages environments which describes how to host a specific application. @@ -25,83 +25,25 @@ module Falcon # end # ~~~ # - class Configuration - # Initialize an empty configuration. - def initialize - @environments = {} + class Configuration < ::Async::Service::Configuration + # Load the specified configuration file. See {Loader#load_file} for more details. + def load_file(path) + Loader.load_file(self, path) end - # The map of named environments. - # @attribute [Hash(String, Build::Environment)] - attr :environments - - # Enumerate all environments that have the specified key. - # @parameter key [Symbol] Filter environments that don't have this key. def each(key = :authority) - return to_enum(key) unless block_given? + return to_enum(__method__, key) unless block_given? - @environments.each do |name, environment| - environment = environment.flatten - - if environment.include?(key) + @environments.each do |environment| + evaluator = environment.evaluator + if evaluator.key?(key) yield environment end end end - # Add the named environment to the configuration. - def add(environment) - name = environment.name - - unless name - raise ArgumentError, "Environment name is nil #{environment.inspect}" - end - - environment = environment.flatten - - raise KeyError.new("#{name.inspect} is already set", key: name) if @environments.key?(name) - - @environments[name] = environment - end - - # Load the specified configuration file. See {Loader#load_file} for more details. - def load_file(path) - Loader.load_file(self, path) - end - # The domain specific language for loading configuration files. - class Loader - # Initialize the loader, attached to a specific configuration instance. - # Any environments generated by the loader will be added to the configuration. - # @parameter configuration [Configuration] - # @parameter root [String] The file-system root path for relative path computations. - def initialize(configuration, root = nil) - @loaded = {} - @configuration = configuration - @environments = {} - @root = root - end - - # The file-system root path which is injected into the environments as required. - # @attribute [String] - attr :root - - # The attached configuration instance. - # @attribute [Configuration] - attr :configuration - - # Load the specified file into the given configuration. - # @parameter configuration [Configuration] - # @oaram path [String] The path to the configuration file, e.g. `falcon.rb`. - def self.load_file(configuration, path) - path = File.realpath(path) - root = File.dirname(path) - - loader = self.new(configuration, root) - - loader.instance_eval(File.read(path), path) - end - + class Loader < ::Async::Service::Loader # Load specific features into the current configuration. # # Falcon provides default environments for different purposes. These are included in the gem, in the `environments/` directory. This method loads the code in those files into the current configuration. @@ -109,93 +51,67 @@ def self.load_file(configuration, path) # @parameter features [Array(Symbol)] The features to load. def load(*features) features.each do |feature| - next if @loaded.include?(feature) - case feature when Symbol - relative_path = File.join(__dir__, "environments", "#{feature}.rb") - - self.instance_eval(File.read(relative_path), relative_path) - - @loaded[feature] = relative_path - when Module - feature.load(self) - - @loaded[feature] = feature + require File.join(__dir__, "environments", "#{feature}.rb") else raise LoadError, "Unsure about how to load #{feature}!" end end end - # Add the named environment, with zero or more parent environments, defined using the specified `block`. - # @parameter name [String] The name of the environment. - # @parameter parents [Array(Symbol)] The names of the parent environments to inherit. - # @yields {...} The block that will generate the environment. - def environment(name, *parents, &block) - raise KeyError.new("#{name} is already set", key: name) if @environments.key?(name) - @environments[name] = merge(name, *parents, &block) - end - # Define a host with the specified name. # Adds `root` and `authority` keys. # @parameter name [String] The name of the environment, usually a hostname. def host(name, *parents, &block) - environment = merge(name, *parents, &block) - - environment[:root] = @root - environment[:authority] = name - - @configuration.add(environment.flatten) + @configuration.add( + merge(*parents, name: name, root: @root, authority: name, &block) + ) end # Define a proxy with the specified name. # Adds `root` and `authority` keys. # @parameter name [String] The name of the environment, usually a hostname. def proxy(name, *parents, &block) - environment = merge(name, :proxy, *parents, &block) - - environment[:root] = @root - environment[:authority] = name - - @configuration.add(environment.flatten) + @configuration.add( + merge(:proxy, *parents, name: name, root: @root, authority: name, &block) + ) end # Define a rack application with the specified name. # Adds `root` and `authority` keys. # @parameter name [String] The name of the environment, usually a hostname. def rack(name, *parents, &block) - environment = merge(name, :rack, *parents, &block) - - environment[:root] = @root - environment[:authority] = name - - @configuration.add(environment.flatten) + @configuration.add( + merge(:rack, *parents, name: name, root: @root, authority: name, &block) + ) end # Define a supervisor instance # Adds `root` key. def supervisor(&block) name = File.join(@root, "supervisor") - environment = merge(name, :supervisor, &block) - - environment[:root] = @root - @configuration.add(environment.flatten) + @configuration.add( + merge(:supervisor, name: name, root: @root, &block) + ) end private # Build a new environment with the specified name and the given parents. # @parameter name [String] - # @parameter parents [Array(Build::Environment)] + # @parameter parents [Array(Symbol)] # @yields {...} The block that will generate the environment. - def merge(name, *parents, &block) - environments = parents.map{|name| @environments.fetch(name)} - - parent = Build::Environment.combine(*environments) - - Build::Environment.new(parent, name: name, &block) + def merge(*parents, **initial, &block) + ::Async::Service::Environment.build(**initial) do + parents.each do |parent| + Console.warn(self) {"Legacy mapping for #{parent.inspect} should be updated to use `include`!"} + include(Environments::LEGACY_ENVIRONMENTS[parent]) + end + + instance_exec(&block) if block + end end end end diff --git a/lib/falcon/environments.rb b/lib/falcon/environments.rb index 0a9cc918..b5635a8c 100644 --- a/lib/falcon/environments.rb +++ b/lib/falcon/environments.rb @@ -3,12 +3,11 @@ # Released under the MIT License. # Copyright, 2019-2023, by Samuel Williams. -require 'build/environment' - module Falcon # Pre-defined environments for hosting web applications. # # See {Configuration::Loader#load} for more details. module Environments + LEGACY_ENVIRONMENTS = {} end end diff --git a/lib/falcon/environments/application.rb b/lib/falcon/environments/application.rb index 8bd33037..7a5a3de3 100644 --- a/lib/falcon/environments/application.rb +++ b/lib/falcon/environments/application.rb @@ -8,49 +8,66 @@ require_relative '../server' require_relative '../service/application' +require_relative '../environments' -# A general application environment. -# Suitable for use with any {Protocol::HTTP::Middleware}. -# -# @scope Falcon Environments -# @name application -environment(:application) do - # The middleware stack for the application. - # @attribute [Protocol::HTTP::Middleware] - middleware do - ::Protocol::HTTP::Middleware::HelloWorld +module Falcon + module Environments + # A general application environment. Suitable for use with any {Protocol::HTTP::Middleware}. + module Application + # The middleware stack for the application. + # @returns [Protocol::HTTP::Middleware] + def middleware + ::Protocol::HTTP::Middleware::HelloWorld + end + + # The scheme to use to communicate with the application. + # @returns [String] + def scheme + 'https' + end + + # The protocol to use to communicate with the application. + # + # Typically one of {Async::HTTP::Protocol::HTTP1} or {Async::HTTP::Protocl::HTTP2}. + # + # @returns [Async::HTTP::Protocol] + def protocol + Async::HTTP::Protocol::HTTP2 + end + + # The IPC path to use for communication with the application. + # @returns [String] + def ipc_path + ::File.expand_path("application.ipc", root) + end + + # The endpoint that will be used for communicating with the application server. + # @returns [Async::IO::Endpoint] + def endpoint + ::Falcon::ProxyEndpoint.unix(ipc_path, + protocol: protocol, + scheme: scheme, + authority: authority + ) + end + + # The service class to use for the application. + # @returns [Class] + def service_class + ::Falcon::Service::Application + end + + # Number of instances to start. + # @returns [Integer | nil] + def count + nil + end + + def preload + [] + end + end + + LEGACY_ENVIRONMENTS[:application] = Application end - - # The scheme to use to communicate with the application. - # @attribute [String] - scheme 'https' - - # The protocol to use to communicate with the application. - # - # Typically one of {Async::HTTP::Protocol::HTTP1} or {Async::HTTP::Protocl::HTTP2}. - # - # @attribute [Async::HTTP::Protocol] - protocol {Async::HTTP::Protocol::HTTP2} - - # The IPC path to use for communication with the application. - # @attribute [String] - ipc_path {::File.expand_path("application.ipc", root)} - - # The endpoint that will be used for communicating with the application server. - # @attribute [Async::IO::Endpoint] - endpoint do - ::Falcon::ProxyEndpoint.unix(ipc_path, - protocol: protocol, - scheme: scheme, - authority: authority - ) - end - - # The service class to use for the application. - # @attribute [Class] - service ::Falcon::Service::Application - - # Number of instances to start. - # @attribute [Integer | nil] - count nil end diff --git a/lib/falcon/environments/lets_encrypt_tls.rb b/lib/falcon/environments/lets_encrypt_tls.rb index 6f347da7..7d4f2b8f 100644 --- a/lib/falcon/environments/lets_encrypt_tls.rb +++ b/lib/falcon/environments/lets_encrypt_tls.rb @@ -3,28 +3,32 @@ # Released under the MIT License. # Copyright, 2020-2023, by Samuel Williams. -load(:tls) +require_relative 'tls' +require_relative '../environments' -# A Lets Encrypt SSL context environment. -# -# Derived from {.tls}. -# -# @scope Falcon Environments -# @name lets_encrypt_tls -environment(:lets_encrypt_tls, :tls) do - # The Lets Encrypt certificate store path. - # @parameter [String] - lets_encrypt_root '/etc/letsencrypt/live' - - # The public certificate path. - # @attribute [String] - ssl_certificate_path do - File.join(lets_encrypt_root, authority, "fullchain.pem") +module Falcon + module Environments + # A Lets Encrypt SSL context environment. + module LetsEncryptTLS + # The Lets Encrypt certificate store path. + # @parameter [String] + def lets_encrypt_root + '/etc/letsencrypt/live' + end + + # The public certificate path. + # @attribute [String] + def ssl_certificate_path + File.join(lets_encrypt_root, authority, "fullchain.pem") + end + + # The private key path. + # @attribute [String] + def ssl_private_key_path + File.join(lets_encrypt_root, authority, "privkey.pem") + end + end + + LEGACY_ENVIRONMENTS[:tls] = TLS end - - # The private key path. - # @attribute [String] - ssl_private_key_path do - File.join(lets_encrypt_root, authority, "privkey.pem") - end -end +end \ No newline at end of file diff --git a/lib/falcon/environments/proxy.rb b/lib/falcon/environments/proxy.rb index 6fdb93e8..323f244c 100644 --- a/lib/falcon/environments/proxy.rb +++ b/lib/falcon/environments/proxy.rb @@ -4,19 +4,25 @@ # Copyright, 2019-2023, by Samuel Williams. require_relative '../service/proxy' +require_relative '../environments' -# A HTTP proxy environment. -# -# Derived from {.application}. -# -# @scope Falcon Environments -# @name rack -environment(:proxy) do - # The upstream endpoint that will handle incoming requests. - # @attribute [Async::HTTP::Endpoint] - endpoint {::Async::HTTP::Endpoint.parse(url)} - - # The service class to use for the proxy. - # @attribute [Class] - service ::Falcon::Service::Proxy +module Falcon + module Environments + # A HTTP proxy environment. + module Proxy + # The upstream endpoint that will handle incoming requests. + # @attribute [Async::HTTP::Endpoint] + def endpoint + ::Async::HTTP::Endpoint.parse(url) + end + + # The service class to use for the proxy. + # @attribute [Class] + def service_class + ::Falcon::Service::Proxy + end + end + + LEGACY_ENVIRONMENTS[:proxy] = Proxy + end end diff --git a/lib/falcon/environments/rack.rb b/lib/falcon/environments/rack.rb index 94f98cbf..225f5f78 100644 --- a/lib/falcon/environments/rack.rb +++ b/lib/falcon/environments/rack.rb @@ -3,31 +3,43 @@ # Released under the MIT License. # Copyright, 2019-2023, by Samuel Williams. -load :application +require_relative 'application' +require_relative '../environments' -# A rack application environment. -# -# Derived from {.application}. -# -# @scope Falcon Environments -# @name rack -environment(:rack, :application) do - # The rack configuration path. - # @attribute [String] - config_path {::File.expand_path("config.ru", root)} - - # Whether to enable the application layer cache. - # @attribute [String] - cache false - - # The middleware stack for the rack application. - # @attribute [Protocol::HTTP::Middleware] - middleware do - app, _ = ::Rack::Builder.parse_file(config_path) +module Falcon + module Environments + # A rack application environment. + module Rack + include Application + + # The rack configuration path e.g. `config.ru`. + # @returns [String] + def config_path + ::File.expand_path("config.ru", root) + end + + # Whether to enable the application layer cache. + # @returns [String] + def cache + false + end + + def verbose + false + end + + # The middleware stack for the rack application. + # @returns [Protocol::HTTP::Middleware] + def middleware + app, _ = ::Rack::Builder.parse_file(config_path) + + ::Falcon::Server.middleware(app, + verbose: verbose, + cache: cache + ) + end + end - ::Falcon::Server.middleware(app, - verbose: verbose, - cache: cache - ) + LEGACY_ENVIRONMENTS[:rack] = Rack end end diff --git a/lib/falcon/environments/self_signed_tls.rb b/lib/falcon/environments/self_signed_tls.rb index e980b834..0ad31ca8 100644 --- a/lib/falcon/environments/self_signed_tls.rb +++ b/lib/falcon/environments/self_signed_tls.rb @@ -4,35 +4,42 @@ # Copyright, 2019-2023, by Samuel Williams. require 'localhost/authority' +require_relative 'tls' +require_relative '../environments' -# A self-signed SSL context environment. -# -# @scope Falcon Environments -# @name self_signed_tls -environment(:self_signed_tls) do - # The default session identifier for the session cache. - # @attribute [String] - ssl_session_id {"falcon"} - - # The SSL context to use for incoming connections. - # @attribute [OpenSSL::SSL::SSLContext] - ssl_context do - contexts = Localhost::Authority.fetch(authority) - - contexts.server_context.tap do |context| - context.alpn_select_cb = lambda do |protocols| - if protocols.include? "h2" - return "h2" - elsif protocols.include? "http/1.1" - return "http/1.1" - elsif protocols.include? "http/1.0" - return "http/1.0" - else - return nil - end +module Falcon + module Environments + # A general SSL context environment. + module SelfSignedTLS + # The default session identifier for the session cache. + # @returns [String] + def ssl_session_id + "falcon" end - context.session_id_context = ssl_session_id + # The SSL context to use for incoming connections. + # @returns [OpenSSL::SSL::SSLContext] + def ssl_context + contexts = Localhost::Authority.fetch(authority) + + contexts.server_context.tap do |context| + context.alpn_select_cb = lambda do |protocols| + if protocols.include? "h2" + return "h2" + elsif protocols.include? "http/1.1" + return "http/1.1" + elsif protocols.include? "http/1.0" + return "http/1.0" + else + return nil + end + end + + context.session_id_context = ssl_session_id + end + end end + + LEGACY_ENVIRONMENTS[:self_signed_tls] = SelfSignedTLS end end diff --git a/lib/falcon/environments/supervisor.rb b/lib/falcon/environments/supervisor.rb index 8314a8f6..8e0006d2 100644 --- a/lib/falcon/environments/supervisor.rb +++ b/lib/falcon/environments/supervisor.rb @@ -4,31 +4,37 @@ # Copyright, 2019-2023, by Samuel Williams. require_relative '../service/supervisor' +require_relative '../environments' -# A application process monitor environment. -# -# @scope Falcon Environments -# @name supervisor -environment(:supervisor) do - # The name of the supervisor - # @attribute [String] - name "supervisor" - - # The IPC path to use for communication with the supervisor. - # @attribute [String] - ipc_path do - ::File.expand_path("supervisor.ipc", root) - end - - # The endpoint the supervisor will bind to. - # @attribute [Async::IO::Endpoint] - endpoint do - Async::IO::Endpoint.unix(ipc_path) - end - - # The service class to use for the supervisor. - # @attribute [Class] - service do - ::Falcon::Service::Supervisor +module Falcon + module Environments + # A application process monitor environment. + module Supervisor + # The name of the supervisor + # @returns [String] + def name + "supervisor" + end + + # The IPC path to use for communication with the supervisor. + # @returns [String] + def ipc_path + ::File.expand_path("supervisor.ipc", root) + end + + # The endpoint the supervisor will bind to. + # @returns [Async::IO::Endpoint] + def endpoint + Async::IO::Endpoint.unix(ipc_path) + end + + # The service class to use for the supervisor. + # @returns [Class] + def service_class + ::Falcon::Service::Supervisor + end + end + + LEGACY_ENVIRONMENTS[:supervisor] = Supervisor end end diff --git a/lib/falcon/environments/tls.rb b/lib/falcon/environments/tls.rb index 786714f3..b4cbf2cc 100644 --- a/lib/falcon/environments/tls.rb +++ b/lib/falcon/environments/tls.rb @@ -5,82 +5,94 @@ require_relative '../controller/proxy' require_relative '../tls' +require_relative '../environments' -# A general SSL context environment. -# -# @scope Falcon Environments -# @name tls -environment(:tls) do - # The default session identifier for the session cache. - # @attribute [String] - ssl_session_id "falcon" - - # The supported ciphers. - # @attribute [Array(String)] - ssl_ciphers Falcon::TLS::SERVER_CIPHERS - - # The public certificate path. - # @attribute [String] - ssl_certificate_path do - File.expand_path("ssl/certificate.pem", root) - end - - # The list of certificates loaded from that path. - # @attribute [Array(OpenSSL::X509::Certificate)] - ssl_certificates do - OpenSSL::X509::Certificate.load_file(ssl_certificate_path) - end - - # The main certificate. - # @attribute [OpenSSL::X509::Certificate] - ssl_certificate {ssl_certificates[0]} - - # The certificate chain. - # @attribute [Array(OpenSSL::X509::Certificate)] - ssl_certificate_chain {ssl_certificates[1..-1]} - - # The private key path. - # @attribute [String] - ssl_private_key_path do - File.expand_path("ssl/private.key", root) - end - - # The private key. - # @attribute [OpenSSL::PKey::RSA] - ssl_private_key do - OpenSSL::PKey::RSA.new(File.read(ssl_private_key_path)) - end - - # The SSL context to use for incoming connections. - # @attribute [OpenSSL::SSL::SSLContext] - ssl_context do - OpenSSL::SSL::SSLContext.new.tap do |context| - context.add_certificate(ssl_certificate, ssl_private_key, ssl_certificate_chain) +module Falcon + module Environments + # A general SSL context environment. + module TLS + # The default session identifier for the session cache. + # @returns [String] + def ssl_session_id + "falcon" + end - context.session_cache_mode = OpenSSL::SSL::SSLContext::SESSION_CACHE_CLIENT - context.session_id_context = ssl_session_id + # The supported ciphers. + # @returns [Array(String)] + def ssl_ciphers + Falcon::TLS::SERVER_CIPHERS + end - context.alpn_select_cb = lambda do |protocols| - if protocols.include? "h2" - return "h2" - elsif protocols.include? "http/1.1" - return "http/1.1" - elsif protocols.include? "http/1.0" - return "http/1.0" - else - return nil - end + # The public certificate path. + # @returns [String] + def ssl_certificate_path + File.expand_path("ssl/certificate.pem", root) end - # TODO Ruby 2.4 requires using ssl_version. - context.ssl_version = :TLSv1_2_server + # The list of certificates loaded from that path. + # @returns [Array(OpenSSL::X509::Certificate)] + def ssl_certificates + OpenSSL::X509::Certificate.load_file(ssl_certificate_path) + end - context.set_params( - ciphers: ssl_ciphers, - verify_mode: OpenSSL::SSL::VERIFY_NONE, - ) + # The main certificate. + # @returns [OpenSSL::X509::Certificate] + def ssl_certificate + ssl_certificates[0] + end - context.setup + # The certificate chain. + # @returns [Array(OpenSSL::X509::Certificate)] + def ssl_certificate_chain + ssl_certificates[1..-1] + end + + # The private key path. + # @returns [String] + def ssl_private_key_path + File.expand_path("ssl/private.key", root) + end + + # The private key. + # @returns [OpenSSL::PKey::RSA] + def ssl_private_key + OpenSSL::PKey::RSA.new(File.read(ssl_private_key_path)) + end + + # The SSL context to use for incoming connections. + # @returns [OpenSSL::SSL::SSLContext] + def ssl_context + OpenSSL::SSL::SSLContext.new.tap do |context| + context.add_certificate(ssl_certificate, ssl_private_key, ssl_certificate_chain) + + context.session_cache_mode = OpenSSL::SSL::SSLContext::SESSION_CACHE_CLIENT + context.session_id_context = ssl_session_id + + context.alpn_select_cb = lambda do |protocols| + if protocols.include? "h2" + return "h2" + elsif protocols.include? "http/1.1" + return "http/1.1" + elsif protocols.include? "http/1.0" + return "http/1.0" + else + return nil + end + end + + # TODO Ruby 2.4 requires using ssl_version. + context.ssl_version = :TLSv1_2_server + + context.set_params( + ciphers: ssl_ciphers, + verify_mode: OpenSSL::SSL::VERIFY_NONE, + ) + + context.setup + end + end end + + LEGACY_ENVIRONMENTS[:tls] = TLS end end diff --git a/lib/falcon/service/generic.rb b/lib/falcon/service/generic.rb index 58fb3a57..8e4110b0 100644 --- a/lib/falcon/service/generic.rb +++ b/lib/falcon/service/generic.rb @@ -14,7 +14,7 @@ class Generic # @parameter environment [Build::Environment] The environment to use to construct the service. def self.wrap(environment) evaluator = environment.evaluator - service = evaluator.service || self + service = evaluator.service_class || self return service.new(environment) end diff --git a/lib/falcon/services.rb b/lib/falcon/services.rb index 2042c270..43f7e69f 100644 --- a/lib/falcon/services.rb +++ b/lib/falcon/services.rb @@ -22,7 +22,7 @@ class Services def initialize(configuration) @named = {} - configuration.each(:service) do |environment| + configuration.each(:service_class) do |environment| service = Service::Generic.wrap(environment) add(service) diff --git a/test/falcon/middleware/proxy.rb b/test/falcon/middleware/proxy.rb index 811fe2a5..48831b9a 100644 --- a/test/falcon/middleware/proxy.rb +++ b/test/falcon/middleware/proxy.rb @@ -5,7 +5,6 @@ require 'falcon/middleware/proxy' require 'falcon/service/proxy' -require 'build/environment' require 'sus/fixtures/async' require 'async/http/client' @@ -16,7 +15,7 @@ def proxy_for(**options) Falcon::Service::Proxy.new( - Build::Environment.new(nil, options) + Async::Service::Environment.build(**options) ) end From 6c7bb45fc1544c0bfaa342be01e4064847901ff4 Mon Sep 17 00:00:00 2001 From: Samuel Williams Date: Mon, 11 Mar 2024 20:42:29 +1300 Subject: [PATCH 02/21] Server service working... --- hello.rb | 11 +++ lib/falcon/command/serve.rb | 73 ++++++++----------- lib/falcon/controller/serve.rb | 110 ---------------------------- lib/falcon/service/generic.rb | 61 ---------------- lib/falcon/service/proxy.rb | 27 ++++++- lib/falcon/service/server.rb | 115 +++++++++++++++++++++++++++++ lib/falcon/service/supervisor.rb | 3 +- lib/falcon/service/virtual.rb | 119 +++++++++++++++++++++++++++++++ 8 files changed, 302 insertions(+), 217 deletions(-) create mode 100755 hello.rb delete mode 100644 lib/falcon/controller/serve.rb delete mode 100644 lib/falcon/service/generic.rb create mode 100644 lib/falcon/service/server.rb create mode 100644 lib/falcon/service/virtual.rb diff --git a/hello.rb b/hello.rb new file mode 100755 index 00000000..bf7c51ff --- /dev/null +++ b/hello.rb @@ -0,0 +1,11 @@ +#!/usr/bin/env async-service + +require 'falcon/service/server' + +service "hello-server" do + include Falcon::Service::Server + + middleware do + ::Protocol::HTTP::Middleware::HelloWorld + end +end diff --git a/lib/falcon/command/serve.rb b/lib/falcon/command/serve.rb index 8a73daab..c47f5f19 100644 --- a/lib/falcon/command/serve.rb +++ b/lib/falcon/command/serve.rb @@ -53,6 +53,31 @@ class Serve < Samovar::Command option '--threads ', "Number of threads (hybrid only).", type: Integer end + # Options for the {endpoint}. + def endpoint_options + @options.slice(:hostname, :port, :reuse_port, :timeout) + end + + def environment + Async::Service::Environment.build do + include Falcon::Service::Server + + rackup_path @options[:config] + + container_options @options.slice(:count, :forks, :threads) + + preload @options[:preload] + + verbose @parent&.verbose? + + cache @options[:cache] + + endpoint do + Endpoint.parse(@options[:bind], **endpoint_options) + end + end + end + # The container class to use. def container_class case @options[:container] @@ -65,37 +90,6 @@ def container_class end end - # Whether verbose logging is enabled. - # @returns [Boolean] - def verbose? - @parent&.verbose? - end - - # Whether to enable the application HTTP cache. - # @returns [Boolean] - def cache? - @options[:cache] - end - - # Load the rack application from the specified configuration path. - # @returns [Protocol::HTTP::Middleware] - def load_app - rack_app, _ = Rack::Builder.parse_file(@options[:config]) - - return Server.middleware(rack_app, verbose: self.verbose?, cache: self.cache?) - end - - # Options for the container. - # See {Controller::Serve#setup}. - def container_options - @options.slice(:count, :forks, :threads) - end - - # Options for the {endpoint}. - def endpoint_options - @options.slice(:hostname, :port, :reuse_port, :timeout) - end - # The endpoint to bind to. def endpoint Endpoint.parse(@options[:bind], **endpoint_options) @@ -111,11 +105,6 @@ def client Async::HTTP::Client.new(client_endpoint) end - # Prepare a new controller for the command. - def controller - Controller::Serve.new(self) - end - # Prepare the environment and run the controller. def call Console.logger.info(self) do |buffer| @@ -125,22 +114,20 @@ def call buffer.puts "- To reload configuration: kill -HUP #{Process.pid}" end - if path = @options[:preload] - full_path = File.expand_path(path) - load(full_path) - end - begin Bundler.require(:preload) rescue Bundler::GemfileNotFound # Ignore. end - if GC.respond_to?(:compact) + if Process.respond_to?(:warmup) + Process.warmup + elsif GC.respond_to?(:compact) + 3.times{GC.start} GC.compact end - self.controller.run + controller = Async::Service::Controller.new(container_class: self.container_class, environment: environment) end end end diff --git a/lib/falcon/controller/serve.rb b/lib/falcon/controller/serve.rb deleted file mode 100644 index 4f7d17a9..00000000 --- a/lib/falcon/controller/serve.rb +++ /dev/null @@ -1,110 +0,0 @@ -# frozen_string_literal: true - -# Released under the MIT License. -# Copyright, 2020-2023, by Samuel Williams. -# Copyright, 2020, by Michael Adams. - -require_relative '../server' - -require 'async/container/controller' -require 'async/io/trap' - -require 'async/io/shared_endpoint' - -module Falcon - module Controller - # A generic controller for serving an application. - # Uses {Server} for handling incoming requests. - class Serve < Async::Container::Controller - # Initialize the server controller. - # @parameter command [Command::Serve] The user-specified command-line options. - def initialize(command, **options) - @command = command - - @endpoint = nil - @bound_endpoint = nil - @debug_trap = Async::IO::Trap.new(:USR1) - - super(**options) - end - - # Create the controller as specified by the command. - # e.g. `Async::Container::Forked`. - def create_container - @command.container_class.new - end - - # The endpoint the server will bind to. - def endpoint - @command.endpoint - end - - # @returns [Protocol::HTTP::Middleware] an instance of the application to be served. - def load_app - @command.load_app - end - - # Prepare the bound endpoint for the server. - def start - @endpoint ||= self.endpoint - - @bound_endpoint = Async do - Async::IO::SharedEndpoint.bound(@endpoint) - end.wait - - Console.logger.info(self) { "Starting #{name} on #{@endpoint.to_url}" } - - @debug_trap.ignore! - - super - end - - # The name of the controller which is used for the process title. - def name - "Falcon Server" - end - - # Setup the container with the application instance. - # @parameter container [Async::Container::Generic] - def setup(container) - container.run(name: self.name, restart: true, **@command.container_options) do |instance| - Async do |task| - # Load one app instance per container: - app = self.load_app - - task.async do - if @debug_trap.install! - Console.logger.info(instance) do - "- Per-process status: kill -USR1 #{Process.pid}" - end - end - - @debug_trap.trap do - Console.logger.info(self) do |buffer| - task.reactor.print_hierarchy(buffer) - end - end - end - - server = Falcon::Server.new(app, @bound_endpoint, protocol: @endpoint.protocol, scheme: @endpoint.scheme) - - server.run - - instance.ready! - - task.children.each(&:wait) - end - end - end - - # Close the bound endpoint. - def stop(*) - @bound_endpoint&.close - - @debug_trap.default! - - super - end - end - end -end diff --git a/lib/falcon/service/generic.rb b/lib/falcon/service/generic.rb deleted file mode 100644 index 8e4110b0..00000000 --- a/lib/falcon/service/generic.rb +++ /dev/null @@ -1,61 +0,0 @@ -# frozen_string_literal: true - -# Released under the MIT License. -# Copyright, 2019-2023, by Samuel Williams. - -module Falcon - module Service - # Captures the stateful behaviour of a specific service. - # Specifies the interfaces required by derived classes. - # - # Designed to be invoked within an {Async::Controller::Container}. - class Generic - # Convert the given environment into a service if possible. - # @parameter environment [Build::Environment] The environment to use to construct the service. - def self.wrap(environment) - evaluator = environment.evaluator - service = evaluator.service_class || self - - return service.new(environment) - end - - # Initialize the service from the given environment. - # @parameter environment [Build::Environment] - def initialize(environment) - @environment = environment - @evaluator = @environment.evaluator - end - - # Whether the service environment contains the specified keys. - # This is used for matching environment configuration to service behaviour. - def include?(keys) - keys.all?{|key| @environment.include?(key)} - end - - # The name of the service. - # e.g. `myapp.com`. - def name - @evaluator.name - end - - # The logger to use for this service. - # @returns [Console::Logger] - def logger - return Console.logger # .with(name: name) - end - - # Start the service. - def start - end - - # Setup the service into the specified container. - # @parameter container [Async::Container::Generic] - def setup(container) - end - - # Stop the service. - def stop - end - end - end -end diff --git a/lib/falcon/service/proxy.rb b/lib/falcon/service/proxy.rb index ff552108..f5c9f96b 100644 --- a/lib/falcon/service/proxy.rb +++ b/lib/falcon/service/proxy.rb @@ -3,14 +3,37 @@ # Released under the MIT License. # Copyright, 2020-2023, by Samuel Williams. -require_relative 'generic' +require 'async/service/generic' require 'async/http/endpoint' require 'async/io/shared_endpoint' module Falcon module Service - class Proxy < Generic + class Proxy < Async::Service::Generic + module Environment + # The host that this proxy will receive connections for. + def url + "https://[::]:443" + end + + # The upstream endpoint that will handle incoming requests. + # @attribute [Async::HTTP::Endpoint] + def endpoint + ::Async::HTTP::Endpoint.parse(url) + end + + # The service class to use for the proxy. + # @attribute [Class] + def service_class + ::Falcon::Service::Proxy + end + end + + def self.included(target) + target.include(Environment) + end + def name "#{self.class} for #{self.authority}" end diff --git a/lib/falcon/service/server.rb b/lib/falcon/service/server.rb new file mode 100644 index 00000000..bfacd743 --- /dev/null +++ b/lib/falcon/service/server.rb @@ -0,0 +1,115 @@ +# frozen_string_literal: true + +# Released under the MIT License. +# Copyright, 2020-2023, by Samuel Williams. + +require 'async/service/generic' +require 'async/http/endpoint' + +require_relative '../server' + +module Falcon + module Service + class Server < Async::Service::Generic + module Environment + # Options to use when creating the container. + def container_options + {restart: true} + end + + # The host that this proxy will receive connections for. + def url + "http://[::]:9292" + end + + # The upstream endpoint that will handle incoming requests. + # @attribute [Async::HTTP::Endpoint] + def endpoint + ::Async::HTTP::Endpoint.parse(url) + end + + # The service class to use for the proxy. + # @attribute [Class] + def service_class + ::Falcon::Service::Server + end + + def rackup_path + 'config.ru' + end + + def rack_app + Rack::Builder.parse_file(rackup_path) + end + + def verbose + false + end + + def cache + false + end + + def middleware + Falcon::Server.middleware(rack_app, verbose: verbose, cache: cache) + end + end + + def self.included(target) + target.include(Environment) + end + + def initialize(...) + super + + @endpoint = nil + @bound_endpoint = nil + end + + # Prepare the bound endpoint for the server. + def start + @endpoint ||= @evaluator.endpoint + + Sync do + @bound_endpoint = @endpoint.bound + end + + Console.logger.info(self) {"Starting #{name} on #{@endpoint.to_url}"} + + super + end + + # Setup the container with the application instance. + # @parameter container [Async::Container::Generic] + def setup(container) + container_options = @evaluator.container_options + + container.run(name: self.name, restart: true, **container_options) do |instance| + evaluator = @environment.evaluator + + Async do |task| + server = Falcon::Server.new(evaluator.middleware, @bound_endpoint, protocol: @endpoint.protocol, scheme: @endpoint.scheme) + + server.run + + instance.ready! + + task.children.each(&:wait) + end + end + end + + # Close the bound endpoint. + def stop(...) + if @bound_endpoint + @bound_endpoint.close + @bound_endpoint = nil + end + + @endpoint = nil + + super + end + end + end +end diff --git a/lib/falcon/service/supervisor.rb b/lib/falcon/service/supervisor.rb index ccaa7cc5..a1139607 100644 --- a/lib/falcon/service/supervisor.rb +++ b/lib/falcon/service/supervisor.rb @@ -8,11 +8,12 @@ require 'async/io/endpoint' require 'async/io/shared_endpoint' +require 'async/service/generic' module Falcon module Service # Implements a host supervisor which can restart the host services and provide various metrics about the running processes. - class Supervisor < Generic + class Supervisor < Async::Service::Generic # Initialize the supervisor using the given environment. # @parameter environment [Build::Environment] def initialize(environment) diff --git a/lib/falcon/service/virtual.rb b/lib/falcon/service/virtual.rb new file mode 100644 index 00000000..7b930a49 --- /dev/null +++ b/lib/falcon/service/virtual.rb @@ -0,0 +1,119 @@ +# frozen_string_literal: true + +# Released under the MIT License. +# Copyright, 2020-2023, by Samuel Williams. + +require 'async/service/generic' + +module Falcon + module Service + # A controller which mananages several virtual hosts. + # Spawns instances of {Proxy} and {Redirect} to handle incoming requests. + # + # A virtual host is an application bound to a specific authority (essentially a hostname). The virtual controller manages multiple hosts and allows a single server to host multiple applications easily. + class Virtual < Async::Service::Generic + module Environment + # All the falcon application configuration paths. + # @returns [Array(String)] Paths to the falcon application configuration files. + def configuration_paths + File.glob("/srv/http/*/falcon.rb") + end + + # The URI to bind the `HTTPS` -> `falcon host` proxy. + def bind_secure + "https://[::]:443" + end + + # The URI to bind the `HTTP` -> `HTTPS` redirector. + def bind_insecure + "http://[::]:80" + end + + # The connection timeout to use for incoming connections. + def timeout + 10.0 + end + end + + def self.included(target) + target.include(Environnment) + end + + # Drop privileges according to the user and group of the specified path. + # @parameter path [String] The path to the application directory. + def assume_privileges(path) + stat = File.stat(path) + + Process::GID.change_privilege(stat.gid) + Process::UID.change_privilege(stat.uid) + + home = Etc.getpwuid(stat.uid).dir + + return { + 'HOME' => home, + } + end + + # Spawn an application instance from the specified path. + # @parameter path [String] The path to the application directory. + # @parameter container [Async::Container::Generic] The container to spawn into. + # @parameter options [Options] The options which are passed to `exec`. + def spawn(path, container, **options) + container.spawn(name: "Falcon Application", restart: true, key: path) do |instance| + env = assume_privileges(path) + + instance.exec(env, + "bundle", "exec", "--keep-file-descriptors", + path, ready: false, **options) + end + end + + # The path to the falcon executable from this gem. + # @returns [String] + def falcon_path + File.expand_path("../../../bin/falcon", __dir__) + end + + # Setup the container with {Redirect} and {Proxy} child processes. + # These processes are gracefully restarted if they are already running. + # @parameter container [Async::Container::Generic] + def setup(container) + if proxy = container[:proxy] + proxy.kill(:HUP) + end + + if redirect = container[:redirect] + redirect.kill(:HUP) + end + + container.reload do + evaluator = @environment.evaluator + + evaluator.configuration_paths.each do |path| + path = File.expand_path(path) + root = File.dirname(path) + + spawn(path, container, chdir: root) + end + + container.spawn(name: "Falcon Redirector", restart: true, key: :redirect) do |instance| + instance.exec(falcon_path, "redirect", + "--bind", @command.bind_insecure, + "--timeout", @command.timeout.to_s, + "--redirect", @command.bind_secure, + *@command.paths, ready: false + ) + end + + container.spawn(name: "Falcon Proxy", restart: true, key: :proxy) do |instance| + instance.exec(falcon_path, "proxy", + "--bind", @command.bind_secure, + "--timeout", @command.timeout.to_s, + *@command.paths, ready: false + ) + end + end + end + end + end +end From 1904358e1c8aaea9af57c318eef3faaaa2c0848e Mon Sep 17 00:00:00 2001 From: Samuel Williams Date: Sun, 17 Mar 2024 14:50:54 +1300 Subject: [PATCH 03/21] WIP. --- falcon.gemspec | 4 +- lib/falcon/command/proxy.rb | 68 ------------------------- lib/falcon/command/redirect.rb | 71 -------------------------- lib/falcon/command/serve.rb | 62 ++++++++--------------- lib/falcon/command/virtual.rb | 55 +++++---------------- lib/falcon/controller/host.rb | 55 --------------------- lib/falcon/controller/proxy.rb | 8 ++- lib/falcon/controller/redirect.rb | 59 ---------------------- lib/falcon/service/application.rb | 13 +---- lib/falcon/service/proxy.rb | 22 +++++++++ lib/falcon/service/rackup.rb | 29 +++++++++++ lib/falcon/service/redirect.rb | 44 +++++++++++++++++ lib/falcon/service/server.rb | 27 +++++----- lib/falcon/service/virtual.rb | 42 ++++++++++++++++ lib/falcon/services.rb | 82 ------------------------------- 15 files changed, 193 insertions(+), 448 deletions(-) delete mode 100644 lib/falcon/command/proxy.rb delete mode 100644 lib/falcon/command/redirect.rb delete mode 100644 lib/falcon/controller/host.rb delete mode 100644 lib/falcon/controller/redirect.rb create mode 100644 lib/falcon/service/rackup.rb create mode 100644 lib/falcon/service/redirect.rb delete mode 100644 lib/falcon/services.rb diff --git a/falcon.gemspec b/falcon.gemspec index 6ff8609c..05d8c1e6 100644 --- a/falcon.gemspec +++ b/falcon.gemspec @@ -26,11 +26,11 @@ Gem::Specification.new do |spec| spec.required_ruby_version = ">= 3.0" spec.add_dependency "async" - spec.add_dependency "async-container", "~> 0.16.0" + spec.add_dependency "async-container", "~> 0.17.0" spec.add_dependency "async-http", "~> 0.57" spec.add_dependency "async-http-cache", "~> 0.4.0" spec.add_dependency "async-io", "~> 1.22" - spec.add_dependency "async-service" + spec.add_dependency "async-service", "~> 0.6.0" spec.add_dependency "bundler" spec.add_dependency "localhost", "~> 1.1" spec.add_dependency "openssl", "~> 3.0" diff --git a/lib/falcon/command/proxy.rb b/lib/falcon/command/proxy.rb deleted file mode 100644 index 1edbd3b5..00000000 --- a/lib/falcon/command/proxy.rb +++ /dev/null @@ -1,68 +0,0 @@ -# frozen_string_literal: true - -# Released under the MIT License. -# Copyright, 2020-2023, by Samuel Williams. - -require_relative '../controller/proxy' -require_relative 'paths' - -require 'samovar' - -module Falcon - module Command - # Implements the `falcon proxy` command. - # - # Manages a {Controller::Proxy} instance which is responsible for proxing incoming requests. - class Proxy < Samovar::Command - self.description = "Proxy to one or more backend hosts." - - # The command line options. - # @attribute [Samovar::Options] - options do - option '--bind
', "Bind to the given hostname/address", default: "https://[::]:443" - - option '-t/--timeout ', "Specify the maximum time to wait for non-blocking operations.", type: Float, default: nil - end - - # One or more paths to the configuration files. - # @name paths - # @attribute [Array(String)] - many :paths - - include Paths - - # Prepare a new controller for the command. - def controller - Controller::Proxy.new(self) - end - - # The container class to use. - def container_class - Async::Container.best_container_class - end - - # Options for the container. - # See {Controller::Serve#setup}. - def container_options - {} - end - - # Prepare the environment and run the controller. - def call - Console.logger.info(self) do |buffer| - buffer.puts "Falcon Proxy v#{VERSION} taking flight!" - buffer.puts "- Binding to: #{@options[:bind]}" - buffer.puts "- To terminate: Ctrl-C or kill #{Process.pid}" - buffer.puts "- To reload: kill -HUP #{Process.pid}" - end - - self.controller.run - end - - # The endpoint to bind to. - def endpoint(**options) - Async::HTTP::Endpoint.parse(@options[:bind], timeout: @options[:timeout], **options) - end - end - end -end diff --git a/lib/falcon/command/redirect.rb b/lib/falcon/command/redirect.rb deleted file mode 100644 index 9632bb98..00000000 --- a/lib/falcon/command/redirect.rb +++ /dev/null @@ -1,71 +0,0 @@ -# frozen_string_literal: true - -# Released under the MIT License. -# Copyright, 2020-2023, by Samuel Williams. - -require_relative '../controller/redirect' -require_relative 'paths' - -require 'samovar' - -module Falcon - module Command - class Redirect < Samovar::Command - self.description = "Redirect from insecure HTTP to secure HTTP." - - # The command line options. - # @attribute [Samovar::Options] - options do - option '--bind
', "Bind to the given hostname/address", default: "http://[::]:80" - option '--redirect
', "Redirect using this address as a template.", default: "https://[::]:443" - - option '-t/--timeout ', "Specify the maximum time to wait for non-blocking operations.", type: Float, default: nil - end - - # One or more paths to the configuration files. - # @name paths - # @attribute [Array(String)] - many :paths - - include Paths - - # Prepare a new controller for the command. - def controller - Controller::Redirect.new(self) - end - - # The container class to use. - def container_class - Async::Container.best_container_class - end - - # Options for the container. - # See {Controller::Serve#setup}. - def container_options - {} - end - - # Prepare the environment and run the controller. - def call - Console.logger.info(self) do |buffer| - buffer.puts "Falcon Redirect v#{VERSION} taking flight!" - buffer.puts "- Binding to: #{@options[:bind]}" - buffer.puts "- To terminate: Ctrl-C or kill #{Process.pid}" - buffer.puts "- To reload: kill -HUP #{Process.pid}" - end - - self.controller.run - end - - # The endpoint to bind to. - def endpoint(**options) - Async::HTTP::Endpoint.parse(@options[:bind], timeout: @options[:timeout], **options) - end - - # The template endpoint to redirect to. - def redirect_endpoint(**options) - Async::HTTP::Endpoint.parse(@options[:redirect], **options) - end - end - end -end diff --git a/lib/falcon/command/serve.rb b/lib/falcon/command/serve.rb index c47f5f19..9eb580cb 100644 --- a/lib/falcon/command/serve.rb +++ b/lib/falcon/command/serve.rb @@ -6,23 +6,12 @@ require_relative '../server' require_relative '../endpoint' -require_relative '../controller/serve' +require_relative '../service/server' require 'async/container' - -require 'async/io/trap' -require 'async/io/host_endpoint' -require 'async/io/shared_endpoint' -require 'async/io/ssl_endpoint' - require 'async/http/client' - require 'samovar' -require 'rack/builder' - -require 'bundler' - module Falcon module Command # Implements the `falcon serve` command. Designed for *development*. @@ -53,29 +42,30 @@ class Serve < Samovar::Command option '--threads ', "Number of threads (hybrid only).", type: Integer end - # Options for the {endpoint}. + def container_options + @options.slice(:count, :forks, :threads) + end + def endpoint_options - @options.slice(:hostname, :port, :reuse_port, :timeout) + @options.slice(:hostname, :port, :timeout) end - def environment - Async::Service::Environment.build do - include Falcon::Service::Server + def service + Async::Service::Environment.new(Falcon::Service::Server).with( + verbose: self.parent&.verbose?, + cache: @options[:cache], - rackup_path @options[:config] + container_options: self.container_options, + endpoint_options: self.endpoint_options, - container_options @options.slice(:count, :forks, :threads) + rackup_path: @options[:config], + preload: [@options[:preload]], + bind: @options[:bind], - preload @options[:preload] + name: "server", - verbose @parent&.verbose? - - cache @options[:cache] - - endpoint do - Endpoint.parse(@options[:bind], **endpoint_options) - end - end + endpoint: ->{Endpoint.parse(bind, **endpoint_options)} + ) end # The container class to use. @@ -114,20 +104,10 @@ def call buffer.puts "- To reload configuration: kill -HUP #{Process.pid}" end - begin - Bundler.require(:preload) - rescue Bundler::GemfileNotFound - # Ignore. - end - - if Process.respond_to?(:warmup) - Process.warmup - elsif GC.respond_to?(:compact) - 3.times{GC.start} - GC.compact - end + configuration = Async::Service::Configuration.new + configuration.add(self.service) - controller = Async::Service::Controller.new(container_class: self.container_class, environment: environment) + Async::Service::Controller.run(configuration, container_class: self.container_class) end end end diff --git a/lib/falcon/command/virtual.rb b/lib/falcon/command/virtual.rb index dbcece33..9ee3a312 100644 --- a/lib/falcon/command/virtual.rb +++ b/lib/falcon/command/virtual.rb @@ -1,9 +1,9 @@ # frozen_string_literal: true # Released under the MIT License. -# Copyright, 2018-2023, by Samuel Williams. +# Copyright, 2018-2024, by Samuel Williams. -require_relative '../controller/virtual' +require_relative '../service/virtual' require_relative 'paths' require 'samovar' @@ -32,24 +32,14 @@ class Virtual < Samovar::Command include Paths - # Prepare a new controller for the command. - def controller - Controller::Virtual.new(self) - end - - # The URI to bind the `HTTPS` -> `falcon host` proxy. - def bind_secure - @options[:bind_secure] - end - - # The URI to bind the `HTTP` -> `HTTPS` redirector. - def bind_insecure - @options[:bind_insecure] - end - - # The connection timeout to use for incoming connections. - def timeout - @options[:timeout] + def service + Async::Service::Environment.new(Falcon::Service::Virtual).with( + verbose: self.parent&.verbose?, + configuration_paths: self.paths, + bind_insecure: @options[:bind_insecure], + bind_secure: @options[:bind_secure], + timeout: @options[:timeout] + ) end # Prepare the environment and run the controller. @@ -60,29 +50,10 @@ def call buffer.puts "- To reload all sites: kill -HUP #{Process.pid}" end - ENV['CONSOLE_LEVEL'] = 'debug' - - self.controller.run - end - - # The insecure endpoint for connecting to the {Redirect} instance. - def insecure_endpoint(**options) - Async::HTTP::Endpoint.parse(@options[:bind_insecure], **options) - end - - # The secure endpoint for connecting to the {Proxy} instance. - def secure_endpoint(**options) - Async::HTTP::Endpoint.parse(@options[:bind_secure], **options) - end - - # An endpoint suitable for connecting to the specified hostname. - def host_endpoint(hostname, **options) - endpoint = secure_endpoint(**options) - - url = URI.parse(@options[:bind_secure]) - url.hostname = hostname + configuration = self.configuration + configuration.add(self.service) - return Async::HTTP::Endpoint.new(url, hostname: endpoint.hostname) + Async::Service::Controller.run(configuration) end end end diff --git a/lib/falcon/controller/host.rb b/lib/falcon/controller/host.rb deleted file mode 100644 index 6936c90a..00000000 --- a/lib/falcon/controller/host.rb +++ /dev/null @@ -1,55 +0,0 @@ -# frozen_string_literal: true - -# Released under the MIT License. -# Copyright, 2020-2023, by Samuel Williams. - -require_relative '../services' - -require 'async/container/controller' - -module Falcon - module Controller - # A generic controller for serving an application. - # Hosts several {Services} based on the command configuration. - # - # The configuration is provided by {Command::Host} and is typically loaded from a `falcon.rb` file. See {Configuration#load_file} for more details. - class Host < Async::Container::Controller - # Initialize the virtual controller. - # @parameter command [Command::Host] The user-specified command-line options. - def initialize(command, **options) - @command = command - - @configuration = command.configuration - @services = Services.new(@configuration) - - super(**options) - end - - # Create the controller as specified by the command. - # e.g. `Async::Container::Forked`. - def create_container - @command.container_class.new - end - - # Start all specified services. - def start - @services.start - - super - end - - # Setup all specified services into the container. - # @parameter container [Async::Container::Generic] - def setup(container) - @services.setup(container) - end - - # Stop all specified services. - def stop(*) - @services.stop - - super - end - end - end -end diff --git a/lib/falcon/controller/proxy.rb b/lib/falcon/controller/proxy.rb index e8b1c797..22c2216b 100644 --- a/lib/falcon/controller/proxy.rb +++ b/lib/falcon/controller/proxy.rb @@ -5,7 +5,6 @@ require 'async/container/controller' -require_relative 'serve' require_relative '../middleware/proxy' require_relative '../service/proxy' @@ -14,9 +13,8 @@ module Falcon module Controller # A controller for proxying requests. - class Proxy < Serve - # The default SSL session identifier. - DEFAULT_SESSION_ID = "falcon" + class Proxy + # Initialize the proxy controller. # @parameter command [Command::Proxy] The user-specified command-line options. @@ -30,7 +28,7 @@ def initialize(command, session_id: DEFAULT_SESSION_ID, **options) # Load the {Middleware::Proxy} application with the specified hosts. def load_app - return Middleware::Proxy.new(Middleware::BadRequest, @hosts) + end # The name of the controller which is used for the process title. diff --git a/lib/falcon/controller/redirect.rb b/lib/falcon/controller/redirect.rb deleted file mode 100644 index 1f2a71de..00000000 --- a/lib/falcon/controller/redirect.rb +++ /dev/null @@ -1,59 +0,0 @@ -# frozen_string_literal: true - -# Released under the MIT License. -# Copyright, 2020-2023, by Samuel Williams. - -require 'async/container/controller' - -require_relative 'serve' -require_relative '../middleware/redirect' -require_relative '../service/proxy' - -module Falcon - module Controller - # A controller for redirecting requests. - class Redirect < Serve - # Initialize the redirect controller. - # @parameter command [Command::Redirect] The user-specified command-line options. - def initialize(command, **options) - super(command, **options) - - @hosts = {} - end - - # Load the {Middleware::Redirect} application with the specified hosts. - def load_app - return Middleware::Redirect.new(Middleware::NotFound, @hosts, @command.redirect_endpoint) - end - - # The name of the controller which is used for the process title. - def name - "Falcon Redirect Server" - end - - # The endpoint the server will bind to. - def endpoint - @command.endpoint.with( - reuse_address: true, - ) - end - - # Builds a map of host redirections. - def start - configuration = @command.configuration - - services = Services.new(configuration) - - @hosts = {} - - services.each do |service| - if service.is_a?(Service::Proxy) - @hosts[service.authority] = service - end - end - - super - end - end - end -end diff --git a/lib/falcon/service/application.rb b/lib/falcon/service/application.rb index 4fccb100..c14aa24a 100644 --- a/lib/falcon/service/application.rb +++ b/lib/falcon/service/application.rb @@ -12,7 +12,7 @@ module Falcon module Service # Implements an application server using an internal clear-text proxy. - class Application < Proxy + class Application < Server def initialize(environment) super @@ -32,17 +32,6 @@ def count @environment.evaluator.count end - # Preload any resources specified by the environment. - def preload! - if scripts = @evaluator.preload - scripts.each do |path| - Console.logger.info(self) {"Preloading #{path}..."} - full_path = File.expand_path(path, self.root) - load(full_path) - end - end - end - # Prepare the bound endpoint for the application instances. # Invoke {preload!} to load shared resources into the parent process. def start diff --git a/lib/falcon/service/proxy.rb b/lib/falcon/service/proxy.rb index f5c9f96b..78ffe284 100644 --- a/lib/falcon/service/proxy.rb +++ b/lib/falcon/service/proxy.rb @@ -28,6 +28,28 @@ def endpoint def service_class ::Falcon::Service::Proxy end + + # The default SSL session identifier. + def tls_session_id + "falcon" + end + + def hosts + services.each do |service| + if service.is_a?(Service::Proxy) + Console.logger.info(self) {"Proxying #{service.authority} to #{service.endpoint}"} + @hosts[service.authority] = service + + # Pre-cache the ssl contexts: + # It seems some OpenSSL objects don't like event-driven I/O. + service.ssl_context + end + end + end + + def middleware + return Middleware::Proxy.new(Middleware::BadRequest, hosts) + end end def self.included(target) diff --git a/lib/falcon/service/rackup.rb b/lib/falcon/service/rackup.rb new file mode 100644 index 00000000..cabced84 --- /dev/null +++ b/lib/falcon/service/rackup.rb @@ -0,0 +1,29 @@ +# frozen_string_literal: true + +# Released under the MIT License. +# Copyright, 2024, by Samuel Williams. + +require_relative 'server' + +module Falcon + module Service + # A controller for redirecting requests. + class Rackup < Server + module Environment + include Server::Environment + + def rackup_path + 'config.ru' + end + + def rack_app + Rack::Builder.parse_file(rackup_path) + end + + def middleware + Falcon::Server.middleware(rack_app, verbose: verbose, cache: cache) + end + end + end + end +end diff --git a/lib/falcon/service/redirect.rb b/lib/falcon/service/redirect.rb new file mode 100644 index 00000000..0b631285 --- /dev/null +++ b/lib/falcon/service/redirect.rb @@ -0,0 +1,44 @@ +# frozen_string_literal: true + +# Released under the MIT License. +# Copyright, 2020-2023, by Samuel Williams. + +require 'async/container/controller' + +# require_relative 'serve' +require_relative '../middleware/redirect' +require_relative '../service/proxy' + +module Falcon + module Service + # A controller for redirecting requests. + class Redirect < Server + module Environment + include Server::Environment + + def redirect_url + "https://[::]:443" + end + + def redirect_endpoint + Async::HTTP::Endpoint.parse(redirect_url) + end + + def hosts + {} + end + + # Load the {Middleware::Redirect} application with the specified hosts. + def middleware + Middleware::Redirect.new(Middleware::NotFound, hosts, redirect_endpoint) + end + end + + # services.each do |service| + # if service.is_a?(Service::Proxy) + # @hosts[service.authority] = service + # end + # end + end + end +end diff --git a/lib/falcon/service/server.rb b/lib/falcon/service/server.rb index bfacd743..7e70e3e4 100644 --- a/lib/falcon/service/server.rb +++ b/lib/falcon/service/server.rb @@ -17,7 +17,7 @@ def container_options {restart: true} end - # The host that this proxy will receive connections for. + # The host that this server will receive connections for. def url "http://[::]:9292" end @@ -34,14 +34,6 @@ def service_class ::Falcon::Service::Server end - def rackup_path - 'config.ru' - end - - def rack_app - Rack::Builder.parse_file(rackup_path) - end - def verbose false end @@ -50,8 +42,8 @@ def cache false end - def middleware - Falcon::Server.middleware(rack_app, verbose: verbose, cache: cache) + def client_endpoint + ::Async::HTTP::Endpoint.parse(url) end end @@ -66,6 +58,17 @@ def initialize(...) @bound_endpoint = nil end + # Preload any resources specified by the environment. + def preload! + if scripts = @evaluator.preload + scripts.each do |path| + Console.logger.info(self) {"Preloading #{path}..."} + full_path = File.expand_path(path, self.root) + load(full_path) + end + end + end + # Prepare the bound endpoint for the server. def start @endpoint ||= @evaluator.endpoint @@ -74,6 +77,8 @@ def start @bound_endpoint = @endpoint.bound end + preload! + Console.logger.info(self) {"Starting #{name} on #{@endpoint.to_url}"} super diff --git a/lib/falcon/service/virtual.rb b/lib/falcon/service/virtual.rb index 7b930a49..fa948243 100644 --- a/lib/falcon/service/virtual.rb +++ b/lib/falcon/service/virtual.rb @@ -19,6 +19,10 @@ def configuration_paths File.glob("/srv/http/*/falcon.rb") end + def configuration + ::Async::Service::Configuration.load(configuration_paths) + end + # The URI to bind the `HTTPS` -> `falcon host` proxy. def bind_secure "https://[::]:443" @@ -33,6 +37,44 @@ def bind_insecure def timeout 10.0 end + + # # The insecure endpoint for connecting to the {Redirect} instance. + # def insecure_endpoint(**options) + # Async::HTTP::Endpoint.parse(bind_insecure, **options) + # end + + # # The secure endpoint for connecting to the {Proxy} instance. + # def secure_endpoint(**options) + # Async::HTTP::Endpoint.parse(bind_secure, **options) + # end + + # # An endpoint suitable for connecting to the specified hostname. + # def host_endpoint(hostname, **options) + # endpoint = secure_endpoint(**options) + + # url = URI.parse(bind_secure) + # url.hostname = hostname + + # return Async::HTTP::Endpoint.new(url, hostname: endpoint.hostname) + # end + end + + def redirect_service + hosts = {} + + @evaluator.configuration.services do |service| + if service.is_a?(Service::Server) + hosts[service.authority] = service + end + end + + Async::Service::Environment.new(Falcon::Service::Redirect).with( + hosts: hosts, + ) + end + + def proxy_service + end def self.included(target) diff --git a/lib/falcon/services.rb b/lib/falcon/services.rb deleted file mode 100644 index 43f7e69f..00000000 --- a/lib/falcon/services.rb +++ /dev/null @@ -1,82 +0,0 @@ -# frozen_string_literal: true - -# Released under the MIT License. -# Copyright, 2019-2023, by Samuel Williams. - -require_relative 'service/generic' - -module Falcon - # Represents one or more services associated with a host. - # - # The services model allows falcon to manage one more more service associated with a given host. Some examples of services include: - # - # - Rack applications wrapped by {Service::Application}. - # - Host supervisor implemented in {Service::Supervisor}. - # - Proxy services wrapped by {Service::Proxy}. - # - # The list of services is typically generated from the user supplied `falcon.rb` configuration file, which is loaded into an immutable {Configuration} instance, which is mapped into a list of services. - class Services - # Initialize the services from the given configuration. - # - # @parameter configuration [Configuration] - def initialize(configuration) - @named = {} - - configuration.each(:service_class) do |environment| - service = Service::Generic.wrap(environment) - - add(service) - end - end - - # Enumerate all named services. - def each(&block) - @named.each_value(&block) - end - - # Add a named service. - # - # @parameter service [Service] - def add(service) - @named[service.name] = service - end - - # Start all named services. - def start - @named.each do |name, service| - Console.logger.debug(self) {"Starting #{name}..."} - service.start - end - end - - # Setup all named services into the given container. - # - # @parameter container [Async::Container::Generic] - def setup(container) - @named.each do |name, service| - Console.logger.debug(self) {"Setup #{name} into #{container}..."} - service.setup(container) - end - - return container - end - - # Stop all named services. - def stop - failed = false - - @named.each do |name, service| - Console.logger.debug(self) {"Stopping #{name}..."} - - begin - service.stop - rescue => error - failed = true - Console.logger.error(self, error) - end - end - - return failed - end - end -end From 8623357b8e176d79aa8ccd4d9bd0585d88821db3 Mon Sep 17 00:00:00 2001 From: Samuel Williams Date: Sun, 17 Mar 2024 16:59:43 +1300 Subject: [PATCH 04/21] Fix up `host` and `serve` command execution. --- examples/hello/preload.rb | 6 +----- lib/falcon/command/host.rb | 36 +++++--------------------------- lib/falcon/command/serve.rb | 8 ++++--- lib/falcon/command/top.rb | 4 ---- lib/falcon/environments/rack.rb | 36 ++------------------------------ lib/falcon/service/server.rb | 6 +++++- lib/falcon/service/supervisor.rb | 2 +- 7 files changed, 19 insertions(+), 79 deletions(-) diff --git a/examples/hello/preload.rb b/examples/hello/preload.rb index e6e28bfc..dda339a8 100644 --- a/examples/hello/preload.rb +++ b/examples/hello/preload.rb @@ -3,8 +3,4 @@ # Released under the MIT License. # Copyright, 2020-2023, by Samuel Williams. -if GC.respond_to?(:compact) - Console.logger.warn(self, "Compacting the mainframe...") - GC.compact - Console.logger.warn(self, "Compacting done...") -end +$stderr.puts "Preloading..." diff --git a/lib/falcon/command/host.rb b/lib/falcon/command/host.rb index 605749e2..52747438 100644 --- a/lib/falcon/command/host.rb +++ b/lib/falcon/command/host.rb @@ -3,12 +3,11 @@ # Released under the MIT License. # Copyright, 2019-2023, by Samuel Williams. -require_relative '../controller/host' -require_relative '../configuration' +require_relative 'paths' require_relative '../version' require 'samovar' -require 'bundler' +require 'async/service/controller' module Falcon module Command @@ -23,28 +22,13 @@ class Host < Samovar::Command # @attribute [Array(String)] many :paths, "Service configuration paths.", default: ["falcon.rb"] + include Paths + # The container class to use. def container_class Async::Container.best_container_class end - # Generate a configuration based on the specified {paths}. - def configuration - configuration = Configuration.new - - @paths.each do |path| - path = File.expand_path(path) - configuration.load_file(path) - end - - return configuration - end - - # Prepare a new controller for the command. - def controller - Controller::Host.new(self) - end - # Prepare the environment and run the controller. def call Console.logger.info(self) do |buffer| @@ -54,17 +38,7 @@ def call buffer.puts "- To reload: kill -HUP #{Process.pid}" end - begin - Bundler.require(:preload) - rescue Bundler::GemfileNotFound - # Ignore. - end - - if GC.respond_to?(:compact) - GC.compact - end - - self.controller.run + Async::Service::Controller.run(self.configuration, container_class: self.container_class) end end end diff --git a/lib/falcon/command/serve.rb b/lib/falcon/command/serve.rb index 9eb580cb..d8a59595 100644 --- a/lib/falcon/command/serve.rb +++ b/lib/falcon/command/serve.rb @@ -6,7 +6,7 @@ require_relative '../server' require_relative '../endpoint' -require_relative '../service/server' +require_relative '../service/rackup' require 'async/container' require 'async/http/client' @@ -51,7 +51,9 @@ def endpoint_options end def service - Async::Service::Environment.new(Falcon::Service::Server).with( + Async::Service::Environment.new(Falcon::Service::Rackup::Environment).with( + root: Dir.pwd, + verbose: self.parent&.verbose?, cache: @options[:cache], @@ -59,7 +61,7 @@ def service endpoint_options: self.endpoint_options, rackup_path: @options[:config], - preload: [@options[:preload]], + preload: [@options[:preload]].compact, bind: @options[:bind], name: "server", diff --git a/lib/falcon/command/top.rb b/lib/falcon/command/top.rb index 75edb1a0..3d6d491d 100644 --- a/lib/falcon/command/top.rb +++ b/lib/falcon/command/top.rb @@ -6,8 +6,6 @@ require_relative 'serve' require_relative 'host' require_relative 'virtual' -require_relative 'proxy' -require_relative 'redirect' require_relative 'supervisor' require_relative '../version' @@ -36,8 +34,6 @@ class Top < Samovar::Command 'serve' => Serve, 'host' => Host, 'virtual' => Virtual, - 'proxy' => Proxy, - 'redirect' => Redirect, 'supervisor' => Supervisor, }, default: 'serve' diff --git a/lib/falcon/environments/rack.rb b/lib/falcon/environments/rack.rb index 225f5f78..5ba855f4 100644 --- a/lib/falcon/environments/rack.rb +++ b/lib/falcon/environments/rack.rb @@ -3,43 +3,11 @@ # Released under the MIT License. # Copyright, 2019-2023, by Samuel Williams. -require_relative 'application' +require_relative '../service/rackup' require_relative '../environments' module Falcon module Environments - # A rack application environment. - module Rack - include Application - - # The rack configuration path e.g. `config.ru`. - # @returns [String] - def config_path - ::File.expand_path("config.ru", root) - end - - # Whether to enable the application layer cache. - # @returns [String] - def cache - false - end - - def verbose - false - end - - # The middleware stack for the rack application. - # @returns [Protocol::HTTP::Middleware] - def middleware - app, _ = ::Rack::Builder.parse_file(config_path) - - ::Falcon::Server.middleware(app, - verbose: verbose, - cache: cache - ) - end - end - - LEGACY_ENVIRONMENTS[:rack] = Rack + LEGACY_ENVIRONMENTS[:rack] = Service::Rackup::Environment end end diff --git a/lib/falcon/service/server.rb b/lib/falcon/service/server.rb index 7e70e3e4..96737341 100644 --- a/lib/falcon/service/server.rb +++ b/lib/falcon/service/server.rb @@ -60,10 +60,14 @@ def initialize(...) # Preload any resources specified by the environment. def preload! + root = @evaluator.root + if scripts = @evaluator.preload + scripts = Array(scripts) + scripts.each do |path| Console.logger.info(self) {"Preloading #{path}..."} - full_path = File.expand_path(path, self.root) + full_path = File.expand_path(path, root) load(full_path) end end diff --git a/lib/falcon/service/supervisor.rb b/lib/falcon/service/supervisor.rb index a1139607..ff28ce1f 100644 --- a/lib/falcon/service/supervisor.rb +++ b/lib/falcon/service/supervisor.rb @@ -16,7 +16,7 @@ module Service class Supervisor < Async::Service::Generic # Initialize the supervisor using the given environment. # @parameter environment [Build::Environment] - def initialize(environment) + def initialize(...) super @bound_endpoint = nil From a97e6460ebd913bfdbb7bcd842a47f6efaae12b5 Mon Sep 17 00:00:00 2001 From: Samuel Williams Date: Mon, 18 Mar 2024 19:36:56 +1300 Subject: [PATCH 05/21] Remove remaining controllers. --- falcon.gemspec | 2 +- lib/falcon/controller/proxy.rb | 107 --------------------------- lib/falcon/controller/virtual.rb | 100 ------------------------- lib/falcon/environments/tls.rb | 1 - lib/falcon/service/proxy.rb | 122 ++++++++++++++++++------------- lib/falcon/service/server.rb | 20 ++--- 6 files changed, 83 insertions(+), 269 deletions(-) delete mode 100644 lib/falcon/controller/proxy.rb delete mode 100644 lib/falcon/controller/virtual.rb diff --git a/falcon.gemspec b/falcon.gemspec index 05d8c1e6..3ca421fa 100644 --- a/falcon.gemspec +++ b/falcon.gemspec @@ -30,7 +30,7 @@ Gem::Specification.new do |spec| spec.add_dependency "async-http", "~> 0.57" spec.add_dependency "async-http-cache", "~> 0.4.0" spec.add_dependency "async-io", "~> 1.22" - spec.add_dependency "async-service", "~> 0.6.0" + spec.add_dependency "async-service", "~> 0.7.0" spec.add_dependency "bundler" spec.add_dependency "localhost", "~> 1.1" spec.add_dependency "openssl", "~> 3.0" diff --git a/lib/falcon/controller/proxy.rb b/lib/falcon/controller/proxy.rb deleted file mode 100644 index 22c2216b..00000000 --- a/lib/falcon/controller/proxy.rb +++ /dev/null @@ -1,107 +0,0 @@ -# frozen_string_literal: true - -# Released under the MIT License. -# Copyright, 2020-2023, by Samuel Williams. - -require 'async/container/controller' - -require_relative '../middleware/proxy' -require_relative '../service/proxy' - -require_relative '../tls' - -module Falcon - module Controller - # A controller for proxying requests. - class Proxy - - - # Initialize the proxy controller. - # @parameter command [Command::Proxy] The user-specified command-line options. - # @parameter session_id [String] The SSL session identifier to use for the session cache. - def initialize(command, session_id: DEFAULT_SESSION_ID, **options) - super(command, **options) - - @session_id = session_id - @hosts = {} - end - - # Load the {Middleware::Proxy} application with the specified hosts. - def load_app - - end - - # The name of the controller which is used for the process title. - def name - "Falcon Proxy Server" - end - - # Look up the host context for the given hostname, and update the socket hostname if necessary. - # @parameter socket [OpenSSL::SSL::SSLSocket] The incoming connection. - # @parameter hostname [String] The negotiated hostname. - def host_context(socket, hostname) - if host = @hosts[hostname] - Console.logger.debug(self) {"Resolving #{hostname} -> #{host}"} - - socket.hostname = hostname - - return host.ssl_context - else - Console.logger.warn(self) {"Unable to resolve #{hostname}!"} - - return nil - end - end - - # Generate an SSL context which delegates to {host_context} to multiplex based on hostname. - def ssl_context - @server_context ||= OpenSSL::SSL::SSLContext.new.tap do |context| - context.servername_cb = Proc.new do |socket, hostname| - self.host_context(socket, hostname) - end - - context.session_id_context = @session_id - - context.ssl_version = :TLSv1_2_server - - context.set_params( - ciphers: TLS::SERVER_CIPHERS, - verify_mode: OpenSSL::SSL::VERIFY_NONE, - ) - - context.setup - end - end - - # The endpoint the server will bind to. - def endpoint - @command.endpoint.with( - ssl_context: self.ssl_context, - reuse_address: true, - ) - end - - # Builds a map of host redirections. - def start - configuration = @command.configuration - - services = Services.new(configuration) - - @hosts = {} - - services.each do |service| - if service.is_a?(Service::Proxy) - Console.logger.info(self) {"Proxying #{service.authority} to #{service.endpoint}"} - @hosts[service.authority] = service - - # Pre-cache the ssl contexts: - # It seems some OpenSSL objects don't like event-driven I/O. - service.ssl_context - end - end - - super - end - end - end -end diff --git a/lib/falcon/controller/virtual.rb b/lib/falcon/controller/virtual.rb deleted file mode 100644 index f26036a6..00000000 --- a/lib/falcon/controller/virtual.rb +++ /dev/null @@ -1,100 +0,0 @@ -# frozen_string_literal: true - -# Released under the MIT License. -# Copyright, 2020-2023, by Samuel Williams. - -require 'async/container/controller' - -module Falcon - module Controller - # A controller which mananages several virtual hosts. - # Spawns instances of {Proxy} and {Redirect} to handle incoming requests. - # - # A virtual host is an application bound to a specific authority (essentially a hostname). The virtual controller manages multiple hosts and allows a single server to host multiple applications easily. - class Virtual < Async::Container::Controller - # Initialize the virtual controller. - # @parameter command [Command::Virtual] The user-specified command-line options. - def initialize(command, **options) - @command = command - - super(**options) - - trap(SIGHUP, &self.method(:reload)) - end - - # Drop privileges according to the user and group of the specified path. - # @parameter path [String] The path to the application directory. - def assume_privileges(path) - stat = File.stat(path) - - Process::GID.change_privilege(stat.gid) - Process::UID.change_privilege(stat.uid) - - home = Etc.getpwuid(stat.uid).dir - - return { - 'HOME' => home, - } - end - - # Spawn an application instance from the specified path. - # @parameter path [String] The path to the application directory. - # @parameter container [Async::Container::Generic] The container to spawn into. - # @parameter options [Options] The options which are passed to `exec`. - def spawn(path, container, **options) - container.spawn(name: "Falcon Application", restart: true, key: path) do |instance| - env = assume_privileges(path) - - instance.exec(env, - "bundle", "exec", "--keep-file-descriptors", - path, ready: false, **options) - end - end - - # The path to the falcon executable from this gem. - # @returns [String] - def falcon_path - File.expand_path("../../../bin/falcon", __dir__) - end - - # Setup the container with {Redirect} and {Proxy} child processes. - # These processes are gracefully restarted if they are already running. - # @parameter container [Async::Container::Generic] - def setup(container) - if proxy = container[:proxy] - proxy.kill(:HUP) - end - - if redirect = container[:redirect] - redirect.kill(:HUP) - end - - container.reload do - @command.resolved_paths do |path| - path = File.expand_path(path) - root = File.dirname(path) - - spawn(path, container, chdir: root) - end - - container.spawn(name: "Falcon Redirector", restart: true, key: :redirect) do |instance| - instance.exec(falcon_path, "redirect", - "--bind", @command.bind_insecure, - "--timeout", @command.timeout.to_s, - "--redirect", @command.bind_secure, - *@command.paths, ready: false - ) - end - - container.spawn(name: "Falcon Proxy", restart: true, key: :proxy) do |instance| - instance.exec(falcon_path, "proxy", - "--bind", @command.bind_secure, - "--timeout", @command.timeout.to_s, - *@command.paths, ready: false - ) - end - end - end - end - end -end diff --git a/lib/falcon/environments/tls.rb b/lib/falcon/environments/tls.rb index b4cbf2cc..262b6452 100644 --- a/lib/falcon/environments/tls.rb +++ b/lib/falcon/environments/tls.rb @@ -3,7 +3,6 @@ # Released under the MIT License. # Copyright, 2019-2023, by Samuel Williams. -require_relative '../controller/proxy' require_relative '../tls' require_relative '../environments' diff --git a/lib/falcon/service/proxy.rb b/lib/falcon/service/proxy.rb index 78ffe284..90121ea5 100644 --- a/lib/falcon/service/proxy.rb +++ b/lib/falcon/service/proxy.rb @@ -10,18 +10,9 @@ module Falcon module Service - class Proxy < Async::Service::Generic + class Proxy < Server module Environment - # The host that this proxy will receive connections for. - def url - "https://[::]:443" - end - - # The upstream endpoint that will handle incoming requests. - # @attribute [Async::HTTP::Endpoint] - def endpoint - ::Async::HTTP::Endpoint.parse(url) - end + include Server::Environment # The service class to use for the proxy. # @attribute [Class] @@ -29,66 +20,95 @@ def service_class ::Falcon::Service::Proxy end + # The host that this proxy will receive connections for. + def url + "https://[::]:443" + end + # The default SSL session identifier. def tls_session_id "falcon" end + # The services we will proxy to. + # @returns [Array(Async::Service::Environment)] + def environments + [] + end + def hosts - services.each do |service| - if service.is_a?(Service::Proxy) - Console.logger.info(self) {"Proxying #{service.authority} to #{service.endpoint}"} - @hosts[service.authority] = service + hosts = {} + + environments.each do |environment| + evaluator = environment.evaluator + + if evaluator.key?(:authority) and evaluator.key?(:ssl_context) and evaluator.key?(:endpoint) + Console.logger.info(self) {"Proxying #{self.url} to #{evaluator.authority} using #{evaluator.endpoint}"} + hosts[evaluator.authority] = evaluator # Pre-cache the ssl contexts: # It seems some OpenSSL objects don't like event-driven I/O. - service.ssl_context + # service.ssl_context + else + Console.logger.warn(self) {"Ignoring environment: #{environment}, missing authority, ssl_context, or endpoint."} + end + end + + return hosts + end + + # Look up the host context for the given hostname, and update the socket hostname if necessary. + # @parameter socket [OpenSSL::SSL::SSLSocket] The incoming connection. + # @parameter hostname [String] The negotiated hostname. + def host_context(socket, hostname) + if host = self.hosts[hostname] + Console.logger.debug(self) {"Resolving #{hostname} -> #{host}"} + + socket.hostname = hostname + + return host.ssl_context + else + Console.logger.warn(self) {"Unable to resolve #{hostname}!"} + + return nil + end + end + + # Generate an SSL context which delegates to {host_context} to multiplex based on hostname. + def ssl_context + @server_context ||= OpenSSL::SSL::SSLContext.new.tap do |context| + context.servername_cb = Proc.new do |socket, hostname| + self.host_context(socket, hostname) end + + context.session_id_context = @session_id + + context.ssl_version = :TLSv1_2_server + + context.set_params( + ciphers: TLS::SERVER_CIPHERS, + verify_mode: OpenSSL::SSL::VERIFY_NONE, + ) + + context.setup end end + # The endpoint the server will bind to. + def endpoint + super.with( + ssl_context: self.ssl_context, + ) + end + def middleware - return Middleware::Proxy.new(Middleware::BadRequest, hosts) + return Middleware::Proxy.new(Middleware::BadRequest, self.hosts) end end def self.included(target) target.include(Environment) end - - def name - "#{self.class} for #{self.authority}" - end - - # The host that this proxy will receive connections for. - def authority - @evaluator.authority - end - - # The upstream endpoint that this proxy will connect to. - def endpoint - @evaluator.endpoint - end - - # The {OpenSSL::SSL::SSLContext} that will be used for incoming connections. - def ssl_context - @evaluator.ssl_context - end - - # The root - def root - @evaluator.root - end - - # The protocol this proxy will use to talk to the upstream host. - def protocol - endpoint.protocol - end - - # The scheme this proxy will use to talk to the upstream host. - def scheme - endpoint.scheme - end end end end diff --git a/lib/falcon/service/server.rb b/lib/falcon/service/server.rb index 96737341..815aaa10 100644 --- a/lib/falcon/service/server.rb +++ b/lib/falcon/service/server.rb @@ -12,6 +12,12 @@ module Falcon module Service class Server < Async::Service::Generic module Environment + # The service class to use for the proxy. + # @returns [Class] + def service_class + ::Falcon::Service::Server + end + # Options to use when creating the container. def container_options {restart: true} @@ -23,15 +29,11 @@ def url end # The upstream endpoint that will handle incoming requests. - # @attribute [Async::HTTP::Endpoint] + # @returns [Async::HTTP::Endpoint] def endpoint - ::Async::HTTP::Endpoint.parse(url) - end - - # The service class to use for the proxy. - # @attribute [Class] - def service_class - ::Falcon::Service::Server + ::Async::HTTP::Endpoint.parse(url).with( + reuse_address: true, + ) end def verbose @@ -93,7 +95,7 @@ def start def setup(container) container_options = @evaluator.container_options - container.run(name: self.name, restart: true, **container_options) do |instance| + container.run(name: self.name, **container_options) do |instance| evaluator = @environment.evaluator Async do |task| From 1a7d220f09508de96e256027ba7323fc5a562194 Mon Sep 17 00:00:00 2001 From: Samuel Williams Date: Mon, 18 Mar 2024 20:43:38 +1300 Subject: [PATCH 06/21] Less noisy. --- config/sus.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/config/sus.rb b/config/sus.rb index 150879a3..f9a5971c 100644 --- a/config/sus.rb +++ b/config/sus.rb @@ -7,4 +7,4 @@ include Covered::Sus require 'openssl' -$stderr.puts "OpenSSL::OPENSSL_LIBRARY_VERSION: #{OpenSSL::OPENSSL_LIBRARY_VERSION}" +# $stderr.puts "OpenSSL::OPENSSL_LIBRARY_VERSION: #{OpenSSL::OPENSSL_LIBRARY_VERSION}" From 10c24f3da8aa4a4851e4ee221db27e2304472b2d Mon Sep 17 00:00:00 2001 From: Samuel Williams Date: Mon, 18 Mar 2024 20:43:49 +1300 Subject: [PATCH 07/21] Fix more tests. --- lib/falcon/service/proxy.rb | 2 +- lib/falcon/service/virtual.rb | 18 ------------------ test/falcon/configuration.rb | 22 ++++++++++++++++++++-- test/falcon/middleware/proxy.rb | 5 ++--- 4 files changed, 23 insertions(+), 24 deletions(-) diff --git a/lib/falcon/service/proxy.rb b/lib/falcon/service/proxy.rb index 90121ea5..0ed98218 100644 --- a/lib/falcon/service/proxy.rb +++ b/lib/falcon/service/proxy.rb @@ -3,7 +3,7 @@ # Released under the MIT License. # Copyright, 2020-2023, by Samuel Williams. -require 'async/service/generic' +require_relative 'server' require 'async/http/endpoint' require 'async/io/shared_endpoint' diff --git a/lib/falcon/service/virtual.rb b/lib/falcon/service/virtual.rb index fa948243..52a4ee64 100644 --- a/lib/falcon/service/virtual.rb +++ b/lib/falcon/service/virtual.rb @@ -59,24 +59,6 @@ def timeout # end end - def redirect_service - hosts = {} - - @evaluator.configuration.services do |service| - if service.is_a?(Service::Server) - hosts[service.authority] = service - end - end - - Async::Service::Environment.new(Falcon::Service::Redirect).with( - hosts: hosts, - ) - end - - def proxy_service - - end - def self.included(target) target.include(Environnment) end diff --git a/test/falcon/configuration.rb b/test/falcon/configuration.rb index 2a493249..5a6204d7 100644 --- a/test/falcon/configuration.rb +++ b/test/falcon/configuration.rb @@ -12,12 +12,30 @@ it "can configurure proxy" do configuration.load_file(File.expand_path(".configuration/proxy.rb", __dir__)) - expect(configuration.environments).to be(:include?, 'localhost') + expect(configuration.environments).to be(:any?) + + environment = configuration.environments.first + evaluator = environment.evaluator + + expect(evaluator).to have_attributes( + service_class: be == Falcon::Service::Proxy, + authority: be == "localhost", + url: be == "https://www.google.com" + ) end it "can configure rack" do configuration.load_file(File.expand_path(".configuration/rack.rb", __dir__)) - expect(configuration.environments).to be(:include?, 'localhost') + expect(configuration.environments).to be(:any?) + + environment = configuration.environments.first + evaluator = environment.evaluator + + expect(evaluator).to have_attributes( + service_class: be == Falcon::Service::Server, + authority: be == "localhost", + count: be == 3 + ) end end diff --git a/test/falcon/middleware/proxy.rb b/test/falcon/middleware/proxy.rb index 48831b9a..50ec17b0 100644 --- a/test/falcon/middleware/proxy.rb +++ b/test/falcon/middleware/proxy.rb @@ -9,14 +9,13 @@ require 'sus/fixtures/async' require 'async/http/client' require 'async/http/endpoint' +require 'async/service/environment' describe Falcon::Middleware::Proxy do include Sus::Fixtures::Async::ReactorContext def proxy_for(**options) - Falcon::Service::Proxy.new( - Async::Service::Environment.build(**options) - ) + Async::Service::Environment.build(**options).evaluator end let(:proxy) do From 156e9b701bc0cb78a2371804b4f11b62e82afa26 Mon Sep 17 00:00:00 2001 From: Samuel Williams Date: Tue, 19 Mar 2024 00:50:57 +1300 Subject: [PATCH 08/21] Fix tests (but not passing). --- lib/falcon/command/serve.rb | 13 ++++++++----- lib/falcon/command/virtual.rb | 15 +++++++++------ lib/falcon/environments/application.rb | 12 ++++++------ lib/falcon/service/proxy.rb | 2 +- lib/falcon/service/server.rb | 2 +- lib/falcon/service/virtual.rb | 6 ++++++ test/falcon/command/serve.rb | 3 ++- test/falcon/command/top.rb | 6 +++--- test/falcon/command/virtual.rb | 4 ++-- 9 files changed, 38 insertions(+), 25 deletions(-) diff --git a/lib/falcon/command/serve.rb b/lib/falcon/command/serve.rb index d8a59595..2c71ffdb 100644 --- a/lib/falcon/command/serve.rb +++ b/lib/falcon/command/serve.rb @@ -50,7 +50,7 @@ def endpoint_options @options.slice(:hostname, :port, :timeout) end - def service + def environment Async::Service::Environment.new(Falcon::Service::Rackup::Environment).with( root: Dir.pwd, @@ -70,6 +70,12 @@ def service ) end + def configuration + Configuration.new.tap do |configuration| + configuration.add(self.environment) + end + end + # The container class to use. def container_class case @options[:container] @@ -106,10 +112,7 @@ def call buffer.puts "- To reload configuration: kill -HUP #{Process.pid}" end - configuration = Async::Service::Configuration.new - configuration.add(self.service) - - Async::Service::Controller.run(configuration, container_class: self.container_class) + Async::Service::Controller.run(self.configuration, container_class: self.container_class) end end end diff --git a/lib/falcon/command/virtual.rb b/lib/falcon/command/virtual.rb index 9ee3a312..028852d1 100644 --- a/lib/falcon/command/virtual.rb +++ b/lib/falcon/command/virtual.rb @@ -32,8 +32,8 @@ class Virtual < Samovar::Command include Paths - def service - Async::Service::Environment.new(Falcon::Service::Virtual).with( + def environment + Async::Service::Environment.new(Falcon::Service::Virtual::Environment).with( verbose: self.parent&.verbose?, configuration_paths: self.paths, bind_insecure: @options[:bind_insecure], @@ -42,6 +42,12 @@ def service ) end + def configuration + super.tap do |configuration| + configuration.add(self.environment) + end + end + # Prepare the environment and run the controller. def call Console.logger.info(self) do |buffer| @@ -50,10 +56,7 @@ def call buffer.puts "- To reload all sites: kill -HUP #{Process.pid}" end - configuration = self.configuration - configuration.add(self.service) - - Async::Service::Controller.run(configuration) + Async::Service::Controller.run(self.configuration) end end end diff --git a/lib/falcon/environments/application.rb b/lib/falcon/environments/application.rb index 7a5a3de3..2eba2974 100644 --- a/lib/falcon/environments/application.rb +++ b/lib/falcon/environments/application.rb @@ -14,6 +14,12 @@ module Falcon module Environments # A general application environment. Suitable for use with any {Protocol::HTTP::Middleware}. module Application + # The service class to use for the application. + # @returns [Class] + def service_class + ::Falcon::Service::Application + end + # The middleware stack for the application. # @returns [Protocol::HTTP::Middleware] def middleware @@ -51,12 +57,6 @@ def endpoint ) end - # The service class to use for the application. - # @returns [Class] - def service_class - ::Falcon::Service::Application - end - # Number of instances to start. # @returns [Integer | nil] def count diff --git a/lib/falcon/service/proxy.rb b/lib/falcon/service/proxy.rb index 0ed98218..b5792762 100644 --- a/lib/falcon/service/proxy.rb +++ b/lib/falcon/service/proxy.rb @@ -17,7 +17,7 @@ module Environment # The service class to use for the proxy. # @attribute [Class] def service_class - ::Falcon::Service::Proxy + Proxy end # The host that this proxy will receive connections for. diff --git a/lib/falcon/service/server.rb b/lib/falcon/service/server.rb index 815aaa10..91c8b35f 100644 --- a/lib/falcon/service/server.rb +++ b/lib/falcon/service/server.rb @@ -15,7 +15,7 @@ module Environment # The service class to use for the proxy. # @returns [Class] def service_class - ::Falcon::Service::Server + Server end # Options to use when creating the container. diff --git a/lib/falcon/service/virtual.rb b/lib/falcon/service/virtual.rb index 52a4ee64..08e21fc4 100644 --- a/lib/falcon/service/virtual.rb +++ b/lib/falcon/service/virtual.rb @@ -13,6 +13,12 @@ module Service # A virtual host is an application bound to a specific authority (essentially a hostname). The virtual controller manages multiple hosts and allows a single server to host multiple applications easily. class Virtual < Async::Service::Generic module Environment + # The service class to use for the virtual host. + # @returns [Class] + def service_class + Virtual + end + # All the falcon application configuration paths. # @returns [Array(String)] Paths to the falcon application configuration files. def configuration_paths diff --git a/test/falcon/command/serve.rb b/test/falcon/command/serve.rb index 7f6e38c4..cdc098cc 100644 --- a/test/falcon/command/serve.rb +++ b/test/falcon/command/serve.rb @@ -15,7 +15,8 @@ end it "can listen on specified port" do - controller = command.controller + configuration = command.configuration + controller = Async::Service::Controller.new(configuration.services.to_a) controller.start diff --git a/test/falcon/command/top.rb b/test/falcon/command/top.rb index 4c0ef721..d7de5ba5 100644 --- a/test/falcon/command/top.rb +++ b/test/falcon/command/top.rb @@ -16,8 +16,8 @@ ] serve = top.command - container = serve.controller - container.start + controller = Async::Service::Controller.new(serve.configuration) + controller.start Async do client = serve.client @@ -29,7 +29,7 @@ client.close end - container.stop + controller.stop end end end diff --git a/test/falcon/command/virtual.rb b/test/falcon/command/virtual.rb index 226e10c9..bb5eb5eb 100644 --- a/test/falcon/command/virtual.rb +++ b/test/falcon/command/virtual.rb @@ -28,8 +28,8 @@ } def around - # Wait for the container to start... - controller = command.controller + configuration = command.configuration + controller = Async::Service::Controller.new(configuration.services.to_a) controller.start From 64c95b81b3ffe72f1c85290c5781e5c7f9ae51fd Mon Sep 17 00:00:00 2001 From: Samuel Williams Date: Tue, 19 Mar 2024 21:06:48 +1300 Subject: [PATCH 09/21] Fix virtual. --- lib/falcon/command/top.rb | 4 ++++ lib/falcon/command/virtual.rb | 4 +--- lib/falcon/service/proxy.rb | 2 ++ lib/falcon/service/server.rb | 5 +++++ lib/falcon/service/virtual.rb | 16 ++++++++-------- 5 files changed, 20 insertions(+), 11 deletions(-) diff --git a/lib/falcon/command/top.rb b/lib/falcon/command/top.rb index 3d6d491d..75edb1a0 100644 --- a/lib/falcon/command/top.rb +++ b/lib/falcon/command/top.rb @@ -6,6 +6,8 @@ require_relative 'serve' require_relative 'host' require_relative 'virtual' +require_relative 'proxy' +require_relative 'redirect' require_relative 'supervisor' require_relative '../version' @@ -34,6 +36,8 @@ class Top < Samovar::Command 'serve' => Serve, 'host' => Host, 'virtual' => Virtual, + 'proxy' => Proxy, + 'redirect' => Redirect, 'supervisor' => Supervisor, }, default: 'serve' diff --git a/lib/falcon/command/virtual.rb b/lib/falcon/command/virtual.rb index 028852d1..d1805403 100644 --- a/lib/falcon/command/virtual.rb +++ b/lib/falcon/command/virtual.rb @@ -30,8 +30,6 @@ class Virtual < Samovar::Command # @attribute [Array(String)] many :paths - include Paths - def environment Async::Service::Environment.new(Falcon::Service::Virtual::Environment).with( verbose: self.parent&.verbose?, @@ -43,7 +41,7 @@ def environment end def configuration - super.tap do |configuration| + Async::Service::Configuration.new.tap do |configuration| configuration.add(self.environment) end end diff --git a/lib/falcon/service/proxy.rb b/lib/falcon/service/proxy.rb index b5792762..9b4b05fc 100644 --- a/lib/falcon/service/proxy.rb +++ b/lib/falcon/service/proxy.rb @@ -4,6 +4,8 @@ # Copyright, 2020-2023, by Samuel Williams. require_relative 'server' +require_relative '../tls' +require_relative '../middleware/proxy' require 'async/http/endpoint' require 'async/io/shared_endpoint' diff --git a/lib/falcon/service/server.rb b/lib/falcon/service/server.rb index 91c8b35f..db41a5c1 100644 --- a/lib/falcon/service/server.rb +++ b/lib/falcon/service/server.rb @@ -47,6 +47,11 @@ def cache def client_endpoint ::Async::HTTP::Endpoint.parse(url) end + + # Any scripts to preload before starting the server. + def preload + [] + end end def self.included(target) diff --git a/lib/falcon/service/virtual.rb b/lib/falcon/service/virtual.rb index 08e21fc4..7268b552 100644 --- a/lib/falcon/service/virtual.rb +++ b/lib/falcon/service/virtual.rb @@ -22,7 +22,7 @@ def service_class # All the falcon application configuration paths. # @returns [Array(String)] Paths to the falcon application configuration files. def configuration_paths - File.glob("/srv/http/*/falcon.rb") + ["/srv/http/*/falcon.rb"] end def configuration @@ -128,18 +128,18 @@ def setup(container) container.spawn(name: "Falcon Redirector", restart: true, key: :redirect) do |instance| instance.exec(falcon_path, "redirect", - "--bind", @command.bind_insecure, - "--timeout", @command.timeout.to_s, - "--redirect", @command.bind_secure, - *@command.paths, ready: false + "--bind", evaluator.bind_insecure, + "--timeout", evaluator.timeout.to_s, + "--redirect", evaluator.bind_secure, + *evaluator.configuration_paths, ready: false ) end container.spawn(name: "Falcon Proxy", restart: true, key: :proxy) do |instance| instance.exec(falcon_path, "proxy", - "--bind", @command.bind_secure, - "--timeout", @command.timeout.to_s, - *@command.paths, ready: false + "--bind", evaluator.bind_secure, + "--timeout", evaluator.timeout.to_s, + *evaluator.configuration_paths, ready: false ) end end From fc330ff96bb6ec473aa032eab483cbcabd0f6a77 Mon Sep 17 00:00:00 2001 From: Samuel Williams Date: Thu, 21 Mar 2024 22:59:43 +1300 Subject: [PATCH 10/21] Restore commands. --- lib/falcon/command/proxy.rb | 73 ++++++++++++++++++++++++++++++++ lib/falcon/command/redirect.rb | 76 ++++++++++++++++++++++++++++++++++ lib/falcon/command/serve.rb | 4 +- test/falcon/command/top.rb | 2 +- 4 files changed, 152 insertions(+), 3 deletions(-) create mode 100644 lib/falcon/command/proxy.rb create mode 100644 lib/falcon/command/redirect.rb diff --git a/lib/falcon/command/proxy.rb b/lib/falcon/command/proxy.rb new file mode 100644 index 00000000..749c9919 --- /dev/null +++ b/lib/falcon/command/proxy.rb @@ -0,0 +1,73 @@ +# frozen_string_literal: true + +# Released under the MIT License. +# Copyright, 2020-2023, by Samuel Williams. + +require_relative '../service/proxy' +require_relative 'paths' + +require 'samovar' + +module Falcon + module Command + # Implements the `falcon proxy` command. + # + # Manages a {Controller::Proxy} instance which is responsible for proxing incoming requests. + class Proxy < Samovar::Command + self.description = "Proxy to one or more backend hosts." + + # The command line options. + # @attribute [Samovar::Options] + options do + option '--bind
', "Bind to the given hostname/address", default: "https://[::]:443" + + option '-t/--timeout ', "Specify the maximum time to wait for non-blocking operations.", type: Float, default: nil + end + + # One or more paths to the configuration files. + # @name paths + # @attribute [Array(String)] + many :paths + + include Paths + + def environment + Async::Service::Environment.new(Falcon::Service::Proxy::Environment).with( + root: Dir.pwd, + verbose: self.parent&.verbose?, + name: "proxy", + + url: @options[:bind], + ) + end + + def configuration + Configuration.new.tap do |configuration| + configuration.add(self.environment) + end + end + + # The container class to use. + def container_class + Async::Container.best_container_class + end + + # Prepare the environment and run the controller. + def call + Console.logger.info(self) do |buffer| + buffer.puts "Falcon Proxy v#{VERSION} taking flight!" + buffer.puts "- Binding to: #{@options[:bind]}" + buffer.puts "- To terminate: Ctrl-C or kill #{Process.pid}" + buffer.puts "- To reload: kill -HUP #{Process.pid}" + end + + Async::Service::Controller.run(self.configuration, container_class: self.container_class) + end + + # The endpoint to bind to. + def endpoint(**options) + Async::HTTP::Endpoint.parse(@options[:bind], timeout: @options[:timeout], **options) + end + end + end +end diff --git a/lib/falcon/command/redirect.rb b/lib/falcon/command/redirect.rb new file mode 100644 index 00000000..5047671a --- /dev/null +++ b/lib/falcon/command/redirect.rb @@ -0,0 +1,76 @@ +# frozen_string_literal: true + +# Released under the MIT License. +# Copyright, 2020-2023, by Samuel Williams. + +require_relative '../service/redirect' +require_relative 'paths' + +require 'samovar' + +module Falcon + module Command + class Redirect < Samovar::Command + self.description = "Redirect from insecure HTTP to secure HTTP." + + # The command line options. + # @attribute [Samovar::Options] + options do + option '--bind
', "Bind to the given hostname/address", default: "http://[::]:80" + option '--redirect
', "Redirect using this address as a template.", default: "https://[::]:443" + + option '-t/--timeout ', "Specify the maximum time to wait for non-blocking operations.", type: Float, default: nil + end + + # One or more paths to the configuration files. + # @name paths + # @attribute [Array(String)] + many :paths + + include Paths + + def environment + Async::Service::Environment.new(Falcon::Service::Proxy::Environment).with( + root: Dir.pwd, + verbose: self.parent&.verbose?, + name: "proxy", + + url: @options[:bind], + ) + end + + def configuration + Configuration.new.tap do |configuration| + configuration.add(self.environment) + end + end + + # The container class to use. + def container_class + Async::Container.best_container_class + end + + # Prepare the environment and run the controller. + def call + Console.logger.info(self) do |buffer| + buffer.puts "Falcon Redirect v#{VERSION} taking flight!" + buffer.puts "- Binding to: #{@options[:bind]}" + buffer.puts "- To terminate: Ctrl-C or kill #{Process.pid}" + buffer.puts "- To reload: kill -HUP #{Process.pid}" + end + + Async::Service::Controller.run(self.configuration, container_class: self.container_class) + end + + # The endpoint to bind to. + def endpoint(**options) + Async::HTTP::Endpoint.parse(@options[:bind], timeout: @options[:timeout], **options) + end + + # The template endpoint to redirect to. + def redirect_endpoint(**options) + Async::HTTP::Endpoint.parse(@options[:redirect], **options) + end + end + end +end diff --git a/lib/falcon/command/serve.rb b/lib/falcon/command/serve.rb index 2c71ffdb..4918d0c6 100644 --- a/lib/falcon/command/serve.rb +++ b/lib/falcon/command/serve.rb @@ -62,11 +62,11 @@ def environment rackup_path: @options[:config], preload: [@options[:preload]].compact, - bind: @options[:bind], + url: @options[:bind], name: "server", - endpoint: ->{Endpoint.parse(bind, **endpoint_options)} + endpoint: ->{Endpoint.parse(url, **endpoint_options)} ) end diff --git a/test/falcon/command/top.rb b/test/falcon/command/top.rb index d7de5ba5..2127c971 100644 --- a/test/falcon/command/top.rb +++ b/test/falcon/command/top.rb @@ -16,7 +16,7 @@ ] serve = top.command - controller = Async::Service::Controller.new(serve.configuration) + controller = Async::Service::Controller.new(serve.configuration.services.to_a) controller.start Async do From ccb42eff2c3824dc3504132e6e2b01c994df5279 Mon Sep 17 00:00:00 2001 From: Samuel Williams Date: Fri, 22 Mar 2024 21:31:58 +1300 Subject: [PATCH 11/21] WIP. --- falcon.gemspec | 2 +- lib/falcon/command/serve.rb | 7 ++- lib/falcon/environments/application.rb | 73 -------------------------- lib/falcon/environments/proxy.rb | 28 ---------- lib/falcon/environments/rack.rb | 21 ++++++-- lib/falcon/service/application.rb | 55 ++++++++++++++++++- lib/falcon/service/rackup.rb | 29 ---------- test/falcon/command/serve.rb | 2 +- test/falcon/command/top.rb | 2 +- test/falcon/command/virtual.rb | 2 +- 10 files changed, 81 insertions(+), 140 deletions(-) delete mode 100644 lib/falcon/environments/application.rb delete mode 100644 lib/falcon/environments/proxy.rb delete mode 100644 lib/falcon/service/rackup.rb diff --git a/falcon.gemspec b/falcon.gemspec index 3ca421fa..a7664375 100644 --- a/falcon.gemspec +++ b/falcon.gemspec @@ -30,7 +30,7 @@ Gem::Specification.new do |spec| spec.add_dependency "async-http", "~> 0.57" spec.add_dependency "async-http-cache", "~> 0.4.0" spec.add_dependency "async-io", "~> 1.22" - spec.add_dependency "async-service", "~> 0.7.0" + spec.add_dependency "async-service", "~> 0.8.0" spec.add_dependency "bundler" spec.add_dependency "localhost", "~> 1.1" spec.add_dependency "openssl", "~> 3.0" diff --git a/lib/falcon/command/serve.rb b/lib/falcon/command/serve.rb index 4918d0c6..05324275 100644 --- a/lib/falcon/command/serve.rb +++ b/lib/falcon/command/serve.rb @@ -6,7 +6,8 @@ require_relative '../server' require_relative '../endpoint' -require_relative '../service/rackup' +require_relative '../service/server' +require_relative '../environment/rackup' require 'async/container' require 'async/http/client' @@ -51,7 +52,9 @@ def endpoint_options end def environment - Async::Service::Environment.new(Falcon::Service::Rackup::Environment).with( + Async::Service::Environment.new(Falcon::Service::Server::Environment).with( + Falcon::Environment::Rackup, + root: Dir.pwd, verbose: self.parent&.verbose?, diff --git a/lib/falcon/environments/application.rb b/lib/falcon/environments/application.rb deleted file mode 100644 index 2eba2974..00000000 --- a/lib/falcon/environments/application.rb +++ /dev/null @@ -1,73 +0,0 @@ -# frozen_string_literal: true - -# Released under the MIT License. -# Copyright, 2019-2023, by Samuel Williams. -# Copyright, 2020, by Daniel Evans. - -require_relative '../proxy_endpoint' -require_relative '../server' - -require_relative '../service/application' -require_relative '../environments' - -module Falcon - module Environments - # A general application environment. Suitable for use with any {Protocol::HTTP::Middleware}. - module Application - # The service class to use for the application. - # @returns [Class] - def service_class - ::Falcon::Service::Application - end - - # The middleware stack for the application. - # @returns [Protocol::HTTP::Middleware] - def middleware - ::Protocol::HTTP::Middleware::HelloWorld - end - - # The scheme to use to communicate with the application. - # @returns [String] - def scheme - 'https' - end - - # The protocol to use to communicate with the application. - # - # Typically one of {Async::HTTP::Protocol::HTTP1} or {Async::HTTP::Protocl::HTTP2}. - # - # @returns [Async::HTTP::Protocol] - def protocol - Async::HTTP::Protocol::HTTP2 - end - - # The IPC path to use for communication with the application. - # @returns [String] - def ipc_path - ::File.expand_path("application.ipc", root) - end - - # The endpoint that will be used for communicating with the application server. - # @returns [Async::IO::Endpoint] - def endpoint - ::Falcon::ProxyEndpoint.unix(ipc_path, - protocol: protocol, - scheme: scheme, - authority: authority - ) - end - - # Number of instances to start. - # @returns [Integer | nil] - def count - nil - end - - def preload - [] - end - end - - LEGACY_ENVIRONMENTS[:application] = Application - end -end diff --git a/lib/falcon/environments/proxy.rb b/lib/falcon/environments/proxy.rb deleted file mode 100644 index 323f244c..00000000 --- a/lib/falcon/environments/proxy.rb +++ /dev/null @@ -1,28 +0,0 @@ -# frozen_string_literal: true - -# Released under the MIT License. -# Copyright, 2019-2023, by Samuel Williams. - -require_relative '../service/proxy' -require_relative '../environments' - -module Falcon - module Environments - # A HTTP proxy environment. - module Proxy - # The upstream endpoint that will handle incoming requests. - # @attribute [Async::HTTP::Endpoint] - def endpoint - ::Async::HTTP::Endpoint.parse(url) - end - - # The service class to use for the proxy. - # @attribute [Class] - def service_class - ::Falcon::Service::Proxy - end - end - - LEGACY_ENVIRONMENTS[:proxy] = Proxy - end -end diff --git a/lib/falcon/environments/rack.rb b/lib/falcon/environments/rack.rb index 5ba855f4..18ceb960 100644 --- a/lib/falcon/environments/rack.rb +++ b/lib/falcon/environments/rack.rb @@ -3,11 +3,26 @@ # Released under the MIT License. # Copyright, 2019-2023, by Samuel Williams. -require_relative '../service/rackup' +require 'rack/builder' +require_relative '../server' require_relative '../environments' module Falcon - module Environments - LEGACY_ENVIRONMENTS[:rack] = Service::Rackup::Environment + module Environment + module Rack + def rackup_path + 'config.ru' + end + + def rack_app + ::Rack::Builder.parse_file(rackup_path) + end + + def middleware + ::Falcon::Server.middleware(rack_app, verbose: verbose, cache: cache) + end + end + + LEGACY_ENVIRONMENTS[:rack] = Rack end end diff --git a/lib/falcon/service/application.rb b/lib/falcon/service/application.rb index c14aa24a..6e607a52 100644 --- a/lib/falcon/service/application.rb +++ b/lib/falcon/service/application.rb @@ -13,7 +13,60 @@ module Falcon module Service # Implements an application server using an internal clear-text proxy. class Application < Server - def initialize(environment) + module Environment + include Server::Environment + + # The service class to use for the application. + # @returns [Class] + def service_class + ::Falcon::Service::Application + end + + # The middleware stack for the application. + # @returns [Protocol::HTTP::Middleware] + def middleware + ::Protocol::HTTP::Middleware::HelloWorld + end + + # The scheme to use to communicate with the application. + # @returns [String] + def scheme + 'https' + end + + # The protocol to use to communicate with the application. + # + # Typically one of {Async::HTTP::Protocol::HTTP1} or {Async::HTTP::Protocl::HTTP2}. + # + # @returns [Async::HTTP::Protocol] + def protocol + Async::HTTP::Protocol::HTTP2 + end + + # The IPC path to use for communication with the application. + # @returns [String] + def ipc_path + ::File.expand_path("application.ipc", root) + end + + # The endpoint that will be used for communicating with the application server. + # @returns [Async::IO::Endpoint] + def endpoint + ::Falcon::ProxyEndpoint.unix(ipc_path, + protocol: protocol, + scheme: scheme, + authority: authority + ) + end + + # Number of instances to start. + # @returns [Integer | nil] + def count + nil + end + end + + def initialize(...) super @bound_endpoint = nil diff --git a/lib/falcon/service/rackup.rb b/lib/falcon/service/rackup.rb deleted file mode 100644 index cabced84..00000000 --- a/lib/falcon/service/rackup.rb +++ /dev/null @@ -1,29 +0,0 @@ -# frozen_string_literal: true - -# Released under the MIT License. -# Copyright, 2024, by Samuel Williams. - -require_relative 'server' - -module Falcon - module Service - # A controller for redirecting requests. - class Rackup < Server - module Environment - include Server::Environment - - def rackup_path - 'config.ru' - end - - def rack_app - Rack::Builder.parse_file(rackup_path) - end - - def middleware - Falcon::Server.middleware(rack_app, verbose: verbose, cache: cache) - end - end - end - end -end diff --git a/test/falcon/command/serve.rb b/test/falcon/command/serve.rb index cdc098cc..26052d2f 100644 --- a/test/falcon/command/serve.rb +++ b/test/falcon/command/serve.rb @@ -16,7 +16,7 @@ it "can listen on specified port" do configuration = command.configuration - controller = Async::Service::Controller.new(configuration.services.to_a) + controller = configuration.controller controller.start diff --git a/test/falcon/command/top.rb b/test/falcon/command/top.rb index 2127c971..0bdfe584 100644 --- a/test/falcon/command/top.rb +++ b/test/falcon/command/top.rb @@ -16,7 +16,7 @@ ] serve = top.command - controller = Async::Service::Controller.new(serve.configuration.services.to_a) + controller = serve.configuration.controller controller.start Async do diff --git a/test/falcon/command/virtual.rb b/test/falcon/command/virtual.rb index bb5eb5eb..06ba80dc 100644 --- a/test/falcon/command/virtual.rb +++ b/test/falcon/command/virtual.rb @@ -29,7 +29,7 @@ def around configuration = command.configuration - controller = Async::Service::Controller.new(configuration.services.to_a) + controller = configuration.controller controller.start From fba1a140cd1a4884357616a790cb0591262b8635 Mon Sep 17 00:00:00 2001 From: Samuel Williams Date: Sat, 23 Mar 2024 00:22:49 +1300 Subject: [PATCH 12/21] WIP. --- falcon.gemspec | 2 +- lib/falcon/command/serve.rb | 5 +++-- lib/falcon/command/virtual.rb | 2 +- lib/falcon/environments/rack.rb | 20 +++++--------------- lib/falcon/environments/rackup.rb | 26 ++++++++++++++++++++++++++ lib/falcon/service/application.rb | 20 +++++--------------- lib/falcon/service/server.rb | 7 ++----- 7 files changed, 43 insertions(+), 39 deletions(-) create mode 100644 lib/falcon/environments/rackup.rb diff --git a/falcon.gemspec b/falcon.gemspec index a7664375..1506b410 100644 --- a/falcon.gemspec +++ b/falcon.gemspec @@ -30,7 +30,7 @@ Gem::Specification.new do |spec| spec.add_dependency "async-http", "~> 0.57" spec.add_dependency "async-http-cache", "~> 0.4.0" spec.add_dependency "async-io", "~> 1.22" - spec.add_dependency "async-service", "~> 0.8.0" + spec.add_dependency "async-service", "~> 0.9.0" spec.add_dependency "bundler" spec.add_dependency "localhost", "~> 1.1" spec.add_dependency "openssl", "~> 3.0" diff --git a/lib/falcon/command/serve.rb b/lib/falcon/command/serve.rb index 05324275..33f372a2 100644 --- a/lib/falcon/command/serve.rb +++ b/lib/falcon/command/serve.rb @@ -6,8 +6,9 @@ require_relative '../server' require_relative '../endpoint' +require_relative '../configuration' require_relative '../service/server' -require_relative '../environment/rackup' +require_relative '../environments/rackup' require 'async/container' require 'async/http/client' @@ -53,7 +54,7 @@ def endpoint_options def environment Async::Service::Environment.new(Falcon::Service::Server::Environment).with( - Falcon::Environment::Rackup, + Falcon::Environments::Rackup, root: Dir.pwd, diff --git a/lib/falcon/command/virtual.rb b/lib/falcon/command/virtual.rb index d1805403..3d56a25f 100644 --- a/lib/falcon/command/virtual.rb +++ b/lib/falcon/command/virtual.rb @@ -36,7 +36,7 @@ def environment configuration_paths: self.paths, bind_insecure: @options[:bind_insecure], bind_secure: @options[:bind_secure], - timeout: @options[:timeout] + timeout: @options[:timeout], ) end diff --git a/lib/falcon/environments/rack.rb b/lib/falcon/environments/rack.rb index 18ceb960..f682ee12 100644 --- a/lib/falcon/environments/rack.rb +++ b/lib/falcon/environments/rack.rb @@ -3,24 +3,14 @@ # Released under the MIT License. # Copyright, 2019-2023, by Samuel Williams. -require 'rack/builder' -require_relative '../server' -require_relative '../environments' +require_relative '../service/application' +require_relative 'rackup' module Falcon - module Environment + module Environments module Rack - def rackup_path - 'config.ru' - end - - def rack_app - ::Rack::Builder.parse_file(rackup_path) - end - - def middleware - ::Falcon::Server.middleware(rack_app, verbose: verbose, cache: cache) - end + include Service::Application::Environment + include Rackup end LEGACY_ENVIRONMENTS[:rack] = Rack diff --git a/lib/falcon/environments/rackup.rb b/lib/falcon/environments/rackup.rb new file mode 100644 index 00000000..dcfe5974 --- /dev/null +++ b/lib/falcon/environments/rackup.rb @@ -0,0 +1,26 @@ +# frozen_string_literal: true + +# Released under the MIT License. +# Copyright, 2019-2023, by Samuel Williams. + +require 'rack/builder' +require_relative '../server' +require_relative '../environments' + +module Falcon + module Environments + module Rackup + def rackup_path + 'config.ru' + end + + def rack_app + ::Rack::Builder.parse_file(rackup_path) + end + + def middleware + ::Falcon::Server.middleware(rack_app, verbose: verbose, cache: cache) + end + end + end +end diff --git a/lib/falcon/service/application.rb b/lib/falcon/service/application.rb index 6e607a52..2af574c5 100644 --- a/lib/falcon/service/application.rb +++ b/lib/falcon/service/application.rb @@ -5,6 +5,7 @@ # Copyright, 2020, by Daniel Evans. require_relative 'proxy' +require_relative '../proxy_endpoint' require 'async/http/endpoint' require 'async/io/shared_endpoint' @@ -72,26 +73,15 @@ def initialize(...) @bound_endpoint = nil end - # The middleware that will be served by this application. - # @returns [Protocol::HTTP::Middleware] - def middleware - # In a multi-threaded container, we don't want to modify the shared evaluator's cache, so we create a new evaluator: - @environment.evaluator.middleware - end - - # Number of instances to start. - # @returns [Integer | nil] - def count - @environment.evaluator.count - end - # Prepare the bound endpoint for the application instances. # Invoke {preload!} to load shared resources into the parent process. def start - Console.logger.info(self) {"Binding to #{self.endpoint}..."} + endpoint = @evaluator.endpoint + + Console.logger.info(self) {"Binding to #{endpoint}..."} @bound_endpoint = Async::Reactor.run do - Async::IO::SharedEndpoint.bound(self.endpoint) + Async::IO::SharedEndpoint.bound(endpoint) end.wait preload! diff --git a/lib/falcon/service/server.rb b/lib/falcon/service/server.rb index db41a5c1..5f2ea7c5 100644 --- a/lib/falcon/service/server.rb +++ b/lib/falcon/service/server.rb @@ -61,7 +61,6 @@ def self.included(target) def initialize(...) super - @endpoint = nil @bound_endpoint = nil end @@ -82,10 +81,10 @@ def preload! # Prepare the bound endpoint for the server. def start - @endpoint ||= @evaluator.endpoint + endpoint = @evaluator.endpoint Sync do - @bound_endpoint = @endpoint.bound + @bound_endpoint = endpoint.bound end preload! @@ -122,8 +121,6 @@ def stop(...) @bound_endpoint = nil end - @endpoint = nil - super end end From fc8e9cbffeb03c211c8d04f91c0f1740a674eabe Mon Sep 17 00:00:00 2001 From: Samuel Williams Date: Sat, 23 Mar 2024 12:36:36 +1300 Subject: [PATCH 13/21] Fix virtual tests. --- examples/hello/config.ru | 2 +- examples/hello/preload.rb | 2 +- lib/falcon/command/proxy.rb | 30 +++++++++++++--- lib/falcon/command/redirect.rb | 33 ++++++++++++++--- lib/falcon/command/virtual.rb | 20 +++++++++++ lib/falcon/service/application.rb | 60 ------------------------------- lib/falcon/service/proxy.rb | 10 ++++-- lib/falcon/service/redirect.rb | 6 ---- lib/falcon/service/server.rb | 17 +++++++-- lib/falcon/service/virtual.rb | 4 +++ test/falcon/command/virtual.rb | 2 +- 11 files changed, 103 insertions(+), 83 deletions(-) diff --git a/examples/hello/config.ru b/examples/hello/config.ru index a82a4bc6..8abcc1ec 100755 --- a/examples/hello/config.ru +++ b/examples/hello/config.ru @@ -3,7 +3,7 @@ require 'async' -Console.logger.debug! +# Console.logger.debug! class RequestLogger def initialize(app) diff --git a/examples/hello/preload.rb b/examples/hello/preload.rb index dda339a8..f1d7ce07 100644 --- a/examples/hello/preload.rb +++ b/examples/hello/preload.rb @@ -3,4 +3,4 @@ # Released under the MIT License. # Copyright, 2020-2023, by Samuel Williams. -$stderr.puts "Preloading..." +# $stderr.puts "Preloading..." diff --git a/lib/falcon/command/proxy.rb b/lib/falcon/command/proxy.rb index 749c9919..407c7f61 100644 --- a/lib/falcon/command/proxy.rb +++ b/lib/falcon/command/proxy.rb @@ -31,19 +31,37 @@ class Proxy < Samovar::Command include Paths - def environment + def environment(**options) Async::Service::Environment.new(Falcon::Service::Proxy::Environment).with( root: Dir.pwd, verbose: self.parent&.verbose?, - name: "proxy", - url: @options[:bind], + timeout: @options[:timeout], + **options ) end + def host_map(environments) + hosts = {} + + environments.each do |environment| + next unless environment.implements?(Falcon::Service::Application::Environment) + evaluator = environment.evaluator + hosts[evaluator.authority] = evaluator + end + + Console.info(self) {"Hosts: #{hosts}"} + + return hosts + end + def configuration + configuration = super + hosts = host_map(configuration.environments) + Configuration.new.tap do |configuration| - configuration.add(self.environment) + environment = self.environment(hosts: hosts) + configuration.add(environment) end end @@ -59,6 +77,10 @@ def call buffer.puts "- Binding to: #{@options[:bind]}" buffer.puts "- To terminate: Ctrl-C or kill #{Process.pid}" buffer.puts "- To reload: kill -HUP #{Process.pid}" + + self.resolved_paths.each do |path| + buffer.puts "- Loading configuration from #{path}" + end end Async::Service::Controller.run(self.configuration, container_class: self.container_class) diff --git a/lib/falcon/command/redirect.rb b/lib/falcon/command/redirect.rb index 5047671a..9bbded93 100644 --- a/lib/falcon/command/redirect.rb +++ b/lib/falcon/command/redirect.rb @@ -29,19 +29,38 @@ class Redirect < Samovar::Command include Paths - def environment - Async::Service::Environment.new(Falcon::Service::Proxy::Environment).with( + def environment(**options) + Async::Service::Environment.new(Falcon::Service::Redirect::Environment).with( root: Dir.pwd, verbose: self.parent&.verbose?, - name: "proxy", - url: @options[:bind], + redirect_url: @options[:redirect], + timeout: @options[:timeout], + **options ) end + def host_map(environments) + hosts = {} + + environments.each do |environment| + next unless environment.implements?(Falcon::Service::Application::Environment) + evaluator = environment.evaluator + hosts[evaluator.authority] = evaluator + end + + Console.info(self) {"Hosts: #{hosts}"} + + return hosts + end + def configuration + configuration = super + hosts = host_map(configuration.environments) + Configuration.new.tap do |configuration| - configuration.add(self.environment) + environment = self.environment(hosts: hosts) + configuration.add(environment) end end @@ -57,6 +76,10 @@ def call buffer.puts "- Binding to: #{@options[:bind]}" buffer.puts "- To terminate: Ctrl-C or kill #{Process.pid}" buffer.puts "- To reload: kill -HUP #{Process.pid}" + + self.resolved_paths.each do |path| + buffer.puts "- Loading configuration from #{path}" + end end Async::Service::Controller.run(self.configuration, container_class: self.container_class) diff --git a/lib/falcon/command/virtual.rb b/lib/falcon/command/virtual.rb index 3d56a25f..1f16d1c4 100644 --- a/lib/falcon/command/virtual.rb +++ b/lib/falcon/command/virtual.rb @@ -56,6 +56,26 @@ def call Async::Service::Controller.run(self.configuration) end + + # The insecure endpoint for connecting to the {Redirect} instance. + def insecure_endpoint(**options) + Async::HTTP::Endpoint.parse(@options[:bind_insecure], **options) + end + + # The secure endpoint for connecting to the {Proxy} instance. + def secure_endpoint(**options) + Async::HTTP::Endpoint.parse(@options[:bind_secure], **options) + end + + # An endpoint suitable for connecting to the specified hostname. + def host_endpoint(hostname, **options) + endpoint = secure_endpoint(**options) + + url = URI.parse(@options[:bind_secure]) + url.hostname = hostname + + return Async::HTTP::Endpoint.new(url, hostname: endpoint.hostname) + end end end end diff --git a/lib/falcon/service/application.rb b/lib/falcon/service/application.rb index 2af574c5..892b9e14 100644 --- a/lib/falcon/service/application.rb +++ b/lib/falcon/service/application.rb @@ -66,66 +66,6 @@ def count nil end end - - def initialize(...) - super - - @bound_endpoint = nil - end - - # Prepare the bound endpoint for the application instances. - # Invoke {preload!} to load shared resources into the parent process. - def start - endpoint = @evaluator.endpoint - - Console.logger.info(self) {"Binding to #{endpoint}..."} - - @bound_endpoint = Async::Reactor.run do - Async::IO::SharedEndpoint.bound(endpoint) - end.wait - - preload! - - super - end - - # Setup instances of the application into the container. - # @parameter container [Async::Container::Generic] - def setup(container) - protocol = self.protocol - scheme = self.scheme - - run_options = { - name: self.name, - restart: true, - } - - run_options[:count] = count unless count.nil? - - container.run(**run_options) do |instance| - Async do |task| - Console.logger.info(self) {"Starting application server for #{self.root}..."} - - server = Server.new(self.middleware, @bound_endpoint, protocol: protocol, scheme: scheme) - - server.run - - instance.ready! - - task.children.each(&:wait) - end - end - - super - end - - # Close the bound endpoint. - def stop - @bound_endpoint&.close - @bound_endpoint = nil - - super - end end end end diff --git a/lib/falcon/service/proxy.rb b/lib/falcon/service/proxy.rb index 9b4b05fc..8b1b56a1 100644 --- a/lib/falcon/service/proxy.rb +++ b/lib/falcon/service/proxy.rb @@ -22,6 +22,10 @@ def service_class Proxy end + def name + service_class.name + end + # The host that this proxy will receive connections for. def url "https://[::]:443" @@ -63,14 +67,16 @@ def hosts # @parameter socket [OpenSSL::SSL::SSLSocket] The incoming connection. # @parameter hostname [String] The negotiated hostname. def host_context(socket, hostname) - if host = self.hosts[hostname] + hosts = self.hosts + + if host = hosts[hostname] Console.logger.debug(self) {"Resolving #{hostname} -> #{host}"} socket.hostname = hostname return host.ssl_context else - Console.logger.warn(self) {"Unable to resolve #{hostname}!"} + Console.logger.warn(self, hosts: hosts.keys) {"Unable to resolve #{hostname}!"} return nil end diff --git a/lib/falcon/service/redirect.rb b/lib/falcon/service/redirect.rb index 0b631285..1a3f6167 100644 --- a/lib/falcon/service/redirect.rb +++ b/lib/falcon/service/redirect.rb @@ -33,12 +33,6 @@ def middleware Middleware::Redirect.new(Middleware::NotFound, hosts, redirect_endpoint) end end - - # services.each do |service| - # if service.is_a?(Service::Proxy) - # @hosts[service.authority] = service - # end - # end end end end diff --git a/lib/falcon/service/server.rb b/lib/falcon/service/server.rb index 5f2ea7c5..0b6079ae 100644 --- a/lib/falcon/service/server.rb +++ b/lib/falcon/service/server.rb @@ -18,6 +18,10 @@ def service_class Server end + def name + "#{service_class.name} (#{url})" + end + # Options to use when creating the container. def container_options {restart: true} @@ -28,11 +32,16 @@ def url "http://[::]:9292" end + def timeout + nil + end + # The upstream endpoint that will handle incoming requests. # @returns [Async::HTTP::Endpoint] def endpoint ::Async::HTTP::Endpoint.parse(url).with( reuse_address: true, + timeout: timeout, ) end @@ -81,15 +90,15 @@ def preload! # Prepare the bound endpoint for the server. def start - endpoint = @evaluator.endpoint + @endpoint = @evaluator.endpoint Sync do - @bound_endpoint = endpoint.bound + @bound_endpoint = @endpoint.bound end preload! - Console.logger.info(self) {"Starting #{name} on #{@endpoint.to_url}"} + Console.logger.info(self) {"Starting #{name} on #{@endpoint}"} super end @@ -121,6 +130,8 @@ def stop(...) @bound_endpoint = nil end + @endpoint = nil + super end end diff --git a/lib/falcon/service/virtual.rb b/lib/falcon/service/virtual.rb index 7268b552..2b499ccd 100644 --- a/lib/falcon/service/virtual.rb +++ b/lib/falcon/service/virtual.rb @@ -19,6 +19,10 @@ def service_class Virtual end + def name + service_class.name + end + # All the falcon application configuration paths. # @returns [Array(String)] Paths to the falcon application configuration files. def configuration_paths diff --git a/test/falcon/command/virtual.rb b/test/falcon/command/virtual.rb index 06ba80dc..9b5f8a0f 100644 --- a/test/falcon/command/virtual.rb +++ b/test/falcon/command/virtual.rb @@ -48,7 +48,7 @@ def around Async do response = insecure_client.call(request) - expect(response).to be_redirection + expect(response).to be(:redirection?) expect(response.headers['location']).to be == "https://hello.localhost:8443/index" response.close From cb05b41622b308c64307bc569e122dde6419b9ab Mon Sep 17 00:00:00 2001 From: Samuel Williams Date: Sat, 23 Mar 2024 14:09:21 +1300 Subject: [PATCH 14/21] Move (almost) everything back to `Falcon::Environments`. --- lib/falcon/command/proxy.rb | 6 +- lib/falcon/command/redirect.rb | 6 +- lib/falcon/command/serve.rb | 2 +- lib/falcon/environments/application.rb | 59 ++++++++++++ lib/falcon/environments/proxy.rb | 105 +++++++++++++++++++++ lib/falcon/environments/rack.rb | 4 +- lib/falcon/environments/redirect.rb | 33 +++++++ lib/falcon/environments/server.rb | 66 +++++++++++++ lib/falcon/service/application.rb | 71 -------------- lib/falcon/service/proxy.rb | 122 ------------------------- lib/falcon/service/redirect.rb | 38 -------- lib/falcon/service/server.rb | 56 ------------ test/falcon/configuration.rb | 2 +- test/falcon/middleware/proxy.rb | 1 - 14 files changed, 273 insertions(+), 298 deletions(-) create mode 100644 lib/falcon/environments/application.rb create mode 100644 lib/falcon/environments/proxy.rb create mode 100644 lib/falcon/environments/redirect.rb create mode 100644 lib/falcon/environments/server.rb delete mode 100644 lib/falcon/service/application.rb delete mode 100644 lib/falcon/service/proxy.rb delete mode 100644 lib/falcon/service/redirect.rb diff --git a/lib/falcon/command/proxy.rb b/lib/falcon/command/proxy.rb index 407c7f61..78cf23ef 100644 --- a/lib/falcon/command/proxy.rb +++ b/lib/falcon/command/proxy.rb @@ -3,7 +3,7 @@ # Released under the MIT License. # Copyright, 2020-2023, by Samuel Williams. -require_relative '../service/proxy' +require_relative '../environments/proxy' require_relative 'paths' require 'samovar' @@ -32,7 +32,7 @@ class Proxy < Samovar::Command include Paths def environment(**options) - Async::Service::Environment.new(Falcon::Service::Proxy::Environment).with( + Async::Service::Environment.new(Falcon::Environments::Proxy).with( root: Dir.pwd, verbose: self.parent&.verbose?, url: @options[:bind], @@ -45,7 +45,7 @@ def host_map(environments) hosts = {} environments.each do |environment| - next unless environment.implements?(Falcon::Service::Application::Environment) + next unless environment.implements?(Falcon::Environments::Application) evaluator = environment.evaluator hosts[evaluator.authority] = evaluator end diff --git a/lib/falcon/command/redirect.rb b/lib/falcon/command/redirect.rb index 9bbded93..74886671 100644 --- a/lib/falcon/command/redirect.rb +++ b/lib/falcon/command/redirect.rb @@ -3,7 +3,7 @@ # Released under the MIT License. # Copyright, 2020-2023, by Samuel Williams. -require_relative '../service/redirect' +require_relative '../environments/redirect' require_relative 'paths' require 'samovar' @@ -30,7 +30,7 @@ class Redirect < Samovar::Command include Paths def environment(**options) - Async::Service::Environment.new(Falcon::Service::Redirect::Environment).with( + Async::Service::Environment.new(Falcon::Environments::Redirect).with( root: Dir.pwd, verbose: self.parent&.verbose?, url: @options[:bind], @@ -44,7 +44,7 @@ def host_map(environments) hosts = {} environments.each do |environment| - next unless environment.implements?(Falcon::Service::Application::Environment) + next unless environment.implements?(Falcon::Environments::Application) evaluator = environment.evaluator hosts[evaluator.authority] = evaluator end diff --git a/lib/falcon/command/serve.rb b/lib/falcon/command/serve.rb index 33f372a2..9f375d65 100644 --- a/lib/falcon/command/serve.rb +++ b/lib/falcon/command/serve.rb @@ -53,7 +53,7 @@ def endpoint_options end def environment - Async::Service::Environment.new(Falcon::Service::Server::Environment).with( + Async::Service::Environment.new(Falcon::Environments::Server).with( Falcon::Environments::Rackup, root: Dir.pwd, diff --git a/lib/falcon/environments/application.rb b/lib/falcon/environments/application.rb new file mode 100644 index 00000000..1dad1164 --- /dev/null +++ b/lib/falcon/environments/application.rb @@ -0,0 +1,59 @@ +# frozen_string_literal: true + +# Released under the MIT License. +# Copyright, 2019-2023, by Samuel Williams. +# Copyright, 2020, by Daniel Evans. + +require_relative 'server' +require_relative '../proxy_endpoint' + +module Falcon + module Environments + module Application + include Server + + # The middleware stack for the application. + # @returns [Protocol::HTTP::Middleware] + def middleware + ::Protocol::HTTP::Middleware::HelloWorld + end + + # The scheme to use to communicate with the application. + # @returns [String] + def scheme + 'https' + end + + # The protocol to use to communicate with the application. + # + # Typically one of {Async::HTTP::Protocol::HTTP1} or {Async::HTTP::Protocl::HTTP2}. + # + # @returns [Async::HTTP::Protocol] + def protocol + Async::HTTP::Protocol::HTTP2 + end + + # The IPC path to use for communication with the application. + # @returns [String] + def ipc_path + ::File.expand_path("application.ipc", root) + end + + # The endpoint that will be used for communicating with the application server. + # @returns [Async::IO::Endpoint] + def endpoint + ::Falcon::ProxyEndpoint.unix(ipc_path, + protocol: protocol, + scheme: scheme, + authority: authority + ) + end + + # Number of instances to start. + # @returns [Integer | nil] + def count + nil + end + end + end +end diff --git a/lib/falcon/environments/proxy.rb b/lib/falcon/environments/proxy.rb new file mode 100644 index 00000000..5ab879db --- /dev/null +++ b/lib/falcon/environments/proxy.rb @@ -0,0 +1,105 @@ +# frozen_string_literal: true + +# Released under the MIT License. +# Copyright, 2020-2023, by Samuel Williams. + +require_relative 'server' +require_relative '../tls' +require_relative '../middleware/proxy' + +module Falcon + module Environments + module Proxy + include Server + + # The host that this proxy will receive connections for. + def url + "https://[::]:443" + end + + # The default SSL session identifier. + def tls_session_id + "falcon" + end + + # The services we will proxy to. + # @returns [Array(Async::Service::Environment)] + def environments + [] + end + + def hosts + hosts = {} + + environments.each do |environment| + evaluator = environment.evaluator + + if evaluator.key?(:authority) and evaluator.key?(:ssl_context) and evaluator.key?(:endpoint) + Console.logger.info(self) {"Proxying #{self.url} to #{evaluator.authority} using #{evaluator.endpoint}"} + hosts[evaluator.authority] = evaluator + + # Pre-cache the ssl contexts: + # It seems some OpenSSL objects don't like event-driven I/O. + # service.ssl_context + else + Console.logger.warn(self) {"Ignoring environment: #{environment}, missing authority, ssl_context, or endpoint."} + end + end + + return hosts + end + + # Look up the host context for the given hostname, and update the socket hostname if necessary. + # @parameter socket [OpenSSL::SSL::SSLSocket] The incoming connection. + # @parameter hostname [String] The negotiated hostname. + def host_context(socket, hostname) + hosts = self.hosts + + if host = hosts[hostname] + Console.logger.debug(self) {"Resolving #{hostname} -> #{host}"} + + socket.hostname = hostname + + return host.ssl_context + else + Console.logger.warn(self, hosts: hosts.keys) {"Unable to resolve #{hostname}!"} + + return nil + end + end + + # Generate an SSL context which delegates to {host_context} to multiplex based on hostname. + def ssl_context + @server_context ||= OpenSSL::SSL::SSLContext.new.tap do |context| + context.servername_cb = Proc.new do |socket, hostname| + self.host_context(socket, hostname) + end + + context.session_id_context = @session_id + + context.ssl_version = :TLSv1_2_server + + context.set_params( + ciphers: ::Falcon::TLS::SERVER_CIPHERS, + verify_mode: ::OpenSSL::SSL::VERIFY_NONE, + ) + + context.setup + end + end + + # The endpoint the server will bind to. + def endpoint + super.with( + ssl_context: self.ssl_context, + ) + end + + def middleware + return Middleware::Proxy.new(Middleware::BadRequest, self.hosts) + end + end + + LEGACY_ENVIRONMENTS[:proxy] = Proxy + end +end diff --git a/lib/falcon/environments/rack.rb b/lib/falcon/environments/rack.rb index f682ee12..03528d4e 100644 --- a/lib/falcon/environments/rack.rb +++ b/lib/falcon/environments/rack.rb @@ -3,13 +3,13 @@ # Released under the MIT License. # Copyright, 2019-2023, by Samuel Williams. -require_relative '../service/application' +require_relative 'application' require_relative 'rackup' module Falcon module Environments module Rack - include Service::Application::Environment + include Application include Rackup end diff --git a/lib/falcon/environments/redirect.rb b/lib/falcon/environments/redirect.rb new file mode 100644 index 00000000..b44f246f --- /dev/null +++ b/lib/falcon/environments/redirect.rb @@ -0,0 +1,33 @@ +# frozen_string_literal: true + +# Released under the MIT License. +# Copyright, 2020-2023, by Samuel Williams. + +require_relative 'server' +require_relative '../middleware/redirect' + +module Falcon + module Environments + # A controller for redirecting requests. + module Redirect + include Server + + def redirect_url + "https://[::]:443" + end + + def redirect_endpoint + Async::HTTP::Endpoint.parse(redirect_url) + end + + def hosts + {} + end + + # Load the {Middleware::Redirect} application with the specified hosts. + def middleware + Middleware::Redirect.new(Middleware::NotFound, hosts, redirect_endpoint) + end + end + end +end diff --git a/lib/falcon/environments/server.rb b/lib/falcon/environments/server.rb new file mode 100644 index 00000000..6653742f --- /dev/null +++ b/lib/falcon/environments/server.rb @@ -0,0 +1,66 @@ +# frozen_string_literal: true + +# Released under the MIT License. +# Copyright, 2020-2023, by Samuel Williams. + +require 'async/service/generic' +require 'async/http/endpoint' + +require_relative '../service/server' +require_relative '../server' + +module Falcon + module Environments + module Server + # The service class to use for the proxy. + # @returns [Class] + def service_class + Service::Server + end + + def name + "#{service_class.name} (#{url})" + end + + # Options to use when creating the container. + def container_options + {restart: true} + end + + # The host that this server will receive connections for. + def url + "http://[::]:9292" + end + + def timeout + nil + end + + # The upstream endpoint that will handle incoming requests. + # @returns [Async::HTTP::Endpoint] + def endpoint + ::Async::HTTP::Endpoint.parse(url).with( + reuse_address: true, + timeout: timeout, + ) + end + + def verbose + false + end + + def cache + false + end + + def client_endpoint + ::Async::HTTP::Endpoint.parse(url) + end + + # Any scripts to preload before starting the server. + def preload + [] + end + end + end +end \ No newline at end of file diff --git a/lib/falcon/service/application.rb b/lib/falcon/service/application.rb deleted file mode 100644 index 892b9e14..00000000 --- a/lib/falcon/service/application.rb +++ /dev/null @@ -1,71 +0,0 @@ -# frozen_string_literal: true - -# Released under the MIT License. -# Copyright, 2019-2023, by Samuel Williams. -# Copyright, 2020, by Daniel Evans. - -require_relative 'proxy' -require_relative '../proxy_endpoint' - -require 'async/http/endpoint' -require 'async/io/shared_endpoint' - -module Falcon - module Service - # Implements an application server using an internal clear-text proxy. - class Application < Server - module Environment - include Server::Environment - - # The service class to use for the application. - # @returns [Class] - def service_class - ::Falcon::Service::Application - end - - # The middleware stack for the application. - # @returns [Protocol::HTTP::Middleware] - def middleware - ::Protocol::HTTP::Middleware::HelloWorld - end - - # The scheme to use to communicate with the application. - # @returns [String] - def scheme - 'https' - end - - # The protocol to use to communicate with the application. - # - # Typically one of {Async::HTTP::Protocol::HTTP1} or {Async::HTTP::Protocl::HTTP2}. - # - # @returns [Async::HTTP::Protocol] - def protocol - Async::HTTP::Protocol::HTTP2 - end - - # The IPC path to use for communication with the application. - # @returns [String] - def ipc_path - ::File.expand_path("application.ipc", root) - end - - # The endpoint that will be used for communicating with the application server. - # @returns [Async::IO::Endpoint] - def endpoint - ::Falcon::ProxyEndpoint.unix(ipc_path, - protocol: protocol, - scheme: scheme, - authority: authority - ) - end - - # Number of instances to start. - # @returns [Integer | nil] - def count - nil - end - end - end - end -end diff --git a/lib/falcon/service/proxy.rb b/lib/falcon/service/proxy.rb deleted file mode 100644 index 8b1b56a1..00000000 --- a/lib/falcon/service/proxy.rb +++ /dev/null @@ -1,122 +0,0 @@ -# frozen_string_literal: true - -# Released under the MIT License. -# Copyright, 2020-2023, by Samuel Williams. - -require_relative 'server' -require_relative '../tls' -require_relative '../middleware/proxy' - -require 'async/http/endpoint' -require 'async/io/shared_endpoint' - -module Falcon - module Service - class Proxy < Server - module Environment - include Server::Environment - - # The service class to use for the proxy. - # @attribute [Class] - def service_class - Proxy - end - - def name - service_class.name - end - - # The host that this proxy will receive connections for. - def url - "https://[::]:443" - end - - # The default SSL session identifier. - def tls_session_id - "falcon" - end - - # The services we will proxy to. - # @returns [Array(Async::Service::Environment)] - def environments - [] - end - - def hosts - hosts = {} - - environments.each do |environment| - evaluator = environment.evaluator - - if evaluator.key?(:authority) and evaluator.key?(:ssl_context) and evaluator.key?(:endpoint) - Console.logger.info(self) {"Proxying #{self.url} to #{evaluator.authority} using #{evaluator.endpoint}"} - hosts[evaluator.authority] = evaluator - - # Pre-cache the ssl contexts: - # It seems some OpenSSL objects don't like event-driven I/O. - # service.ssl_context - else - Console.logger.warn(self) {"Ignoring environment: #{environment}, missing authority, ssl_context, or endpoint."} - end - end - - return hosts - end - - # Look up the host context for the given hostname, and update the socket hostname if necessary. - # @parameter socket [OpenSSL::SSL::SSLSocket] The incoming connection. - # @parameter hostname [String] The negotiated hostname. - def host_context(socket, hostname) - hosts = self.hosts - - if host = hosts[hostname] - Console.logger.debug(self) {"Resolving #{hostname} -> #{host}"} - - socket.hostname = hostname - - return host.ssl_context - else - Console.logger.warn(self, hosts: hosts.keys) {"Unable to resolve #{hostname}!"} - - return nil - end - end - - # Generate an SSL context which delegates to {host_context} to multiplex based on hostname. - def ssl_context - @server_context ||= OpenSSL::SSL::SSLContext.new.tap do |context| - context.servername_cb = Proc.new do |socket, hostname| - self.host_context(socket, hostname) - end - - context.session_id_context = @session_id - - context.ssl_version = :TLSv1_2_server - - context.set_params( - ciphers: TLS::SERVER_CIPHERS, - verify_mode: OpenSSL::SSL::VERIFY_NONE, - ) - - context.setup - end - end - - # The endpoint the server will bind to. - def endpoint - super.with( - ssl_context: self.ssl_context, - ) - end - - def middleware - return Middleware::Proxy.new(Middleware::BadRequest, self.hosts) - end - end - - def self.included(target) - target.include(Environment) - end - end - end -end diff --git a/lib/falcon/service/redirect.rb b/lib/falcon/service/redirect.rb deleted file mode 100644 index 1a3f6167..00000000 --- a/lib/falcon/service/redirect.rb +++ /dev/null @@ -1,38 +0,0 @@ -# frozen_string_literal: true - -# Released under the MIT License. -# Copyright, 2020-2023, by Samuel Williams. - -require 'async/container/controller' - -# require_relative 'serve' -require_relative '../middleware/redirect' -require_relative '../service/proxy' - -module Falcon - module Service - # A controller for redirecting requests. - class Redirect < Server - module Environment - include Server::Environment - - def redirect_url - "https://[::]:443" - end - - def redirect_endpoint - Async::HTTP::Endpoint.parse(redirect_url) - end - - def hosts - {} - end - - # Load the {Middleware::Redirect} application with the specified hosts. - def middleware - Middleware::Redirect.new(Middleware::NotFound, hosts, redirect_endpoint) - end - end - end - end -end diff --git a/lib/falcon/service/server.rb b/lib/falcon/service/server.rb index 0b6079ae..f07061b3 100644 --- a/lib/falcon/service/server.rb +++ b/lib/falcon/service/server.rb @@ -11,62 +11,6 @@ module Falcon module Service class Server < Async::Service::Generic - module Environment - # The service class to use for the proxy. - # @returns [Class] - def service_class - Server - end - - def name - "#{service_class.name} (#{url})" - end - - # Options to use when creating the container. - def container_options - {restart: true} - end - - # The host that this server will receive connections for. - def url - "http://[::]:9292" - end - - def timeout - nil - end - - # The upstream endpoint that will handle incoming requests. - # @returns [Async::HTTP::Endpoint] - def endpoint - ::Async::HTTP::Endpoint.parse(url).with( - reuse_address: true, - timeout: timeout, - ) - end - - def verbose - false - end - - def cache - false - end - - def client_endpoint - ::Async::HTTP::Endpoint.parse(url) - end - - # Any scripts to preload before starting the server. - def preload - [] - end - end - - def self.included(target) - target.include(Environment) - end - def initialize(...) super diff --git a/test/falcon/configuration.rb b/test/falcon/configuration.rb index 5a6204d7..ae73d177 100644 --- a/test/falcon/configuration.rb +++ b/test/falcon/configuration.rb @@ -18,7 +18,7 @@ evaluator = environment.evaluator expect(evaluator).to have_attributes( - service_class: be == Falcon::Service::Proxy, + service_class: be == Falcon::Service::Server, authority: be == "localhost", url: be == "https://www.google.com" ) diff --git a/test/falcon/middleware/proxy.rb b/test/falcon/middleware/proxy.rb index 50ec17b0..c7e920cb 100644 --- a/test/falcon/middleware/proxy.rb +++ b/test/falcon/middleware/proxy.rb @@ -4,7 +4,6 @@ # Copyright, 2018-2023, by Samuel Williams. require 'falcon/middleware/proxy' -require 'falcon/service/proxy' require 'sus/fixtures/async' require 'async/http/client' From 5d2caaa9225f2bf6c6c5afe5e54040bcb05f6dad Mon Sep 17 00:00:00 2001 From: Samuel Williams Date: Sat, 23 Mar 2024 14:31:02 +1300 Subject: [PATCH 15/21] Print OpenSSL version... --- config/sus.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/config/sus.rb b/config/sus.rb index f9a5971c..150879a3 100644 --- a/config/sus.rb +++ b/config/sus.rb @@ -7,4 +7,4 @@ include Covered::Sus require 'openssl' -# $stderr.puts "OpenSSL::OPENSSL_LIBRARY_VERSION: #{OpenSSL::OPENSSL_LIBRARY_VERSION}" +$stderr.puts "OpenSSL::OPENSSL_LIBRARY_VERSION: #{OpenSSL::OPENSSL_LIBRARY_VERSION}" From 83431a33834d95a9625a3099819e14e6e8ee58ee Mon Sep 17 00:00:00 2001 From: Samuel Williams Date: Sat, 23 Mar 2024 14:43:44 +1300 Subject: [PATCH 16/21] Prepare the ssl_context. --- lib/falcon/command/proxy.rb | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/lib/falcon/command/proxy.rb b/lib/falcon/command/proxy.rb index 78cf23ef..b19f8277 100644 --- a/lib/falcon/command/proxy.rb +++ b/lib/falcon/command/proxy.rb @@ -47,6 +47,10 @@ def host_map(environments) environments.each do |environment| next unless environment.implements?(Falcon::Environments::Application) evaluator = environment.evaluator + + # Prepare the ssl_context: + evaluator.ssl_context + hosts[evaluator.authority] = evaluator end From c0e4e2ad39fa57571a22bfcb2af1d1fa8c557092 Mon Sep 17 00:00:00 2001 From: Samuel Williams Date: Sat, 23 Mar 2024 14:55:33 +1300 Subject: [PATCH 17/21] It's only required on Ruby < 3.1. --- lib/falcon/command/proxy.rb | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/lib/falcon/command/proxy.rb b/lib/falcon/command/proxy.rb index b19f8277..bb5108ff 100644 --- a/lib/falcon/command/proxy.rb +++ b/lib/falcon/command/proxy.rb @@ -48,8 +48,10 @@ def host_map(environments) next unless environment.implements?(Falcon::Environments::Application) evaluator = environment.evaluator - # Prepare the ssl_context: - evaluator.ssl_context + if RUBY_VERSION < '3.1' + # Prepare the ssl_context: + evaluator.ssl_context + end hosts[evaluator.authority] = evaluator end From 17e2ca62ef9cc26daf4cc40511b21f1aa084154d Mon Sep 17 00:00:00 2001 From: Samuel Williams Date: Sat, 23 Mar 2024 15:09:05 +1300 Subject: [PATCH 18/21] Update copyrights. --- config/sus.rb | 2 +- examples/hello/preload.rb | 2 +- falcon.gemspec | 2 +- gems.rb | 2 +- hello.rb | 4 ++++ lib/falcon/command/host.rb | 2 +- lib/falcon/command/proxy.rb | 2 +- lib/falcon/command/redirect.rb | 2 +- lib/falcon/command/serve.rb | 2 +- lib/falcon/command/top.rb | 2 +- lib/falcon/configuration.rb | 2 +- lib/falcon/environments.rb | 2 +- lib/falcon/environments/application.rb | 2 +- lib/falcon/environments/lets_encrypt_tls.rb | 4 ++-- lib/falcon/environments/proxy.rb | 2 +- lib/falcon/environments/rack.rb | 2 +- lib/falcon/environments/rackup.rb | 2 +- lib/falcon/environments/redirect.rb | 2 +- lib/falcon/environments/self_signed_tls.rb | 2 +- lib/falcon/environments/server.rb | 4 ++-- lib/falcon/environments/supervisor.rb | 2 +- lib/falcon/environments/tls.rb | 2 +- lib/falcon/service/server.rb | 3 ++- lib/falcon/service/supervisor.rb | 2 +- lib/falcon/service/virtual.rb | 2 +- lib/falcon/version.rb | 2 +- license.md | 2 ++ test/falcon/command/serve.rb | 2 +- test/falcon/command/top.rb | 2 +- test/falcon/command/virtual.rb | 2 +- test/falcon/configuration.rb | 2 +- test/falcon/middleware/proxy.rb | 2 +- 32 files changed, 39 insertions(+), 32 deletions(-) diff --git a/config/sus.rb b/config/sus.rb index 150879a3..871a42bc 100644 --- a/config/sus.rb +++ b/config/sus.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true # Released under the MIT License. -# Copyright, 2017-2023, by Samuel Williams. +# Copyright, 2017-2024, by Samuel Williams. require 'covered/sus' include Covered::Sus diff --git a/examples/hello/preload.rb b/examples/hello/preload.rb index f1d7ce07..8598526f 100644 --- a/examples/hello/preload.rb +++ b/examples/hello/preload.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true # Released under the MIT License. -# Copyright, 2020-2023, by Samuel Williams. +# Copyright, 2020-2024, by Samuel Williams. # $stderr.puts "Preloading..." diff --git a/falcon.gemspec b/falcon.gemspec index 1506b410..03347241 100644 --- a/falcon.gemspec +++ b/falcon.gemspec @@ -7,7 +7,7 @@ Gem::Specification.new do |spec| spec.version = Falcon::VERSION spec.summary = "A fast, asynchronous, rack-compatible web server." - spec.authors = ["Samuel Williams", "Janko Marohnić", "Bryan Powell", "Claudiu Garba", "Kyle Tam", "Mitsutaka Mimura", "Sho Ito", "Colby Swandale", "Daniel Evans", "Kent Gruber", "Michael Adams", "Mikel Kew", "Nick Janetakis", "Olle Jonsson", "Peter Schrammel", "Santiago Bartesaghi", "Sh Lin", "Tad Thorley", "Tasos Latsas"] + spec.authors = ["Samuel Williams", "Janko Marohnić", "Bryan Powell", "Claudiu Garba", "Kyle Tam", "Mitsutaka Mimura", "Sho Ito", "Trevor Turk", "Colby Swandale", "Daniel Evans", "Kent Gruber", "Michael Adams", "Mikel Kew", "Nick Janetakis", "Olle Jonsson", "Peter Schrammel", "Santiago Bartesaghi", "Sh Lin", "Tad Thorley", "Tasos Latsas", "dependabot[bot]"] spec.license = "MIT" spec.cert_chain = ['release.cert'] diff --git a/gems.rb b/gems.rb index 735ea16c..c48f5ca6 100644 --- a/gems.rb +++ b/gems.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true # Released under the MIT License. -# Copyright, 2017-2023, by Samuel Williams. +# Copyright, 2017-2024, by Samuel Williams. source 'https://rubygems.org' diff --git a/hello.rb b/hello.rb index bf7c51ff..917659a8 100755 --- a/hello.rb +++ b/hello.rb @@ -1,4 +1,8 @@ #!/usr/bin/env async-service +# frozen_string_literal: true + +# Released under the MIT License. +# Copyright, 2024, by Samuel Williams. require 'falcon/service/server' diff --git a/lib/falcon/command/host.rb b/lib/falcon/command/host.rb index 52747438..41525d27 100644 --- a/lib/falcon/command/host.rb +++ b/lib/falcon/command/host.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true # Released under the MIT License. -# Copyright, 2019-2023, by Samuel Williams. +# Copyright, 2019-2024, by Samuel Williams. require_relative 'paths' require_relative '../version' diff --git a/lib/falcon/command/proxy.rb b/lib/falcon/command/proxy.rb index bb5108ff..e16d20a4 100644 --- a/lib/falcon/command/proxy.rb +++ b/lib/falcon/command/proxy.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true # Released under the MIT License. -# Copyright, 2020-2023, by Samuel Williams. +# Copyright, 2020-2024, by Samuel Williams. require_relative '../environments/proxy' require_relative 'paths' diff --git a/lib/falcon/command/redirect.rb b/lib/falcon/command/redirect.rb index 74886671..87930502 100644 --- a/lib/falcon/command/redirect.rb +++ b/lib/falcon/command/redirect.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true # Released under the MIT License. -# Copyright, 2020-2023, by Samuel Williams. +# Copyright, 2020-2024, by Samuel Williams. require_relative '../environments/redirect' require_relative 'paths' diff --git a/lib/falcon/command/serve.rb b/lib/falcon/command/serve.rb index 9f375d65..666c2197 100644 --- a/lib/falcon/command/serve.rb +++ b/lib/falcon/command/serve.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true # Released under the MIT License. -# Copyright, 2018-2023, by Samuel Williams. +# Copyright, 2018-2024, by Samuel Williams. # Copyright, 2018, by Mitsutaka Mimura. require_relative '../server' diff --git a/lib/falcon/command/top.rb b/lib/falcon/command/top.rb index 75edb1a0..c31b8606 100644 --- a/lib/falcon/command/top.rb +++ b/lib/falcon/command/top.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true # Released under the MIT License. -# Copyright, 2020-2023, by Samuel Williams. +# Copyright, 2020-2024, by Samuel Williams. require_relative 'serve' require_relative 'host' diff --git a/lib/falcon/configuration.rb b/lib/falcon/configuration.rb index a0b778c7..9d9f88a0 100644 --- a/lib/falcon/configuration.rb +++ b/lib/falcon/configuration.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true # Released under the MIT License. -# Copyright, 2019-2023, by Samuel Williams. +# Copyright, 2019-2024, by Samuel Williams. # Copyright, 2019, by Sho Ito. require 'async/service' diff --git a/lib/falcon/environments.rb b/lib/falcon/environments.rb index b5635a8c..f698bc15 100644 --- a/lib/falcon/environments.rb +++ b/lib/falcon/environments.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true # Released under the MIT License. -# Copyright, 2019-2023, by Samuel Williams. +# Copyright, 2019-2024, by Samuel Williams. module Falcon # Pre-defined environments for hosting web applications. diff --git a/lib/falcon/environments/application.rb b/lib/falcon/environments/application.rb index 1dad1164..0242240d 100644 --- a/lib/falcon/environments/application.rb +++ b/lib/falcon/environments/application.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true # Released under the MIT License. -# Copyright, 2019-2023, by Samuel Williams. +# Copyright, 2019-2024, by Samuel Williams. # Copyright, 2020, by Daniel Evans. require_relative 'server' diff --git a/lib/falcon/environments/lets_encrypt_tls.rb b/lib/falcon/environments/lets_encrypt_tls.rb index 7d4f2b8f..ad926086 100644 --- a/lib/falcon/environments/lets_encrypt_tls.rb +++ b/lib/falcon/environments/lets_encrypt_tls.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true # Released under the MIT License. -# Copyright, 2020-2023, by Samuel Williams. +# Copyright, 2020-2024, by Samuel Williams. require_relative 'tls' require_relative '../environments' @@ -31,4 +31,4 @@ def ssl_private_key_path LEGACY_ENVIRONMENTS[:tls] = TLS end -end \ No newline at end of file +end diff --git a/lib/falcon/environments/proxy.rb b/lib/falcon/environments/proxy.rb index 5ab879db..801e6e40 100644 --- a/lib/falcon/environments/proxy.rb +++ b/lib/falcon/environments/proxy.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true # Released under the MIT License. -# Copyright, 2020-2023, by Samuel Williams. +# Copyright, 2019-2024, by Samuel Williams. require_relative 'server' require_relative '../tls' diff --git a/lib/falcon/environments/rack.rb b/lib/falcon/environments/rack.rb index 03528d4e..ee311535 100644 --- a/lib/falcon/environments/rack.rb +++ b/lib/falcon/environments/rack.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true # Released under the MIT License. -# Copyright, 2019-2023, by Samuel Williams. +# Copyright, 2019-2024, by Samuel Williams. require_relative 'application' require_relative 'rackup' diff --git a/lib/falcon/environments/rackup.rb b/lib/falcon/environments/rackup.rb index dcfe5974..eff7f413 100644 --- a/lib/falcon/environments/rackup.rb +++ b/lib/falcon/environments/rackup.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true # Released under the MIT License. -# Copyright, 2019-2023, by Samuel Williams. +# Copyright, 2024, by Samuel Williams. require 'rack/builder' require_relative '../server' diff --git a/lib/falcon/environments/redirect.rb b/lib/falcon/environments/redirect.rb index b44f246f..abe48f5a 100644 --- a/lib/falcon/environments/redirect.rb +++ b/lib/falcon/environments/redirect.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true # Released under the MIT License. -# Copyright, 2020-2023, by Samuel Williams. +# Copyright, 2020-2024, by Samuel Williams. require_relative 'server' require_relative '../middleware/redirect' diff --git a/lib/falcon/environments/self_signed_tls.rb b/lib/falcon/environments/self_signed_tls.rb index 0ad31ca8..6a9580b3 100644 --- a/lib/falcon/environments/self_signed_tls.rb +++ b/lib/falcon/environments/self_signed_tls.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true # Released under the MIT License. -# Copyright, 2019-2023, by Samuel Williams. +# Copyright, 2019-2024, by Samuel Williams. require 'localhost/authority' require_relative 'tls' diff --git a/lib/falcon/environments/server.rb b/lib/falcon/environments/server.rb index 6653742f..57c2eb24 100644 --- a/lib/falcon/environments/server.rb +++ b/lib/falcon/environments/server.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true # Released under the MIT License. -# Copyright, 2020-2023, by Samuel Williams. +# Copyright, 2024, by Samuel Williams. require 'async/service/generic' require 'async/http/endpoint' @@ -63,4 +63,4 @@ def preload end end end -end \ No newline at end of file +end diff --git a/lib/falcon/environments/supervisor.rb b/lib/falcon/environments/supervisor.rb index 8e0006d2..a0f7268a 100644 --- a/lib/falcon/environments/supervisor.rb +++ b/lib/falcon/environments/supervisor.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true # Released under the MIT License. -# Copyright, 2019-2023, by Samuel Williams. +# Copyright, 2019-2024, by Samuel Williams. require_relative '../service/supervisor' require_relative '../environments' diff --git a/lib/falcon/environments/tls.rb b/lib/falcon/environments/tls.rb index 262b6452..b69bf4db 100644 --- a/lib/falcon/environments/tls.rb +++ b/lib/falcon/environments/tls.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true # Released under the MIT License. -# Copyright, 2019-2023, by Samuel Williams. +# Copyright, 2019-2024, by Samuel Williams. require_relative '../tls' require_relative '../environments' diff --git a/lib/falcon/service/server.rb b/lib/falcon/service/server.rb index f07061b3..3b1678d5 100644 --- a/lib/falcon/service/server.rb +++ b/lib/falcon/service/server.rb @@ -1,7 +1,8 @@ # frozen_string_literal: true # Released under the MIT License. -# Copyright, 2020-2023, by Samuel Williams. +# Copyright, 2020-2024, by Samuel Williams. +# Copyright, 2020, by Michael Adams. require 'async/service/generic' require 'async/http/endpoint' diff --git a/lib/falcon/service/supervisor.rb b/lib/falcon/service/supervisor.rb index ff28ce1f..d38b4b49 100644 --- a/lib/falcon/service/supervisor.rb +++ b/lib/falcon/service/supervisor.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true # Released under the MIT License. -# Copyright, 2019-2023, by Samuel Williams. +# Copyright, 2019-2024, by Samuel Williams. require 'process/metrics' require 'json' diff --git a/lib/falcon/service/virtual.rb b/lib/falcon/service/virtual.rb index 2b499ccd..c5ef1e04 100644 --- a/lib/falcon/service/virtual.rb +++ b/lib/falcon/service/virtual.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true # Released under the MIT License. -# Copyright, 2020-2023, by Samuel Williams. +# Copyright, 2024, by Samuel Williams. require 'async/service/generic' diff --git a/lib/falcon/version.rb b/lib/falcon/version.rb index 647f2c41..bb7e526c 100644 --- a/lib/falcon/version.rb +++ b/lib/falcon/version.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true # Released under the MIT License. -# Copyright, 2017-2023, by Samuel Williams. +# Copyright, 2017-2024, by Samuel Williams. module Falcon VERSION = "0.43.0" diff --git a/license.md b/license.md index 51063ae2..2ecd60ca 100644 --- a/license.md +++ b/license.md @@ -19,6 +19,8 @@ Copyright, 2021, by Olle Jonsson. Copyright, 2023, by Nick Janetakis. Copyright, 2024, by Peter Schrammel. Copyright, 2024, by Santiago Bartesaghi. +Copyright, 2024, by Trevor Turk. +Copyright, 2024, by dependabot[bot]. Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/test/falcon/command/serve.rb b/test/falcon/command/serve.rb index 26052d2f..ee96d3f1 100644 --- a/test/falcon/command/serve.rb +++ b/test/falcon/command/serve.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true # Released under the MIT License. -# Copyright, 2018-2023, by Samuel Williams. +# Copyright, 2018-2024, by Samuel Williams. # Copyright, 2019, by Sho Ito. require 'falcon/command/serve' diff --git a/test/falcon/command/top.rb b/test/falcon/command/top.rb index 0bdfe584..c4fda880 100644 --- a/test/falcon/command/top.rb +++ b/test/falcon/command/top.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true # Released under the MIT License. -# Copyright, 2018-2023, by Samuel Williams. +# Copyright, 2018-2024, by Samuel Williams. require 'falcon/command' diff --git a/test/falcon/command/virtual.rb b/test/falcon/command/virtual.rb index 9b5f8a0f..addc84a5 100644 --- a/test/falcon/command/virtual.rb +++ b/test/falcon/command/virtual.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true # Released under the MIT License. -# Copyright, 2019-2023, by Samuel Williams. +# Copyright, 2019-2024, by Samuel Williams. require 'falcon/command/virtual' diff --git a/test/falcon/configuration.rb b/test/falcon/configuration.rb index ae73d177..2e04aab5 100644 --- a/test/falcon/configuration.rb +++ b/test/falcon/configuration.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true # Released under the MIT License. -# Copyright, 2019-2023, by Samuel Williams. +# Copyright, 2019-2024, by Samuel Williams. # Copyright, 2020, by Daniel Evans. require 'falcon/configuration' diff --git a/test/falcon/middleware/proxy.rb b/test/falcon/middleware/proxy.rb index c7e920cb..b2e29670 100644 --- a/test/falcon/middleware/proxy.rb +++ b/test/falcon/middleware/proxy.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true # Released under the MIT License. -# Copyright, 2018-2023, by Samuel Williams. +# Copyright, 2018-2024, by Samuel Williams. require 'falcon/middleware/proxy' From 66f7b3f5519adb1a0b12d304248afa4512b00f3c Mon Sep 17 00:00:00 2001 From: Samuel Williams Date: Sat, 23 Mar 2024 15:14:22 +1300 Subject: [PATCH 19/21] Move `async-service` example to `examples/`. --- hello.rb => examples/async-service/hello.rb | 4 ++-- examples/async-service/readme.md | 18 ++++++++++++++++++ 2 files changed, 20 insertions(+), 2 deletions(-) rename hello.rb => examples/async-service/hello.rb (75%) create mode 100644 examples/async-service/readme.md diff --git a/hello.rb b/examples/async-service/hello.rb similarity index 75% rename from hello.rb rename to examples/async-service/hello.rb index 917659a8..4990e9d0 100755 --- a/hello.rb +++ b/examples/async-service/hello.rb @@ -4,10 +4,10 @@ # Released under the MIT License. # Copyright, 2024, by Samuel Williams. -require 'falcon/service/server' +require 'falcon/environments/server' service "hello-server" do - include Falcon::Service::Server + include Falcon::Environments::Server middleware do ::Protocol::HTTP::Middleware::HelloWorld diff --git a/examples/async-service/readme.md b/examples/async-service/readme.md new file mode 100644 index 00000000..f193b8b3 --- /dev/null +++ b/examples/async-service/readme.md @@ -0,0 +1,18 @@ +# Async::Service + +Async::Service provides a generic service layer for managing the lifecycle of services. + +Falcon provides several services which are managed by the service layer, including: + +- `Falcon::Service::Server`: An HTTP server. +- `Falcon::Service::Supervisor`: A process supervisor (memory usage, etc). + +The service layer can support multiple different services, and can be used to manage the lifecycle of any service, or group of services. + +This simple example shows how to use `async-service` to start a web server. + +``` shell +$ bundle exec async-service ./hello.rb +``` + +This will start a web server on port 9292 which responds with "Hello World!" to all requests. From 5d8ca2dcbaf0ebda1f9c6c8019c9847c7579e652 Mon Sep 17 00:00:00 2001 From: Samuel Williams Date: Sat, 23 Mar 2024 16:09:55 +1300 Subject: [PATCH 20/21] Rename `Falcon::Environments` -> `Falcon::Environment`. --- changes.md | 22 +++++++++++ examples/async-service/hello.rb | 4 +- guides/deployment/readme.md | 39 ++++++++++++------- lib/falcon/command/proxy.rb | 7 ++-- lib/falcon/command/redirect.rb | 7 ++-- lib/falcon/command/serve.rb | 6 +-- lib/falcon/configuration.rb | 23 ++++------- .../{environments.rb => environment.rb} | 2 +- .../application.rb | 2 +- .../lets_encrypt_tls.rb | 4 +- .../{environments => environment}/proxy.rb | 3 +- .../{environments => environment}/rack.rb | 3 +- .../{environments => environment}/rackup.rb | 3 +- .../{environments => environment}/redirect.rb | 2 +- .../self_signed_tls.rb | 4 +- .../{environments => environment}/server.rb | 8 ++-- .../supervisor.rb | 4 +- .../{environments => environment}/tls.rb | 4 +- lib/falcon/service/server.rb | 2 +- 19 files changed, 88 insertions(+), 61 deletions(-) create mode 100644 changes.md rename lib/falcon/{environments.rb => environment.rb} (92%) rename lib/falcon/{environments => environment}/application.rb (98%) rename lib/falcon/{environments => environment}/lets_encrypt_tls.rb (92%) rename lib/falcon/{environments => environment}/proxy.rb (98%) rename lib/falcon/{environments => environment}/rack.rb (84%) rename lib/falcon/{environments => environment}/rackup.rb (88%) rename lib/falcon/{environments => environment}/redirect.rb (96%) rename lib/falcon/{environments => environment}/self_signed_tls.rb (94%) rename lib/falcon/{environments => environment}/server.rb (89%) rename lib/falcon/{environments => environment}/supervisor.rb (93%) rename lib/falcon/{environments => environment}/tls.rb (97%) diff --git a/changes.md b/changes.md new file mode 100644 index 00000000..6a4b0d71 --- /dev/null +++ b/changes.md @@ -0,0 +1,22 @@ +# Changes + +# Unreleased + +## Falcon Host + +`async-service` is a new gem that exposes a generic service interface on top of `async-container`. Previously, `falcon host` used `async-container` directly and `build-environment` for configuration. In order to allow for more generic service definitions and configuration, `async-service` now provides a similar interface to `build-environment` and exposes this in a way that can be used for services other tha falcon. This makes it simpler to integrate multiple services into a single application. + +The current configuration format uses definitions like this: + +```ruby +rack 'hello.localhost', :self_signed_tls +``` + +This changes to: + +```ruby +service 'hello.localhost' do + include Falcon::Environment::Rack + include Falcon::Environment::SelfSignedTLS +end +``` diff --git a/examples/async-service/hello.rb b/examples/async-service/hello.rb index 4990e9d0..48be69a1 100755 --- a/examples/async-service/hello.rb +++ b/examples/async-service/hello.rb @@ -4,10 +4,10 @@ # Released under the MIT License. # Copyright, 2024, by Samuel Williams. -require 'falcon/environments/server' +require 'falcon/environment/server' service "hello-server" do - include Falcon::Environments::Server + include Falcon::Environment::Server middleware do ::Protocol::HTTP::Middleware::HelloWorld diff --git a/guides/deployment/readme.md b/guides/deployment/readme.md index 6b1a6ace..e24d8961 100644 --- a/guides/deployment/readme.md +++ b/guides/deployment/readme.md @@ -6,11 +6,11 @@ Falcon can be deployed into production either as a standalone application server ## Falcon Serve -`falcon serve` is not designed for deployment. Do not use it for deployment. +`falcon serve` is not designed for deployment because the command line interface is not guaranteed to be stable nor does it expose every possible configuration option. ## Falcon Hosts -`falcon host` is designed for deployment. +`falcon host` is designed for deployment, and is the recommended way to deploy Falcon in production. It exposes a well defined interface for configuring services (web applications, job servers, etc). ### Configuration @@ -19,37 +19,46 @@ Falcon can be deployed into production either as a standalone application server Here is a basic example which hosts a rack application: ~~~ ruby -#!/usr/bin/env -S falcon host +#!/usr/bin/env falcon-host # frozen_string_literal: true -load :rack, :lets_encrypt_tls, :supervisor - hostname = File.basename(__dir__) -rack hostname, :lets_encrypt_tls do +service hostname do + include Falcon::Environment::Rack + include Falcon::Environment::LetsEncryptTLS + + # Insert an in-memory cache in front of the application (using async-http-cache). cache true end -supervisor +service "supervisor" do + include Falcon::Environment::Supervisor +end ~~~ -These configuration blocks are constructed using [build-environment](https://github.com/ioquatix/build-environment), and the defaults are listed in the [Falcon source code](https://github.com/socketry/falcon/tree/master/lib/falcon/environments). +These configuration blocks are evaluated using [async-service](https://github.com/socketry/async-service). ### Application Configuration -The [`rack` environment](https://github.com/socketry/falcon/blob/master/lib/falcon/environments/rack.rb) inherits the [application environment](https://github.com/socketry/falcon/blob/master/lib/falcon/environments/application.rb). These environments by default are defined for usage with `falcon virtual`, but you can customise any parts of the configuration, e.g. to bind a production host to `localhost:3000` using plaintext HTTP/2: +The environment configuration is defined in the `Falcon::Environment` module. The {ruby Falcon::Environment::Application} environment supports the generic virtual host functionality, but you can customise any parts of the configuration, e.g. to bind a production host to `localhost:3000` using plaintext HTTP/2: ~~~ ruby -#!/usr/bin/env -S falcon host +#!/usr/bin/env falcon-host # frozen_string_literal: true -load :rack, :supervisor - hostname = File.basename(__dir__) -rack hostname do - endpoint Async::HTTP::Endpoint.parse('http://localhost:3000').with(protocol: Async::HTTP::Protocol::HTTP2) +service hostname do + include Falcon::Environment::Rack + include Falcon::Environment::LetsEncryptTLS + + endpoint do + Async::HTTP::Endpoint.parse('http://localhost:3000').with(protocol: Async::HTTP::Protocol::HTTP2) + end end -supervisor +service "supervisor" do + include Falcon::Environment::Supervisor +end ~~~ You can verify this is working using `nghttp -v http://localhost:3000`. diff --git a/lib/falcon/command/proxy.rb b/lib/falcon/command/proxy.rb index e16d20a4..93e15f4c 100644 --- a/lib/falcon/command/proxy.rb +++ b/lib/falcon/command/proxy.rb @@ -3,7 +3,7 @@ # Released under the MIT License. # Copyright, 2020-2024, by Samuel Williams. -require_relative '../environments/proxy' +require_relative '../environment/proxy' require_relative 'paths' require 'samovar' @@ -32,8 +32,9 @@ class Proxy < Samovar::Command include Paths def environment(**options) - Async::Service::Environment.new(Falcon::Environments::Proxy).with( + Async::Service::Environment.new(Falcon::Environment::Proxy).with( root: Dir.pwd, + name: self.class.name, verbose: self.parent&.verbose?, url: @options[:bind], timeout: @options[:timeout], @@ -45,7 +46,7 @@ def host_map(environments) hosts = {} environments.each do |environment| - next unless environment.implements?(Falcon::Environments::Application) + next unless environment.implements?(Falcon::Environment::Application) evaluator = environment.evaluator if RUBY_VERSION < '3.1' diff --git a/lib/falcon/command/redirect.rb b/lib/falcon/command/redirect.rb index 87930502..a44ef0a1 100644 --- a/lib/falcon/command/redirect.rb +++ b/lib/falcon/command/redirect.rb @@ -3,7 +3,7 @@ # Released under the MIT License. # Copyright, 2020-2024, by Samuel Williams. -require_relative '../environments/redirect' +require_relative '../environment/redirect' require_relative 'paths' require 'samovar' @@ -30,8 +30,9 @@ class Redirect < Samovar::Command include Paths def environment(**options) - Async::Service::Environment.new(Falcon::Environments::Redirect).with( + Async::Service::Environment.new(Falcon::Environment::Redirect).with( root: Dir.pwd, + name: self.class.name, verbose: self.parent&.verbose?, url: @options[:bind], redirect_url: @options[:redirect], @@ -44,7 +45,7 @@ def host_map(environments) hosts = {} environments.each do |environment| - next unless environment.implements?(Falcon::Environments::Application) + next unless environment.implements?(Falcon::Environment::Application) evaluator = environment.evaluator hosts[evaluator.authority] = evaluator end diff --git a/lib/falcon/command/serve.rb b/lib/falcon/command/serve.rb index 666c2197..2b359726 100644 --- a/lib/falcon/command/serve.rb +++ b/lib/falcon/command/serve.rb @@ -8,7 +8,7 @@ require_relative '../endpoint' require_relative '../configuration' require_relative '../service/server' -require_relative '../environments/rackup' +require_relative '../environment/rackup' require 'async/container' require 'async/http/client' @@ -53,8 +53,8 @@ def endpoint_options end def environment - Async::Service::Environment.new(Falcon::Environments::Server).with( - Falcon::Environments::Rackup, + Async::Service::Environment.new(Falcon::Environment::Server).with( + Falcon::Environment::Rackup, root: Dir.pwd, diff --git a/lib/falcon/configuration.rb b/lib/falcon/configuration.rb index 9d9f88a0..85837efc 100644 --- a/lib/falcon/configuration.rb +++ b/lib/falcon/configuration.rb @@ -31,29 +31,17 @@ def load_file(path) Loader.load_file(self, path) end - def each(key = :authority) - return to_enum(__method__, key) unless block_given? - - @environments.each do |environment| - evaluator = environment.evaluator - if evaluator.key?(key) - yield environment - end - end - end - # The domain specific language for loading configuration files. class Loader < ::Async::Service::Loader # Load specific features into the current configuration. # - # Falcon provides default environments for different purposes. These are included in the gem, in the `environments/` directory. This method loads the code in those files into the current configuration. - # + # @deprecated Use `require` instead. # @parameter features [Array(Symbol)] The features to load. def load(*features) features.each do |feature| case feature when Symbol - require File.join(__dir__, "environments", "#{feature}.rb") + require File.join(__dir__, "environment", "#{feature}.rb") else raise LoadError, "Unsure about how to load #{feature}!" end @@ -62,6 +50,7 @@ def load(*features) # Define a host with the specified name. # Adds `root` and `authority` keys. + # @deprecated Use `service` and `include Falcon::Environment::Server` instead. # @parameter name [String] The name of the environment, usually a hostname. def host(name, *parents, &block) @configuration.add( @@ -71,6 +60,7 @@ def host(name, *parents, &block) # Define a proxy with the specified name. # Adds `root` and `authority` keys. + # @deprecated Use `service` and `include Falcon::Environment::Proxy` instead. # @parameter name [String] The name of the environment, usually a hostname. def proxy(name, *parents, &block) @configuration.add( @@ -80,6 +70,7 @@ def proxy(name, *parents, &block) # Define a rack application with the specified name. # Adds `root` and `authority` keys. + # @deprecated Use `service` and `include Falcon::Environment::Rack` instead. # @parameter name [String] The name of the environment, usually a hostname. def rack(name, *parents, &block) @configuration.add( @@ -88,7 +79,7 @@ def rack(name, *parents, &block) end # Define a supervisor instance - # Adds `root` key. + # @deprecated Use `service` and `include Falcon::Environment::Supervisor` instead. def supervisor(&block) name = File.join(@root, "supervisor") @@ -107,7 +98,7 @@ def merge(*parents, **initial, &block) ::Async::Service::Environment.build(**initial) do parents.each do |parent| Console.warn(self) {"Legacy mapping for #{parent.inspect} should be updated to use `include`!"} - include(Environments::LEGACY_ENVIRONMENTS[parent]) + include(Environment::LEGACY_ENVIRONMENTS[parent]) end instance_exec(&block) if block diff --git a/lib/falcon/environments.rb b/lib/falcon/environment.rb similarity index 92% rename from lib/falcon/environments.rb rename to lib/falcon/environment.rb index f698bc15..4dc0cbca 100644 --- a/lib/falcon/environments.rb +++ b/lib/falcon/environment.rb @@ -7,7 +7,7 @@ module Falcon # Pre-defined environments for hosting web applications. # # See {Configuration::Loader#load} for more details. - module Environments + module Environment LEGACY_ENVIRONMENTS = {} end end diff --git a/lib/falcon/environments/application.rb b/lib/falcon/environment/application.rb similarity index 98% rename from lib/falcon/environments/application.rb rename to lib/falcon/environment/application.rb index 0242240d..ed618989 100644 --- a/lib/falcon/environments/application.rb +++ b/lib/falcon/environment/application.rb @@ -8,7 +8,7 @@ require_relative '../proxy_endpoint' module Falcon - module Environments + module Environment module Application include Server diff --git a/lib/falcon/environments/lets_encrypt_tls.rb b/lib/falcon/environment/lets_encrypt_tls.rb similarity index 92% rename from lib/falcon/environments/lets_encrypt_tls.rb rename to lib/falcon/environment/lets_encrypt_tls.rb index ad926086..1837b42b 100644 --- a/lib/falcon/environments/lets_encrypt_tls.rb +++ b/lib/falcon/environment/lets_encrypt_tls.rb @@ -4,10 +4,10 @@ # Copyright, 2020-2024, by Samuel Williams. require_relative 'tls' -require_relative '../environments' +require_relative '../environment' module Falcon - module Environments + module Environment # A Lets Encrypt SSL context environment. module LetsEncryptTLS # The Lets Encrypt certificate store path. diff --git a/lib/falcon/environments/proxy.rb b/lib/falcon/environment/proxy.rb similarity index 98% rename from lib/falcon/environments/proxy.rb rename to lib/falcon/environment/proxy.rb index 801e6e40..c34f5cbe 100644 --- a/lib/falcon/environments/proxy.rb +++ b/lib/falcon/environment/proxy.rb @@ -6,9 +6,10 @@ require_relative 'server' require_relative '../tls' require_relative '../middleware/proxy' +require_relative '../environment' module Falcon - module Environments + module Environment module Proxy include Server diff --git a/lib/falcon/environments/rack.rb b/lib/falcon/environment/rack.rb similarity index 84% rename from lib/falcon/environments/rack.rb rename to lib/falcon/environment/rack.rb index ee311535..a7de2172 100644 --- a/lib/falcon/environments/rack.rb +++ b/lib/falcon/environment/rack.rb @@ -5,9 +5,10 @@ require_relative 'application' require_relative 'rackup' +require_relative '../environment' module Falcon - module Environments + module Environment module Rack include Application include Rackup diff --git a/lib/falcon/environments/rackup.rb b/lib/falcon/environment/rackup.rb similarity index 88% rename from lib/falcon/environments/rackup.rb rename to lib/falcon/environment/rackup.rb index eff7f413..aca9992d 100644 --- a/lib/falcon/environments/rackup.rb +++ b/lib/falcon/environment/rackup.rb @@ -5,10 +5,9 @@ require 'rack/builder' require_relative '../server' -require_relative '../environments' module Falcon - module Environments + module Environment module Rackup def rackup_path 'config.ru' diff --git a/lib/falcon/environments/redirect.rb b/lib/falcon/environment/redirect.rb similarity index 96% rename from lib/falcon/environments/redirect.rb rename to lib/falcon/environment/redirect.rb index abe48f5a..06f7d8ce 100644 --- a/lib/falcon/environments/redirect.rb +++ b/lib/falcon/environment/redirect.rb @@ -7,7 +7,7 @@ require_relative '../middleware/redirect' module Falcon - module Environments + module Environment # A controller for redirecting requests. module Redirect include Server diff --git a/lib/falcon/environments/self_signed_tls.rb b/lib/falcon/environment/self_signed_tls.rb similarity index 94% rename from lib/falcon/environments/self_signed_tls.rb rename to lib/falcon/environment/self_signed_tls.rb index 6a9580b3..8d5aa59b 100644 --- a/lib/falcon/environments/self_signed_tls.rb +++ b/lib/falcon/environment/self_signed_tls.rb @@ -5,10 +5,10 @@ require 'localhost/authority' require_relative 'tls' -require_relative '../environments' +require_relative '../environment' module Falcon - module Environments + module Environment # A general SSL context environment. module SelfSignedTLS # The default session identifier for the session cache. diff --git a/lib/falcon/environments/server.rb b/lib/falcon/environment/server.rb similarity index 89% rename from lib/falcon/environments/server.rb rename to lib/falcon/environment/server.rb index 57c2eb24..536a527d 100644 --- a/lib/falcon/environments/server.rb +++ b/lib/falcon/environment/server.rb @@ -10,7 +10,7 @@ require_relative '../server' module Falcon - module Environments + module Environment module Server # The service class to use for the proxy. # @returns [Class] @@ -18,8 +18,10 @@ def service_class Service::Server end - def name - "#{service_class.name} (#{url})" + # The server authority. Defaults to the server name. + # @returns [String] + def authority + self.name end # Options to use when creating the container. diff --git a/lib/falcon/environments/supervisor.rb b/lib/falcon/environment/supervisor.rb similarity index 93% rename from lib/falcon/environments/supervisor.rb rename to lib/falcon/environment/supervisor.rb index a0f7268a..570354cc 100644 --- a/lib/falcon/environments/supervisor.rb +++ b/lib/falcon/environment/supervisor.rb @@ -4,10 +4,10 @@ # Copyright, 2019-2024, by Samuel Williams. require_relative '../service/supervisor' -require_relative '../environments' +require_relative '../environment' module Falcon - module Environments + module Environment # A application process monitor environment. module Supervisor # The name of the supervisor diff --git a/lib/falcon/environments/tls.rb b/lib/falcon/environment/tls.rb similarity index 97% rename from lib/falcon/environments/tls.rb rename to lib/falcon/environment/tls.rb index b69bf4db..8bbf3d42 100644 --- a/lib/falcon/environments/tls.rb +++ b/lib/falcon/environment/tls.rb @@ -4,10 +4,10 @@ # Copyright, 2019-2024, by Samuel Williams. require_relative '../tls' -require_relative '../environments' +require_relative '../environment' module Falcon - module Environments + module Environment # A general SSL context environment. module TLS # The default session identifier for the session cache. diff --git a/lib/falcon/service/server.rb b/lib/falcon/service/server.rb index 3b1678d5..7e6d7f15 100644 --- a/lib/falcon/service/server.rb +++ b/lib/falcon/service/server.rb @@ -43,7 +43,7 @@ def start preload! - Console.logger.info(self) {"Starting #{name} on #{@endpoint}"} + Console.logger.info(self) {"Starting #{self.name} on #{@endpoint}"} super end From 68fb4e1449b16b577978c4d9b1d55ca9ffd6bcb2 Mon Sep 17 00:00:00 2001 From: Samuel Williams Date: Sat, 23 Mar 2024 16:33:02 +1300 Subject: [PATCH 21/21] Move `host_map` logic to environment. --- falcon.gemspec | 2 +- lib/falcon/command/proxy.rb | 37 ++++-------------------------- lib/falcon/command/redirect.rb | 31 ++++--------------------- lib/falcon/environment/proxy.rb | 12 +++++----- lib/falcon/environment/redirect.rb | 19 ++++++++++++++- 5 files changed, 33 insertions(+), 68 deletions(-) diff --git a/falcon.gemspec b/falcon.gemspec index 03347241..dd631057 100644 --- a/falcon.gemspec +++ b/falcon.gemspec @@ -30,7 +30,7 @@ Gem::Specification.new do |spec| spec.add_dependency "async-http", "~> 0.57" spec.add_dependency "async-http-cache", "~> 0.4.0" spec.add_dependency "async-io", "~> 1.22" - spec.add_dependency "async-service", "~> 0.9.0" + spec.add_dependency "async-service", "~> 0.10.0" spec.add_dependency "bundler" spec.add_dependency "localhost", "~> 1.1" spec.add_dependency "openssl", "~> 3.0" diff --git a/lib/falcon/command/proxy.rb b/lib/falcon/command/proxy.rb index 93e15f4c..f7f96e75 100644 --- a/lib/falcon/command/proxy.rb +++ b/lib/falcon/command/proxy.rb @@ -42,39 +42,10 @@ def environment(**options) ) end - def host_map(environments) - hosts = {} - - environments.each do |environment| - next unless environment.implements?(Falcon::Environment::Application) - evaluator = environment.evaluator - - if RUBY_VERSION < '3.1' - # Prepare the ssl_context: - evaluator.ssl_context - end - - hosts[evaluator.authority] = evaluator - end - - Console.info(self) {"Hosts: #{hosts}"} - - return hosts - end - def configuration - configuration = super - hosts = host_map(configuration.environments) - - Configuration.new.tap do |configuration| - environment = self.environment(hosts: hosts) - configuration.add(environment) - end - end - - # The container class to use. - def container_class - Async::Container.best_container_class + Configuration.for( + self.environment(environments: super.environments) + ) end # Prepare the environment and run the controller. @@ -90,7 +61,7 @@ def call end end - Async::Service::Controller.run(self.configuration, container_class: self.container_class) + Async::Service::Controller.run(self.configuration) end # The endpoint to bind to. diff --git a/lib/falcon/command/redirect.rb b/lib/falcon/command/redirect.rb index a44ef0a1..12d29deb 100644 --- a/lib/falcon/command/redirect.rb +++ b/lib/falcon/command/redirect.rb @@ -41,33 +41,10 @@ def environment(**options) ) end - def host_map(environments) - hosts = {} - - environments.each do |environment| - next unless environment.implements?(Falcon::Environment::Application) - evaluator = environment.evaluator - hosts[evaluator.authority] = evaluator - end - - Console.info(self) {"Hosts: #{hosts}"} - - return hosts - end - def configuration - configuration = super - hosts = host_map(configuration.environments) - - Configuration.new.tap do |configuration| - environment = self.environment(hosts: hosts) - configuration.add(environment) - end - end - - # The container class to use. - def container_class - Async::Container.best_container_class + Configuration.for( + self.environment(environments: super.environments) + ) end # Prepare the environment and run the controller. @@ -83,7 +60,7 @@ def call end end - Async::Service::Controller.run(self.configuration, container_class: self.container_class) + Async::Service::Controller.run(self.configuration) end # The endpoint to bind to. diff --git a/lib/falcon/environment/proxy.rb b/lib/falcon/environment/proxy.rb index c34f5cbe..386b7e4b 100644 --- a/lib/falcon/environment/proxy.rb +++ b/lib/falcon/environment/proxy.rb @@ -35,15 +35,15 @@ def hosts environments.each do |environment| evaluator = environment.evaluator + # next unless environment.implements?(Falcon::Environment::Application) if evaluator.key?(:authority) and evaluator.key?(:ssl_context) and evaluator.key?(:endpoint) - Console.logger.info(self) {"Proxying #{self.url} to #{evaluator.authority} using #{evaluator.endpoint}"} + Console.info(self) {"Proxying #{self.url} to #{evaluator.authority} using #{evaluator.endpoint}"} hosts[evaluator.authority] = evaluator - # Pre-cache the ssl contexts: - # It seems some OpenSSL objects don't like event-driven I/O. - # service.ssl_context - else - Console.logger.warn(self) {"Ignoring environment: #{environment}, missing authority, ssl_context, or endpoint."} + if RUBY_VERSION < '3.1' + # Ensure the SSL context is set up before forking - it's buggy on Ruby < 3.1: + evaluator.ssl_context + end end end diff --git a/lib/falcon/environment/redirect.rb b/lib/falcon/environment/redirect.rb index 06f7d8ce..b6ad0bd0 100644 --- a/lib/falcon/environment/redirect.rb +++ b/lib/falcon/environment/redirect.rb @@ -20,8 +20,25 @@ def redirect_endpoint Async::HTTP::Endpoint.parse(redirect_url) end + # The services we will redirect to. + # @returns [Array(Async::Service::Environment)] + def environments + [] + end + def hosts - {} + hosts = {} + + environments.each do |environment| + evaluator = environment.evaluator + + if environment.implements?(Falcon::Environment::Application) + Console.info(self) {"Redirecting #{self.url} to #{evaluator.authority}"} + hosts[evaluator.authority] = evaluator + end + end + + return hosts end # Load the {Middleware::Redirect} application with the specified hosts.