falcon 0.36.3 → 0.37.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 (89) hide show
  1. checksums.yaml +4 -4
  2. data/lib/falcon/adapters/input.rb +32 -11
  3. data/lib/falcon/adapters/output.rb +20 -1
  4. data/lib/falcon/adapters/rack.rb +60 -39
  5. data/lib/falcon/adapters/response.rb +23 -1
  6. data/lib/falcon/adapters/rewindable.rb +10 -3
  7. data/lib/falcon/command.rb +2 -0
  8. data/lib/falcon/command/host.rb +13 -2
  9. data/lib/falcon/command/paths.rb +4 -0
  10. data/lib/falcon/command/proxy.rb +14 -0
  11. data/lib/falcon/command/redirect.rb +12 -0
  12. data/lib/falcon/command/serve.rb +22 -15
  13. data/lib/falcon/command/supervisor.rb +15 -1
  14. data/lib/falcon/command/top.rb +16 -0
  15. data/lib/falcon/command/virtual.rb +15 -0
  16. data/lib/falcon/configuration.rb +69 -7
  17. data/lib/falcon/controller/host.rb +12 -0
  18. data/lib/falcon/controller/proxy.rb +13 -0
  19. data/lib/falcon/controller/redirect.rb +7 -0
  20. data/lib/falcon/controller/serve.rb +17 -2
  21. data/lib/falcon/controller/virtual.rb +17 -0
  22. data/lib/falcon/endpoint.rb +8 -0
  23. data/lib/falcon/{configuration/proxy.rb → environments.rb} +9 -5
  24. data/lib/falcon/environments/application.rb +72 -0
  25. data/lib/falcon/{configuration/application.rb → environments/lets_encrypt_tls.rb} +21 -20
  26. data/lib/falcon/{configuration/lets_encrypt_tls.rb → environments/proxy.rb} +13 -6
  27. data/lib/falcon/{configuration → environments}/rack.rb +14 -2
  28. data/lib/falcon/{configuration → environments}/self_signed_tls.rb +9 -1
  29. data/lib/falcon/{configuration → environments}/supervisor.rb +19 -5
  30. data/lib/falcon/{configuration → environments}/tls.rb +39 -5
  31. data/lib/falcon/extensions/openssl.rb +1 -0
  32. data/lib/falcon/middleware/proxy.rb +26 -5
  33. data/lib/falcon/middleware/redirect.rb +11 -0
  34. data/lib/falcon/{verbose.rb → middleware/verbose.rb} +34 -26
  35. data/lib/falcon/proxy_endpoint.rb +21 -0
  36. data/lib/falcon/server.rb +8 -2
  37. data/lib/falcon/service/application.rb +24 -2
  38. data/lib/falcon/service/generic.rb +18 -0
  39. data/lib/falcon/service/proxy.rb +6 -0
  40. data/lib/falcon/service/supervisor.rb +13 -1
  41. data/lib/falcon/services.rb +21 -0
  42. data/lib/falcon/tls.rb +4 -2
  43. data/lib/falcon/version.rb +1 -1
  44. data/lib/rack/handler/falcon.rb +8 -2
  45. metadata +65 -122
  46. data/.editorconfig +0 -5
  47. data/.github/FUNDING.yml +0 -3
  48. data/.github/workflows/development.yml +0 -60
  49. data/.gitignore +0 -14
  50. data/.rspec +0 -3
  51. data/Gemfile +0 -17
  52. data/README.md +0 -316
  53. data/examples/beer/config.ru +0 -57
  54. data/examples/beer/falcon.rb +0 -8
  55. data/examples/benchmark/config.ru +0 -39
  56. data/examples/benchmark/falcon.rb +0 -6
  57. data/examples/csv/config.ru +0 -31
  58. data/examples/google/falcon.rb +0 -14
  59. data/examples/hello/config.ru +0 -22
  60. data/examples/hello/falcon.rb +0 -24
  61. data/examples/hello/preload.rb +0 -7
  62. data/examples/internet/config.ru +0 -54
  63. data/examples/memory/allocations.rb +0 -39
  64. data/examples/memory/config.ru +0 -14
  65. data/examples/push/client.rb +0 -29
  66. data/examples/push/config.ru +0 -28
  67. data/examples/push/index.html +0 -14
  68. data/examples/push/script.js +0 -2
  69. data/examples/push/style.css +0 -4
  70. data/examples/redis/Gemfile +0 -9
  71. data/examples/redis/config.ru +0 -28
  72. data/examples/sequel/Gemfile +0 -4
  73. data/examples/sequel/config.ru +0 -8
  74. data/examples/sequel/data.sqlite3 +0 -0
  75. data/examples/server/standalone.rb +0 -27
  76. data/examples/sinatra/Gemfile +0 -7
  77. data/examples/sinatra/Gemfile.lock +0 -53
  78. data/examples/sinatra/config.ru +0 -16
  79. data/examples/trailers/config.ru +0 -34
  80. data/examples/trailers/falcon.rb +0 -8
  81. data/falcon.gemspec +0 -45
  82. data/gems/rack1.gemfile +0 -4
  83. data/gems/rack3.gemfile +0 -4
  84. data/lib/falcon/adapters/early_hints.rb +0 -49
  85. data/logo-square.afdesign +0 -0
  86. data/logo.afdesign +0 -0
  87. data/logo.svg +0 -107
  88. data/server.rb +0 -21
  89. data/tasks/benchmark.rake +0 -103
@@ -26,7 +26,13 @@ require 'async/container/controller'
26
26
 
27
27
  module Falcon
28
28
  module Controller
29
+ # A generic controller for serving an application.
30
+ # Hosts several {Services} based on the command configuration.
31
+ #
32
+ # The configuration is provided by {Command::Host} and is typically loaded from a `falcon.rb` file. See {Configuration#load_file} for more details.
29
33
  class Host < Async::Container::Controller
34
+ # Initialize the virtual controller.
35
+ # @parameter command [Command::Host] The user-specified command-line options.
30
36
  def initialize(command, **options)
31
37
  @command = command
32
38
 
@@ -36,20 +42,26 @@ module Falcon
36
42
  super(**options)
37
43
  end
38
44
 
45
+ # Create the controller as specified by the command.
46
+ # e.g. `Async::Container::Forked`.
39
47
  def create_container
40
48
  @command.container_class.new
41
49
  end
42
50
 
51
+ # Start all specified services.
43
52
  def start
44
53
  @services.start
45
54
 
46
55
  super
47
56
  end
48
57
 
58
+ # Setup all specified services into the container.
59
+ # @parameter container [Async::Container::Generic]
49
60
  def setup(container)
50
61
  @services.setup(container)
51
62
  end
52
63
 
64
+ # Stop all specified services.
53
65
  def stop(*)
54
66
  @services.stop
55
67
 
@@ -30,9 +30,14 @@ require_relative '../tls'
30
30
 
31
31
  module Falcon
32
32
  module Controller
33
+ # A controller for proxying requests.
33
34
  class Proxy < Serve
35
+ # The default SSL session identifier.
34
36
  DEFAULT_SESSION_ID = "falcon"
35
37
 
38
+ # Initialize the proxy controller.
39
+ # @parameter command [Command::Proxy] The user-specified command-line options.
40
+ # @parameter session_id [String] The SSL session identifier to use for the session cache.
36
41
  def initialize(command, session_id: DEFAULT_SESSION_ID, **options)
37
42
  super(command, **options)
38
43
 
@@ -40,14 +45,19 @@ module Falcon
40
45
  @hosts = {}
41
46
  end
42
47
 
48
+ # Load the {Middleware::Proxy} application with the specified hosts.
43
49
  def load_app
44
50
  return Middleware::Proxy.new(Middleware::BadRequest, @hosts)
45
51
  end
46
52
 
53
+ # The name of the controller which is used for the process title.
47
54
  def name
48
55
  "Falcon Proxy Server"
49
56
  end
50
57
 
58
+ # Look up the host context for the given hostname, and update the socket hostname if necessary.
59
+ # @parameter socket [OpenSSL::SSL::SSLSocket] The incoming connection.
60
+ # @parameter hostname [String] The negotiated hostname.
51
61
  def host_context(socket, hostname)
52
62
  if host = @hosts[hostname]
53
63
  Async.logger.debug(self) {"Resolving #{hostname} -> #{host}"}
@@ -62,6 +72,7 @@ module Falcon
62
72
  end
63
73
  end
64
74
 
75
+ # Generate an SSL context which delegates to {host_context} to multiplex based on hostname.
65
76
  def ssl_context
66
77
  @server_context ||= OpenSSL::SSL::SSLContext.new.tap do |context|
67
78
  context.servername_cb = Proc.new do |socket, hostname|
@@ -81,6 +92,7 @@ module Falcon
81
92
  end
82
93
  end
83
94
 
95
+ # The endpoint the server will bind to.
84
96
  def endpoint
85
97
  @command.endpoint.with(
86
98
  ssl_context: self.ssl_context,
@@ -88,6 +100,7 @@ module Falcon
88
100
  )
89
101
  end
90
102
 
103
+ # Builds a map of host redirections.
91
104
  def start
92
105
  configuration = @command.configuration
93
106
 
@@ -28,27 +28,34 @@ require_relative '../service/proxy'
28
28
 
29
29
  module Falcon
30
30
  module Controller
31
+ # A controller for redirecting requests.
31
32
  class Redirect < Serve
33
+ # Initialize the redirect controller.
34
+ # @parameter command [Command::Redirect] The user-specified command-line options.
32
35
  def initialize(command, **options)
33
36
  super(command, **options)
34
37
 
35
38
  @hosts = {}
36
39
  end
37
40
 
41
+ # Load the {Middleware::Redirect} application with the specified hosts.
38
42
  def load_app
39
43
  return Middleware::Redirect.new(Middleware::NotFound, @hosts, @command.redirect_endpoint)
40
44
  end
41
45
 
46
+ # The name of the controller which is used for the process title.
42
47
  def name
43
48
  "Falcon Redirect Server"
44
49
  end
45
50
 
51
+ # The endpoint the server will bind to.
46
52
  def endpoint
47
53
  @command.endpoint.with(
48
54
  reuse_address: true,
49
55
  )
50
56
  end
51
57
 
58
+ # Builds a map of host redirections.
52
59
  def start
53
60
  configuration = @command.configuration
54
61
 
@@ -29,7 +29,11 @@ require 'async/io/shared_endpoint'
29
29
 
30
30
  module Falcon
31
31
  module Controller
32
+ # A generic controller for serving an application.
33
+ # Uses {Server} for handling incoming requests.
32
34
  class Serve < Async::Container::Controller
35
+ # Initialize the server controller.
36
+ # @parameter command [Command::Serve] The user-specified command-line options.
33
37
  def initialize(command, **options)
34
38
  @command = command
35
39
 
@@ -40,34 +44,44 @@ module Falcon
40
44
  super(**options)
41
45
  end
42
46
 
47
+ # Create the controller as specified by the command.
48
+ # e.g. `Async::Container::Forked`.
43
49
  def create_container
44
50
  @command.container_class.new
45
51
  end
46
52
 
53
+ # The endpoint the server will bind to.
47
54
  def endpoint
48
55
  @command.endpoint
49
56
  end
50
57
 
58
+ # @returns [Protocol::HTTP::Middleware] an instance of the application to be served.
51
59
  def load_app
52
60
  @command.load_app
53
61
  end
54
62
 
63
+ # Prepare the bound endpoint for the server.
55
64
  def start
56
65
  @endpoint ||= self.endpoint
57
66
 
58
- @bound_endpoint = Async::Reactor.run do
67
+ @bound_endpoint = Async do
59
68
  Async::IO::SharedEndpoint.bound(@endpoint)
60
69
  end.wait
61
70
 
71
+ Async.logger.info(self) { "Starting #{name} on #{@endpoint.to_url}" }
72
+
62
73
  @debug_trap.ignore!
63
74
 
64
75
  super
65
76
  end
66
77
 
78
+ # The name of the controller which is used for the process title.
67
79
  def name
68
80
  "Falcon Server"
69
81
  end
70
82
 
83
+ # Setup the container with the application instance.
84
+ # @parameter container [Async::Container::Generic]
71
85
  def setup(container)
72
86
  container.run(name: self.name, restart: true, **@command.container_options) do |instance|
73
87
  Async do |task|
@@ -88,7 +102,7 @@ module Falcon
88
102
  end
89
103
  end
90
104
 
91
- server = Falcon::Server.new(app, @bound_endpoint, @endpoint.protocol, @endpoint.scheme)
105
+ server = Falcon::Server.new(app, @bound_endpoint, protocol: @endpoint.protocol, scheme: @endpoint.scheme)
92
106
 
93
107
  server.run
94
108
 
@@ -99,6 +113,7 @@ module Falcon
99
113
  end
100
114
  end
101
115
 
116
+ # Close the bound endpoint.
102
117
  def stop(*)
103
118
  @bound_endpoint&.close
104
119
 
@@ -24,7 +24,13 @@ require 'async/container/controller'
24
24
 
25
25
  module Falcon
26
26
  module Controller
27
+ # A controller which mananages several virtual hosts.
28
+ # Spawns instances of {Proxy} and {Redirect} to handle incoming requests.
29
+ #
30
+ # 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.
27
31
  class Virtual < Async::Container::Controller
32
+ # Initialize the virtual controller.
33
+ # @parameter command [Command::Virtual] The user-specified command-line options.
28
34
  def initialize(command, **options)
29
35
  @command = command
30
36
 
@@ -33,6 +39,8 @@ module Falcon
33
39
  trap(SIGHUP, &self.method(:reload))
34
40
  end
35
41
 
42
+ # Drop privileges according to the user and group of the specified path.
43
+ # @parameter path [String] The path to the application directory.
36
44
  def assume_privileges(path)
37
45
  stat = File.stat(path)
38
46
 
@@ -46,6 +54,10 @@ module Falcon
46
54
  }
47
55
  end
48
56
 
57
+ # Spawn an application instance from the specified path.
58
+ # @parameter path [String] The path to the application directory.
59
+ # @parameter container [Async::Container::Generic] The container to spawn into.
60
+ # @parameter options [Options] The options which are passed to `exec`.
49
61
  def spawn(path, container, **options)
50
62
  container.spawn(name: "Falcon Application", restart: true, key: path) do |instance|
51
63
  env = assume_privileges(path)
@@ -56,10 +68,15 @@ module Falcon
56
68
  end
57
69
  end
58
70
 
71
+ # The path to the falcon executable from this gem.
72
+ # @returns [String]
59
73
  def falcon_path
60
74
  File.expand_path("../../../bin/falcon", __dir__)
61
75
  end
62
76
 
77
+ # Setup the container with {Redirect} and {Proxy} child processes.
78
+ # These processes are gracefully restarted if they are already running.
79
+ # @parameter container [Async::Container::Generic]
63
80
  def setup(container)
64
81
  if proxy = container[:proxy]
65
82
  proxy.kill(:HUP)
@@ -24,11 +24,19 @@ require 'async/http/endpoint'
24
24
  require 'localhost/authority'
25
25
 
26
26
  module Falcon
27
+ # An HTTP-specific endpoint which adds localhost TLS.
27
28
  class Endpoint < Async::HTTP::Endpoint
29
+ # The SSL context to use, which invokes {build_ssl_context} if not otherwise specified.
30
+ # @returns [OpenSSL::SSL::SSLContext]
28
31
  def ssl_context
29
32
  @options[:ssl_context] || build_ssl_context
30
33
  end
31
34
 
35
+ # Build an appropriate SSL context for the given hostname.
36
+ #
37
+ # Uses {Localhost::Authority} to generate self-signed certficates.
38
+ #
39
+ # @returns [OpenSSL::SSL::SSLContext]
32
40
  def build_ssl_context(hostname = self.hostname)
33
41
  authority = Localhost::Authority.fetch(hostname)
34
42
 
@@ -1,6 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- # Copyright, 2019, by Samuel G. D. Williams. <http://www.codeotaku.com>
3
+ # Copyright, 2018, by Samuel G. D. Williams. <http://www.codeotaku.com>
4
4
  #
5
5
  # Permission is hereby granted, free of charge, to any person obtaining a copy
6
6
  # of this software and associated documentation files (the "Software"), to deal
@@ -20,8 +20,12 @@
20
20
  # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
21
  # THE SOFTWARE.
22
22
 
23
- add(:proxy) do
24
- endpoint {::Async::HTTP::Endpoint.parse(url)}
25
-
26
- service ::Falcon::Service::Proxy
23
+ require 'build/environment'
24
+
25
+ module Falcon
26
+ # Pre-defined environments for hosting web applications.
27
+ #
28
+ # See {Configuration::Loader#load} for more details.
29
+ module Environments
30
+ end
27
31
  end
@@ -0,0 +1,72 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Copyright, 2019, by Samuel G. D. Williams. <http://www.codeotaku.com>
4
+ #
5
+ # Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ # of this software and associated documentation files (the "Software"), to deal
7
+ # in the Software without restriction, including without limitation the rights
8
+ # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ # copies of the Software, and to permit persons to whom the Software is
10
+ # furnished to do so, subject to the following conditions:
11
+ #
12
+ # The above copyright notice and this permission notice shall be included in
13
+ # all copies or substantial portions of the Software.
14
+ #
15
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ # THE SOFTWARE.
22
+
23
+ require_relative '../proxy_endpoint'
24
+ require_relative '../server'
25
+
26
+ require_relative '../service/application'
27
+
28
+ # A general application environment.
29
+ # Suitable for use with any {Protocol::HTTP::Middleware}.
30
+ #
31
+ # @scope Falcon Environments
32
+ # @name application
33
+ environment(:application) do
34
+ # The middleware stack for the application.
35
+ # @attribute [Protocol::HTTP::Middleware]
36
+ middleware do
37
+ ::Protocol::HTTP::Middleware::HelloWorld
38
+ end
39
+
40
+ # The scheme to use to communicate with the application.
41
+ # @attribute [String]
42
+ scheme 'https'
43
+
44
+ # The protocol to use to communicate with the application.
45
+ #
46
+ # Typically one of {Async::HTTP::Protocol::HTTP1} or {Async::HTTP::Protocl::HTTP2}.
47
+ #
48
+ # @attribute [Async::HTTP::Protocol]
49
+ protocol {Async::HTTP::Protocol::HTTP2}
50
+
51
+ # The IPC path to use for communication with the application.
52
+ # @attribute [String]
53
+ ipc_path {::File.expand_path("application.ipc", root)}
54
+
55
+ # The endpoint that will be used for communicating with the application server.
56
+ # @attribute [Async::IO::Endpoint]
57
+ endpoint do
58
+ ::Falcon::ProxyEndpoint.unix(ipc_path,
59
+ protocol: protocol,
60
+ scheme: scheme,
61
+ authority: authority
62
+ )
63
+ end
64
+
65
+ # The service class to use for the application.
66
+ # @attribute [Class]
67
+ service ::Falcon::Service::Application
68
+
69
+ # Number of instances to start.
70
+ # @attribute [Integer | nil]
71
+ count nil
72
+ end
@@ -20,27 +20,28 @@
20
20
  # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
21
  # THE SOFTWARE.
22
22
 
23
- require_relative '../proxy_endpoint'
24
- require_relative '../server'
23
+ load(:tls)
25
24
 
26
- require_relative '../service/application'
27
-
28
- add(:application) do
29
- middleware do
30
- ::Protocol::HTTP::Middleware::HelloWorld
31
- end
25
+ # A Lets Encrypt SSL context environment.
26
+ #
27
+ # Derived from {.tls}.
28
+ #
29
+ # @scope Falcon Environments
30
+ # @name lets_encrypt_tls
31
+ environment(:lets_encrypt_tls, :tls) do
32
+ # The Lets Encrypt certificate store path.
33
+ # @parameter [String]
34
+ lets_encrypt_root '/etc/letsencrypt/live'
32
35
 
33
- scheme 'https'
34
- protocol {Async::HTTP::Protocol::HTTP2}
35
- ipc_path {::File.expand_path("application.ipc", root)}
36
-
37
- endpoint do
38
- ::Falcon::ProxyEndpoint.unix(ipc_path,
39
- protocol: protocol,
40
- scheme: scheme,
41
- authority: authority
42
- )
36
+ # The public certificate path.
37
+ # @attribute [String]
38
+ ssl_certificate_path do
39
+ File.join(lets_encrypt_root, authority, "fullchain.pem")
43
40
  end
44
41
 
45
- service ::Falcon::Service::Application
46
- end
42
+ # The private key path.
43
+ # @attribute [String]
44
+ ssl_private_key_path do
45
+ File.join(lets_encrypt_root, authority, "privkey.pem")
46
+ end
47
+ end
@@ -20,11 +20,18 @@
20
20
  # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
21
  # THE SOFTWARE.
22
22
 
23
- load(:tls)
24
-
25
- add(:lets_encrypt_tls, :tls) do
26
- lets_encrypt_root '/etc/letsencrypt/live'
23
+ # A HTTP proxy environment.
24
+ #
25
+ # Derived from {.application}.
26
+ #
27
+ # @scope Falcon Environments
28
+ # @name rack
29
+ environment(:proxy) do
30
+ # The upstream endpoint that will handle incoming requests.
31
+ # @attribute [Async::HTTP::Endpoint]
32
+ endpoint {::Async::HTTP::Endpoint.parse(url)}
27
33
 
28
- ssl_certificate_path {File.join(lets_encrypt_root, authority, "fullchain.pem")}
29
- ssl_private_key_path {File.join(lets_encrypt_root, authority, "privkey.pem")}
34
+ # The service class to use for the proxy.
35
+ # @attribute [Class]
36
+ service ::Falcon::Service::Proxy
30
37
  end