falcon 0.34.5 → 0.35.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (51) hide show
  1. checksums.yaml +4 -4
  2. data/.travis.yml +3 -2
  3. data/Gemfile +2 -0
  4. data/bin/falcon +1 -1
  5. data/bin/falcon-host +1 -1
  6. data/examples/beer/config.ru +25 -23
  7. data/examples/beer/falcon.rb +2 -0
  8. data/examples/hello/config.ru +1 -1
  9. data/examples/hello/falcon.rb +14 -2
  10. data/examples/hello/preload.rb +6 -0
  11. data/examples/trailers/config.ru +33 -0
  12. data/examples/trailers/falcon.rb +7 -0
  13. data/falcon.gemspec +3 -1
  14. data/lib/falcon.rb +0 -4
  15. data/lib/falcon/adapters/response.rb +2 -2
  16. data/lib/falcon/command.rb +3 -53
  17. data/lib/falcon/command/host.rb +22 -39
  18. data/lib/falcon/command/paths.rb +45 -0
  19. data/lib/falcon/{host.rb → command/proxy.rb} +39 -45
  20. data/lib/falcon/command/redirect.rb +72 -0
  21. data/lib/falcon/command/serve.rb +28 -58
  22. data/lib/falcon/command/supervisor.rb +5 -5
  23. data/lib/falcon/command/top.rb +79 -0
  24. data/lib/falcon/command/virtual.rb +18 -53
  25. data/lib/falcon/configuration.rb +1 -1
  26. data/lib/falcon/{configurations/host.rb → configuration/application.rb} +13 -11
  27. data/lib/falcon/{configurations → configuration}/lets_encrypt_tls.rb +0 -0
  28. data/lib/falcon/{configurations → configuration}/proxy.rb +2 -2
  29. data/lib/falcon/{configurations → configuration}/rack.rb +2 -2
  30. data/lib/falcon/{configurations → configuration}/self_signed_tls.rb +0 -0
  31. data/lib/falcon/{configurations → configuration}/supervisor.rb +2 -2
  32. data/lib/falcon/{configurations → configuration}/tls.rb +0 -0
  33. data/lib/falcon/controller/host.rb +58 -0
  34. data/lib/falcon/controller/proxy.rb +102 -0
  35. data/lib/falcon/{service.rb → controller/redirect.rb} +37 -24
  36. data/lib/falcon/controller/serve.rb +112 -0
  37. data/lib/falcon/controller/virtual.rb +89 -0
  38. data/lib/falcon/middleware/proxy.rb +143 -0
  39. data/lib/falcon/{redirection.rb → middleware/redirect.rb} +31 -29
  40. data/lib/falcon/proxy_endpoint.rb +1 -1
  41. data/lib/falcon/service/application.rb +113 -0
  42. data/lib/falcon/service/generic.rb +53 -0
  43. data/lib/falcon/service/supervisor.rb +95 -0
  44. data/lib/falcon/services.rb +32 -5
  45. data/lib/falcon/version.rb +1 -1
  46. data/lib/rack/handler/falcon.rb +2 -1
  47. data/logo-square.afdesign +0 -0
  48. metadata +43 -17
  49. data/lib/falcon/hosts.rb +0 -135
  50. data/lib/falcon/proxy.rb +0 -141
  51. data/lib/falcon/supervisor.rb +0 -106
@@ -82,7 +82,7 @@ module Falcon
82
82
  features.each do |feature|
83
83
  next if @loaded.include?(feature)
84
84
 
85
- relative_path = File.join(__dir__, "configurations", "#{feature}.rb")
85
+ relative_path = File.join(__dir__, "configuration", "#{feature}.rb")
86
86
 
87
87
  self.instance_eval(File.read(relative_path), relative_path)
88
88
 
@@ -21,24 +21,26 @@
21
21
  require_relative '../proxy_endpoint'
22
22
  require_relative '../server'
23
23
 
24
- add(:host) do
24
+ require_relative '../service/application'
25
+
26
+ add(:application) do
25
27
  middleware do
26
28
  ::Protocol::HTTP::Middleware::HelloWorld
27
29
  end
28
30
 
29
- ipc_path {::File.expand_path("server.ipc", root)}
30
- protocol {Async::HTTP::Protocol::HTTP2}
31
31
  scheme 'https'
32
+ protocol {Async::HTTP::Protocol::HTTP2}
33
+ ipc_path {::File.expand_path("application.ipc", root)}
32
34
 
33
- endpoint {::Falcon::ProxyEndpoint.unix(ipc_path, protocol: protocol, scheme: scheme, authority: authority)}
34
-
35
- bound_endpoint do
36
- Async::Reactor.run do
37
- Async::IO::SharedEndpoint.bound(endpoint)
38
- end.wait
35
+ endpoint do
36
+ ::Falcon::ProxyEndpoint.unix(ipc_path,
37
+ protocol: protocol,
38
+ scheme: scheme,
39
+ authority: authority
40
+ )
39
41
  end
40
42
 
41
- server do
42
- ::Falcon::Server.new(middleware, bound_endpoint, protocol, scheme)
43
+ service do
44
+ ::Falcon::Service::Application
43
45
  end
44
46
  end
@@ -18,8 +18,8 @@
18
18
  # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
19
19
  # THE SOFTWARE.
20
20
 
21
- load(:host)
21
+ load(:application)
22
22
 
23
- add(:proxy, :host) do
23
+ add(:proxy, :application) do
24
24
  endpoint {::Async::HTTP::Endpoint.parse(url)}
25
25
  end
@@ -18,9 +18,9 @@
18
18
  # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
19
19
  # THE SOFTWARE.
20
20
 
21
- load :host
21
+ load :application
22
22
 
23
- add(:rack, :host) do
23
+ add(:rack, :application) do
24
24
  config_path {::File.expand_path("config.ru", root)}
25
25
 
26
26
  middleware do
@@ -18,7 +18,7 @@
18
18
  # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
19
19
  # THE SOFTWARE.
20
20
 
21
- require_relative '../supervisor'
21
+ require_relative '../service/supervisor'
22
22
 
23
23
  add(:supervisor) do
24
24
  start true
@@ -30,6 +30,6 @@ add(:supervisor) do
30
30
  endpoint {Async::IO::Endpoint.unix(ipc_path)}
31
31
 
32
32
  service do
33
- ::Falcon::Supervisor.new(endpoint)
33
+ ::Falcon::Service::Supervisor
34
34
  end
35
35
  end
@@ -0,0 +1,58 @@
1
+ # Copyright, 2019, by Samuel G. D. Williams. <http://www.codeotaku.com>
2
+ #
3
+ # Permission is hereby granted, free of charge, to any person obtaining a copy
4
+ # of this software and associated documentation files (the "Software"), to deal
5
+ # in the Software without restriction, including without limitation the rights
6
+ # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7
+ # copies of the Software, and to permit persons to whom the Software is
8
+ # furnished to do so, subject to the following conditions:
9
+ #
10
+ # The above copyright notice and this permission notice shall be included in
11
+ # all copies or substantial portions of the Software.
12
+ #
13
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14
+ # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15
+ # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16
+ # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17
+ # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18
+ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
19
+ # THE SOFTWARE.
20
+
21
+ require_relative '../services'
22
+
23
+ require 'async/container/controller'
24
+
25
+ module Falcon
26
+ module Controller
27
+ class Host < Async::Container::Controller
28
+ def initialize(command, **options)
29
+ @command = command
30
+
31
+ @configuration = command.configuration
32
+ @services = Services.new(@configuration)
33
+
34
+ super(**options)
35
+ end
36
+
37
+ def create_container
38
+ @command.container_class.new
39
+ end
40
+
41
+ def start
42
+ @services.start
43
+
44
+ super
45
+ end
46
+
47
+ def setup(container)
48
+ @services.setup(container)
49
+ end
50
+
51
+ def stop(*)
52
+ @services.stop
53
+
54
+ super
55
+ end
56
+ end
57
+ end
58
+ end
@@ -0,0 +1,102 @@
1
+ # Copyright, 2018, by Samuel G. D. Williams. <http://www.codeotaku.com>
2
+ #
3
+ # Permission is hereby granted, free of charge, to any person obtaining a copy
4
+ # of this software and associated documentation files (the "Software"), to deal
5
+ # in the Software without restriction, including without limitation the rights
6
+ # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7
+ # copies of the Software, and to permit persons to whom the Software is
8
+ # furnished to do so, subject to the following conditions:
9
+ #
10
+ # The above copyright notice and this permission notice shall be included in
11
+ # all copies or substantial portions of the Software.
12
+ #
13
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14
+ # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15
+ # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16
+ # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17
+ # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18
+ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
19
+ # THE SOFTWARE.
20
+
21
+ require 'async/container/controller'
22
+
23
+ require_relative 'serve'
24
+ require_relative '../middleware/proxy'
25
+
26
+ module Falcon
27
+ module Controller
28
+ class Proxy < Serve
29
+ SERVER_CIPHERS = "EECDH+CHACHA20:EECDH+AES128:RSA+AES128:EECDH+AES256:RSA+AES256:EECDH+3DES:RSA+3DES:!MD5".freeze
30
+ DEFAULT_SESSION_ID = "falcon"
31
+
32
+ def initialize(command, session_id: DEFAULT_SESSION_ID, **options)
33
+ super(command, **options)
34
+
35
+ @session_id = session_id
36
+ @hosts = {}
37
+ end
38
+
39
+ def load_app
40
+ return Middleware::Proxy.new(Middleware::BadRequest, @hosts)
41
+ end
42
+
43
+ def name
44
+ "Falcon Proxy Server"
45
+ end
46
+
47
+ def host_context(socket, hostname)
48
+ if host = @hosts[hostname]
49
+ Async.logger.debug(self) {"Resolving #{hostname} -> #{host}"}
50
+
51
+ socket.hostname = hostname
52
+
53
+ return host.ssl_context
54
+ else
55
+ Async.logger.warn(self) {"Unable to resolve #{hostname}!"}
56
+
57
+ return nil
58
+ end
59
+ end
60
+
61
+ def ssl_context
62
+ @server_context ||= OpenSSL::SSL::SSLContext.new.tap do |context|
63
+ context.servername_cb = Proc.new do |socket, hostname|
64
+ self.host_context(socket, hostname)
65
+ end
66
+
67
+ context.session_id_context = @session_id
68
+
69
+ context.set_params(
70
+ ciphers: SERVER_CIPHERS,
71
+ verify_mode: OpenSSL::SSL::VERIFY_NONE,
72
+ )
73
+
74
+ context.setup
75
+ end
76
+ end
77
+
78
+ def endpoint
79
+ @command.endpoint.with(
80
+ ssl_context: self.ssl_context,
81
+ reuse_address: true,
82
+ )
83
+ end
84
+
85
+ def start
86
+ configuration = @command.configuration
87
+
88
+ services = Services.new(configuration)
89
+
90
+ @hosts = {}
91
+
92
+ services.each do |service|
93
+ if service.is_a?(Service::Application)
94
+ @hosts[service.authority] = service
95
+ end
96
+ end
97
+
98
+ super
99
+ end
100
+ end
101
+ end
102
+ end
@@ -18,35 +18,48 @@
18
18
  # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
19
19
  # THE SOFTWARE.
20
20
 
21
- require 'async/io/endpoint'
22
-
23
- require_relative 'proxy'
24
- require_relative 'redirection'
25
-
26
- require 'async/container'
27
21
  require 'async/container/controller'
28
- require 'async/http/endpoint'
22
+
23
+ require_relative 'serve'
24
+ require_relative '../middleware/redirect'
29
25
 
30
26
  module Falcon
31
- class Service
32
- def initialize(environment)
33
- @environment = environment
34
- @evaluator = @environment.evaluator
35
- end
36
-
37
- def name
38
- @evaluator.name
39
- end
40
-
41
- def run(container)
42
- container.run(name: self.name, count: 1, restart: true) do |task, instance|
43
- Async.logger.info(self) {"Starting #{self.name}..."}
27
+ module Controller
28
+ class Redirect < Serve
29
+ def initialize(command, **options)
30
+ super(command, **options)
44
31
 
45
- if service = @evaluator.service
46
- service.run
47
- else
48
- Async.logger.error(self) {"Could not determine how to start service: #{@environment.inspect}"}
32
+ @hosts = {}
33
+ end
34
+
35
+ def load_app
36
+ return Middleware::Redirect.new(Middleware::NotFound, @hosts, @command.redirect_endpoint)
37
+ end
38
+
39
+ def name
40
+ "Falcon Redirect Server"
41
+ end
42
+
43
+ def endpoint
44
+ @command.endpoint.with(
45
+ reuse_address: true,
46
+ )
47
+ end
48
+
49
+ def start
50
+ configuration = @command.configuration
51
+
52
+ services = Services.new(configuration)
53
+
54
+ @hosts = {}
55
+
56
+ services.each do |service|
57
+ if service.is_a?(Service::Application)
58
+ @hosts[service.authority] = service
59
+ end
49
60
  end
61
+
62
+ super
50
63
  end
51
64
  end
52
65
  end
@@ -0,0 +1,112 @@
1
+ # Copyright, 2019, by Samuel G. D. Williams. <http://www.codeotaku.com>
2
+ #
3
+ # Permission is hereby granted, free of charge, to any person obtaining a copy
4
+ # of this software and associated documentation files (the "Software"), to deal
5
+ # in the Software without restriction, including without limitation the rights
6
+ # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7
+ # copies of the Software, and to permit persons to whom the Software is
8
+ # furnished to do so, subject to the following conditions:
9
+ #
10
+ # The above copyright notice and this permission notice shall be included in
11
+ # all copies or substantial portions of the Software.
12
+ #
13
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14
+ # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15
+ # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16
+ # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17
+ # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18
+ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
19
+ # THE SOFTWARE.
20
+
21
+ require_relative '../server'
22
+
23
+ require 'async/container/controller'
24
+ require 'async/io/trap'
25
+
26
+ require 'async/io/shared_endpoint'
27
+
28
+ module Falcon
29
+ module Controller
30
+ class Serve < Async::Container::Controller
31
+ def initialize(command, **options)
32
+ @command = command
33
+
34
+ @endpoint = nil
35
+ @bound_endpoint = nil
36
+ @debug_trap = Async::IO::Trap.new(:USR1)
37
+
38
+ super(**options)
39
+ end
40
+
41
+ def create_container
42
+ @command.container_class.new
43
+ end
44
+
45
+ def endpoint
46
+ @command.endpoint
47
+ end
48
+
49
+ def load_app
50
+ @command.load_app
51
+ end
52
+
53
+ def start
54
+ @endpoint ||= self.endpoint
55
+
56
+ @bound_endpoint = Async::Reactor.run do
57
+ Async::IO::SharedEndpoint.bound(@endpoint)
58
+ end.wait
59
+
60
+ @debug_trap.ignore!
61
+
62
+ super
63
+ end
64
+
65
+ def name
66
+ "Falcon Server"
67
+ end
68
+
69
+ def setup(container)
70
+ app, _ = self.load_app
71
+
72
+ if GC.respond_to?(:compact)
73
+ GC.compact
74
+ end
75
+
76
+ container.run(name: self.name, restart: true, **@command.container_options) do |instance|
77
+ Async do |task|
78
+ task.async do
79
+ if @debug_trap.install!
80
+ Async.logger.info(instance) do
81
+ "- Per-process status: kill -USR1 #{Process.pid}"
82
+ end
83
+ end
84
+
85
+ @debug_trap.trap do
86
+ Async.logger.info(self) do |buffer|
87
+ task.reactor.print_hierarchy(buffer)
88
+ end
89
+ end
90
+ end
91
+
92
+ server = Falcon::Server.new(app, @bound_endpoint, @endpoint.protocol, @endpoint.scheme)
93
+
94
+ server.run
95
+
96
+ instance.ready!
97
+
98
+ task.children.each(&:wait)
99
+ end
100
+ end
101
+ end
102
+
103
+ def stop(*)
104
+ @bound_endpoint&.close
105
+
106
+ @debug_trap.default!
107
+
108
+ super
109
+ end
110
+ end
111
+ end
112
+ end
@@ -0,0 +1,89 @@
1
+ # Copyright, 2018, by Samuel G. D. Williams. <http://www.codeotaku.com>
2
+ #
3
+ # Permission is hereby granted, free of charge, to any person obtaining a copy
4
+ # of this software and associated documentation files (the "Software"), to deal
5
+ # in the Software without restriction, including without limitation the rights
6
+ # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7
+ # copies of the Software, and to permit persons to whom the Software is
8
+ # furnished to do so, subject to the following conditions:
9
+ #
10
+ # The above copyright notice and this permission notice shall be included in
11
+ # all copies or substantial portions of the Software.
12
+ #
13
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14
+ # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15
+ # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16
+ # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17
+ # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18
+ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
19
+ # THE SOFTWARE.
20
+
21
+ require 'async/container/controller'
22
+
23
+ module Falcon
24
+ module Controller
25
+ class Virtual < Async::Container::Controller
26
+ def initialize(command, **options)
27
+ @command = command
28
+
29
+ super(**options)
30
+
31
+ trap(SIGHUP, &self.method(:reload))
32
+ end
33
+
34
+ def assume_privileges(path)
35
+ stat = File.stat(path)
36
+
37
+ Process::GID.change_privilege(stat.gid)
38
+ Process::UID.change_privilege(stat.uid)
39
+
40
+ home = Etc.getpwuid(stat.uid).dir
41
+
42
+ return {
43
+ 'HOME' => home,
44
+ }
45
+ end
46
+
47
+ def spawn(path, container, **options)
48
+ container.spawn(name: "Falcon Application", restart: true, key: path) do |instance|
49
+ env = assume_privileges(path)
50
+
51
+ instance.exec(env,
52
+ "bundle", "exec", "--keep-file-descriptors",
53
+ path, ready: false, **options)
54
+ end
55
+ end
56
+
57
+ def falcon_path
58
+ File.expand_path("../../../bin/falcon", __dir__)
59
+ end
60
+
61
+ def setup(container)
62
+ if proxy = container[:proxy]
63
+ proxy.kill(:HUP)
64
+ end
65
+
66
+ if redirect = container[:redirect]
67
+ redirect.kill(:HUP)
68
+ end
69
+
70
+ container.reload do
71
+ @command.resolved_paths do |path|
72
+ path = File.expand_path(path)
73
+ root = File.dirname(path)
74
+
75
+ spawn(path, container, chdir: root)
76
+ end
77
+
78
+ container.spawn(name: "Falcon Redirector", restart: true, key: :redirect) do |instance|
79
+ instance.exec(falcon_path, "redirect", "--bind", @command.bind_insecure, "--redirect", @command.bind_secure, *@command.paths, ready: false)
80
+ end
81
+
82
+ container.spawn(name: "Falcon Proxy", restart: true, key: :proxy) do |instance|
83
+ instance.exec(falcon_path, "proxy", "--bind", @command.bind_secure, *@command.paths, ready: false)
84
+ end
85
+ end
86
+ end
87
+ end
88
+ end
89
+ end