falcon 0.43.0 → 0.44.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (45) hide show
  1. checksums.yaml +4 -4
  2. checksums.yaml.gz.sig +0 -0
  3. data/changes.md +22 -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 +41 -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 +24 -26
  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
+ ::Rack::Builder.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