falcon 0.36.0 → 0.36.5

Sign up to get free protection for your applications and to get access to all the features.
Files changed (90) hide show
  1. checksums.yaml +4 -4
  2. data/lib/falcon/adapters/early_hints.rb +8 -0
  3. data/lib/falcon/adapters/input.rb +32 -11
  4. data/lib/falcon/adapters/output.rb +20 -1
  5. data/lib/falcon/adapters/rack.rb +59 -32
  6. data/lib/falcon/adapters/response.rb +23 -1
  7. data/lib/falcon/adapters/rewindable.rb +10 -3
  8. data/lib/falcon/command.rb +2 -0
  9. data/lib/falcon/command/host.rb +13 -2
  10. data/lib/falcon/command/paths.rb +4 -0
  11. data/lib/falcon/command/proxy.rb +17 -1
  12. data/lib/falcon/command/redirect.rb +15 -1
  13. data/lib/falcon/command/serve.rb +22 -15
  14. data/lib/falcon/command/supervisor.rb +15 -1
  15. data/lib/falcon/command/top.rb +16 -0
  16. data/lib/falcon/command/virtual.rb +21 -0
  17. data/lib/falcon/configuration.rb +69 -7
  18. data/lib/falcon/controller/host.rb +12 -0
  19. data/lib/falcon/controller/proxy.rb +13 -0
  20. data/lib/falcon/controller/redirect.rb +7 -0
  21. data/lib/falcon/controller/serve.rb +14 -1
  22. data/lib/falcon/controller/virtual.rb +28 -2
  23. data/lib/falcon/endpoint.rb +8 -0
  24. data/lib/falcon/{configuration/proxy.rb → environments.rb} +9 -5
  25. data/lib/falcon/environments/application.rb +68 -0
  26. data/lib/falcon/{configuration/application.rb → environments/lets_encrypt_tls.rb} +21 -20
  27. data/lib/falcon/{configuration/lets_encrypt_tls.rb → environments/proxy.rb} +13 -6
  28. data/lib/falcon/{configuration → environments}/rack.rb +14 -2
  29. data/lib/falcon/{configuration → environments}/self_signed_tls.rb +9 -1
  30. data/lib/falcon/{configuration → environments}/supervisor.rb +19 -5
  31. data/lib/falcon/{configuration → environments}/tls.rb +39 -5
  32. data/lib/falcon/extensions/openssl.rb +1 -0
  33. data/lib/falcon/middleware/proxy.rb +26 -5
  34. data/lib/falcon/middleware/redirect.rb +11 -0
  35. data/lib/falcon/{verbose.rb → middleware/verbose.rb} +34 -26
  36. data/lib/falcon/proxy_endpoint.rb +21 -0
  37. data/lib/falcon/server.rb +8 -2
  38. data/lib/falcon/service/application.rb +9 -0
  39. data/lib/falcon/service/generic.rb +18 -0
  40. data/lib/falcon/service/proxy.rb +6 -0
  41. data/lib/falcon/service/supervisor.rb +14 -2
  42. data/lib/falcon/services.rb +21 -0
  43. data/lib/falcon/tls.rb +4 -2
  44. data/lib/falcon/version.rb +1 -1
  45. data/lib/rack/handler/falcon.rb +7 -1
  46. metadata +20 -77
  47. data/.editorconfig +0 -5
  48. data/.github/FUNDING.yml +0 -3
  49. data/.github/workflows/development.yml +0 -45
  50. data/.gitignore +0 -14
  51. data/.rspec +0 -3
  52. data/.travis.yml +0 -41
  53. data/Gemfile +0 -16
  54. data/README.md +0 -316
  55. data/examples/beer/config.ru +0 -57
  56. data/examples/beer/falcon.rb +0 -8
  57. data/examples/benchmark/config.ru +0 -39
  58. data/examples/benchmark/falcon.rb +0 -6
  59. data/examples/csv/config.ru +0 -31
  60. data/examples/google/falcon.rb +0 -14
  61. data/examples/hello/config.ru +0 -22
  62. data/examples/hello/falcon.rb +0 -24
  63. data/examples/hello/preload.rb +0 -7
  64. data/examples/internet/config.ru +0 -54
  65. data/examples/memory/allocations.rb +0 -39
  66. data/examples/memory/config.ru +0 -14
  67. data/examples/push/client.rb +0 -29
  68. data/examples/push/config.ru +0 -28
  69. data/examples/push/index.html +0 -14
  70. data/examples/push/script.js +0 -2
  71. data/examples/push/style.css +0 -4
  72. data/examples/redis/Gemfile +0 -9
  73. data/examples/redis/config.ru +0 -28
  74. data/examples/sequel/Gemfile +0 -4
  75. data/examples/sequel/config.ru +0 -8
  76. data/examples/sequel/data.sqlite3 +0 -0
  77. data/examples/server/standalone.rb +0 -27
  78. data/examples/sinatra/Gemfile +0 -7
  79. data/examples/sinatra/Gemfile.lock +0 -53
  80. data/examples/sinatra/config.ru +0 -16
  81. data/examples/trailers/config.ru +0 -34
  82. data/examples/trailers/falcon.rb +0 -8
  83. data/falcon.gemspec +0 -45
  84. data/gems/rack1.gemfile +0 -4
  85. data/gems/rack3.gemfile +0 -4
  86. data/logo-square.afdesign +0 -0
  87. data/logo.afdesign +0 -0
  88. data/logo.svg +0 -107
  89. data/server.rb +0 -21
  90. data/tasks/benchmark.rake +0 -103
@@ -22,11 +22,23 @@
22
22
 
23
23
  load :application
24
24
 
25
- add(:rack, :application) do
25
+ # A rack application environment.
26
+ #
27
+ # Derived from {.application}.
28
+ #
29
+ # @scope Falcon Environments
30
+ # @name rack
31
+ environment(:rack, :application) do
32
+ # The rack configuration path.
33
+ # @attribute [String]
26
34
  config_path {::File.expand_path("config.ru", root)}
27
35
 
36
+ # Whether to enable the application layer cache.
37
+ # @attribute [String]
28
38
  cache false
29
39
 
40
+ # The middleware stack for the rack application.
41
+ # @attribute [Protocol::HTTP::Middleware]
30
42
  middleware do
31
43
  app, _ = ::Rack::Builder.parse_file(config_path)
32
44
 
@@ -35,4 +47,4 @@ add(:rack, :application) do
35
47
  cache: cache
36
48
  )
37
49
  end
38
- end
50
+ end
@@ -22,9 +22,17 @@
22
22
 
23
23
  require 'localhost/authority'
24
24
 
25
- add(:self_signed_tls) do
25
+ # A self-signed SSL context environment.
26
+ #
27
+ # @scope Falcon Environments
28
+ # @name self_signed_tls
29
+ environment(:self_signed_tls) do
30
+ # The default session identifier for the session cache.
31
+ # @attribute [String]
26
32
  ssl_session_id {"falcon"}
27
33
 
34
+ # The SSL context to use for incoming connections.
35
+ # @attribute [OpenSSL::SSL::SSLContext]
28
36
  ssl_context do
29
37
  contexts = Localhost::Authority.fetch(authority)
30
38
 
@@ -22,15 +22,29 @@
22
22
 
23
23
  require_relative '../service/supervisor'
24
24
 
25
- add(:supervisor) do
26
- start true
27
-
25
+ # A application process monitor environment.
26
+ #
27
+ # @scope Falcon Environments
28
+ # @name supervisor
29
+ environment(:supervisor) do
30
+ # The name of the supervisor
31
+ # @attribute [String]
28
32
  name "supervisor"
29
33
 
30
- ipc_path {::File.expand_path("supervisor.ipc", root)}
34
+ # The IPC path to use for communication with the supervisor.
35
+ # @attribute [String]
36
+ ipc_path do
37
+ ::File.expand_path("supervisor.ipc", root)
38
+ end
31
39
 
32
- endpoint {Async::IO::Endpoint.unix(ipc_path)}
40
+ # The endpoint the supervisor will bind to.
41
+ # @attribute [Async::IO::Endpoint]
42
+ endpoint do
43
+ Async::IO::Endpoint.unix(ipc_path)
44
+ end
33
45
 
46
+ # The service class to use for the supervisor.
47
+ # @attribute [Class]
34
48
  service do
35
49
  ::Falcon::Service::Supervisor
36
50
  end
@@ -24,19 +24,53 @@ require_relative '../extensions/openssl'
24
24
  require_relative '../controller/proxy'
25
25
  require_relative '../tls'
26
26
 
27
- add(:tls) do
27
+ # A general SSL context environment.
28
+ #
29
+ # @scope Falcon Environments
30
+ # @name tls
31
+ environment(:tls) do
32
+ # The default session identifier for the session cache.
33
+ # @attribute [String]
28
34
  ssl_session_id "falcon"
35
+
36
+ # The supported ciphers.
37
+ # @attribute [Array(String)]
29
38
  ssl_ciphers Falcon::TLS::SERVER_CIPHERS
30
39
 
31
- ssl_certificate_path {File.expand_path("ssl/certificate.pem", root)}
32
- ssl_certificates {OpenSSL::X509.load_certificates(ssl_certificate_path)}
40
+ # The public certificate path.
41
+ # @attribute [String]
42
+ ssl_certificate_path do
43
+ File.expand_path("ssl/certificate.pem", root)
44
+ end
45
+
46
+ # The list of certificates loaded from that path.
47
+ # @attribute [Array(OpenSSL::X509::Certificate)]
48
+ ssl_certificates do
49
+ OpenSSL::X509.load_certificates(ssl_certificate_path)
50
+ end
33
51
 
52
+ # The main certificate.
53
+ # @attribute [OpenSSL::X509::Certificate]
34
54
  ssl_certificate {ssl_certificates[0]}
55
+
56
+ # The certificate chain.
57
+ # @attribute [Array(OpenSSL::X509::Certificate)]
35
58
  ssl_certificate_chain {ssl_certificates[1..-1]}
36
59
 
37
- ssl_private_key_path {File.expand_path("ssl/private.key", root)}
38
- ssl_private_key {OpenSSL::PKey::RSA.new(File.read(ssl_private_key_path))}
60
+ # The private key path.
61
+ # @attribute [String]
62
+ ssl_private_key_path do
63
+ File.expand_path("ssl/private.key", root)
64
+ end
65
+
66
+ # The private key.
67
+ # @attribute [OpenSSL::PKey::RSA]
68
+ ssl_private_key do
69
+ OpenSSL::PKey::RSA.new(File.read(ssl_private_key_path))
70
+ end
39
71
 
72
+ # The SSL context to use for incoming connections.
73
+ # @attribute [OpenSSL::SSL::SSLContext]
40
74
  ssl_context do
41
75
  OpenSSL::SSL::SSLContext.new.tap do |context|
42
76
  context.add_certificate(ssl_certificate, ssl_private_key, ssl_certificate_chain)
@@ -25,6 +25,7 @@ require 'openssl/x509'
25
25
  module OpenSSL::X509
26
26
  CERTIFICATE_PATTERN = /-----BEGIN CERTIFICATE-----.*?-----END CERTIFICATE-----/m
27
27
 
28
+ # An extension to load an array of certificates from a file at the given path.
28
29
  def self.load_certificates(path)
29
30
  File.read(path).scan(CERTIFICATE_PATTERN).collect do |text|
30
31
  Certificate.new(text)
@@ -26,6 +26,7 @@ require 'protocol/http/middleware'
26
26
 
27
27
  module Falcon
28
28
  module Middleware
29
+ # A static middleware which always returns a 400 bad request response.
29
30
  module BadRequest
30
31
  def self.call(request)
31
32
  return Protocol::HTTP::Response[400, {}, []]
@@ -35,14 +36,17 @@ module Falcon
35
36
  end
36
37
  end
37
38
 
39
+ # A HTTP middleware for proxying requests to a given set of hosts.
40
+ # Typically used for implementing virtual servers.
38
41
  class Proxy < Protocol::HTTP::Middleware
39
- FORWARDED = 'forwarded'.freeze
40
- X_FORWARDED_FOR = 'x-forwarded-for'.freeze
41
- X_FORWARDED_PROTO = 'x-forwarded-proto'.freeze
42
+ FORWARDED = 'forwarded'
43
+ X_FORWARDED_FOR = 'x-forwarded-for'
44
+ X_FORWARDED_PROTO = 'x-forwarded-proto'
42
45
 
43
- VIA = 'via'.freeze
44
- CONNECTION = 'connection'.freeze
46
+ VIA = 'via'
47
+ CONNECTION = 'connection'
45
48
 
49
+ # HTTP hop headers which *should* not be passed through the proxy.
46
50
  HOP_HEADERS = [
47
51
  'connection',
48
52
  'keep-alive',
@@ -52,6 +56,9 @@ module Falcon
52
56
  'upgrade',
53
57
  ]
54
58
 
59
+ # Initialize the proxy middleware.
60
+ # @parameter app [Protocol::HTTP::Middleware] The middleware to use if a request can't be proxied.
61
+ # @parameter hosts [Array(Service::Proxy)] The host applications to proxy to.
55
62
  def initialize(app, hosts)
56
63
  super(app)
57
64
 
@@ -63,18 +70,26 @@ module Falcon
63
70
  @count = 0
64
71
  end
65
72
 
73
+ # The number of requests that have been proxied.
74
+ # @attribute [Integer]
66
75
  attr :count
67
76
 
77
+ # Close all the connections to the upstream hosts.
68
78
  def close
69
79
  @clients.each_value(&:close)
70
80
 
71
81
  super
72
82
  end
73
83
 
84
+ # Establish a connection to the specified upstream endpoint.
85
+ # @parameter endpoint [Async::HTTP::Endpoint]
74
86
  def connect(endpoint)
75
87
  @clients[endpoint] ||= Async::HTTP::Client.new(endpoint)
76
88
  end
77
89
 
90
+ # Lookup the appropriate host for the given request.
91
+ # @parameter request [Protocol::HTTP::Request]
92
+ # @returns [Service::Proxy]
78
93
  def lookup(request)
79
94
  # Trailing dot and port is ignored/normalized.
80
95
  if authority = request.authority&.sub(/(\.)?(:\d+)?$/, '')
@@ -82,6 +97,8 @@ module Falcon
82
97
  end
83
98
  end
84
99
 
100
+ # Prepare the headers to be sent to an upstream host.
101
+ # In particular, we delete all connection and hop headers.
85
102
  def prepare_headers(headers)
86
103
  if connection = headers[CONNECTION]
87
104
  headers.extract(connection)
@@ -90,6 +107,8 @@ module Falcon
90
107
  headers.extract(HOP_HEADERS)
91
108
  end
92
109
 
110
+ # Prepare the request to be proxied to the specified host.
111
+ # In particular, we set appropriate {VIA}, {FORWARDED}, {X_FORWARDED_FOR} and {X_FORWARDED_PROTO} headers.
93
112
  def prepare_request(request, host)
94
113
  forwarded = []
95
114
 
@@ -124,6 +143,8 @@ module Falcon
124
143
  return request
125
144
  end
126
145
 
146
+ # Proxy the request if the authority matches a specific host.
147
+ # @parameter request [Protocol::HTTP::Request]
127
148
  def call(request)
128
149
  if host = lookup(request)
129
150
  @count += 1
@@ -24,6 +24,7 @@ require 'async/http/client'
24
24
 
25
25
  module Falcon
26
26
  module Middleware
27
+ # A static middleware which always returns a 404 not found response.
27
28
  module NotFound
28
29
  def self.call(request)
29
30
  return Protocol::HTTP::Response[404, {}, []]
@@ -33,7 +34,13 @@ module Falcon
33
34
  end
34
35
  end
35
36
 
37
+ # A HTTP middleware for redirecting a given set of hosts to a different endpoint.
38
+ # Typically used for implementing HTTP -> HTTPS redirects.
36
39
  class Redirect < Protocol::HTTP::Middleware
40
+ # Initialize the redirect middleware.
41
+ # @parameter app [Protocol::HTTP::Middleware] The middleware to wrap.
42
+ # @parameter hosts [Hash(String, Service::Proxy)] The map of hosts.
43
+ # @parameter endpoint [Endpoint] The template endpoint to use to build the redirect location.
37
44
  def initialize(app, hosts, endpoint)
38
45
  super(app)
39
46
 
@@ -41,6 +48,8 @@ module Falcon
41
48
  @endpoint = endpoint
42
49
  end
43
50
 
51
+ # Lookup the appropriate host for the given request.
52
+ # @parameter request [Protocol::HTTP::Request]
44
53
  def lookup(request)
45
54
  # Trailing dot and port is ignored/normalized.
46
55
  if authority = request.authority&.sub(/(\.)?(:\d+)?$/, '')
@@ -48,6 +57,8 @@ module Falcon
48
57
  end
49
58
  end
50
59
 
60
+ # Redirect the request if the authority matches a specific host.
61
+ # @parameter request [Protocol::HTTP::Request]
51
62
  def call(request)
52
63
  if host = lookup(request)
53
64
  if @endpoint.default_port?
@@ -24,36 +24,44 @@ require 'async/logger'
24
24
  require 'async/http/statistics'
25
25
 
26
26
  module Falcon
27
- class Verbose < Protocol::HTTP::Middleware
28
- def initialize(app, logger = Async.logger)
29
- super(app)
30
-
31
- @logger = logger
32
- end
33
-
34
- def annotate(request)
35
- task = Async::Task.current
36
- address = request.remote_address
37
-
38
- @logger.info(request) {"Headers: #{request.headers.to_h} from #{address.inspect}"}
39
-
40
- task.annotate("#{request.method} #{request.path} from #{address.inspect}")
41
- end
42
-
43
- def call(request)
44
- annotate(request)
45
-
46
- statistics = Async::HTTP::Statistics.start
47
-
48
- response = super
27
+ module Middleware
28
+ # A HTTP middleware for logging requests and responses.
29
+ class Verbose < Protocol::HTTP::Middleware
30
+ # Initialize the verbose middleware.
31
+ # @parameter app [Protocol::HTTP::Middleware] The middleware to wrap.
32
+ # @parameter logger [Console::Logger] The logger to use.
33
+ def initialize(app, logger = Async.logger)
34
+ super(app)
35
+
36
+ @logger = logger
37
+ end
49
38
 
50
- statistics.wrap(response) do |statistics, error|
51
- @logger.info(request) {"Responding with: #{response.status} #{response.headers.to_h}; #{statistics.inspect}"}
39
+ # Log details of the incoming request.
40
+ def annotate(request)
41
+ task = Async::Task.current
42
+ address = request.remote_address
43
+
44
+ @logger.info(request) {"Headers: #{request.headers.to_h} from #{address.inspect}"}
52
45
 
53
- @logger.error(request) {"#{error.class}: #{error.message}"} if error
46
+ task.annotate("#{request.method} #{request.path} from #{address.inspect}")
54
47
  end
55
48
 
56
- return response
49
+ # Log details of the incoming request using {annotate} and wrap the response to log response details too.
50
+ def call(request)
51
+ annotate(request)
52
+
53
+ statistics = Async::HTTP::Statistics.start
54
+
55
+ response = super
56
+
57
+ statistics.wrap(response) do |statistics, error|
58
+ @logger.info(request) {"Responding with: #{response.status} #{response.headers.to_h}; #{statistics.inspect}"}
59
+
60
+ @logger.error(request) {"#{error.class}: #{error.message}"} if error
61
+ end
62
+
63
+ return response
64
+ end
57
65
  end
58
66
  end
59
67
  end
@@ -23,7 +23,10 @@
23
23
  require 'async/io/unix_endpoint'
24
24
 
25
25
  module Falcon
26
+ # An endpoint suitable for proxing requests, typically via a unix pipe.
26
27
  class ProxyEndpoint < Async::IO::Endpoint
28
+ # Initialize the proxy endpoint.
29
+ # @parameter endpoint [Async::IO::Endpoint] The endpoint which will be used for connecting/binding.
27
30
  def initialize(endpoint, **options)
28
31
  super(**options)
29
32
 
@@ -34,28 +37,44 @@ module Falcon
34
37
  "\#<#{self.class} endpoint=#{@endpoint}>"
35
38
  end
36
39
 
40
+ # The actual endpoint for I/O.
41
+ # @attribute [Async::IO::Endpoint]
37
42
  attr :endpoint
38
43
 
44
+ # The protocol to use for this connection.
45
+ # @returns [Async::HTTP::Protocol] A specific protocol, e.g. {Async::HTTP::P}
39
46
  def protocol
40
47
  @options[:protocol]
41
48
  end
42
49
 
50
+ # The scheme to use for this endpoint.
51
+ # e.g. `"http"`.
52
+ # @returns [String]
43
53
  def scheme
44
54
  @options[:scheme]
45
55
  end
46
56
 
57
+ # The authority to use for this endpoint.
58
+ # e.g. `"myapp.com"`.
59
+ # @returns [String]
47
60
  def authority
48
61
  @options[:authority]
49
62
  end
50
63
 
64
+ # Connect to the endpoint.
51
65
  def connect(&block)
52
66
  @endpoint.connect(&block)
53
67
  end
54
68
 
69
+ # Bind to the endpoint.
55
70
  def bind(&block)
56
71
  @endpoint.bind(&block)
57
72
  end
58
73
 
74
+ # Enumerate the endpoint.
75
+ # If the endpoint has multiple underlying endpoints, this will enumerate them individually.
76
+ # @yields {|endpoint| ...}
77
+ # @parameter endpoint [ProxyEndpoint]
59
78
  def each
60
79
  return to_enum unless block_given?
61
80
 
@@ -64,6 +83,8 @@ module Falcon
64
83
  end
65
84
  end
66
85
 
86
+ # Create a proxy unix endpoint with the specific path.
87
+ # @returns [ProxyEndpoint]
67
88
  def self.unix(path, **options)
68
89
  self.new(::Async::IO::Endpoint.unix(path), **options)
69
90
  end
@@ -27,16 +27,22 @@ require 'protocol/http/content_encoding'
27
27
 
28
28
  require 'async/http/cache'
29
29
 
30
- require_relative 'verbose'
30
+ require_relative 'middleware/verbose'
31
+
31
32
  require_relative 'adapters/rewindable'
32
33
  require_relative 'adapters/rack'
33
34
 
34
35
  module Falcon
36
+ # A server listening on a specific endpoint, hosting a specific middleware.
35
37
  class Server < Async::HTTP::Server
38
+ # Wrap a rack application into a middleware suitable the server.
39
+ # @parameter rack_app [Proc | Object] A rack application/middleware.
40
+ # @parameter verbose [Boolean] Whether to add the {Verbose} middleware.
41
+ # @parameter cache [Boolean] Whether to add the {Async::HTTP::Cache} middleware.
36
42
  def self.middleware(rack_app, verbose: false, cache: true)
37
43
  ::Protocol::HTTP::Middleware.build do
38
44
  if verbose
39
- use Verbose
45
+ use Middleware::Verbose
40
46
  end
41
47
 
42
48
  if cache
@@ -27,6 +27,7 @@ require 'async/io/shared_endpoint'
27
27
 
28
28
  module Falcon
29
29
  module Service
30
+ # Implements an application server using an internal clear-text proxy.
30
31
  class Application < Proxy
31
32
  def initialize(environment)
32
33
  super
@@ -34,11 +35,14 @@ module Falcon
34
35
  @bound_endpoint = nil
35
36
  end
36
37
 
38
+ # The middleware that will be served by this application.
39
+ # @returns [Protocol::HTTP::Middleware]
37
40
  def middleware
38
41
  # In a multi-threaded container, we don't want to modify the shared evaluator's cache, so we create a new evaluator:
39
42
  @environment.evaluator.middleware
40
43
  end
41
44
 
45
+ # Preload any resources specified by the environment.
42
46
  def preload!
43
47
  if scripts = @evaluator.preload
44
48
  scripts.each do |path|
@@ -49,6 +53,8 @@ module Falcon
49
53
  end
50
54
  end
51
55
 
56
+ # Prepare the bound endpoint for the application instances.
57
+ # Invoke {preload!} to load shared resources into the parent process.
52
58
  def start
53
59
  Async.logger.info(self) {"Binding to #{self.endpoint}..."}
54
60
 
@@ -61,6 +67,8 @@ module Falcon
61
67
  super
62
68
  end
63
69
 
70
+ # Setup instances of the application into the container.
71
+ # @parameter container [Async::Container::Generic]
64
72
  def setup(container)
65
73
  protocol = self.protocol
66
74
  scheme = self.scheme
@@ -82,6 +90,7 @@ module Falcon
82
90
  super
83
91
  end
84
92
 
93
+ # Close the bound endpoint.
85
94
  def stop
86
95
  @bound_endpoint&.close
87
96
  @bound_endpoint = nil