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.
This commit is contained in:
parent
564b01da62
commit
bf1558eeb0
15 changed files with 209 additions and 104 deletions
|
@ -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"
|
||||
|
|
8
packages/ruby/server/.gitignore
vendored
Normal file
8
packages/ruby/server/.gitignore
vendored
Normal file
|
@ -0,0 +1,8 @@
|
|||
build/
|
||||
tmp/
|
||||
node_modules/
|
||||
.bundle/
|
||||
*.log
|
||||
*.sh
|
||||
*.core
|
||||
Gemfile.lock
|
4
packages/ruby/server/Gemfile
Normal file
4
packages/ruby/server/Gemfile
Normal file
|
@ -0,0 +1,4 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
source "https://rubygems.org"
|
||||
gemspec
|
15
packages/ruby/server/LICENSE
Normal file
15
packages/ruby/server/LICENSE
Normal file
|
@ -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.
|
40
packages/ruby/server/README.md
Normal file
40
packages/ruby/server/README.md
Normal file
|
@ -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/).
|
||||
<br>
|
||||
See [LICENSE](./LICENSE).
|
47
packages/ruby/server/lib/server.rb
Normal file
47
packages/ruby/server/lib/server.rb
Normal file
|
@ -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
|
34
packages/ruby/server/lib/server/dir.rb
Normal file
34
packages/ruby/server/lib/server/dir.rb
Normal file
|
@ -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
|
8
packages/ruby/server/lib/server/puma.rb
Normal file
8
packages/ruby/server/lib/server/puma.rb
Normal file
|
@ -0,0 +1,8 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require "puma"
|
||||
require "puma/events"
|
||||
|
||||
class Puma::Server
|
||||
public :binder
|
||||
end
|
18
packages/ruby/server/server.rb.gemspec
Normal file
18
packages/ruby/server/server.rb.gemspec
Normal file
|
@ -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
|
8
packages/ruby/server/test/fakeweb/index.html
Normal file
8
packages/ruby/server/test/fakeweb/index.html
Normal file
|
@ -0,0 +1,8 @@
|
|||
<!DOCTYPE html>
|
||||
<head>
|
||||
<title>/index.html</title>
|
||||
</head>
|
||||
<body>
|
||||
<b>/index.html</b>
|
||||
</body>
|
||||
</html>
|
23
packages/ruby/server/test/server_dir_test.rb
Normal file
23
packages/ruby/server/test/server_dir_test.rb
Normal file
|
@ -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
|
3
packages/ruby/server/test/setup.rb
Normal file
3
packages/ruby/server/test/setup.rb
Normal file
|
@ -0,0 +1,3 @@
|
|||
require "bundler/setup"
|
||||
require "test/unit"
|
||||
require "server"
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
Loading…
Reference in a new issue