Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Move Configuration logic to Async::Service. #226

Merged
merged 21 commits into from
Mar 23, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading