falcon 0.43.0 → 0.45.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (45) hide show
  1. checksums.yaml +4 -4
  2. checksums.yaml.gz.sig +1 -1
  3. data/changes.md +36 -0
  4. data/lib/falcon/command/host.rb +6 -32
  5. data/lib/falcon/command/proxy.rb +20 -15
  6. data/lib/falcon/command/redirect.rb +21 -15
  7. data/lib/falcon/command/serve.rb +44 -65
  8. data/lib/falcon/command/top.rb +1 -1
  9. data/lib/falcon/command/virtual.rb +15 -23
  10. data/lib/falcon/configuration.rb +26 -124
  11. data/lib/falcon/environment/application.rb +60 -0
  12. data/lib/falcon/environment/lets_encrypt_tls.rb +34 -0
  13. data/lib/falcon/environment/proxy.rb +109 -0
  14. data/lib/falcon/environment/rack.rb +20 -0
  15. data/lib/falcon/environment/rackup.rb +26 -0
  16. data/lib/falcon/environment/redirect.rb +50 -0
  17. data/lib/falcon/environment/self_signed_tls.rb +45 -0
  18. data/lib/falcon/environment/server.rb +69 -0
  19. data/lib/falcon/environment/supervisor.rb +40 -0
  20. data/lib/falcon/environment/tls.rb +97 -0
  21. data/lib/falcon/{environments.rb → environment.rb} +3 -4
  22. data/lib/falcon/service/server.rb +84 -0
  23. data/lib/falcon/service/supervisor.rb +4 -3
  24. data/lib/falcon/{controller → service}/virtual.rb +71 -18
  25. data/lib/falcon/version.rb +2 -2
  26. data/license.md +2 -0
  27. data.tar.gz.sig +0 -0
  28. metadata +28 -30
  29. metadata.gz.sig +0 -0
  30. data/lib/.DS_Store +0 -0
  31. data/lib/falcon/controller/host.rb +0 -55
  32. data/lib/falcon/controller/proxy.rb +0 -109
  33. data/lib/falcon/controller/redirect.rb +0 -59
  34. data/lib/falcon/controller/serve.rb +0 -110
  35. data/lib/falcon/environments/application.rb +0 -56
  36. data/lib/falcon/environments/lets_encrypt_tls.rb +0 -30
  37. data/lib/falcon/environments/proxy.rb +0 -22
  38. data/lib/falcon/environments/rack.rb +0 -33
  39. data/lib/falcon/environments/self_signed_tls.rb +0 -38
  40. data/lib/falcon/environments/supervisor.rb +0 -34
  41. data/lib/falcon/environments/tls.rb +0 -86
  42. data/lib/falcon/service/application.rb +0 -99
  43. data/lib/falcon/service/generic.rb +0 -61
  44. data/lib/falcon/service/proxy.rb +0 -49
  45. data/lib/falcon/services.rb +0 -82
@@ -0,0 +1,60 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Released under the MIT License.
4
+ # Copyright, 2019-2024, by Samuel Williams.
5
+ # Copyright, 2020, by Daniel Evans.
6
+
7
+ require_relative 'server'
8
+ require_relative '../proxy_endpoint'
9
+
10
+ module Falcon
11
+ module Environment
12
+ # Provides an environment for hosting a web application that uses TLS.
13
+ module Application
14
+ include Server
15
+
16
+ # The middleware stack for the application.
17
+ # @returns [Protocol::HTTP::Middleware]
18
+ def middleware
19
+ ::Protocol::HTTP::Middleware::HelloWorld
20
+ end
21
+
22
+ # The scheme to use to communicate with the application.
23
+ # @returns [String]
24
+ def scheme
25
+ 'https'
26
+ end
27
+
28
+ # The protocol to use to communicate with the application.
29
+ #
30
+ # Typically one of {Async::HTTP::Protocol::HTTP1} or {Async::HTTP::Protocl::HTTP2}.
31
+ #
32
+ # @returns [Async::HTTP::Protocol]
33
+ def protocol
34
+ Async::HTTP::Protocol::HTTP2
35
+ end
36
+
37
+ # The IPC path to use for communication with the application.
38
+ # @returns [String]
39
+ def ipc_path
40
+ ::File.expand_path("application.ipc", root)
41
+ end
42
+
43
+ # The endpoint that will be used for communicating with the application server.
44
+ # @returns [Async::IO::Endpoint]
45
+ def endpoint
46
+ ::Falcon::ProxyEndpoint.unix(ipc_path,
47
+ protocol: protocol,
48
+ scheme: scheme,
49
+ authority: authority
50
+ )
51
+ end
52
+
53
+ # Number of instances to start.
54
+ # @returns [Integer | nil]
55
+ def count
56
+ nil
57
+ end
58
+ end
59
+ end
60
+ end
@@ -0,0 +1,34 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Released under the MIT License.
4
+ # Copyright, 2020-2024, by Samuel Williams.
5
+
6
+ require_relative 'tls'
7
+ require_relative '../environment'
8
+
9
+ module Falcon
10
+ module Environment
11
+ # Provides an environment that uses "Lets Encrypt" for TLS.
12
+ module LetsEncryptTLS
13
+ # The Lets Encrypt certificate store path.
14
+ # @parameter [String]
15
+ def lets_encrypt_root
16
+ '/etc/letsencrypt/live'
17
+ end
18
+
19
+ # The public certificate path.
20
+ # @attribute [String]
21
+ def ssl_certificate_path
22
+ File.join(lets_encrypt_root, authority, "fullchain.pem")
23
+ end
24
+
25
+ # The private key path.
26
+ # @attribute [String]
27
+ def ssl_private_key_path
28
+ File.join(lets_encrypt_root, authority, "privkey.pem")
29
+ end
30
+ end
31
+
32
+ LEGACY_ENVIRONMENTS[:tls] = TLS
33
+ end
34
+ end
@@ -0,0 +1,109 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Released under the MIT License.
4
+ # Copyright, 2019-2024, by Samuel Williams.
5
+
6
+ require_relative 'server'
7
+ require_relative '../tls'
8
+ require_relative '../middleware/proxy'
9
+ require_relative '../environment'
10
+
11
+ module Falcon
12
+ module Environment
13
+ # Provides an environment for hosting a TLS-capable reverse proxy using SNI.
14
+ module Proxy
15
+ include Server
16
+
17
+ # The host that this proxy will receive connections for.
18
+ def url
19
+ "https://[::]:443"
20
+ end
21
+
22
+ # The default SSL session identifier.
23
+ def tls_session_id
24
+ "falcon"
25
+ end
26
+
27
+ # The services we will proxy to.
28
+ # @returns [Array(Async::Service::Environment)]
29
+ def environments
30
+ []
31
+ end
32
+
33
+ # The hosts we will proxy to. This is a hash of SNI authority -> evaluator.
34
+ # @returns [Hash(String, Async::Service::Environment::Evaluator)]
35
+ def hosts
36
+ hosts = {}
37
+
38
+ environments.each do |environment|
39
+ evaluator = environment.evaluator
40
+
41
+ # next unless environment.implements?(Falcon::Environment::Application)
42
+ if evaluator.key?(:authority) and evaluator.key?(:ssl_context) and evaluator.key?(:endpoint)
43
+ Console.info(self) {"Proxying #{self.url} to #{evaluator.authority} using #{evaluator.endpoint}"}
44
+ hosts[evaluator.authority] = evaluator
45
+
46
+ if RUBY_VERSION < '3.1'
47
+ # Ensure the SSL context is set up before forking - it's buggy on Ruby < 3.1:
48
+ evaluator.ssl_context
49
+ end
50
+ end
51
+ end
52
+
53
+ return hosts
54
+ end
55
+
56
+ # Look up the host context for the given hostname, and update the socket hostname if necessary.
57
+ # @parameter socket [OpenSSL::SSL::SSLSocket] The incoming connection.
58
+ # @parameter hostname [String] The negotiated hostname.
59
+ def host_context(socket, hostname)
60
+ hosts = self.hosts
61
+
62
+ if host = hosts[hostname]
63
+ Console.logger.debug(self) {"Resolving #{hostname} -> #{host}"}
64
+
65
+ socket.hostname = hostname
66
+
67
+ return host.ssl_context
68
+ else
69
+ Console.logger.warn(self, hosts: hosts.keys) {"Unable to resolve #{hostname}!"}
70
+
71
+ return nil
72
+ end
73
+ end
74
+
75
+ # Generate an SSL context which delegates to {host_context} to multiplex based on hostname.
76
+ def ssl_context
77
+ @server_context ||= OpenSSL::SSL::SSLContext.new.tap do |context|
78
+ context.servername_cb = Proc.new do |socket, hostname|
79
+ self.host_context(socket, hostname)
80
+ end
81
+
82
+ context.session_id_context = @session_id
83
+
84
+ context.ssl_version = :TLSv1_2_server
85
+
86
+ context.set_params(
87
+ ciphers: ::Falcon::TLS::SERVER_CIPHERS,
88
+ verify_mode: ::OpenSSL::SSL::VERIFY_NONE,
89
+ )
90
+
91
+ context.setup
92
+ end
93
+ end
94
+
95
+ # The endpoint the server will bind to.
96
+ def endpoint
97
+ super.with(
98
+ ssl_context: self.ssl_context,
99
+ )
100
+ end
101
+
102
+ def middleware
103
+ return Middleware::Proxy.new(Middleware::BadRequest, self.hosts)
104
+ end
105
+ end
106
+
107
+ LEGACY_ENVIRONMENTS[:proxy] = Proxy
108
+ end
109
+ end
@@ -0,0 +1,20 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Released under the MIT License.
4
+ # Copyright, 2019-2024, by Samuel Williams.
5
+
6
+ require_relative 'application'
7
+ require_relative 'rackup'
8
+ require_relative '../environment'
9
+
10
+ module Falcon
11
+ module Environment
12
+ # Provides an environment for hosting a web application that use a Rackup `config.ru` file.
13
+ module Rack
14
+ include Application
15
+ include Rackup
16
+ end
17
+
18
+ LEGACY_ENVIRONMENTS[:rack] = Rack
19
+ end
20
+ end
@@ -0,0 +1,26 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Released under the MIT License.
4
+ # Copyright, 2024, by Samuel Williams.
5
+
6
+ require 'rack/builder'
7
+ require_relative '../server'
8
+
9
+ module Falcon
10
+ module Environment
11
+ # Provides an environment for hosting loading a Rackup `config.ru` file.
12
+ module Rackup
13
+ def rackup_path
14
+ 'config.ru'
15
+ end
16
+
17
+ def rack_app
18
+ ::Protocol::Rack::Adapter.parse_file(rackup_path)
19
+ end
20
+
21
+ def middleware
22
+ ::Falcon::Server.middleware(rack_app, verbose: verbose, cache: cache)
23
+ end
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,50 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Released under the MIT License.
4
+ # Copyright, 2020-2024, by Samuel Williams.
5
+
6
+ require_relative 'server'
7
+ require_relative '../middleware/redirect'
8
+
9
+ module Falcon
10
+ module Environment
11
+ # Provides an environment for redirecting insecure web traffic to a secure endpoint.
12
+ module Redirect
13
+ include Server
14
+
15
+ def redirect_url
16
+ "https://[::]:443"
17
+ end
18
+
19
+ def redirect_endpoint
20
+ Async::HTTP::Endpoint.parse(redirect_url)
21
+ end
22
+
23
+ # The services we will redirect to.
24
+ # @returns [Array(Async::Service::Environment)]
25
+ def environments
26
+ []
27
+ end
28
+
29
+ def hosts
30
+ hosts = {}
31
+
32
+ environments.each do |environment|
33
+ evaluator = environment.evaluator
34
+
35
+ if environment.implements?(Falcon::Environment::Application)
36
+ Console.info(self) {"Redirecting #{self.url} to #{evaluator.authority}"}
37
+ hosts[evaluator.authority] = evaluator
38
+ end
39
+ end
40
+
41
+ return hosts
42
+ end
43
+
44
+ # Load the {Middleware::Redirect} application with the specified hosts.
45
+ def middleware
46
+ Middleware::Redirect.new(Middleware::NotFound, hosts, redirect_endpoint)
47
+ end
48
+ end
49
+ end
50
+ end
@@ -0,0 +1,45 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Released under the MIT License.
4
+ # Copyright, 2019-2024, by Samuel Williams.
5
+
6
+ require 'localhost/authority'
7
+ require_relative 'tls'
8
+ require_relative '../environment'
9
+
10
+ module Falcon
11
+ module Environment
12
+ # Provides an environment that exposes a self-signed TLS certificate using the `localhost` gem.
13
+ module SelfSignedTLS
14
+ # The default session identifier for the session cache.
15
+ # @returns [String]
16
+ def ssl_session_id
17
+ "falcon"
18
+ end
19
+
20
+ # The SSL context to use for incoming connections.
21
+ # @returns [OpenSSL::SSL::SSLContext]
22
+ def ssl_context
23
+ contexts = Localhost::Authority.fetch(authority)
24
+
25
+ contexts.server_context.tap do |context|
26
+ context.alpn_select_cb = lambda do |protocols|
27
+ if protocols.include? "h2"
28
+ return "h2"
29
+ elsif protocols.include? "http/1.1"
30
+ return "http/1.1"
31
+ elsif protocols.include? "http/1.0"
32
+ return "http/1.0"
33
+ else
34
+ return nil
35
+ end
36
+ end
37
+
38
+ context.session_id_context = ssl_session_id
39
+ end
40
+ end
41
+ end
42
+
43
+ LEGACY_ENVIRONMENTS[:self_signed_tls] = SelfSignedTLS
44
+ end
45
+ end
@@ -0,0 +1,69 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Released under the MIT License.
4
+ # Copyright, 2024, by Samuel Williams.
5
+
6
+ require 'async/service/generic'
7
+ require 'async/http/endpoint'
8
+
9
+ require_relative '../service/server'
10
+ require_relative '../server'
11
+
12
+ module Falcon
13
+ module Environment
14
+ # Provides an environment for hosting a web application that uses a Falcon server.
15
+ module Server
16
+ # The service class to use for the proxy.
17
+ # @returns [Class]
18
+ def service_class
19
+ Service::Server
20
+ end
21
+
22
+ # The server authority. Defaults to the server name.
23
+ # @returns [String]
24
+ def authority
25
+ self.name
26
+ end
27
+
28
+ # Options to use when creating the container.
29
+ def container_options
30
+ {restart: true}
31
+ end
32
+
33
+ # The host that this server will receive connections for.
34
+ def url
35
+ "http://[::]:9292"
36
+ end
37
+
38
+ def timeout
39
+ nil
40
+ end
41
+
42
+ # The upstream endpoint that will handle incoming requests.
43
+ # @returns [Async::HTTP::Endpoint]
44
+ def endpoint
45
+ ::Async::HTTP::Endpoint.parse(url).with(
46
+ reuse_address: true,
47
+ timeout: timeout,
48
+ )
49
+ end
50
+
51
+ def verbose
52
+ false
53
+ end
54
+
55
+ def cache
56
+ false
57
+ end
58
+
59
+ def client_endpoint
60
+ ::Async::HTTP::Endpoint.parse(url)
61
+ end
62
+
63
+ # Any scripts to preload before starting the server.
64
+ def preload
65
+ []
66
+ end
67
+ end
68
+ end
69
+ end
@@ -0,0 +1,40 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Released under the MIT License.
4
+ # Copyright, 2019-2024, by Samuel Williams.
5
+
6
+ require_relative '../service/supervisor'
7
+ require_relative '../environment'
8
+
9
+ module Falcon
10
+ module Environment
11
+ # Provides an environment for hosting a supervisor which can monitor multiple applications.
12
+ module Supervisor
13
+ # The name of the supervisor
14
+ # @returns [String]
15
+ def name
16
+ "supervisor"
17
+ end
18
+
19
+ # The IPC path to use for communication with the supervisor.
20
+ # @returns [String]
21
+ def ipc_path
22
+ ::File.expand_path("supervisor.ipc", root)
23
+ end
24
+
25
+ # The endpoint the supervisor will bind to.
26
+ # @returns [Async::IO::Endpoint]
27
+ def endpoint
28
+ Async::IO::Endpoint.unix(ipc_path)
29
+ end
30
+
31
+ # The service class to use for the supervisor.
32
+ # @returns [Class]
33
+ def service_class
34
+ ::Falcon::Service::Supervisor
35
+ end
36
+ end
37
+
38
+ LEGACY_ENVIRONMENTS[:supervisor] = Supervisor
39
+ end
40
+ end
@@ -0,0 +1,97 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Released under the MIT License.
4
+ # Copyright, 2019-2024, by Samuel Williams.
5
+
6
+ require_relative '../tls'
7
+ require_relative '../environment'
8
+
9
+ module Falcon
10
+ module Environment
11
+ # Provides an environment that exposes a TLS context for hosting a secure web application.
12
+ module TLS
13
+ # The default session identifier for the session cache.
14
+ # @returns [String]
15
+ def ssl_session_id
16
+ "falcon"
17
+ end
18
+
19
+ # The supported ciphers.
20
+ # @returns [Array(String)]
21
+ def ssl_ciphers
22
+ Falcon::TLS::SERVER_CIPHERS
23
+ end
24
+
25
+ # The public certificate path.
26
+ # @returns [String]
27
+ def ssl_certificate_path
28
+ File.expand_path("ssl/certificate.pem", root)
29
+ end
30
+
31
+ # The list of certificates loaded from that path.
32
+ # @returns [Array(OpenSSL::X509::Certificate)]
33
+ def ssl_certificates
34
+ OpenSSL::X509::Certificate.load_file(ssl_certificate_path)
35
+ end
36
+
37
+ # The main certificate.
38
+ # @returns [OpenSSL::X509::Certificate]
39
+ def ssl_certificate
40
+ ssl_certificates[0]
41
+ end
42
+
43
+ # The certificate chain.
44
+ # @returns [Array(OpenSSL::X509::Certificate)]
45
+ def ssl_certificate_chain
46
+ ssl_certificates[1..-1]
47
+ end
48
+
49
+ # The private key path.
50
+ # @returns [String]
51
+ def ssl_private_key_path
52
+ File.expand_path("ssl/private.key", root)
53
+ end
54
+
55
+ # The private key.
56
+ # @returns [OpenSSL::PKey::RSA]
57
+ def ssl_private_key
58
+ OpenSSL::PKey::RSA.new(File.read(ssl_private_key_path))
59
+ end
60
+
61
+ # The SSL context to use for incoming connections.
62
+ # @returns [OpenSSL::SSL::SSLContext]
63
+ def ssl_context
64
+ OpenSSL::SSL::SSLContext.new.tap do |context|
65
+ context.add_certificate(ssl_certificate, ssl_private_key, ssl_certificate_chain)
66
+
67
+ context.session_cache_mode = OpenSSL::SSL::SSLContext::SESSION_CACHE_CLIENT
68
+ context.session_id_context = ssl_session_id
69
+
70
+ context.alpn_select_cb = lambda do |protocols|
71
+ if protocols.include? "h2"
72
+ return "h2"
73
+ elsif protocols.include? "http/1.1"
74
+ return "http/1.1"
75
+ elsif protocols.include? "http/1.0"
76
+ return "http/1.0"
77
+ else
78
+ return nil
79
+ end
80
+ end
81
+
82
+ # TODO Ruby 2.4 requires using ssl_version.
83
+ context.ssl_version = :TLSv1_2_server
84
+
85
+ context.set_params(
86
+ ciphers: ssl_ciphers,
87
+ verify_mode: OpenSSL::SSL::VERIFY_NONE,
88
+ )
89
+
90
+ context.setup
91
+ end
92
+ end
93
+ end
94
+
95
+ LEGACY_ENVIRONMENTS[:tls] = TLS
96
+ end
97
+ end
@@ -1,14 +1,13 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  # Released under the MIT License.
4
- # Copyright, 2019-2023, by Samuel Williams.
5
-
6
- require 'build/environment'
4
+ # Copyright, 2019-2024, by Samuel Williams.
7
5
 
8
6
  module Falcon
9
7
  # Pre-defined environments for hosting web applications.
10
8
  #
11
9
  # See {Configuration::Loader#load} for more details.
12
- module Environments
10
+ module Environment
11
+ LEGACY_ENVIRONMENTS = {}
13
12
  end
14
13
  end
@@ -0,0 +1,84 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Released under the MIT License.
4
+ # Copyright, 2020-2024, by Samuel Williams.
5
+ # Copyright, 2020, by Michael Adams.
6
+
7
+ require 'async/service/generic'
8
+ require 'async/http/endpoint'
9
+
10
+ require_relative '../server'
11
+
12
+ module Falcon
13
+ module Service
14
+ class Server < Async::Service::Generic
15
+ def initialize(...)
16
+ super
17
+
18
+ @bound_endpoint = nil
19
+ end
20
+
21
+ # Preload any resources specified by the environment.
22
+ def preload!
23
+ root = @evaluator.root
24
+
25
+ if scripts = @evaluator.preload
26
+ scripts = Array(scripts)
27
+
28
+ scripts.each do |path|
29
+ Console.logger.info(self) {"Preloading #{path}..."}
30
+ full_path = File.expand_path(path, root)
31
+ load(full_path)
32
+ end
33
+ end
34
+ end
35
+
36
+ # Prepare the bound endpoint for the server.
37
+ def start
38
+ @endpoint = @evaluator.endpoint
39
+
40
+ Sync do
41
+ @bound_endpoint = @endpoint.bound
42
+ end
43
+
44
+ preload!
45
+
46
+ Console.logger.info(self) {"Starting #{self.name} on #{@endpoint}"}
47
+
48
+ super
49
+ end
50
+
51
+ # Setup the container with the application instance.
52
+ # @parameter container [Async::Container::Generic]
53
+ def setup(container)
54
+ container_options = @evaluator.container_options
55
+
56
+ container.run(name: self.name, **container_options) do |instance|
57
+ evaluator = @environment.evaluator
58
+
59
+ Async do |task|
60
+ server = Falcon::Server.new(evaluator.middleware, @bound_endpoint, protocol: @endpoint.protocol, scheme: @endpoint.scheme)
61
+
62
+ server.run
63
+
64
+ instance.ready!
65
+
66
+ task.children.each(&:wait)
67
+ end
68
+ end
69
+ end
70
+
71
+ # Close the bound endpoint.
72
+ def stop(...)
73
+ if @bound_endpoint
74
+ @bound_endpoint.close
75
+ @bound_endpoint = nil
76
+ end
77
+
78
+ @endpoint = nil
79
+
80
+ super
81
+ end
82
+ end
83
+ end
84
+ end
@@ -1,21 +1,22 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  # Released under the MIT License.
4
- # Copyright, 2019-2023, by Samuel Williams.
4
+ # Copyright, 2019-2024, by Samuel Williams.
5
5
 
6
6
  require 'process/metrics'
7
7
  require 'json'
8
8
 
9
9
  require 'async/io/endpoint'
10
10
  require 'async/io/shared_endpoint'
11
+ require 'async/service/generic'
11
12
 
12
13
  module Falcon
13
14
  module Service
14
15
  # Implements a host supervisor which can restart the host services and provide various metrics about the running processes.
15
- class Supervisor < Generic
16
+ class Supervisor < Async::Service::Generic
16
17
  # Initialize the supervisor using the given environment.
17
18
  # @parameter environment [Build::Environment]
18
- def initialize(environment)
19
+ def initialize(...)
19
20
  super
20
21
 
21
22
  @bound_endpoint = nil