falcon 0.25.0 → 0.26.0

Sign up to get free protection for your applications and to get access to all the features.
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