falcon 0.36.4
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.editorconfig +5 -0
- data/.github/FUNDING.yml +3 -0
- data/.github/workflows/development.yml +60 -0
- data/.gitignore +14 -0
- data/.rspec +3 -0
- data/Gemfile +17 -0
- data/README.md +316 -0
- data/bake/falcon/supervisor.rb +8 -0
- data/bin/falcon +32 -0
- data/bin/falcon-host +28 -0
- data/examples/beer/config.ru +57 -0
- data/examples/beer/falcon.rb +8 -0
- data/examples/benchmark/config.ru +39 -0
- data/examples/benchmark/falcon.rb +6 -0
- data/examples/csv/config.ru +31 -0
- data/examples/google/falcon.rb +14 -0
- data/examples/hello/config.ru +22 -0
- data/examples/hello/falcon.rb +24 -0
- data/examples/hello/preload.rb +7 -0
- data/examples/internet/config.ru +54 -0
- data/examples/memory/allocations.rb +39 -0
- data/examples/memory/config.ru +14 -0
- data/examples/push/client.rb +29 -0
- data/examples/push/config.ru +28 -0
- data/examples/push/index.html +14 -0
- data/examples/push/script.js +2 -0
- data/examples/push/style.css +4 -0
- data/examples/redis/Gemfile +9 -0
- data/examples/redis/config.ru +28 -0
- data/examples/sequel/Gemfile +4 -0
- data/examples/sequel/config.ru +8 -0
- data/examples/sequel/data.sqlite3 +0 -0
- data/examples/server/standalone.rb +27 -0
- data/examples/sinatra/Gemfile +7 -0
- data/examples/sinatra/Gemfile.lock +53 -0
- data/examples/sinatra/config.ru +16 -0
- data/examples/trailers/config.ru +34 -0
- data/examples/trailers/falcon.rb +8 -0
- data/falcon.gemspec +45 -0
- data/gems/rack1.gemfile +4 -0
- data/gems/rack3.gemfile +4 -0
- data/lib/falcon.rb +23 -0
- data/lib/falcon/adapters/early_hints.rb +49 -0
- data/lib/falcon/adapters/input.rb +131 -0
- data/lib/falcon/adapters/output.rb +101 -0
- data/lib/falcon/adapters/rack.rb +202 -0
- data/lib/falcon/adapters/response.rb +91 -0
- data/lib/falcon/adapters/rewindable.rb +67 -0
- data/lib/falcon/command.rb +31 -0
- data/lib/falcon/command/host.rb +69 -0
- data/lib/falcon/command/paths.rb +47 -0
- data/lib/falcon/command/proxy.rb +71 -0
- data/lib/falcon/command/redirect.rb +76 -0
- data/lib/falcon/command/serve.rb +151 -0
- data/lib/falcon/command/supervisor.rb +78 -0
- data/lib/falcon/command/top.rb +94 -0
- data/lib/falcon/command/virtual.rb +89 -0
- data/lib/falcon/configuration.rb +147 -0
- data/lib/falcon/configuration/application.rb +46 -0
- data/lib/falcon/configuration/lets_encrypt_tls.rb +30 -0
- data/lib/falcon/configuration/proxy.rb +27 -0
- data/lib/falcon/configuration/rack.rb +38 -0
- data/lib/falcon/configuration/self_signed_tls.rb +47 -0
- data/lib/falcon/configuration/supervisor.rb +37 -0
- data/lib/falcon/configuration/tls.rb +70 -0
- data/lib/falcon/controller/host.rb +60 -0
- data/lib/falcon/controller/proxy.rb +109 -0
- data/lib/falcon/controller/redirect.rb +69 -0
- data/lib/falcon/controller/serve.rb +111 -0
- data/lib/falcon/controller/virtual.rb +100 -0
- data/lib/falcon/endpoint.rb +52 -0
- data/lib/falcon/extensions/openssl.rb +33 -0
- data/lib/falcon/middleware/proxy.rb +145 -0
- data/lib/falcon/middleware/redirect.rb +66 -0
- data/lib/falcon/proxy_endpoint.rb +71 -0
- data/lib/falcon/server.rb +54 -0
- data/lib/falcon/service/application.rb +93 -0
- data/lib/falcon/service/generic.rb +60 -0
- data/lib/falcon/service/proxy.rb +60 -0
- data/lib/falcon/service/supervisor.rb +104 -0
- data/lib/falcon/services.rb +78 -0
- data/lib/falcon/tls.rb +46 -0
- data/lib/falcon/verbose.rb +59 -0
- data/lib/falcon/version.rb +25 -0
- data/lib/rack/handler/falcon.rb +40 -0
- data/logo-square.afdesign +0 -0
- data/logo.afdesign +0 -0
- data/logo.svg +107 -0
- data/server.rb +21 -0
- data/tasks/benchmark.rake +103 -0
- metadata +386 -0
data/bin/falcon-host
ADDED
@@ -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,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,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,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>
|