falcon 0.36.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (92) hide show
  1. checksums.yaml +7 -0
  2. data/.editorconfig +5 -0
  3. data/.github/FUNDING.yml +3 -0
  4. data/.github/workflows/development.yml +60 -0
  5. data/.gitignore +14 -0
  6. data/.rspec +3 -0
  7. data/Gemfile +17 -0
  8. data/README.md +316 -0
  9. data/bake/falcon/supervisor.rb +8 -0
  10. data/bin/falcon +32 -0
  11. data/bin/falcon-host +28 -0
  12. data/examples/beer/config.ru +57 -0
  13. data/examples/beer/falcon.rb +8 -0
  14. data/examples/benchmark/config.ru +39 -0
  15. data/examples/benchmark/falcon.rb +6 -0
  16. data/examples/csv/config.ru +31 -0
  17. data/examples/google/falcon.rb +14 -0
  18. data/examples/hello/config.ru +22 -0
  19. data/examples/hello/falcon.rb +24 -0
  20. data/examples/hello/preload.rb +7 -0
  21. data/examples/internet/config.ru +54 -0
  22. data/examples/memory/allocations.rb +39 -0
  23. data/examples/memory/config.ru +14 -0
  24. data/examples/push/client.rb +29 -0
  25. data/examples/push/config.ru +28 -0
  26. data/examples/push/index.html +14 -0
  27. data/examples/push/script.js +2 -0
  28. data/examples/push/style.css +4 -0
  29. data/examples/redis/Gemfile +9 -0
  30. data/examples/redis/config.ru +28 -0
  31. data/examples/sequel/Gemfile +4 -0
  32. data/examples/sequel/config.ru +8 -0
  33. data/examples/sequel/data.sqlite3 +0 -0
  34. data/examples/server/standalone.rb +27 -0
  35. data/examples/sinatra/Gemfile +7 -0
  36. data/examples/sinatra/Gemfile.lock +53 -0
  37. data/examples/sinatra/config.ru +16 -0
  38. data/examples/trailers/config.ru +34 -0
  39. data/examples/trailers/falcon.rb +8 -0
  40. data/falcon.gemspec +45 -0
  41. data/gems/rack1.gemfile +4 -0
  42. data/gems/rack3.gemfile +4 -0
  43. data/lib/falcon.rb +23 -0
  44. data/lib/falcon/adapters/early_hints.rb +49 -0
  45. data/lib/falcon/adapters/input.rb +131 -0
  46. data/lib/falcon/adapters/output.rb +101 -0
  47. data/lib/falcon/adapters/rack.rb +202 -0
  48. data/lib/falcon/adapters/response.rb +91 -0
  49. data/lib/falcon/adapters/rewindable.rb +67 -0
  50. data/lib/falcon/command.rb +31 -0
  51. data/lib/falcon/command/host.rb +69 -0
  52. data/lib/falcon/command/paths.rb +47 -0
  53. data/lib/falcon/command/proxy.rb +71 -0
  54. data/lib/falcon/command/redirect.rb +76 -0
  55. data/lib/falcon/command/serve.rb +151 -0
  56. data/lib/falcon/command/supervisor.rb +78 -0
  57. data/lib/falcon/command/top.rb +94 -0
  58. data/lib/falcon/command/virtual.rb +89 -0
  59. data/lib/falcon/configuration.rb +147 -0
  60. data/lib/falcon/configuration/application.rb +46 -0
  61. data/lib/falcon/configuration/lets_encrypt_tls.rb +30 -0
  62. data/lib/falcon/configuration/proxy.rb +27 -0
  63. data/lib/falcon/configuration/rack.rb +38 -0
  64. data/lib/falcon/configuration/self_signed_tls.rb +47 -0
  65. data/lib/falcon/configuration/supervisor.rb +37 -0
  66. data/lib/falcon/configuration/tls.rb +70 -0
  67. data/lib/falcon/controller/host.rb +60 -0
  68. data/lib/falcon/controller/proxy.rb +109 -0
  69. data/lib/falcon/controller/redirect.rb +69 -0
  70. data/lib/falcon/controller/serve.rb +111 -0
  71. data/lib/falcon/controller/virtual.rb +100 -0
  72. data/lib/falcon/endpoint.rb +52 -0
  73. data/lib/falcon/extensions/openssl.rb +33 -0
  74. data/lib/falcon/middleware/proxy.rb +145 -0
  75. data/lib/falcon/middleware/redirect.rb +66 -0
  76. data/lib/falcon/proxy_endpoint.rb +71 -0
  77. data/lib/falcon/server.rb +54 -0
  78. data/lib/falcon/service/application.rb +93 -0
  79. data/lib/falcon/service/generic.rb +60 -0
  80. data/lib/falcon/service/proxy.rb +60 -0
  81. data/lib/falcon/service/supervisor.rb +104 -0
  82. data/lib/falcon/services.rb +78 -0
  83. data/lib/falcon/tls.rb +46 -0
  84. data/lib/falcon/verbose.rb +59 -0
  85. data/lib/falcon/version.rb +25 -0
  86. data/lib/rack/handler/falcon.rb +40 -0
  87. data/logo-square.afdesign +0 -0
  88. data/logo.afdesign +0 -0
  89. data/logo.svg +107 -0
  90. data/server.rb +21 -0
  91. data/tasks/benchmark.rake +103 -0
  92. metadata +386 -0
@@ -0,0 +1,28 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ # Copyright, 2017, by Samuel G. D. Williams. <http://www.codeotaku.com>
4
+ #
5
+ # Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ # of this software and associated documentation files (the "Software"), to deal
7
+ # in the Software without restriction, including without limitation the rights
8
+ # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ # copies of the Software, and to permit persons to whom the Software is
10
+ # furnished to do so, subject to the following conditions:
11
+ #
12
+ # The above copyright notice and this permission notice shall be included in
13
+ # all copies or substantial portions of the Software.
14
+ #
15
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ # THE SOFTWARE.
22
+
23
+ require_relative '../lib/falcon/command/host'
24
+
25
+ begin
26
+ Falcon::Command::Host.call
27
+ rescue Interrupt
28
+ end
@@ -0,0 +1,57 @@
1
+ #!/usr/bin/env falcon --verbose serve -c
2
+ # frozen_string_literal: true
3
+
4
+ require 'rack'
5
+ require 'cgi'
6
+
7
+ def bottles(n)
8
+ n == 1 ? "#{n} bottle" : "#{n} bottles"
9
+ end
10
+
11
+ # Browsers don't show streaming content until a certain threshold has been met. For most browers, it's about 1024 bytes. So, we have a comment of about that length which we feed to the client before streaming actual content. For more details see https://stackoverflow.com/questions/16909227
12
+ COMMENT = "<!--#{'-' * 1024}-->"
13
+
14
+ # To test this example with the curl command-line tool, you'll need to add the `--no-buffer` flag or set the COMMENT size to the required 4096 bytes in order for curl to start streaming.
15
+ # curl http://localhost:9292/ --no-buffer
16
+
17
+ run lambda {|env|
18
+ task = Async::Task.current
19
+ body = Async::HTTP::Body::Writable.new
20
+
21
+ request = Rack::Request.new(env)
22
+ count = (request.params['count'] || 99).to_i
23
+
24
+ body.write("<!DOCTYPE html><html><head><title>#{count} Bottles of Beer</title></head><body>")
25
+
26
+ task.async do |task|
27
+ begin
28
+ body.write(COMMENT)
29
+
30
+ count.downto(1) do |i|
31
+ task.annotate "bottles of beer #{i}"
32
+
33
+ Async.logger.info(body) {"#{bottles(i)} of beer on the wall..."}
34
+ body.write("<p>#{bottles(i)} of beer on the wall, ")
35
+ task.sleep(0.1)
36
+ body.write("#{bottles(i)} of beer, ")
37
+ task.sleep(0.1)
38
+ body.write("take one down and pass it around, ")
39
+ task.sleep(0.1)
40
+ body.write("#{bottles(i-1)} of beer on the wall.</p>")
41
+ task.sleep(0.1)
42
+ body.write("<script>var child; while (child = document.body.firstChild) child.remove();</script>")
43
+ end
44
+
45
+ code = File.read(__FILE__)
46
+ body.write("<h1>Source Code</h1>")
47
+ body.write("<pre><code>#{CGI.escapeHTML code}</code></pre>")
48
+ body.write("</body></html>")
49
+ rescue
50
+ puts "Remote end closed connection: #{$!}"
51
+ ensure
52
+ body.close
53
+ end
54
+ end
55
+
56
+ [200, {'content-type' => 'text/html; charset=utf-8'}, body]
57
+ }
@@ -0,0 +1,8 @@
1
+ #!/usr/bin/env falcon-host
2
+ # frozen_string_literal: true
3
+
4
+ load :rack, :self_signed_tls, :supervisor
5
+
6
+ rack 'beer.localhost', :self_signed_tls
7
+
8
+ supervisor
@@ -0,0 +1,39 @@
1
+ #!/usr/bin/env falcon --verbose serve -c
2
+ # frozen_string_literal: true
3
+
4
+ class Benchmark
5
+ def initialize(app)
6
+ @app = app
7
+ end
8
+
9
+ PATH_INFO = 'PATH_INFO'.freeze
10
+
11
+ SMALL = [200, {}, ["Hello World\n" * 10] * 10].freeze
12
+
13
+ def small(env)
14
+ SMALL
15
+ end
16
+
17
+ BIG = [200, {}, ["Hello World\n" * 1000] * 1000].freeze
18
+
19
+ def big(env)
20
+ BIG
21
+ end
22
+
23
+ def call(env)
24
+ _, name, *path = env[PATH_INFO].split("/")
25
+
26
+ method = name&.to_sym
27
+
28
+ if method and self.respond_to?(method)
29
+ self.send(method, env)
30
+ else
31
+ @app.call(env)
32
+ end
33
+ end
34
+ end
35
+
36
+ use Benchmark
37
+
38
+ run lambda {|env| [200, {}, ["Hello World"]]}
39
+
@@ -0,0 +1,6 @@
1
+ #!/usr/bin/env -S ./bin/falcon virtual
2
+ # frozen_string_literal: true
3
+
4
+ load :rack, :self_signed_tls, :supervisor
5
+
6
+ rack 'benchmark.local', :self_signed_tls
@@ -0,0 +1,31 @@
1
+ #!/usr/bin/env falcon --verbose serve -c
2
+ # frozen_string_literal: true
3
+
4
+ class MyApp
5
+ def initialize(app)
6
+ @app = app
7
+
8
+ @words = File.readlines('/usr/share/dict/words', chomp: true).each_slice(3).to_a
9
+ end
10
+
11
+ def call(env)
12
+ body = Async::HTTP::Body::Writable.new(queue: Async::LimitedQueue.new(8))
13
+
14
+ Async do |task|
15
+ @words.each do |words|
16
+ Async.logger.debug("Sending #{words.inspect}")
17
+ body.write(words.join(",") + "\n")
18
+ task.sleep(1)
19
+ end
20
+ ensure
21
+ body.close($!)
22
+ end
23
+
24
+ return [200, [], body]
25
+ end
26
+ end
27
+
28
+ # Build the middleware stack:
29
+ use MyApp
30
+
31
+ run lambda {|env| [404, {}, []]}
@@ -0,0 +1,14 @@
1
+ #!/usr/bin/env falcon-host
2
+ # frozen_string_literal: true
3
+
4
+ load :proxy, :self_signed_tls, :supervisor
5
+
6
+ supervisor
7
+
8
+ proxy "google.localhost", :self_signed_tls do
9
+ url 'https://www.google.com'
10
+ end
11
+
12
+ proxy "codeotaku.localhost", :self_signed_tls do
13
+ url 'https://www.codeotaku.com'
14
+ end
@@ -0,0 +1,22 @@
1
+ #!/usr/bin/env falcon --verbose serve -c
2
+ # frozen_string_literal: true
3
+
4
+ require 'async'
5
+
6
+ class RequestLogger
7
+ def initialize(app)
8
+ @app = app
9
+ end
10
+
11
+ def call(env)
12
+ logger = Async.logger.with(level: :debug)
13
+
14
+ Async(logger: logger) do
15
+ @app.call(env)
16
+ end.wait
17
+ end
18
+ end
19
+
20
+ # use RequestLogger
21
+
22
+ run lambda {|env| [200, {'cache-control' => 'max-age=10, public'}, ["Hello World"]]}
@@ -0,0 +1,24 @@
1
+ #!/usr/bin/env falcon-host
2
+ # frozen_string_literal: true
3
+
4
+ load :rack, :self_signed_tls, :supervisor
5
+
6
+ supervisor
7
+
8
+ rack 'hello.localhost', :self_signed_tls do
9
+ # scheme 'http'
10
+ # protocol {Async::HTTP::Protocol::HTTP1}
11
+ #
12
+ # endpoint do
13
+ # Async::HTTP::Endpoint.for(scheme, "localhost", port: 9292, protocol: protocol)
14
+ # end
15
+
16
+ append preload "preload.rb"
17
+
18
+ # Process will connect to supervisor to report statistics periodically, otherwise it would be killed.
19
+ # report :supervisor
20
+ end
21
+
22
+ # service 'jobs' do
23
+ # shell ['rake', 'background:jobs:process']
24
+ # end
@@ -0,0 +1,7 @@
1
+ # frozen_string_literal: true
2
+
3
+ if GC.respond_to?(:compact)
4
+ Async.logger.warn(self, "Compacting the mainframe...")
5
+ GC.compact
6
+ Async.logger.warn(self, "Compacting done...")
7
+ end
@@ -0,0 +1,54 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'async'
4
+ require 'async/http/internet'
5
+
6
+ # Experimental.
7
+ require 'kernel/sync'
8
+
9
+ class Search
10
+ def initialize(app)
11
+ @internet = Async::HTTP::Internet.new
12
+
13
+ @app = app
14
+ end
15
+
16
+ # This method uses the `Async` method to create a reactor if required, and then executes the contained code without waiting for the result. So even if the search query takes a long time (e.g. 100ms), it won't hold up the request processing.
17
+ def async(close: !Async::Task.current?)
18
+ Async do
19
+ response = @internet.get("https://google.com/search?q=async+ruby")
20
+
21
+ puts response.inspect
22
+ ensure
23
+ response&.finish
24
+ end
25
+ end
26
+
27
+ # This method uses the experimental `Sync` method to create a reactor if required. If the code is already running in a reactor, it runs synchronously, otherwise it's effectively the same as `Async` and a blocking operation. This allow you to write efficient non-blocking code that works in both traditional web application servers, but gains additional scalability in `Async`-aware servers like Falcon.
28
+ # You can achieve a similar result by calling `Async{}.wait`, but this is less efficient as it will allocate a sub-task even thought it's not needed.
29
+ def sync(close: !Async::Task.current?)
30
+ Sync do
31
+ response = @internet.get("https://google.com/search?q=async+ruby")
32
+
33
+ puts response.inspect
34
+ ensure
35
+ response&.finish
36
+ end
37
+ end
38
+
39
+ # The only point of this is to invoke one of the above two methods.
40
+ def call(env)
41
+ case env['PATH_INFO']
42
+ when '/sync'
43
+ self.sync
44
+ when '/async'
45
+ self.async
46
+ end
47
+
48
+ return @app.call(env)
49
+ end
50
+ end
51
+
52
+ use Search
53
+
54
+ run lambda{|env| [404, [], []]}
@@ -0,0 +1,39 @@
1
+ # frozen_string_literal: true
2
+
3
+ class Allocations
4
+ def initialize(app)
5
+ @app = app
6
+ end
7
+
8
+ def allocations
9
+ counts = Hash.new{|h,k| h[k] = 0}
10
+
11
+ ObjectSpace.each_object do |object|
12
+ counts[object.class] += 1
13
+ end
14
+
15
+ return counts
16
+ end
17
+
18
+ def print_allocations(minimum = 100)
19
+ buffer = StringIO.new
20
+
21
+ total = allocations.values.sum
22
+
23
+ allocations.select{|k,v| v >= minimum}.sort_by{|k,v| -v}.each do |key, value|
24
+ buffer.puts "#{key}: #{value} allocations"
25
+ end
26
+
27
+ buffer.puts "** Total: #{total} allocations."
28
+
29
+ return buffer.string
30
+ end
31
+
32
+ def call(env)
33
+ if env["PATH_INFO"] == "/allocations"
34
+ return [200, [], [print_allocations]]
35
+ else
36
+ return @app.call(env)
37
+ end
38
+ end
39
+ end
@@ -0,0 +1,14 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'allocations'
4
+
5
+ use Allocations
6
+
7
+ run lambda{|env| [404, [], []]}
8
+
9
+ # % curl --insecure https://localhost:9292/allocations
10
+ # String: 32179 allocations
11
+ # Array: 10228 allocations
12
+ # Hash: 1299 allocations
13
+ # Class: 1118 allocations
14
+ # ** Total: 50162 allocations.
@@ -0,0 +1,29 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ require 'async'
5
+ require 'async/http/endpoint'
6
+ require 'async/http/client'
7
+
8
+ Async do
9
+ endpoint = Async::HTTP::Endpoint.parse("https://localhost:9292")
10
+ client = Async::HTTP::Client.new(endpoint, Async::HTTP::Protocol::HTTP2::WithPush)
11
+
12
+ response = client.get("/index.html")
13
+
14
+ puts response.status
15
+ puts response.read
16
+ puts
17
+
18
+ while promise = response.promises.dequeue
19
+ promise.wait
20
+
21
+ puts "** Promise: #{promise.request.path} **"
22
+ puts promise.read
23
+ puts
24
+ end
25
+ ensure
26
+ client.close
27
+ end
28
+
29
+ puts "Done"
@@ -0,0 +1,28 @@
1
+ #!/usr/bin/env falcon --verbose serve --concurrency 1 --config
2
+ # frozen_string_literal: true
3
+
4
+ class EarlyHints
5
+ def initialize(app)
6
+ @app = app
7
+ end
8
+
9
+ def call(env)
10
+ path = env['PATH_INFO']
11
+ early_hints = early_hints = env['rack.early_hints']
12
+
13
+ Async.logger.debug("path: #{path} #{early_hints}")
14
+
15
+ if path == "/index.html" and early_hints
16
+ early_hints.push("/style.css")
17
+ early_hints.push("/script.js")
18
+ end
19
+
20
+ @app.call(env)
21
+ end
22
+ end
23
+
24
+ use EarlyHints
25
+
26
+ use Rack::Static, :urls => [""], :root => __dir__, :index => 'index.html'
27
+
28
+ run lambda{|env| [404, [], ["Not Found"]]}
@@ -0,0 +1,14 @@
1
+ <!doctype HTML>
2
+ <html>
3
+ <head>
4
+ <title>Hello World</title>
5
+
6
+ <link rel="stylesheet" href="/style.css" />
7
+ <script type="text/javascript" src="script.js"></script>
8
+ </head>
9
+ <body>
10
+ <h1>Early Hints</h1>
11
+
12
+ <p>Check the developer tools to see if early hints worked!</p>
13
+ </body>
14
+ </html>
@@ -0,0 +1,2 @@
1
+
2
+ console.log("Hello World")
@@ -0,0 +1,4 @@
1
+
2
+ html {
3
+ font-family: Helvetica;
4
+ }