diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md
new file mode 100644
index 00000000..1de68b21
--- /dev/null
+++ b/.github/copilot-instructions.md
@@ -0,0 +1,20 @@
+# GitHub Copilot Instructions
+
+## Use Agent Context
+
+When working on this project, consult the `agents.md` file for project-specific guidelines, architecture decisions, and development patterns. This file contains curated information that will help you make better decisions aligned with the project's goals and standards.
+
+If the file does not exist, you will need to install it, by running the following command:
+
+```bash
+$ bundle install
+$ bundle exec bake agent:context:install
+```
+
+This command will set up the necessary context files that help you understand the project structure, dependencies, and conventions.
+
+## Ignoring Files
+
+The `.gitignore` file is split into two sections, separated by a blank line. The first section is automatically generated, while the second section is user controlled.
+
+While working on pull requests, you should not add unrelated changes to the `.gitignore` file as part of the pull request.
diff --git a/.github/workflows/documentation-coverage.yaml b/.github/workflows/documentation-coverage.yaml
index 8d801c5a..4e8d7ff7 100644
--- a/.github/workflows/documentation-coverage.yaml
+++ b/.github/workflows/documentation-coverage.yaml
@@ -6,7 +6,6 @@ permissions:
contents: read
env:
- CONSOLE_OUTPUT: XTerm
COVERAGE: PartialSummary
jobs:
@@ -17,7 +16,7 @@ jobs:
- uses: actions/checkout@v4
- uses: ruby/setup-ruby@v1
with:
- ruby-version: "3.4"
+ ruby-version: ruby
bundler-cache: true
- name: Validate coverage
diff --git a/.github/workflows/documentation.yaml b/.github/workflows/documentation.yaml
index e47c6b30..e112a90b 100644
--- a/.github/workflows/documentation.yaml
+++ b/.github/workflows/documentation.yaml
@@ -17,7 +17,6 @@ concurrency:
cancel-in-progress: true
env:
- CONSOLE_OUTPUT: XTerm
BUNDLE_WITH: maintenance
jobs:
@@ -29,7 +28,7 @@ jobs:
- uses: ruby/setup-ruby@v1
with:
- ruby-version: "3.4"
+ ruby-version: ruby
bundler-cache: true
- name: Installing packages
diff --git a/.github/workflows/rubocop.yaml b/.github/workflows/rubocop.yaml
index 287c06d9..d6dd892e 100644
--- a/.github/workflows/rubocop.yaml
+++ b/.github/workflows/rubocop.yaml
@@ -5,9 +5,6 @@ on: [push, pull_request]
permissions:
contents: read
-env:
- CONSOLE_OUTPUT: XTerm
-
jobs:
check:
runs-on: ubuntu-latest
diff --git a/.github/workflows/test-coverage.yaml b/.github/workflows/test-coverage.yaml
index e6dc5c39..5f673df5 100644
--- a/.github/workflows/test-coverage.yaml
+++ b/.github/workflows/test-coverage.yaml
@@ -6,7 +6,6 @@ permissions:
contents: read
env:
- CONSOLE_OUTPUT: XTerm
COVERAGE: PartialSummary
jobs:
@@ -21,7 +20,7 @@ jobs:
- macos
ruby:
- - "3.4"
+ - ruby
steps:
- uses: actions/checkout@v4
@@ -49,7 +48,7 @@ jobs:
- uses: actions/checkout@v4
- uses: ruby/setup-ruby@v1
with:
- ruby-version: "3.4"
+ ruby-version: ruby
bundler-cache: true
- uses: actions/download-artifact@v4
diff --git a/.github/workflows/test-external.yaml b/.github/workflows/test-external.yaml
index 217a86a2..287124cd 100644
--- a/.github/workflows/test-external.yaml
+++ b/.github/workflows/test-external.yaml
@@ -5,9 +5,6 @@ on: [push, pull_request]
permissions:
contents: read
-env:
- CONSOLE_OUTPUT: XTerm
-
jobs:
test:
name: ${{matrix.ruby}} on ${{matrix.os}}
diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml
index 9be40c37..d39f21f0 100644
--- a/.github/workflows/test.yaml
+++ b/.github/workflows/test.yaml
@@ -5,11 +5,6 @@ on: [push, pull_request]
permissions:
contents: read
-env:
- CONSOLE_OUTPUT: XTerm
- TRACES_BACKEND: traces/backend/test
- METRICS_BACKEND: metrics/backend/test
-
jobs:
test:
name: ${{matrix.ruby}} on ${{matrix.os}} ${{matrix.gemfile}}
diff --git a/.gitignore b/.gitignore
index 8603321b..77f079f2 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,3 +1,5 @@
+/agents.md
+/.context
/.bundle
/pkg
/gems.locked
diff --git a/.rubocop.yml b/.rubocop.yml
index 7fbb2220..e2011448 100644
--- a/.rubocop.yml
+++ b/.rubocop.yml
@@ -1,6 +1,13 @@
+plugins:
+ - rubocop-md
+ - rubocop-socketry
+
AllCops:
DisabledByDefault: true
+Layout/ConsistentBlankLineIndentation:
+ Enabled: true
+
Layout/IndentationStyle:
Enabled: true
EnforcedStyle: tabs
diff --git a/config/sus.rb b/config/sus.rb
index 0be2f421..013a813b 100644
--- a/config/sus.rb
+++ b/config/sus.rb
@@ -7,3 +7,5 @@
include Covered::Sus
ENV["CONSOLE_LEVEL"] ||= "warn"
+ENV["TRACES_BACKEND"] ||= "traces/backend/test"
+ENV["METRICS_BACKEND"] ||= "metrics/backend/test"
diff --git a/examples/beer/config.ru b/examples/beer/config.ru
index 03ad7e7a..b51939fe 100755
--- a/examples/beer/config.ru
+++ b/examples/beer/config.ru
@@ -24,7 +24,7 @@ run lambda {|env|
body.write("
#{count} Bottles of Beer")
task.async do |task|
- begin
+ task.defer_stop do
body.write(COMMENT)
count.downto(1) do |i|
diff --git a/examples/beer/falcon.rb b/examples/beer/falcon.rb
index 2cd3e0e5..f8906d9a 100755
--- a/examples/beer/falcon.rb
+++ b/examples/beer/falcon.rb
@@ -10,7 +10,10 @@
service "beer.localhost" do
include Falcon::Environment::Rack
- include Falcon::Environment::SelfSignedTLS
+ # include Falcon::Environment::SelfSignedTLS
+
+ count 1
+ endpoint{Async::HTTP::Endpoint.parse("http://localhost:9293")}
end
service "supervisor" do
diff --git a/examples/benchmark/bake.rb b/examples/benchmark/bake.rb
index e38d523e..4d4463a9 100644
--- a/examples/benchmark/bake.rb
+++ b/examples/benchmark/bake.rb
@@ -91,11 +91,11 @@ def compare
threads.times do |n|
c = (n*n).to_s
puts "Running #{command.first} with #{c} concurrent connections..."
-
+
Async::Process.spawn("curl", "-o", "/dev/null", "#{host}#{@request_path}")
-
+
# Async::Process.spawn("ab", "-k", "-n", "1000", "#{host}#{@request_path}")
-
+
Async::Process.spawn("wrk", "-c", c.to_s, "-t", (n).to_s, "-d", "10", "#{host}#{@request_path}")
end
ensure
diff --git a/examples/echo/config.ru b/examples/echo/config.ru
index 7c961385..f80be404 100644
--- a/examples/echo/config.ru
+++ b/examples/echo/config.ru
@@ -4,7 +4,7 @@ class EchoBody
def initialize(input)
@input = input
end
-
+
def each(&output)
while chunk = @input.read(1024)
output.call(chunk)
diff --git a/examples/limited/limited.rb b/examples/limited/limited.rb
index b0bacb35..a9cb0c11 100644
--- a/examples/limited/limited.rb
+++ b/examples/limited/limited.rb
@@ -33,7 +33,7 @@ def acquire
Console.debug(self, "Acquiring semaphore...")
@queue.pop
Console.debug(self, "Acquired semaphore...")
-
+
return Token.new(self)
end
@@ -67,7 +67,7 @@ def wait_for_inbound_connection(server)
# Wait until there is a connection ready to be accepted:
while true
server.wait_readable
-
+
# Acquire the semaphore:
if token = semaphore.acquire
return token
diff --git a/examples/service/config.ru b/examples/service/config.ru
new file mode 100755
index 00000000..a05891c6
--- /dev/null
+++ b/examples/service/config.ru
@@ -0,0 +1,8 @@
+#!/usr/bin/env falcon --verbose serve -c
+# frozen_string_literal: true
+
+run do |env|
+ # To test the fiber profiler, you can uncomment the following line:
+ # Fiber.blocking{sleep 0.1}
+ [200, {}, ["Hello World"]]
+end
diff --git a/examples/service/falcon.rb b/examples/service/falcon.rb
new file mode 100755
index 00000000..e315f627
--- /dev/null
+++ b/examples/service/falcon.rb
@@ -0,0 +1,38 @@
+#!/usr/bin/env falcon-host
+# frozen_string_literal: true
+
+require "falcon/environment/rack"
+require "async/redis"
+
+service "localhost" do
+ include Falcon::Environment::Rack
+ endpoint {Async::HTTP::Endpoint.parse("http://0.0.0.0:8013")}
+end
+
+class MyService < Async::Service::Generic
+ def setup(container)
+ container.spawn do |instance|
+ evaluator = @environment.evaluator
+ Console.info(self, "Connecting to Redis at", evaluator.redis_endpoint)
+
+ Async do
+ client = Async::Redis::Client.new(evaluator.redis_endpoint)
+
+ instance.ready!
+
+ client.subscribe "status" do |context|
+ Console.info(self, "Subscribed to Redis channel 'status'.")
+ while response = context.listen
+ Console.info(self, "Received event:", response)
+ end
+ end
+ end
+ end
+ end
+end
+
+service "myservice" do
+ service_class MyService
+
+ redis_endpoint {Async::Redis::Endpoint.local}
+end
diff --git a/examples/service/gems.locked b/examples/service/gems.locked
new file mode 100644
index 00000000..f77c5da1
--- /dev/null
+++ b/examples/service/gems.locked
@@ -0,0 +1,98 @@
+GEM
+ remote: https://rubygems.org/
+ specs:
+ async (2.27.2)
+ console (~> 1.29)
+ fiber-annotation
+ io-event (~> 1.11)
+ metrics (~> 0.12)
+ traces (~> 0.15)
+ async-container (0.25.0)
+ async (~> 2.22)
+ async-container-supervisor (0.5.2)
+ async-container (~> 0.22)
+ async-service
+ io-endpoint
+ io-stream
+ memory-leak (~> 0.5)
+ async-http (0.90.1)
+ async (>= 2.10.2)
+ async-pool (~> 0.11)
+ io-endpoint (~> 0.14)
+ io-stream (~> 0.6)
+ metrics (~> 0.12)
+ protocol-http (~> 0.49)
+ protocol-http1 (~> 0.30)
+ protocol-http2 (~> 0.22)
+ traces (~> 0.10)
+ async-http-cache (0.4.5)
+ async-http (~> 0.56)
+ async-pool (0.11.0)
+ async (>= 2.0)
+ async-redis (0.12.0)
+ async (~> 2.10)
+ async-pool (~> 0.2)
+ io-endpoint (~> 0.10)
+ io-stream (~> 0.4)
+ protocol-redis (~> 0.11)
+ async-service (0.13.0)
+ async
+ async-container (~> 0.16)
+ console (1.33.0)
+ fiber-annotation
+ fiber-local (~> 1.1)
+ json
+ falcon (0.52.3)
+ async
+ async-container (~> 0.20)
+ async-container-supervisor (~> 0.5.0)
+ async-http (~> 0.75)
+ async-http-cache (~> 0.4)
+ async-service (~> 0.10)
+ bundler
+ localhost (~> 1.1)
+ openssl (~> 3.0)
+ protocol-http (~> 0.31)
+ protocol-rack (~> 0.7)
+ samovar (~> 2.3)
+ fiber-annotation (0.2.0)
+ fiber-local (1.1.0)
+ fiber-storage
+ fiber-storage (1.0.1)
+ io-endpoint (0.15.2)
+ io-event (1.12.1)
+ io-stream (0.10.0)
+ json (2.13.2)
+ localhost (1.6.0)
+ mapping (1.1.3)
+ memory-leak (0.5.2)
+ metrics (0.13.0)
+ openssl (3.3.0)
+ protocol-hpack (1.5.1)
+ protocol-http (0.51.1)
+ protocol-http1 (0.34.1)
+ protocol-http (~> 0.22)
+ protocol-http2 (0.22.1)
+ protocol-hpack (~> 1.4)
+ protocol-http (~> 0.47)
+ protocol-rack (0.16.0)
+ io-stream (>= 0.10)
+ protocol-http (~> 0.43)
+ rack (>= 1.0)
+ protocol-redis (0.12.0)
+ rack (3.2.3)
+ samovar (2.3.0)
+ console (~> 1.0)
+ mapping (~> 1.0)
+ traces (0.16.2)
+
+PLATFORMS
+ arm64-darwin-24
+ ruby
+
+DEPENDENCIES
+ async-redis
+ falcon
+
+BUNDLED WITH
+ 2.6.9
diff --git a/examples/service/gems.rb b/examples/service/gems.rb
new file mode 100644
index 00000000..73711aec
--- /dev/null
+++ b/examples/service/gems.rb
@@ -0,0 +1,6 @@
+# frozen_string_literal: true
+
+source "https://rubygems.org"
+
+gem "falcon"
+gem "async-redis"
diff --git a/examples/streaming_upload/config.ru b/examples/streaming_upload/config.ru
index 0a447232..2a46cc7d 100755
--- a/examples/streaming_upload/config.ru
+++ b/examples/streaming_upload/config.ru
@@ -7,19 +7,19 @@ require "securerandom"
class BodyHandler
attr_reader :time, :md5, :size, :uuid
-
+
def initialize(input, length)
@uuid = SecureRandom.uuid
@input = input
@length = length
end
-
+
def receive
start = Time.now
@md5 = Digest::MD5.new
@size = 0
@done = false
-
+
until @done
begin
chunk = @input.read(1024*1024) # read will raise EOF so we have to check
@@ -36,7 +36,7 @@ class BodyHandler
@done = true if @length == @size
end
end
-
+
@time = Time.now - start
end
end
diff --git a/examples/tls-client-verify/readme.md b/examples/tls-client-verify/readme.md
index 320cc6a9..293f79e5 100644
--- a/examples/tls-client-verify/readme.md
+++ b/examples/tls-client-verify/readme.md
@@ -3,7 +3,7 @@
It is possible to verify inbound client requests using TLS client certificates. This is useful for ensuring that only authorized clients can access your service. In order to do this, you need to configure Falcon to use a TLS context with additional client verification options.
```ruby
- ssl_context do
+ssl_context do
super().tap do |context|
context.verify_mode = OpenSSL::SSL::VERIFY_PEER
@@ -12,7 +12,7 @@ It is possible to verify inbound client requests using TLS client certificates.
true
end
end
- end
+end
```
## Server
diff --git a/falcon.gemspec b/falcon.gemspec
index 7fcaef32..2f805777 100644
--- a/falcon.gemspec
+++ b/falcon.gemspec
@@ -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", "dependabot[bot]", "Janko Marohnić", "Bryan Powell", "Trevor Turk", "Claudiu Garba", "Kyle Tam", "Mitsutaka Mimura", "Peter Schrammel", "Sho Ito", "Adam Daniels", "Colby Swandale", "Daniel Evans", "Ismael Celis", "Jared Smith", "Kent Gruber", "Martin Hrdlicka", "Michael Adams", "Mikel Kew", "Nick Janetakis", "Olle Jonsson", "Pierre Montelle", "Santiago Bartesaghi", "Sh Lin", "Stefan Buhrmester", "Tad Thorley", "Tasos Latsas"]
+ spec.authors = ["Samuel Williams", "dependabot[bot]", "Janko Marohnić", "Bryan Powell", "Trevor Turk", "Claudiu Garba", "Kyle Tam", "Mitsutaka Mimura", "Peter Schrammel", "Sho Ito", "Adam Daniels", "Colby Swandale", "Daniel Evans", "Ismael Celis", "Jared Smith", "Kent Gruber", "Martin Hrdlicka", "Michael Adams", "Mikel Kew", "Nick Janetakis", "Olle Jonsson", "Pierre Montelle", "Santiago Bartesaghi", "Sh Lin", "Stefan Buhrmester", "Tad Thorley", "Tasos Latsas", "Yoji Shidara"]
spec.license = "MIT"
spec.cert_chain = ["release.cert"]
@@ -20,7 +20,7 @@ Gem::Specification.new do |spec|
"source_code_uri" => "https://github.com/socketry/falcon.git",
}
- spec.files = Dir.glob(["{bake,context,bin,lib}/**/*", "*.md"], File::FNM_DOTMATCH, base: __dir__)
+ spec.files = Dir.glob(["{bake,bin,context,lib}/**/*", "*.md"], File::FNM_DOTMATCH, base: __dir__)
spec.executables = ["falcon", "falcon-host"]
@@ -28,7 +28,7 @@ Gem::Specification.new do |spec|
spec.add_dependency "async"
spec.add_dependency "async-container", "~> 0.20"
- spec.add_dependency "async-container-supervisor", "~> 0.5.0"
+ spec.add_dependency "async-container-supervisor", "~> 0.6"
spec.add_dependency "async-http", "~> 0.75"
spec.add_dependency "async-http-cache", "~> 0.4"
spec.add_dependency "async-service", "~> 0.10"
diff --git a/gems.rb b/gems.rb
index 873ffc8e..d20421ec 100644
--- a/gems.rb
+++ b/gems.rb
@@ -29,7 +29,9 @@
group :maintenance, optional: true do
gem "bake-modernize"
gem "bake-gem"
-
+
+ gem "agent-context"
+
gem "utopia-project"
gem "bake-releases"
end
@@ -43,7 +45,10 @@
gem "sus"
gem "covered"
gem "decode"
+
gem "rubocop"
+ gem "rubocop-md"
+ gem "rubocop-socketry"
gem "sus-fixtures-async"
gem "sus-fixtures-async-http"
diff --git a/lib/falcon/command/serve.rb b/lib/falcon/command/serve.rb
index 5993d2d6..a6b82bc5 100644
--- a/lib/falcon/command/serve.rb
+++ b/lib/falcon/command/serve.rb
@@ -64,7 +64,6 @@ def name
def environment
Async::Service::Environment.new(Falcon::Environment::Server).with(
Falcon::Environment::Rackup,
-
root: Dir.pwd,
verbose: self.parent&.verbose?,
diff --git a/lib/falcon/configuration.rb b/lib/falcon/configuration.rb
index 07c95039..a969c4e7 100644
--- a/lib/falcon/configuration.rb
+++ b/lib/falcon/configuration.rb
@@ -73,7 +73,7 @@ def supervisor(&block)
merge(:supervisor, name: name, root: @root, &block)
)
end
-
+
private
# Build a new environment with the specified name and the given parents.
diff --git a/lib/falcon/environment/virtual.rb b/lib/falcon/environment/virtual.rb
index cc66ba92..34bc710b 100644
--- a/lib/falcon/environment/virtual.rb
+++ b/lib/falcon/environment/virtual.rb
@@ -56,10 +56,10 @@ def falcon_path
# # An endpoint suitable for connecting to the specified hostname.
# def host_endpoint(hostname, **options)
# endpoint = secure_endpoint(**options)
-
+ #
# url = URI.parse(bind_secure)
# url.hostname = hostname
-
+ #
# return Async::HTTP::Endpoint.new(url, hostname: endpoint.hostname)
# end
end
diff --git a/license.md b/license.md
index bf1bbeb3..8b1b8761 100644
--- a/license.md
+++ b/license.md
@@ -27,6 +27,7 @@ Copyright, 2024, by Stefan Buhrmester.
Copyright, 2024, by Ismael Celis.
Copyright, 2025, by Pierre Montelle.
Copyright, 2025, by Jared Smith.
+Copyright, 2025, by Yoji Shidara.
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
diff --git a/readme.md b/readme.md
index c7eb6d2b..06890c5f 100644
--- a/readme.md
+++ b/readme.md
@@ -98,13 +98,13 @@ In addition, `falcon serve` provides two new options:
The current configuration format uses definitions like this:
``` ruby
-rack 'hello.localhost', :self_signed_tls
+rack "hello.localhost", :self_signed_tls
```
This changes to:
``` ruby
-service 'hello.localhost' do
+service "hello.localhost" do
include Falcon::Environment::Rack
include Falcon::Environment::SelfSignedTLS
end
diff --git a/releases.md b/releases.md
index 12c8bec8..f2d7d2e2 100644
--- a/releases.md
+++ b/releases.md
@@ -106,13 +106,13 @@ In addition, `falcon serve` provides two new options:
The current configuration format uses definitions like this:
``` ruby
-rack 'hello.localhost', :self_signed_tls
+rack "hello.localhost", :self_signed_tls
```
This changes to:
``` ruby
-service 'hello.localhost' do
+service "hello.localhost" do
include Falcon::Environment::Rack
include Falcon::Environment::SelfSignedTLS
end
diff --git a/test/falcon/command/serve.rb b/test/falcon/command/serve.rb
index 96beee1b..71cda3ba 100644
--- a/test/falcon/command/serve.rb
+++ b/test/falcon/command/serve.rb
@@ -1,7 +1,7 @@
# frozen_string_literal: true
# Released under the MIT License.
-# Copyright, 2018-2024, by Samuel Williams.
+# Copyright, 2018-2025, by Samuel Williams.
# Copyright, 2019, by Sho Ito.
require "falcon/command/serve"
@@ -38,7 +38,7 @@
describe Falcon::Command::Serve do
let(:options) {[]}
-
+
with "custom port" do
let(:port) {8090}
it_behaves_like ServeCommand
diff --git a/test/falcon/command/virtual.rb b/test/falcon/command/virtual.rb
index ea5ccc33..d0956453 100644
--- a/test/falcon/command/virtual.rb
+++ b/test/falcon/command/virtual.rb
@@ -1,7 +1,7 @@
# frozen_string_literal: true
# Released under the MIT License.
-# Copyright, 2019-2024, by Samuel Williams.
+# Copyright, 2019-2025, by Samuel Williams.
require "falcon/command/virtual"
@@ -18,7 +18,7 @@
]}
let(:examples_root) {File.expand_path("../../../examples", __dir__)}
-
+
let(:options) {[]}
let(:command) do
diff --git a/test/falcon/rackup/handler/falcon.rb b/test/falcon/rackup/handler/falcon.rb
index 36c01ede..98ce7233 100644
--- a/test/falcon/rackup/handler/falcon.rb
+++ b/test/falcon/rackup/handler/falcon.rb
@@ -50,9 +50,9 @@
describe Falcon::Rackup::Handler do
it_behaves_like RackupHandler, "falcon"
-
+
let(:app) {lambda {|env| [200, {}, ["Hello World"]]}}
-
+
it "can start and stop server" do
Rackup::Handler::Falcon.run(app) do |server|
server.stop