From 514e8fada84e373037025079e71642b1beb7b56c Mon Sep 17 00:00:00 2001 From: DualFroz Date: Wed, 26 Aug 2026 17:00:00 +0200 Subject: [PATCH] feat(middleware): add request logger and static file server --- lib/prism/middleware/logger.rb | 18 ++++++++++++++ lib/prism/middleware/static.rb | 45 ++++++++++++++++++++++++++++++++++ 2 files changed, 63 insertions(+) create mode 100644 lib/prism/middleware/logger.rb create mode 100644 lib/prism/middleware/static.rb diff --git a/lib/prism/middleware/logger.rb b/lib/prism/middleware/logger.rb new file mode 100644 index 0000000..1a8d99c --- /dev/null +++ b/lib/prism/middleware/logger.rb @@ -0,0 +1,18 @@ +module Prism + module Middleware + class Logger + def initialize(app, io: $stderr) + @app = app + @io = io + end + + def call(env) + started = Time.now + status, headers, body = @app.call(env) + elapsed = ((Time.now - started) * 1000).round(2) + @io.puts(format('%s %s -> %d (%.2f ms)', env['REQUEST_METHOD'], env['PATH_INFO'], status, elapsed)) + [status, headers, body] + end + end + end +end diff --git a/lib/prism/middleware/static.rb b/lib/prism/middleware/static.rb new file mode 100644 index 0000000..513edb6 --- /dev/null +++ b/lib/prism/middleware/static.rb @@ -0,0 +1,45 @@ +module Prism + module Middleware + class Static + ContentTypes = { + '.html' => 'text/html;charset=utf-8', '.htm' => 'text/html;charset=utf-8', + '.css' => 'text/css', '.js' => 'application/javascript', '.json' => 'application/json', + '.png' => 'image/png', '.jpg' => 'image/jpeg', '.jpeg' => 'image/jpeg', '.gif' => 'image/gif', + '.svg' => 'image/svg+xml', '.ico' => 'image/x-icon', '.txt' => 'text/plain', '.pdf' => 'application/pdf' + }.freeze + + def initialize(app, root:) + @app = app + @root = File.expand_path(root) + end + + def call(env) + method = env['REQUEST_METHOD'] + return @app.call(env) unless %w[GET HEAD].include?(method) + + file = resolve(env['PATH_INFO']) + return @app.call(env) unless file + + serve(file) + end + + private + + def resolve(path_info) + return nil if path_info.nil? || path_info.include?("\0") + decoded = CGI.unescape(path_info) + candidate = File.expand_path(File.join(@root, decoded)) + return nil unless candidate.start_with?("#{@root}#{File::SEPARATOR}") || candidate == @root + return File.join(candidate, 'index.html') if File.directory?(candidate) && File.file?(File.join(candidate, 'index.html')) + return nil unless File.file?(candidate) + candidate + end + + def serve(file) + body = File.binread(file) + type = ContentTypes.fetch(File.extname(file).downcase, 'application/octet-stream') + [200, { 'Content-Type' => type, 'Content-Length' => body.bytesize.to_s }, [body]] + end + end + end +end