falcon 0.25.0 → 0.26.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.
- checksums.yaml +4 -4
- data/examples/beer/config.ru +5 -1
- data/examples/push/client.rb +28 -0
- data/examples/push/config.ru +9 -1
- data/examples/push/script.js +2 -0
- data/falcon.gemspec +10 -9
- data/lib/falcon/command/virtual.rb +9 -66
- data/lib/falcon/configuration.rb +195 -0
- data/lib/falcon/hosts.rb +78 -140
- data/lib/falcon/proxy.rb +13 -4
- data/lib/falcon/redirection.rb +9 -4
- data/lib/falcon/version.rb +1 -1
- data/server.rb +13 -29
- metadata +34 -4
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: fab1eafce5a511075f7339beabec2ef2364693ffeb050b84e90a8223e3faf614
|
4
|
+
data.tar.gz: 6f2512f83e06c16ab5600c66ceefc0d2ea910e6f8a53df3d8d919600c75671af
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 0ce28b54426aa8ccbba9801e18c548a284ffbc8c64b54a9cf03b6d3af75ed11b029b999de1e923509b8e16ff525c465ac82695e6a37c9a207f002447333d5c83
|
7
|
+
data.tar.gz: 916bf5e17a59d440c633471a809fad988693f050d8912dd236150983bbb81a4fd2235406418f5a740072aec8e21ed8844593c123d47533d6b6255ddaa7221d14
|
data/examples/beer/config.ru
CHANGED
@@ -1,6 +1,7 @@
|
|
1
1
|
#!/usr/bin/env falcon --verbose serve -c
|
2
2
|
|
3
3
|
require 'rack'
|
4
|
+
require 'trenni'
|
4
5
|
|
5
6
|
def bottles(n)
|
6
7
|
n == 1 ? "#{n} bottle" : "#{n} bottles"
|
@@ -27,7 +28,7 @@ run lambda {|env|
|
|
27
28
|
count.downto(1) do |i|
|
28
29
|
task.annotate "bottles of beer #{i}"
|
29
30
|
|
30
|
-
|
31
|
+
Async.logger.info(body) {"#{bottles(i)} of beer on the wall..."}
|
31
32
|
body.write("<p>#{bottles(i)} of beer on the wall, ")
|
32
33
|
task.sleep(0.1)
|
33
34
|
body.write("#{bottles(i)} of beer, ")
|
@@ -39,6 +40,9 @@ run lambda {|env|
|
|
39
40
|
body.write("<script>var child; while (child = document.body.firstChild) child.remove();</script>")
|
40
41
|
end
|
41
42
|
|
43
|
+
code = File.read(__FILE__)
|
44
|
+
body.write("<h1>Source Code</h1>")
|
45
|
+
body.write("<pre><code>#{Trenni::Markup.escape_string code}</code></pre>")
|
42
46
|
body.write("</body></html>")
|
43
47
|
rescue
|
44
48
|
puts "Remote end closed connection: #{$!}"
|
@@ -0,0 +1,28 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require 'async'
|
4
|
+
require 'async/http/url_endpoint'
|
5
|
+
require 'async/http/client'
|
6
|
+
|
7
|
+
Async do
|
8
|
+
endpoint = Async::HTTP::URLEndpoint.parse("https://localhost:9292")
|
9
|
+
client = Async::HTTP::Client.new(endpoint, Async::HTTP::Protocol::HTTP2::WithPush)
|
10
|
+
|
11
|
+
response = client.get("/index.html")
|
12
|
+
|
13
|
+
puts response.status
|
14
|
+
puts response.read
|
15
|
+
puts
|
16
|
+
|
17
|
+
while promise = response.promises.dequeue
|
18
|
+
promise.wait
|
19
|
+
|
20
|
+
puts "** Promise: #{promise.request.path} **"
|
21
|
+
puts promise.read
|
22
|
+
puts
|
23
|
+
end
|
24
|
+
ensure
|
25
|
+
client.close
|
26
|
+
end
|
27
|
+
|
28
|
+
puts "Done"
|
data/examples/push/config.ru
CHANGED
@@ -1,3 +1,4 @@
|
|
1
|
+
#!/usr/bin/env falcon --verbose serve --concurrency 1 --config
|
1
2
|
|
2
3
|
class EarlyHints
|
3
4
|
def initialize(app)
|
@@ -5,7 +6,12 @@ class EarlyHints
|
|
5
6
|
end
|
6
7
|
|
7
8
|
def call(env)
|
8
|
-
|
9
|
+
path = env['PATH_INFO']
|
10
|
+
early_hints = early_hints = env['rack.early_hints']
|
11
|
+
|
12
|
+
Async.logger.debug("path: #{path} #{early_hints}")
|
13
|
+
|
14
|
+
if path == "/index.html" and early_hints
|
9
15
|
early_hints.push("/style.css")
|
10
16
|
early_hints.push("/script.js")
|
11
17
|
end
|
@@ -15,5 +21,7 @@ class EarlyHints
|
|
15
21
|
end
|
16
22
|
|
17
23
|
use EarlyHints
|
24
|
+
|
18
25
|
use Rack::Static, :urls => [""], :root => __dir__, :index => 'index.html'
|
26
|
+
|
19
27
|
run lambda{|env| [404, [], ["Not Found"]]}
|
data/examples/push/script.js
CHANGED
data/falcon.gemspec
CHANGED
@@ -16,21 +16,22 @@ Gem::Specification.new do |spec|
|
|
16
16
|
spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
|
17
17
|
spec.require_paths = ["lib"]
|
18
18
|
|
19
|
-
spec.add_dependency
|
19
|
+
spec.add_dependency "http-protocol", "~> 0.15"
|
20
20
|
|
21
|
-
spec.add_dependency
|
22
|
-
spec.add_dependency
|
23
|
-
spec.add_dependency
|
24
|
-
spec.add_dependency
|
21
|
+
spec.add_dependency "async", "~> 1.13"
|
22
|
+
spec.add_dependency "async-io", "~> 1.20"
|
23
|
+
spec.add_dependency "async-http", "~> 0.38.0"
|
24
|
+
spec.add_dependency "async-container", "~> 0.10.0"
|
25
25
|
|
26
|
-
spec.add_dependency
|
26
|
+
spec.add_dependency "rack", ">= 1.0"
|
27
27
|
|
28
|
-
spec.add_dependency
|
29
|
-
spec.add_dependency
|
28
|
+
spec.add_dependency 'samovar', "~> 1.3"
|
29
|
+
spec.add_dependency 'localhost', "~> 1.1"
|
30
|
+
spec.add_dependency 'build-environment', '~> 1.6'
|
30
31
|
|
32
|
+
spec.add_development_dependency "trenni"
|
31
33
|
spec.add_development_dependency "async-rspec", "~> 1.7"
|
32
34
|
spec.add_development_dependency "async-websocket", "~> 0.6.0"
|
33
|
-
|
34
35
|
spec.add_development_dependency "async-process", "~> 1.1"
|
35
36
|
|
36
37
|
spec.add_development_dependency "covered", "~> 0.10"
|
@@ -21,6 +21,7 @@
|
|
21
21
|
require_relative '../server'
|
22
22
|
require_relative '../endpoint'
|
23
23
|
require_relative '../hosts'
|
24
|
+
require_relative '../configuration'
|
24
25
|
|
25
26
|
require 'async/container'
|
26
27
|
require 'async/container/controller'
|
@@ -40,80 +41,22 @@ module Falcon
|
|
40
41
|
self.description = "Run an HTTP server with one or more virtual hosts."
|
41
42
|
|
42
43
|
options do
|
43
|
-
option '--bind-insecure <address>', "Bind redirection to the given hostname/address", default: "http://
|
44
|
-
option '--bind-secure <address>', "Bind proxy to the given hostname/address", default: "https://
|
45
|
-
|
46
|
-
option '--self-signed', "Use self-signed SSL", default: false
|
47
|
-
end
|
48
|
-
|
49
|
-
many :sites
|
50
|
-
|
51
|
-
CONFIG_RU = "config.ru"
|
52
|
-
|
53
|
-
def load_app(path, verbose)
|
54
|
-
config = File.join(path, CONFIG_RU)
|
55
|
-
|
56
|
-
rack_app, options = Rack::Builder.parse_file(config)
|
57
|
-
|
58
|
-
return Server.middleware(rack_app, verbose: verbose), options
|
44
|
+
option '--bind-insecure <address>', "Bind redirection to the given hostname/address", default: "http://[::]"
|
45
|
+
option '--bind-secure <address>', "Bind proxy to the given hostname/address", default: "https://[::]"
|
59
46
|
end
|
60
47
|
|
61
|
-
|
62
|
-
Async::HTTP::Client.new(client_endpoint)
|
63
|
-
end
|
48
|
+
many :paths
|
64
49
|
|
65
50
|
def run(verbose = false)
|
66
|
-
|
67
|
-
root = Dir.pwd
|
68
|
-
|
69
|
-
sites.each do |path|
|
70
|
-
name = File.basename(path)
|
71
|
-
|
72
|
-
hosts.add(name) do |host|
|
73
|
-
host.app_root = File.expand_path(path, root)
|
74
|
-
|
75
|
-
if @options[:self_signed]
|
76
|
-
host.self_signed!(name)
|
77
|
-
else
|
78
|
-
host.ssl_certificate_path = File.join(path, "ssl", "fullchain.pem")
|
79
|
-
host.ssl_key_path = File.join(path, "ssl", "privkey.pem")
|
80
|
-
end
|
81
|
-
end
|
82
|
-
end
|
83
|
-
|
84
|
-
controller = Async::Container::Controller.new
|
85
|
-
|
86
|
-
hosts.each do |name, host|
|
87
|
-
if container = host.start
|
88
|
-
controller << container
|
89
|
-
end
|
90
|
-
end
|
91
|
-
|
92
|
-
controller << Async::Container::Forked.new do |task|
|
93
|
-
proxy = hosts.proxy
|
94
|
-
secure_endpoint = Async::HTTP::URLEndpoint.parse(@options[:bind_secure], ssl_context: hosts.ssl_context)
|
95
|
-
|
96
|
-
Process.setproctitle("Falcon Proxy")
|
97
|
-
|
98
|
-
proxy_server = Falcon::Server.new(proxy, secure_endpoint)
|
99
|
-
|
100
|
-
proxy_server.run
|
101
|
-
end
|
51
|
+
configuration = Configuration.new(verbose)
|
102
52
|
|
103
|
-
|
104
|
-
|
105
|
-
insecure_endpoint = Async::HTTP::URLEndpoint.parse(@options[:bind_insecure])
|
106
|
-
|
107
|
-
Process.setproctitle("Falcon Redirector")
|
108
|
-
|
109
|
-
redirection_server = Falcon::Server.new(redirection, insecure_endpoint)
|
110
|
-
|
111
|
-
redirection_server.run
|
53
|
+
@paths.each do |path|
|
54
|
+
configuration.load_file(path)
|
112
55
|
end
|
113
56
|
|
114
|
-
|
57
|
+
hosts = Hosts.new(configuration)
|
115
58
|
|
116
|
-
return
|
59
|
+
return hosts.run(@options)
|
117
60
|
end
|
118
61
|
|
119
62
|
def invoke(parent)
|
@@ -0,0 +1,195 @@
|
|
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 'build/environment'
|
22
|
+
require 'async/io/unix_endpoint'
|
23
|
+
|
24
|
+
module Falcon
|
25
|
+
class ProxyEndpoint < Async::IO::Endpoint
|
26
|
+
def initialize(endpoint, **options)
|
27
|
+
super(**options)
|
28
|
+
|
29
|
+
@endpoint = endpoint
|
30
|
+
end
|
31
|
+
|
32
|
+
attr :endpoint
|
33
|
+
|
34
|
+
def protocol
|
35
|
+
@options[:protocol]
|
36
|
+
end
|
37
|
+
|
38
|
+
def scheme
|
39
|
+
@options[:scheme]
|
40
|
+
end
|
41
|
+
|
42
|
+
def authority
|
43
|
+
@options[:authority]
|
44
|
+
end
|
45
|
+
|
46
|
+
def connect(&block)
|
47
|
+
@endpoint.connect(&block)
|
48
|
+
end
|
49
|
+
|
50
|
+
def bind(&block)
|
51
|
+
@endpoint.bind(&block)
|
52
|
+
end
|
53
|
+
|
54
|
+
def each
|
55
|
+
return to_enum unless block_given?
|
56
|
+
|
57
|
+
@endpoint.each do |endpoint|
|
58
|
+
yield self.class.new(endpoint, @options)
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
def self.unix(path, **options)
|
63
|
+
self.new(::Async::IO::Endpoint.unix(path), **options)
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
class Configuration
|
68
|
+
def initialize(verbose = false)
|
69
|
+
@environments = {}
|
70
|
+
@verbose = verbose
|
71
|
+
|
72
|
+
add(:ssl) do
|
73
|
+
ssl_session_id {"falcon"}
|
74
|
+
end
|
75
|
+
|
76
|
+
add(:host, :ssl) do
|
77
|
+
ssl_certificate_path {File.expand_path("ssl/certificate.pem", root)}
|
78
|
+
ssl_certificate {OpenSSL::X509::Certificate.new(File.read(ssl_certificate_path))}
|
79
|
+
|
80
|
+
ssl_private_key_path {File.expand_path("ssl/private.key", root)}
|
81
|
+
ssl_private_key {OpenSSL::PKey::RSA.new(File.read(ssl_private_key_path))}
|
82
|
+
|
83
|
+
ssl_context do
|
84
|
+
OpenSSL::SSL::SSLContext.new.tap do |context|
|
85
|
+
context.cert = ssl_certificate
|
86
|
+
context.key = ssl_private_key
|
87
|
+
|
88
|
+
context.session_id_context = ssl_session_id
|
89
|
+
|
90
|
+
context.set_params
|
91
|
+
|
92
|
+
context.setup
|
93
|
+
end
|
94
|
+
end
|
95
|
+
end
|
96
|
+
|
97
|
+
add(:self_signed, :ssl) do
|
98
|
+
ssl_context do
|
99
|
+
contexts = Localhost::Authority.fetch(authority)
|
100
|
+
|
101
|
+
contexts.server_context.tap do |context|
|
102
|
+
context.alpn_select_cb = lambda do |protocols|
|
103
|
+
if protocols.include? "h2"
|
104
|
+
return "h2"
|
105
|
+
elsif protocols.include? "http/1.1"
|
106
|
+
return "http/1.1"
|
107
|
+
elsif protocols.include? "http/1.0"
|
108
|
+
return "http/1.0"
|
109
|
+
else
|
110
|
+
return nil
|
111
|
+
end
|
112
|
+
end
|
113
|
+
|
114
|
+
context.session_id_context = "falcon"
|
115
|
+
end
|
116
|
+
end
|
117
|
+
end
|
118
|
+
|
119
|
+
add(:proxy, :host) do
|
120
|
+
endpoint {::Async::HTTP::URLEndpoint.parse(url)}
|
121
|
+
end
|
122
|
+
|
123
|
+
add(:rack, :host) do
|
124
|
+
config_path {::File.expand_path("config.ru", root)}
|
125
|
+
|
126
|
+
middleware do
|
127
|
+
::Falcon::Server.middleware(
|
128
|
+
::Rack::Builder.parse_file(config_path).first, verbose: verbose
|
129
|
+
)
|
130
|
+
end
|
131
|
+
|
132
|
+
authority 'localhost'
|
133
|
+
scheme 'https'
|
134
|
+
protocol {::Async::HTTP::Protocol::HTTP2}
|
135
|
+
ipc_path {::File.expand_path("server.ipc", root)}
|
136
|
+
|
137
|
+
endpoint {ProxyEndpoint.unix(ipc_path, protocol: protocol, scheme: scheme, authority: authority)}
|
138
|
+
|
139
|
+
bound_endpoint do
|
140
|
+
Async::Reactor.run do
|
141
|
+
Async::IO::SharedEndpoint.bound(endpoint)
|
142
|
+
end.wait
|
143
|
+
end
|
144
|
+
|
145
|
+
server do
|
146
|
+
::Falcon::Server.new(middleware, bound_endpoint, protocol, scheme)
|
147
|
+
end
|
148
|
+
end
|
149
|
+
end
|
150
|
+
|
151
|
+
attr :environments
|
152
|
+
|
153
|
+
def add(name, *parents, &block)
|
154
|
+
raise KeyError.new("#{name} is already set", key: name) if @environments.key?(name)
|
155
|
+
|
156
|
+
environments = parents.map{|name| @environments.fetch(name)}
|
157
|
+
|
158
|
+
parent = Build::Environment.combine(*environments)
|
159
|
+
|
160
|
+
@environments[name] = Build::Environment.new(parent, name: name, &block)
|
161
|
+
end
|
162
|
+
|
163
|
+
def each
|
164
|
+
return to_enum unless block_given?
|
165
|
+
|
166
|
+
@environments.each do |name, environment|
|
167
|
+
if environment.include?(:authority)
|
168
|
+
yield environment
|
169
|
+
end
|
170
|
+
end
|
171
|
+
end
|
172
|
+
|
173
|
+
def host(name, *parents, &block)
|
174
|
+
add(name, :host, *parents, &block).tap do |environment|
|
175
|
+
environment[:authority] = name
|
176
|
+
end
|
177
|
+
end
|
178
|
+
|
179
|
+
def proxy(name, *parents, &block)
|
180
|
+
add(name, :proxy, *parents, &block).tap do |environment|
|
181
|
+
environment[:authority] = name
|
182
|
+
end
|
183
|
+
end
|
184
|
+
|
185
|
+
def rack(name, *parents, &block)
|
186
|
+
add(name, :rack, *parents, &block).tap do |environment|
|
187
|
+
environment[:authority] = name
|
188
|
+
end
|
189
|
+
end
|
190
|
+
|
191
|
+
def load_file(path)
|
192
|
+
self.instance_eval(File.read(path), path)
|
193
|
+
end
|
194
|
+
end
|
195
|
+
end
|
data/lib/falcon/hosts.rb
CHANGED
@@ -23,107 +23,71 @@ require 'async/io/endpoint'
|
|
23
23
|
require_relative 'proxy'
|
24
24
|
require_relative 'redirection'
|
25
25
|
|
26
|
-
require 'async/container
|
26
|
+
require 'async/container'
|
27
|
+
require 'async/container/controller'
|
28
|
+
require 'async/http/url_endpoint'
|
27
29
|
|
28
30
|
module Falcon
|
29
31
|
class Host
|
30
|
-
def initialize
|
31
|
-
@
|
32
|
-
@
|
33
|
-
@config_path = "config.ru"
|
34
|
-
|
35
|
-
@endpoint = nil
|
36
|
-
|
37
|
-
@ssl_certificate = nil
|
38
|
-
@ssl_key = nil
|
39
|
-
|
40
|
-
@ssl_context = nil
|
32
|
+
def initialize(environment)
|
33
|
+
@environment = environment.flatten
|
34
|
+
@evaluator = @environment.evaluator
|
41
35
|
end
|
42
36
|
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
attr_accessor :endpoint
|
48
|
-
|
49
|
-
attr_accessor :ssl_certificate
|
50
|
-
attr_accessor :ssl_key
|
51
|
-
|
52
|
-
attr_accessor :ssl_context
|
37
|
+
def name
|
38
|
+
"Falcon Host for #{self.authority}"
|
39
|
+
end
|
53
40
|
|
54
|
-
def
|
55
|
-
|
56
|
-
|
57
|
-
ssl_context
|
58
|
-
|
59
|
-
super
|
41
|
+
def authority
|
42
|
+
@evaluator.authority
|
60
43
|
end
|
61
44
|
|
62
|
-
def
|
63
|
-
@
|
45
|
+
def endpoint
|
46
|
+
@evaluator.endpoint
|
64
47
|
end
|
65
48
|
|
66
|
-
def
|
67
|
-
|
68
|
-
|
69
|
-
if @config_path
|
70
|
-
rack_app, options = Rack::Builder.parse_file(@config_path)
|
71
|
-
|
72
|
-
return Server.middleware(rack_app, verbose: verbose)
|
73
|
-
end
|
49
|
+
def ssl_context
|
50
|
+
@evaluator.ssl_context
|
74
51
|
end
|
75
52
|
|
76
|
-
def
|
77
|
-
|
78
|
-
|
79
|
-
@ssl_context = authority.server_context.tap do |context|
|
80
|
-
context.alpn_select_cb = lambda do |protocols|
|
81
|
-
if protocols.include? "h2"
|
82
|
-
return "h2"
|
83
|
-
elsif protocols.include? "http/1.1"
|
84
|
-
return "http/1.1"
|
85
|
-
elsif protocols.include? "http/1.0"
|
86
|
-
return "http/1.0"
|
87
|
-
else
|
88
|
-
return nil
|
89
|
-
end
|
90
|
-
end
|
91
|
-
|
92
|
-
context.session_id_context = "falcon"
|
93
|
-
end
|
53
|
+
def root
|
54
|
+
@evaluator.root
|
94
55
|
end
|
95
56
|
|
96
|
-
def
|
97
|
-
@
|
57
|
+
def bound_endpoint
|
58
|
+
@evaluator.bound_endpoint
|
98
59
|
end
|
99
60
|
|
100
|
-
def
|
101
|
-
@
|
61
|
+
def to_s
|
62
|
+
"\#<#{self.class} #{@evaluator.authority}>"
|
102
63
|
end
|
103
64
|
|
104
|
-
def
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
context.session_id_context = "falcon"
|
110
|
-
|
111
|
-
context.set_params
|
112
|
-
|
113
|
-
context.setup
|
114
|
-
end
|
65
|
+
def assume_privileges(path)
|
66
|
+
stat = File.stat(path)
|
67
|
+
|
68
|
+
Process::GID.change_privilege(stat.gid)
|
69
|
+
Process::UID.change_privilege(stat.uid)
|
115
70
|
end
|
116
71
|
|
117
|
-
def
|
118
|
-
if
|
119
|
-
|
120
|
-
|
72
|
+
def run(container)
|
73
|
+
if @environment.include?(:server)
|
74
|
+
bound_endpoint = self.bound_endpoint
|
75
|
+
|
76
|
+
container.run(count: 1, name: self.name) do |task, instance|
|
77
|
+
Async.logger.info(self) {"Starting application server..."}
|
121
78
|
|
122
|
-
|
79
|
+
if root = self.root
|
80
|
+
Dir.chdir(root)
|
81
|
+
end
|
82
|
+
|
83
|
+
server = @evaluator.server
|
123
84
|
|
124
|
-
|
85
|
+
# Drop root privileges:
|
86
|
+
assume_privileges(root)
|
125
87
|
|
126
88
|
server.run
|
89
|
+
|
90
|
+
task.children.each(&:wait)
|
127
91
|
end
|
128
92
|
end
|
129
93
|
end
|
@@ -132,10 +96,14 @@ module Falcon
|
|
132
96
|
class Hosts
|
133
97
|
DEFAULT_ALPN_PROTOCOLS = ['h2', 'http/1.1'].freeze
|
134
98
|
|
135
|
-
def initialize
|
99
|
+
def initialize(configuration)
|
136
100
|
@named = {}
|
137
101
|
@server_context = nil
|
138
102
|
@server_endpoint = nil
|
103
|
+
|
104
|
+
configuration.each do |environment|
|
105
|
+
add(Host.new(environment))
|
106
|
+
end
|
139
107
|
end
|
140
108
|
|
141
109
|
def each(&block)
|
@@ -157,9 +125,7 @@ module Falcon
|
|
157
125
|
end
|
158
126
|
|
159
127
|
context.session_id_context = "falcon"
|
160
|
-
|
161
128
|
context.alpn_protocols = DEFAULT_ALPN_PROTOCOLS
|
162
|
-
|
163
129
|
context.set_params
|
164
130
|
|
165
131
|
context.setup
|
@@ -168,83 +134,55 @@ module Falcon
|
|
168
134
|
|
169
135
|
def host_context(socket, hostname)
|
170
136
|
if host = @named[hostname]
|
137
|
+
Async.logger.debug(self) {"Resolving #{hostname} -> #{host}"}
|
138
|
+
|
171
139
|
socket.hostname = hostname
|
172
140
|
|
173
141
|
return host.ssl_context
|
142
|
+
else
|
143
|
+
Async.logger.warn(self) {"Unable to resolve #{hostname}!"}
|
144
|
+
|
145
|
+
return nil
|
174
146
|
end
|
175
147
|
end
|
176
148
|
|
177
|
-
def add(
|
178
|
-
host =
|
179
|
-
|
180
|
-
yield host if block_given?
|
181
|
-
|
182
|
-
@named[name] = host.freeze
|
183
|
-
end
|
184
|
-
|
185
|
-
def client_endpoints
|
186
|
-
Hash[
|
187
|
-
@named.collect{|name, host| [name, host.endpoint]}
|
188
|
-
]
|
149
|
+
def add(host)
|
150
|
+
@named[host.authority] = host
|
189
151
|
end
|
190
152
|
|
191
153
|
def proxy
|
192
|
-
Proxy.new(Falcon::BadRequest,
|
154
|
+
Proxy.new(Falcon::BadRequest, @named)
|
193
155
|
end
|
194
156
|
|
195
|
-
def redirection
|
196
|
-
Redirection.new(Falcon::BadRequest,
|
157
|
+
def redirection(secure_endpoint)
|
158
|
+
Redirection.new(Falcon::BadRequest, @named, secure_endpoint)
|
197
159
|
end
|
198
160
|
|
199
|
-
def
|
200
|
-
|
201
|
-
|
202
|
-
controller << container
|
203
|
-
end
|
161
|
+
def run(container = Async::Container::Forked.new, **options)
|
162
|
+
@named.each do |name, host|
|
163
|
+
host.run(container)
|
204
164
|
end
|
205
|
-
|
206
|
-
|
207
|
-
|
208
|
-
|
209
|
-
|
210
|
-
|
211
|
-
controller << Async::Container::Forked.new do |task|
|
212
|
-
Process.setproctitle("Falcon Proxy")
|
165
|
+
|
166
|
+
secure_endpoint = Async::HTTP::URLEndpoint.parse(options[:bind_secure], ssl_context: self.ssl_context)
|
167
|
+
insecure_endpoint = Async::HTTP::URLEndpoint.parse(options[:bind_insecure])
|
168
|
+
|
169
|
+
container.run(count: 1, name: "Falcon Proxy") do |task, instance|
|
170
|
+
proxy = self.proxy
|
213
171
|
|
214
|
-
|
215
|
-
proxy,
|
216
|
-
Async::HTTP::URLEndpoint.parse(
|
217
|
-
'https://0.0.0.0',
|
218
|
-
reuse_address: true,
|
219
|
-
ssl_context: hosts.ssl_context
|
220
|
-
)
|
221
|
-
)
|
172
|
+
proxy_server = Falcon::Server.new(proxy, secure_endpoint)
|
222
173
|
|
223
|
-
|
224
|
-
task.async do
|
225
|
-
debug_trap.install!
|
226
|
-
$stderr.puts "Send `kill -USR1 #{Process.pid}` for detailed status :)"
|
227
|
-
|
228
|
-
debug_trap.trap do
|
229
|
-
task.reactor.print_hierarchy($stderr)
|
230
|
-
# Async.logger.level = Logger::DEBUG
|
231
|
-
end
|
232
|
-
end
|
233
|
-
|
234
|
-
task.async do |task|
|
235
|
-
start_time = Async::Clock.now
|
236
|
-
|
237
|
-
while true
|
238
|
-
task.sleep(600)
|
239
|
-
duration = Async::Clock.now - start_time
|
240
|
-
puts "Handled #{proxy.count} requests; #{(proxy.count.to_f / duration.to_f).round(1)} requests per second."
|
241
|
-
end
|
242
|
-
end
|
243
|
-
|
244
|
-
$stderr.puts "Starting server"
|
245
|
-
server.run
|
246
|
-
end
|
174
|
+
proxy_server.run
|
247
175
|
end
|
176
|
+
|
177
|
+
container.run(count: 1, name: "Falcon Redirector") do |task, instance|
|
178
|
+
redirection = self.redirection(secure_endpoint)
|
179
|
+
|
180
|
+
redirection_server = Falcon::Server.new(redirection, insecure_endpoint)
|
181
|
+
|
182
|
+
redirection_server.run
|
183
|
+
end
|
184
|
+
|
185
|
+
return container
|
248
186
|
end
|
249
187
|
end
|
250
188
|
end
|
data/lib/falcon/proxy.rb
CHANGED
@@ -86,9 +86,18 @@ module Falcon
|
|
86
86
|
headers.slice!(HOP_HEADERS)
|
87
87
|
end
|
88
88
|
|
89
|
-
def prepare_request(request)
|
89
|
+
def prepare_request(request, host)
|
90
90
|
forwarded = []
|
91
91
|
|
92
|
+
# Async.logger.info(self) do |buffer|
|
93
|
+
# buffer.puts "Request authority: #{request.authority}"
|
94
|
+
# buffer.puts "Host authority: #{host.authority}"
|
95
|
+
# buffer.puts "Endpoint authority: #{host.endpoint.authority}"
|
96
|
+
# end
|
97
|
+
|
98
|
+
# The authority of the request must match the authority of the endpoint we are proxying to, otherwise SNI and other things won't work correctly.
|
99
|
+
request.authority = host.endpoint.authority
|
100
|
+
|
92
101
|
if address = request.remote_address
|
93
102
|
request.headers.add(X_FORWARDED_FOR, address.ip_address)
|
94
103
|
forwarded << "for=#{address.ip_address}"
|
@@ -111,12 +120,12 @@ module Falcon
|
|
111
120
|
end
|
112
121
|
|
113
122
|
def call(request)
|
114
|
-
if
|
123
|
+
if host = lookup(request)
|
115
124
|
@count += 1
|
116
125
|
|
117
|
-
request = self.prepare_request(request)
|
126
|
+
request = self.prepare_request(request, host)
|
118
127
|
|
119
|
-
client = connect(endpoint)
|
128
|
+
client = connect(host.endpoint)
|
120
129
|
|
121
130
|
client.call(request)
|
122
131
|
else
|
data/lib/falcon/redirection.rb
CHANGED
@@ -32,10 +32,11 @@ module Falcon
|
|
32
32
|
end
|
33
33
|
|
34
34
|
class Redirection < Async::HTTP::Middleware
|
35
|
-
def initialize(app, hosts)
|
35
|
+
def initialize(app, hosts, endpoint)
|
36
36
|
super(app)
|
37
37
|
|
38
38
|
@hosts = hosts
|
39
|
+
@endpoint = endpoint
|
39
40
|
end
|
40
41
|
|
41
42
|
def lookup(request)
|
@@ -46,10 +47,14 @@ module Falcon
|
|
46
47
|
end
|
47
48
|
|
48
49
|
def call(request)
|
49
|
-
if
|
50
|
-
|
50
|
+
if host = lookup(request)
|
51
|
+
if @endpoint.default_port?
|
52
|
+
location = "#{@endpoint.scheme}://#{host.authority}#{request.path}"
|
53
|
+
else
|
54
|
+
location = "#{@endpoint.scheme}://#{host.authority}:#{@endpoint.port}#{request.path}"
|
55
|
+
end
|
51
56
|
|
52
|
-
return Async::HTTP::Response[301,
|
57
|
+
return Async::HTTP::Response[301, [['location', location]], []]
|
53
58
|
else
|
54
59
|
super
|
55
60
|
end
|
data/lib/falcon/version.rb
CHANGED
data/server.rb
CHANGED
@@ -1,36 +1,20 @@
|
|
1
|
-
#!/usr/bin/env
|
1
|
+
#!/usr/bin/env -S ./bin/falcon virtual --bind-insecure http://[::]:1080 --bind-secure https://[::]:1443
|
2
2
|
|
3
|
-
|
3
|
+
# You will want edit your `/etc/hosts`, adding the following:
|
4
|
+
# 127.0.0.1 benchmark.localhost beer.localhost hello.localhost
|
4
5
|
|
5
|
-
|
6
|
-
|
7
|
-
require 'async/container/controller'
|
8
|
-
require 'async/container/forked'
|
9
|
-
|
10
|
-
require 'async/clock'
|
11
|
-
require 'ruby-prof'
|
12
|
-
|
13
|
-
Async.logger.level = Logger::INFO
|
14
|
-
|
15
|
-
hosts = Falcon::Hosts.new
|
16
|
-
|
17
|
-
hosts.add('mc.oriontransfer.co.nz') do |host|
|
18
|
-
host.endpoint = Async::HTTP::URLEndpoint.parse('http://hana.local:8123')
|
19
|
-
|
20
|
-
host.ssl_certificate_path = '/etc/letsencrypt/live/mc.oriontransfer.co.nz/fullchain.pem'
|
21
|
-
host.ssl_key_path = '/etc/letsencrypt/live/mc.oriontransfer.co.nz/privkey.pem'
|
6
|
+
rack 'benchmark.localhost', :self_signed do
|
7
|
+
root File.expand_path("examples/benchmark/", __dir__)
|
22
8
|
end
|
23
9
|
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
host.ssl_certificate_path = '/etc/letsencrypt/live/chick.nz/fullchain.pem'
|
28
|
-
host.ssl_key_path = '/etc/letsencrypt/live/chick.nz/privkey.pem'
|
10
|
+
rack 'beer.localhost', :self_signed do
|
11
|
+
root File.expand_path("examples/beer/", __dir__)
|
29
12
|
end
|
30
13
|
|
31
|
-
|
32
|
-
|
33
|
-
|
14
|
+
rack 'hello.localhost', :self_signed do
|
15
|
+
root File.expand_path("examples/hello/", __dir__)
|
16
|
+
end
|
34
17
|
|
35
|
-
|
36
|
-
|
18
|
+
proxy 'codeotaku.localhost', :self_signed do
|
19
|
+
url 'https://www.codeotaku.com'
|
20
|
+
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: falcon
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.26.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Samuel Williams
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2019-03-
|
11
|
+
date: 2019-03-04 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: http-protocol
|
@@ -44,14 +44,14 @@ dependencies:
|
|
44
44
|
requirements:
|
45
45
|
- - "~>"
|
46
46
|
- !ruby/object:Gem::Version
|
47
|
-
version: '1.
|
47
|
+
version: '1.20'
|
48
48
|
type: :runtime
|
49
49
|
prerelease: false
|
50
50
|
version_requirements: !ruby/object:Gem::Requirement
|
51
51
|
requirements:
|
52
52
|
- - "~>"
|
53
53
|
- !ruby/object:Gem::Version
|
54
|
-
version: '1.
|
54
|
+
version: '1.20'
|
55
55
|
- !ruby/object:Gem::Dependency
|
56
56
|
name: async-http
|
57
57
|
requirement: !ruby/object:Gem::Requirement
|
@@ -122,6 +122,34 @@ dependencies:
|
|
122
122
|
- - "~>"
|
123
123
|
- !ruby/object:Gem::Version
|
124
124
|
version: '1.1'
|
125
|
+
- !ruby/object:Gem::Dependency
|
126
|
+
name: build-environment
|
127
|
+
requirement: !ruby/object:Gem::Requirement
|
128
|
+
requirements:
|
129
|
+
- - "~>"
|
130
|
+
- !ruby/object:Gem::Version
|
131
|
+
version: '1.6'
|
132
|
+
type: :runtime
|
133
|
+
prerelease: false
|
134
|
+
version_requirements: !ruby/object:Gem::Requirement
|
135
|
+
requirements:
|
136
|
+
- - "~>"
|
137
|
+
- !ruby/object:Gem::Version
|
138
|
+
version: '1.6'
|
139
|
+
- !ruby/object:Gem::Dependency
|
140
|
+
name: trenni
|
141
|
+
requirement: !ruby/object:Gem::Requirement
|
142
|
+
requirements:
|
143
|
+
- - ">="
|
144
|
+
- !ruby/object:Gem::Version
|
145
|
+
version: '0'
|
146
|
+
type: :development
|
147
|
+
prerelease: false
|
148
|
+
version_requirements: !ruby/object:Gem::Requirement
|
149
|
+
requirements:
|
150
|
+
- - ">="
|
151
|
+
- !ruby/object:Gem::Version
|
152
|
+
version: '0'
|
125
153
|
- !ruby/object:Gem::Dependency
|
126
154
|
name: async-rspec
|
127
155
|
requirement: !ruby/object:Gem::Requirement
|
@@ -239,6 +267,7 @@ files:
|
|
239
267
|
- examples/beer/config.ru
|
240
268
|
- examples/benchmark/config.ru
|
241
269
|
- examples/hello/config.ru
|
270
|
+
- examples/push/client.rb
|
242
271
|
- examples/push/config.ru
|
243
272
|
- examples/push/index.html
|
244
273
|
- examples/push/script.js
|
@@ -261,6 +290,7 @@ files:
|
|
261
290
|
- lib/falcon/command.rb
|
262
291
|
- lib/falcon/command/serve.rb
|
263
292
|
- lib/falcon/command/virtual.rb
|
293
|
+
- lib/falcon/configuration.rb
|
264
294
|
- lib/falcon/endpoint.rb
|
265
295
|
- lib/falcon/hosts.rb
|
266
296
|
- lib/falcon/proxy.rb
|