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