falcon 0.36.4

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