From bf1558eeb0985fa1b1cfe661426e0d16af8535f3 Mon Sep 17 00:00:00 2001 From: Robert <8934693+0x1eef@users.noreply.github.com> Date: Sun, 25 Jun 2023 21:03:07 -0300 Subject: [PATCH] Add packages/ruby/server/ (#132) ### Summary The `server` package implements a static file web server intended to be used during development. The goal is to have it as close to a production environment as possible. Not there yet. This is the first step. --- Rakefile.rb | 5 +- packages/ruby/server/.gitignore | 8 +++ packages/ruby/server/Gemfile | 4 ++ packages/ruby/server/LICENSE | 15 +++++ packages/ruby/server/README.md | 40 ++++++++++++ packages/ruby/server/lib/server.rb | 47 +++++++++++++ packages/ruby/server/lib/server/dir.rb | 34 ++++++++++ packages/ruby/server/lib/server/puma.rb | 8 +++ packages/ruby/server/server.rb.gemspec | 18 +++++ packages/ruby/server/test/fakeweb/index.html | 8 +++ packages/ruby/server/test/server_dir_test.rb | 23 +++++++ packages/ruby/server/test/setup.rb | 3 + tasks.lib/pf.rb | 21 ------ tasks/config/build.rake | 69 -------------------- tasks/config/install.rake | 10 --- 15 files changed, 209 insertions(+), 104 deletions(-) create mode 100644 packages/ruby/server/.gitignore create mode 100644 packages/ruby/server/Gemfile create mode 100644 packages/ruby/server/LICENSE create mode 100644 packages/ruby/server/README.md create mode 100644 packages/ruby/server/lib/server.rb create mode 100644 packages/ruby/server/lib/server/dir.rb create mode 100644 packages/ruby/server/lib/server/puma.rb create mode 100644 packages/ruby/server/server.rb.gemspec create mode 100644 packages/ruby/server/test/fakeweb/index.html create mode 100644 packages/ruby/server/test/server_dir_test.rb create mode 100644 packages/ruby/server/test/setup.rb delete mode 100644 tasks.lib/pf.rb delete mode 100644 tasks/config/build.rake delete mode 100644 tasks/config/install.rake diff --git a/Rakefile.rb b/Rakefile.rb index 038924a..48ee08f 100644 --- a/Rakefile.rb +++ b/Rakefile.rb @@ -2,9 +2,6 @@ require "ryo" require "listen" - -load "tasks/config/build.rake" -load "tasks/config/install.rake" load "tasks/deploy.rake" namespace :nanoc do @@ -58,5 +55,5 @@ namespace :lint do end end end -task lint: ["linter:ruby", "linter:typescript"] +task lint: ["lint:rubocop", "lint:eslint"] task default: "deploy:local" diff --git a/packages/ruby/server/.gitignore b/packages/ruby/server/.gitignore new file mode 100644 index 0000000..8926010 --- /dev/null +++ b/packages/ruby/server/.gitignore @@ -0,0 +1,8 @@ +build/ +tmp/ +node_modules/ +.bundle/ +*.log +*.sh +*.core +Gemfile.lock diff --git a/packages/ruby/server/Gemfile b/packages/ruby/server/Gemfile new file mode 100644 index 0000000..bb94df8 --- /dev/null +++ b/packages/ruby/server/Gemfile @@ -0,0 +1,4 @@ +# frozen_string_literal: true + +source "https://rubygems.org" +gemspec diff --git a/packages/ruby/server/LICENSE b/packages/ruby/server/LICENSE new file mode 100644 index 0000000..548a1d0 --- /dev/null +++ b/packages/ruby/server/LICENSE @@ -0,0 +1,15 @@ +Copyright (C) 2023 by 0x1eef <0x1eef@protonmail.com> + +Permission to use, copy, modify, and/or distribute this +software for any purpose with or without fee is hereby +granted. + +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS +ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO +EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, +INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER +RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, +ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE +OF THIS SOFTWARE. diff --git a/packages/ruby/server/README.md b/packages/ruby/server/README.md new file mode 100644 index 0000000..1312301 --- /dev/null +++ b/packages/ruby/server/README.md @@ -0,0 +1,40 @@ +## About + +server.rb implements a static file web server +by using the fast performing Ruby web server +[Puma](https://github.com/puma/puma) +and a small +[Rack](https://github.com/rack/rack) +application. + +## Examples + +### Server.for_dir + +The `Server.for_dir` method returns a Server instance +that serves the contents of a directory. `Server#start` spawns +a new thread to listen for requests, and afterwards returns +the thread. `Thread#join` can block execution at that point, +or execution can continue as normal by not calling `Thread#join`: + +```ruby +require "server" + +## +# Create a Server instance for the contents of a directory +server = Server.for_dir("./build/website/") + +## +# Start listening for connections +thr = server.start + +## +# Prevent the main thread from exiting +thr.join +``` + +## License + +[BSD Zero Clause](https://choosealicense.com/licenses/0bsd/). +
+See [LICENSE](./LICENSE). diff --git a/packages/ruby/server/lib/server.rb b/packages/ruby/server/lib/server.rb new file mode 100644 index 0000000..543d35d --- /dev/null +++ b/packages/ruby/server/lib/server.rb @@ -0,0 +1,47 @@ +# frozen_string_literal: true + +class Server + require "rack" + require_relative "server/puma" + require_relative "server/dir" + + def self.app(path) + Rack::Builder.app do + run Server::Dir.new(path) + end + end + + def self.for_dir(path, options = {}) + new(app(path), options) + end + + def initialize(app, options = {}) + @app = app + @options = default_options.merge!(options) + @events = Puma::Events.new + @server = Puma::Server.new(@app, @events, @options) + end + + def start + @server.binder.parse(@options[:binds]) + @server.run + end + + def stop + @server.stop + end + + private + + def default_options + { + tcp_host: "127.0.0.1", + tcp_port: 7777, + binds: ["tcp://127.0.0.1:7777"], + supported_http_methods: %w[GET HEAD], + min_threads: 1, + max_threads: 5, + workers: 1 + } + end +end diff --git a/packages/ruby/server/lib/server/dir.rb b/packages/ruby/server/lib/server/dir.rb new file mode 100644 index 0000000..8397a3d --- /dev/null +++ b/packages/ruby/server/lib/server/dir.rb @@ -0,0 +1,34 @@ +# frozen_string_literal: true + +class Server::Dir + def initialize(root) + @root = File.realpath(root) + end + + def call(env) + req = Rack::Request.new(env) + headers = Rack::Headers.new(env) + h, body = read(local_path(req.path)) + [200, headers.merge!(h), body] + rescue Errno::ENOENT + not_found + end + + private + + attr_reader :root + + def read(path) + body = File.binread(path) + [{"Content-Length" => body.bytesize}, body.each_line] + end + + def local_path(req_path) + lpath = File.join root, File.expand_path(req_path) + File.directory?(lpath) ? File.join(lpath, "index.html") : lpath + end + + def not_found + [404, {"Content-Type" => "text/plain"}, ["The requested URL was not found"]] + end +end diff --git a/packages/ruby/server/lib/server/puma.rb b/packages/ruby/server/lib/server/puma.rb new file mode 100644 index 0000000..f84b988 --- /dev/null +++ b/packages/ruby/server/lib/server/puma.rb @@ -0,0 +1,8 @@ +# frozen_string_literal: true + +require "puma" +require "puma/events" + +class Puma::Server + public :binder +end diff --git a/packages/ruby/server/server.rb.gemspec b/packages/ruby/server/server.rb.gemspec new file mode 100644 index 0000000..6f3d259 --- /dev/null +++ b/packages/ruby/server/server.rb.gemspec @@ -0,0 +1,18 @@ +# frozen_string_literal: true +Gem::Specification.new do |gem| + gem.name = "server.rb" + gem.authors = ["0x1eef"] + gem.email = ["0x1eef@protonmail.com"] + gem.homepage = "https://github.com/0x1eef/server.rb#readme" + gem.version = "0.1.0" + gem.licenses = ["0BSD"] + gem.files = Dir["lib/*", "lib/**/*.rb"] + gem.require_paths = ["lib"] + gem.summary = "A static file web server" + gem.description = gem.summary + gem.add_runtime_dependency "puma", "~> 6.3" + gem.add_runtime_dependency "rack", "~> 3.0" + gem.add_development_dependency "standard", "~> 1.24" + gem.add_development_dependency "rack-test", "~> 2.1" + gem.add_development_dependency "test-unit", "~> 3.5" +end diff --git a/packages/ruby/server/test/fakeweb/index.html b/packages/ruby/server/test/fakeweb/index.html new file mode 100644 index 0000000..95d1b90 --- /dev/null +++ b/packages/ruby/server/test/fakeweb/index.html @@ -0,0 +1,8 @@ + + + /index.html + + + /index.html + + diff --git a/packages/ruby/server/test/server_dir_test.rb b/packages/ruby/server/test/server_dir_test.rb new file mode 100644 index 0000000..561e127 --- /dev/null +++ b/packages/ruby/server/test/server_dir_test.rb @@ -0,0 +1,23 @@ +require_relative "setup" +require "rack/test" + +class ServerDirTest < Test::Unit::TestCase + include Rack::Test::Methods + + def test_index + get "/" + assert_equal 200, last_response.status + assert_equal "text/html", last_response.content_type + assert_equal bytesize("./test/fakeweb/index.html"), last_response.content_length + end + + private + + def app + @app ||= Server.app("./test/fakeweb/") + end + + def bytesize(path) + File.binread(path).bytesize + end +end diff --git a/packages/ruby/server/test/setup.rb b/packages/ruby/server/test/setup.rb new file mode 100644 index 0000000..39ecb19 --- /dev/null +++ b/packages/ruby/server/test/setup.rb @@ -0,0 +1,3 @@ +require "bundler/setup" +require "test/unit" +require "server" diff --git a/tasks.lib/pf.rb b/tasks.lib/pf.rb deleted file mode 100644 index 972f73c..0000000 --- a/tasks.lib/pf.rb +++ /dev/null @@ -1,21 +0,0 @@ -## -# frozen_string_literal: true - -module PF - def pf_in(rule) - [ - rule.proto && "proto #{rule.proto}", - "from #{rule.from}", - "to #{rule.to}", - rule.port && "port #{rule.port}" - ].compact.join(" ") - end - - def pf_out(rule) - [ - rule.proto && "proto #{rule.proto}", - "to #{rule.to}", - rule.port && "port #{rule.port}" - ].compact.join(" ") - end -end diff --git a/tasks/config/build.rake b/tasks/config/build.rake deleted file mode 100644 index 68c8199..0000000 --- a/tasks/config/build.rake +++ /dev/null @@ -1,69 +0,0 @@ -## -# frozen_string_literal: true - -require "bundler/setup" -require "erb" -require "ryo" -require "yaml" -require_relative "../../tasks.lib/erb_context" - -read_options = ->(env:) do - path = File.join(Dir.getwd, "config", "#{env}.yml") - Ryo.from(YAML.load_file(path)) -end - -get_build_dir = -> (env:) do - path = File.join("./build", env) - mkdir_p(path) unless Dir.exist?(path) - path -end - -build_files = -> (env:, base:, glob:) do - options = read_options.call(env:) - build_dir = get_build_dir.call(env:) - context = ERBContext.with_locals(options) - Dir.glob(glob, base:).each do |file| - erbf = File.join(base, file) - path = File.join(build_dir, File.dirname(file)) - dest = File.join(path, File.basename(file, ".erb")) - mkdir_p(path) - File.binwrite dest, - ERB.new(File.binread(erbf), trim_mode: "-").result(context) - print "View #{dest} [y/n]:" - system("cat #{dest} | less") if $stdin.gets.chomp == "y" - end -end - -desc "Build configuration files" -task "config:build", :env do |task, args| - env = args[:env] - case env - when "remote" - Rake::Task["config:build:etc"].invoke(env) - when "local" - # no-op - else - warn "env should be 'remote', or 'local', got: #{env}" - end - Rake::Task["config:build:nginx"].invoke(env) -end - -desc "Build /etc configuration files" -task "config:build:etc", :env do |task, args| - env = args[:env] - build_files.call( - env:, - base: "config/#{env}", - glob: "etc/*.conf.erb" - ) -end - -desc "Build nginx configuration files" -task "config:build:nginx", :env do |task, args| - env = args[:env] - build_files.call( - env:, - base: "config/generic", - glob: "usr.local.etc/**/*.conf.erb" - ) -end diff --git a/tasks/config/install.rake b/tasks/config/install.rake deleted file mode 100644 index 433d5ec..0000000 --- a/tasks/config/install.rake +++ /dev/null @@ -1,10 +0,0 @@ -desc "Install configuration files" -task "config:install", :env do |tasks, args| - env = args[:env] - if Process.euid != 0 - sh "doas -u root bundle exec rake config:install[#{env}]" - exit $?.exitstatus - end - copy_entry File.join(Dir.getwd, "build", env, "usr.local.etc"), - "/usr/local/etc" -end