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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 96425fc2402b25445c421f2d8dec1739c0f2b04701c6d47de48db140f0a55123
4
- data.tar.gz: 3c6b31df8560bbf133873e9c37d9600204e2bd893b139c23a0587678c5cbe22e
3
+ metadata.gz: fab1eafce5a511075f7339beabec2ef2364693ffeb050b84e90a8223e3faf614
4
+ data.tar.gz: 6f2512f83e06c16ab5600c66ceefc0d2ea910e6f8a53df3d8d919600c75671af
5
5
  SHA512:
6
- metadata.gz: c3ce086375cceb639ba87947f286b66c1d53c2693717233bedb1f1fa2222b0b31b1747e3f39566592e508ad1f97545a5334bd5ff051c397fd7a93591a9c28dd5
7
- data.tar.gz: 8bbf6a1e2717969ad54dadef79faa2566a3299069d97f675b697f1b0b5f8283419150f2430141bc6bdae09ea6272fb7a284aac44d131874c79aab3a5a24df5f2
6
+ metadata.gz: 0ce28b54426aa8ccbba9801e18c548a284ffbc8c64b54a9cf03b6d3af75ed11b029b999de1e923509b8e16ff525c465ac82695e6a37c9a207f002447333d5c83
7
+ data.tar.gz: 916bf5e17a59d440c633471a809fad988693f050d8912dd236150983bbb81a4fd2235406418f5a740072aec8e21ed8844593c123d47533d6b6255ddaa7221d14
@@ -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
- puts "#{bottles(i)} of beer on the wall..."
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"
@@ -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
- if env['PATH_INFO'] == "/index.html" and early_hints = env['rack.early_hints']
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"]]}
@@ -0,0 +1,2 @@
1
+
2
+ console.log("Hello World")
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("http-protocol", "~> 0.15")
19
+ spec.add_dependency "http-protocol", "~> 0.15"
20
20
 
21
- spec.add_dependency("async", "~> 1.13")
22
- spec.add_dependency("async-io", "~> 1.18")
23
- spec.add_dependency("async-http", "~> 0.38.0")
24
- spec.add_dependency("async-container", "~> 0.10.0")
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("rack", ">= 1.0")
26
+ spec.add_dependency "rack", ">= 1.0"
27
27
 
28
- spec.add_dependency('samovar', "~> 1.3")
29
- spec.add_dependency('localhost', "~> 1.1")
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://localhost"
44
- option '--bind-secure <address>', "Bind proxy to the given hostname/address", default: "https://localhost"
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
- def client
62
- Async::HTTP::Client.new(client_endpoint)
63
- end
48
+ many :paths
64
49
 
65
50
  def run(verbose = false)
66
- hosts = Falcon::Hosts.new
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
- controller << Async::Container::Forked.new do |task|
104
- redirection = hosts.redirection
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
- Process.setproctitle("Falcon Controller")
57
+ hosts = Hosts.new(configuration)
115
58
 
116
- return controller
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/forked'
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
- @app = nil
32
- @app_root = nil
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
- attr_accessor :app
44
- attr_accessor :app_root
45
- attr_accessor :config_path
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 freeze
55
- return if frozen?
56
-
57
- ssl_context
58
-
59
- super
41
+ def authority
42
+ @evaluator.authority
60
43
  end
61
44
 
62
- def app?
63
- @app || @config_path
45
+ def endpoint
46
+ @evaluator.endpoint
64
47
  end
65
48
 
66
- def load_app(verbose = false)
67
- return @app if @app
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 self_signed!(hostname)
77
- authority = Localhost::Authority.fetch(hostname)
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 ssl_certificate_path= path
97
- @ssl_certificate = OpenSSL::X509::Certificate.new(File.read(path))
57
+ def bound_endpoint
58
+ @evaluator.bound_endpoint
98
59
  end
99
60
 
100
- def ssl_key_path= path
101
- @ssl_key = OpenSSL::PKey::RSA.new(File.read(path))
61
+ def to_s
62
+ "\#<#{self.class} #{@evaluator.authority}>"
102
63
  end
103
64
 
104
- def ssl_context
105
- @ssl_context ||= OpenSSL::SSL::SSLContext.new.tap do |context|
106
- context.cert = @ssl_certificate
107
- context.key = @ssl_key
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 start(*args)
118
- if self.app?
119
- Async::Container::Forked.new do
120
- Dir.chdir(@app_root) if @app_root
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
- app = self.load_app(*args)
79
+ if root = self.root
80
+ Dir.chdir(root)
81
+ end
82
+
83
+ server = @evaluator.server
123
84
 
124
- server = Falcon::Server.new(app, self.server_endpoint)
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(name, host = Host.new, &block)
178
- host = Host.new
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, self.client_endpoints)
154
+ Proxy.new(Falcon::BadRequest, @named)
193
155
  end
194
156
 
195
- def redirection
196
- Redirection.new(Falcon::BadRequest, self.client_endpoints)
157
+ def redirection(secure_endpoint)
158
+ Redirection.new(Falcon::BadRequest, @named, secure_endpoint)
197
159
  end
198
160
 
199
- def call(controller)
200
- self.each do |name, host|
201
- if container = host.start
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
- proxy = hosts.proxy
207
- debug_trap = Async::IO::Trap.new(:USR1)
208
-
209
- profile = RubyProf::Profile.new(merge_fibers: true)
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
- server = Falcon::Server.new(
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
- Async::Reactor.run do |task|
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 endpoint = lookup(request)
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
@@ -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 endpoint = lookup(request)
50
- location = "https://#{request.authority}#{request.path}"
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, {'location' => location}, []]
57
+ return Async::HTTP::Response[301, [['location', location]], []]
53
58
  else
54
59
  super
55
60
  end
@@ -19,5 +19,5 @@
19
19
  # THE SOFTWARE.
20
20
 
21
21
  module Falcon
22
- VERSION = "0.25.0"
22
+ VERSION = "0.26.0"
23
23
  end
data/server.rb CHANGED
@@ -1,36 +1,20 @@
1
- #!/usr/bin/env ruby
1
+ #!/usr/bin/env -S ./bin/falcon virtual --bind-insecure http://[::]:1080 --bind-secure https://[::]:1443
2
2
 
3
- $LOAD_PATH << File.expand_path('lib', __dir__)
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
- require 'falcon'
6
- require 'async/http/url_endpoint'
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
- hosts.add('chick.nz') do |host|
25
- host.endpoint = Async::HTTP::URLEndpoint.parse('http://hana.local:8765')
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
- controller = Async::Container::Controller.new
32
-
33
- hosts.call(controller)
14
+ rack 'hello.localhost', :self_signed do
15
+ root File.expand_path("examples/hello/", __dir__)
16
+ end
34
17
 
35
- Process.setproctitle("Falcon Controller")
36
- controller.wait
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.25.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-01 00:00:00.000000000 Z
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.18'
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.18'
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