Skip to content

Commit

Permalink
Move configuration logic/environments to Async::Service. (#226)
Browse files Browse the repository at this point in the history
  • Loading branch information
ioquatix authored Mar 23, 2024
1 parent 2f1a7ad commit 1567816
Show file tree
Hide file tree
Showing 52 changed files with 967 additions and 1,271 deletions.
22 changes: 22 additions & 0 deletions changes.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
# Changes

# Unreleased

## Falcon Host

`async-service` is a new gem that exposes a generic service interface on top of `async-container`. Previously, `falcon host` used `async-container` directly and `build-environment` for configuration. In order to allow for more generic service definitions and configuration, `async-service` now provides a similar interface to `build-environment` and exposes this in a way that can be used for services other tha falcon. This makes it simpler to integrate multiple services into a single application.

The current configuration format uses definitions like this:

```ruby
rack 'hello.localhost', :self_signed_tls
```

This changes to:

```ruby
service 'hello.localhost' do
include Falcon::Environment::Rack
include Falcon::Environment::SelfSignedTLS
end
```
2 changes: 1 addition & 1 deletion config/sus.rb
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
# frozen_string_literal: true

# Released under the MIT License.
# Copyright, 2017-2023, by Samuel Williams.
# Copyright, 2017-2024, by Samuel Williams.

require 'covered/sus'
include Covered::Sus
Expand Down
15 changes: 15 additions & 0 deletions examples/async-service/hello.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
#!/usr/bin/env async-service
# frozen_string_literal: true

# Released under the MIT License.
# Copyright, 2024, by Samuel Williams.

require 'falcon/environment/server'

service "hello-server" do
include Falcon::Environment::Server

middleware do
::Protocol::HTTP::Middleware::HelloWorld
end
end
18 changes: 18 additions & 0 deletions examples/async-service/readme.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
# Async::Service

Async::Service provides a generic service layer for managing the lifecycle of services.

Falcon provides several services which are managed by the service layer, including:

- `Falcon::Service::Server`: An HTTP server.
- `Falcon::Service::Supervisor`: A process supervisor (memory usage, etc).

The service layer can support multiple different services, and can be used to manage the lifecycle of any service, or group of services.

This simple example shows how to use `async-service` to start a web server.

``` shell
$ bundle exec async-service ./hello.rb
```

This will start a web server on port 9292 which responds with "Hello World!" to all requests.
2 changes: 1 addition & 1 deletion examples/hello/config.ru
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@

require 'async'

Console.logger.debug!
# Console.logger.debug!

class RequestLogger
def initialize(app)
Expand Down
8 changes: 2 additions & 6 deletions examples/hello/preload.rb
Original file line number Diff line number Diff line change
@@ -1,10 +1,6 @@
# frozen_string_literal: true

# Released under the MIT License.
# Copyright, 2020-2023, by Samuel Williams.
# Copyright, 2020-2024, by Samuel Williams.

if GC.respond_to?(:compact)
Console.logger.warn(self, "Compacting the mainframe...")
GC.compact
Console.logger.warn(self, "Compacting done...")
end
# $stderr.puts "Preloading..."
6 changes: 3 additions & 3 deletions falcon.gemspec
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ Gem::Specification.new do |spec|
spec.version = Falcon::VERSION

spec.summary = "A fast, asynchronous, rack-compatible web server."
spec.authors = ["Samuel Williams", "Janko Marohnić", "Bryan Powell", "Claudiu Garba", "Kyle Tam", "Mitsutaka Mimura", "Sho Ito", "Colby Swandale", "Daniel Evans", "Kent Gruber", "Michael Adams", "Mikel Kew", "Nick Janetakis", "Olle Jonsson", "Peter Schrammel", "Santiago Bartesaghi", "Sh Lin", "Tad Thorley", "Tasos Latsas"]
spec.authors = ["Samuel Williams", "Janko Marohnić", "Bryan Powell", "Claudiu Garba", "Kyle Tam", "Mitsutaka Mimura", "Sho Ito", "Trevor Turk", "Colby Swandale", "Daniel Evans", "Kent Gruber", "Michael Adams", "Mikel Kew", "Nick Janetakis", "Olle Jonsson", "Peter Schrammel", "Santiago Bartesaghi", "Sh Lin", "Tad Thorley", "Tasos Latsas", "dependabot[bot]"]
spec.license = "MIT"

spec.cert_chain = ['release.cert']
Expand All @@ -26,11 +26,11 @@ Gem::Specification.new do |spec|
spec.required_ruby_version = ">= 3.0"

spec.add_dependency "async"
spec.add_dependency "async-container", "~> 0.16.0"
spec.add_dependency "async-container", "~> 0.17.0"
spec.add_dependency "async-http", "~> 0.57"
spec.add_dependency "async-http-cache", "~> 0.4.0"
spec.add_dependency "async-io", "~> 1.22"
spec.add_dependency "build-environment", "~> 1.13"
spec.add_dependency "async-service", "~> 0.10.0"
spec.add_dependency "bundler"
spec.add_dependency "localhost", "~> 1.1"
spec.add_dependency "openssl", "~> 3.0"
Expand Down
3 changes: 2 additions & 1 deletion gems.rb
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
# frozen_string_literal: true

# Released under the MIT License.
# Copyright, 2017-2023, by Samuel Williams.
# Copyright, 2017-2024, by Samuel Williams.

source 'https://rubygems.org'

Expand All @@ -15,6 +15,7 @@
# gem "protocol-http1", path: "../protocol-http1"
# gem "utopia-project", path: "../utopia-project"
# gem "protocol-rack", path: "../protocol-rack"
# gem "async-service", path: "../async-service"

group :maintenance, optional: true do
gem "bake-modernize"
Expand Down
39 changes: 24 additions & 15 deletions guides/deployment/readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,11 @@ Falcon can be deployed into production either as a standalone application server

## Falcon Serve

`falcon serve` is not designed for deployment. Do not use it for deployment.
`falcon serve` is not designed for deployment because the command line interface is not guaranteed to be stable nor does it expose every possible configuration option.

## Falcon Hosts

`falcon host` is designed for deployment.
`falcon host` is designed for deployment, and is the recommended way to deploy Falcon in production. It exposes a well defined interface for configuring services (web applications, job servers, etc).

### Configuration

Expand All @@ -19,37 +19,46 @@ Falcon can be deployed into production either as a standalone application server
Here is a basic example which hosts a rack application:

~~~ ruby
#!/usr/bin/env -S falcon host
#!/usr/bin/env falcon-host
# frozen_string_literal: true

load :rack, :lets_encrypt_tls, :supervisor

hostname = File.basename(__dir__)
rack hostname, :lets_encrypt_tls do
service hostname do
include Falcon::Environment::Rack
include Falcon::Environment::LetsEncryptTLS

# Insert an in-memory cache in front of the application (using async-http-cache).
cache true
end

supervisor
service "supervisor" do
include Falcon::Environment::Supervisor
end
~~~

These configuration blocks are constructed using [build-environment](https://github.com/ioquatix/build-environment), and the defaults are listed in the [Falcon source code](https://github.com/socketry/falcon/tree/master/lib/falcon/environments).
These configuration blocks are evaluated using [async-service](https://github.com/socketry/async-service).

### Application Configuration

The [`rack` environment](https://github.com/socketry/falcon/blob/master/lib/falcon/environments/rack.rb) inherits the [application environment](https://github.com/socketry/falcon/blob/master/lib/falcon/environments/application.rb). These environments by default are defined for usage with `falcon virtual`, but you can customise any parts of the configuration, e.g. to bind a production host to `localhost:3000` using plaintext HTTP/2:
The environment configuration is defined in the `Falcon::Environment` module. The {ruby Falcon::Environment::Application} environment supports the generic virtual host functionality, but you can customise any parts of the configuration, e.g. to bind a production host to `localhost:3000` using plaintext HTTP/2:

~~~ ruby
#!/usr/bin/env -S falcon host
#!/usr/bin/env falcon-host
# frozen_string_literal: true

load :rack, :supervisor

hostname = File.basename(__dir__)
rack hostname do
endpoint Async::HTTP::Endpoint.parse('http://localhost:3000').with(protocol: Async::HTTP::Protocol::HTTP2)
service hostname do
include Falcon::Environment::Rack
include Falcon::Environment::LetsEncryptTLS

endpoint do
Async::HTTP::Endpoint.parse('http://localhost:3000').with(protocol: Async::HTTP::Protocol::HTTP2)
end
end

supervisor
service "supervisor" do
include Falcon::Environment::Supervisor
end
~~~

You can verify this is working using `nghttp -v http://localhost:3000`.
Expand Down
38 changes: 6 additions & 32 deletions lib/falcon/command/host.rb
Original file line number Diff line number Diff line change
@@ -1,14 +1,13 @@
# frozen_string_literal: true

# Released under the MIT License.
# Copyright, 2019-2023, by Samuel Williams.
# Copyright, 2019-2024, by Samuel Williams.

require_relative '../controller/host'
require_relative '../configuration'
require_relative 'paths'
require_relative '../version'

require 'samovar'
require 'bundler'
require 'async/service/controller'

module Falcon
module Command
Expand All @@ -23,28 +22,13 @@ class Host < Samovar::Command
# @attribute [Array(String)]
many :paths, "Service configuration paths.", default: ["falcon.rb"]

include Paths

# The container class to use.
def container_class
Async::Container.best_container_class
end

# Generate a configuration based on the specified {paths}.
def configuration
configuration = Configuration.new

@paths.each do |path|
path = File.expand_path(path)
configuration.load_file(path)
end

return configuration
end

# Prepare a new controller for the command.
def controller
Controller::Host.new(self)
end

# Prepare the environment and run the controller.
def call
Console.logger.info(self) do |buffer|
Expand All @@ -54,17 +38,7 @@ def call
buffer.puts "- To reload: kill -HUP #{Process.pid}"
end

begin
Bundler.require(:preload)
rescue Bundler::GemfileNotFound
# Ignore.
end

if GC.respond_to?(:compact)
GC.compact
end

self.controller.run
Async::Service::Controller.run(self.configuration, container_class: self.container_class)
end
end
end
Expand Down
35 changes: 20 additions & 15 deletions lib/falcon/command/proxy.rb
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
# frozen_string_literal: true

# Released under the MIT License.
# Copyright, 2020-2023, by Samuel Williams.
# Copyright, 2020-2024, by Samuel Williams.

require_relative '../controller/proxy'
require_relative '../environment/proxy'
require_relative 'paths'

require 'samovar'
Expand Down Expand Up @@ -31,20 +31,21 @@ class Proxy < Samovar::Command

include Paths

# Prepare a new controller for the command.
def controller
Controller::Proxy.new(self)
def environment(**options)
Async::Service::Environment.new(Falcon::Environment::Proxy).with(
root: Dir.pwd,
name: self.class.name,
verbose: self.parent&.verbose?,
url: @options[:bind],
timeout: @options[:timeout],
**options
)
end

# The container class to use.
def container_class
Async::Container.best_container_class
end

# Options for the container.
# See {Controller::Serve#setup}.
def container_options
{}
def configuration
Configuration.for(
self.environment(environments: super.environments)
)
end

# Prepare the environment and run the controller.
Expand All @@ -54,9 +55,13 @@ def call
buffer.puts "- Binding to: #{@options[:bind]}"
buffer.puts "- To terminate: Ctrl-C or kill #{Process.pid}"
buffer.puts "- To reload: kill -HUP #{Process.pid}"

self.resolved_paths.each do |path|
buffer.puts "- Loading configuration from #{path}"
end
end

self.controller.run
Async::Service::Controller.run(self.configuration)
end

# The endpoint to bind to.
Expand Down
36 changes: 21 additions & 15 deletions lib/falcon/command/redirect.rb
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
# frozen_string_literal: true

# Released under the MIT License.
# Copyright, 2020-2023, by Samuel Williams.
# Copyright, 2020-2024, by Samuel Williams.

require_relative '../controller/redirect'
require_relative '../environment/redirect'
require_relative 'paths'

require 'samovar'
Expand All @@ -29,20 +29,22 @@ class Redirect < Samovar::Command

include Paths

# Prepare a new controller for the command.
def controller
Controller::Redirect.new(self)
def environment(**options)
Async::Service::Environment.new(Falcon::Environment::Redirect).with(
root: Dir.pwd,
name: self.class.name,
verbose: self.parent&.verbose?,
url: @options[:bind],
redirect_url: @options[:redirect],
timeout: @options[:timeout],
**options
)
end

# The container class to use.
def container_class
Async::Container.best_container_class
end

# Options for the container.
# See {Controller::Serve#setup}.
def container_options
{}
def configuration
Configuration.for(
self.environment(environments: super.environments)
)
end

# Prepare the environment and run the controller.
Expand All @@ -52,9 +54,13 @@ def call
buffer.puts "- Binding to: #{@options[:bind]}"
buffer.puts "- To terminate: Ctrl-C or kill #{Process.pid}"
buffer.puts "- To reload: kill -HUP #{Process.pid}"

self.resolved_paths.each do |path|
buffer.puts "- Loading configuration from #{path}"
end
end

self.controller.run
Async::Service::Controller.run(self.configuration)
end

# The endpoint to bind to.
Expand Down
Loading

0 comments on commit 1567816

Please sign in to comment.