Skip to content

Commit b3ee196

Browse files
Static server.
1 parent 6cc4e91 commit b3ee196

File tree

4 files changed

+493
-2
lines changed

4 files changed

+493
-2
lines changed

lib/falcon/command/static.rb

Lines changed: 161 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,161 @@
1+
# frozen_string_literal: true
2+
3+
# Released under the MIT License.
4+
# Copyright, 2025, by Samuel Williams.
5+
6+
require_relative "../server"
7+
require_relative "../endpoint"
8+
require_relative "../configuration"
9+
require_relative "../service/server"
10+
require_relative "../middleware/static"
11+
12+
require "async/container"
13+
require "samovar"
14+
15+
module Falcon
16+
module Command
17+
# Implements the `falcon static` command. Designed for serving static files.
18+
#
19+
# Manages a static file server for the current directory.
20+
class Static < Samovar::Command
21+
self.description = "Serve static files from the current directory."
22+
23+
# The command line options.
24+
# @attribute [Samovar::Options]
25+
options do
26+
option "-b/--bind <address>", "Bind to the given hostname/address.", default: "http://localhost:3000"
27+
28+
option "-p/--port <number>", "Override the specified port.", type: Integer
29+
option "-h/--hostname <hostname>", "Specify the hostname which would be used for certificates, etc."
30+
option "-t/--timeout <duration>", "Specify the maximum time to wait for non-blocking operations.", type: Float, default: nil
31+
32+
option "-r/--root <path>", "Root directory to serve static files from.", default: Dir.pwd
33+
option "-i/--index <filename>", "Index file to serve for directories.", default: "index.html"
34+
option "--[no]-directory-listing", "Enable/disable directory listings.", default: true
35+
36+
option "--cache", "Enable the response cache."
37+
38+
option "--forked | --threaded | --hybrid", "Select a specific parallelism model.", key: :container, default: :forked
39+
40+
option "-n/--count <count>", "Number of instances to start.", default: 1, type: Integer
41+
42+
option "--forks <count>", "Number of forks (hybrid only).", type: Integer
43+
option "--threads <count>", "Number of threads (hybrid only).", type: Integer
44+
45+
option "--[no]-restart", "Enable/disable automatic restart.", default: true
46+
option "--graceful-stop <timeout>", "Duration to wait for graceful stop.", type: Float, default: 1.0
47+
48+
option "--health-check-timeout <duration>", "Duration to wait for health check.", type: Float, default: 30.0
49+
end
50+
51+
def container_options
52+
@options.slice(:count, :forks, :threads, :restart, :health_check_timeout)
53+
end
54+
55+
def endpoint_options
56+
@options.slice(:hostname, :port, :timeout)
57+
end
58+
59+
def name
60+
@options[:hostname] || @options[:bind]
61+
end
62+
63+
def root_directory
64+
File.expand_path(@options[:root])
65+
end
66+
67+
# Create a middleware stack for serving static files
68+
def middleware_app
69+
# Create a 404 fallback
70+
not_found_app = lambda do |request|
71+
Protocol::HTTP::Response[404, {'content-type' => 'text/plain'}, ['Not Found']]
72+
end
73+
74+
# Create the static middleware
75+
Middleware::Static.new(
76+
not_found_app,
77+
root: root_directory,
78+
index: @options[:index],
79+
directory_listing: @options[:directory_listing]
80+
)
81+
end
82+
83+
def environment
84+
static_middleware = middleware_app
85+
verbose_mode = self.parent&.verbose?
86+
cache_enabled = @options[:cache]
87+
88+
Async::Service::Environment.new(Falcon::Environment::Server).with(
89+
root: root_directory,
90+
91+
verbose: verbose_mode,
92+
cache: cache_enabled,
93+
94+
container_options: self.container_options,
95+
endpoint_options: self.endpoint_options,
96+
97+
url: @options[:bind],
98+
99+
name: self.name,
100+
101+
endpoint: ->{Endpoint.parse(url, **endpoint_options)},
102+
103+
# Use our custom static middleware directly
104+
middleware: ->{
105+
::Protocol::HTTP::Middleware.build do
106+
if verbose_mode
107+
use Falcon::Middleware::Verbose
108+
end
109+
110+
if cache_enabled
111+
use Async::HTTP::Cache::General
112+
end
113+
114+
use ::Protocol::HTTP::ContentEncoding
115+
116+
run static_middleware
117+
end
118+
}
119+
)
120+
end
121+
122+
def configuration
123+
Configuration.new.tap do |configuration|
124+
configuration.add(self.environment)
125+
end
126+
end
127+
128+
# The container class to use.
129+
def container_class
130+
case @options[:container]
131+
when :threaded
132+
return Async::Container::Threaded
133+
when :forked
134+
return Async::Container::Forked
135+
when :hybrid
136+
return Async::Container::Hybrid
137+
end
138+
end
139+
140+
# The endpoint to bind to.
141+
def endpoint
142+
Endpoint.parse(@options[:bind], **endpoint_options)
143+
end
144+
145+
# Prepare the environment and run the controller.
146+
def call
147+
Console.logger.info(self) do |buffer|
148+
buffer.puts "Falcon Static v#{VERSION} taking flight! Using #{self.container_class} #{self.container_options}."
149+
buffer.puts "- Running on #{RUBY_DESCRIPTION}"
150+
buffer.puts "- Serving files from: #{root_directory}"
151+
buffer.puts "- Index file: #{@options[:index]}"
152+
buffer.puts "- Binding to: #{self.endpoint}"
153+
buffer.puts "- To terminate: Ctrl-C or kill #{Process.pid}"
154+
buffer.puts "- To reload configuration: kill -HUP #{Process.pid}"
155+
end
156+
157+
Async::Service::Controller.run(self.configuration, container_class: self.container_class, graceful_stop: @options[:graceful_stop])
158+
end
159+
end
160+
end
161+
end

lib/falcon/command/top.rb

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
require_relative "virtual"
99
require_relative "proxy"
1010
require_relative "redirect"
11+
require_relative "static"
1112

1213
require_relative "../version"
1314

@@ -37,6 +38,7 @@ class Top < Samovar::Command
3738
"virtual" => Virtual,
3839
"proxy" => Proxy,
3940
"redirect" => Redirect,
41+
"static" => Static,
4042
}, default: "serve"
4143

4244
# Whether verbose logging is enabled.

0 commit comments

Comments
 (0)