Skip to content
This repository has been archived by the owner on Feb 28, 2023. It is now read-only.

Commit

Permalink
mantis explorer server
Browse files Browse the repository at this point in the history
  • Loading branch information
manveru committed Nov 24, 2020
1 parent 82a1a3b commit b16cd65
Show file tree
Hide file tree
Showing 8 changed files with 202 additions and 19 deletions.
16 changes: 0 additions & 16 deletions docker/darkhttpd.nix

This file was deleted.

20 changes: 20 additions & 0 deletions docker/mantis-explorer.nix
@@ -0,0 +1,20 @@
{ lib, mkEnv, buildLayeredImage, writeShellScript, mantis-explorer
, mantis-explorer-server }:
let
entrypoint = writeShellScript "mantis-explorer-server" ''
set -exuo pipefail
exec mantis-explorer-server \
--root ${mantis-explorer} \
--port "$NOMAD_PORT_http" \
--host 0.0.0.0
'';
in {
mantis-explorer-server = buildLayeredImage {
name = "docker.mantis.ws/mantis-explorer-server";
config = {
Entrypoint = [ entrypoint ];
Env = mkEnv { PATH = lib.makeBinPath [ mantis-explorer-server ]; };
};
};
}
4 changes: 4 additions & 0 deletions flake.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions flake.nix
Expand Up @@ -10,6 +10,7 @@
terranix.follows = "bitte/terranix";
utils.url = "github:numtide/flake-utils";
ops-lib.url = "github:input-output-hk/ops-lib/zfs-image?dir=zfs";
inclusive.follows = "bitte/inclusive";
midnight-automation = {
url =
"git+ssh://github.com/input-output-hk/midnight-automation?ref=etcm-99-mantis-agen";
Expand Down
6 changes: 3 additions & 3 deletions jobs/mantis.nix
Expand Up @@ -369,11 +369,11 @@ let
};

config = {
image = dockerImages.darkhttpd.id;
image = dockerImages.mantis-explorer-server.id;
ports = [ "http" ];
labels = [{
inherit namespace name;
imageTag = dockerImages.darkhttpd.image.imageTag;
imageTag = dockerImages.mantis-explorer-server.image.imageTag;
}];

logging = {
Expand Down Expand Up @@ -596,7 +596,7 @@ let
}
}
'';
changeMode = "noop";
changeMode = "restart";
destination = "local/faucet.conf";
}
genesisJson
Expand Down
7 changes: 7 additions & 0 deletions overlay.nix
Expand Up @@ -29,6 +29,10 @@ in {

mantis-faucet = import final.mantis-faucet-source { inherit system; };

mantis-explorer-server = prev.callPackage ./pkgs/mantis-explorer-server.nix {
inherit (self.inputs.inclusive.lib) inclusive;
};

# Any:
# - run of this command with a parameter different than the testnet (currently 10)
# - change in the genesis file here
Expand Down Expand Up @@ -275,6 +279,9 @@ in {
final.nixFlakes
final.jq
final.fd
# final.crystal
# final.pkgconfig
# final.openssl
];
};

Expand Down
147 changes: 147 additions & 0 deletions pkgs/mantis-explorer-server.cr
@@ -0,0 +1,147 @@
require "http/server"
require "uri"
require "mime"
require "digest/md5"
require "option_parser"

class HTTP::ExplorerFileHandler
include HTTP::Handler

@public_dir : Path

# Creates a handler that will serve files in the given *public_dir*, after
# expanding it (using `File#expand_path`).
#
# If *fallthrough* is `false`, this handler does not call next handler when
# request method is neither GET or HEAD, then serves `405 Method Not Allowed`.
# Otherwise, it calls next handler.
def initialize(public_dir : String, fallthrough = true)
@public_dir = Path.new(public_dir).expand
@fallthrough = !!fallthrough
end

def call(context)
unless context.request.method.in?("GET", "HEAD")
if @fallthrough
call_next(context)
else
context.response.status = :method_not_allowed
context.response.headers.add("Allow", "GET, HEAD")
end
return
end

original_path = context.request.path.not_nil!
is_dir_path = original_path.ends_with?("/")
request_path = URI.decode(original_path)

# File path cannot contains '\0' (NUL) because all filesystem I know
# don't accept '\0' character as file name.
if request_path.includes? '\0'
context.response.respond_with_status(:bad_request)
return
end

request_path = Path.posix(request_path)
expanded_path = request_path.expand("/")

file_path = @public_dir.join(expanded_path.to_kind(Path::Kind.native))
is_dir = Dir.exists? file_path
is_file = !is_dir && File.exists?(file_path)

if request_path != expanded_path || is_dir && !is_dir_path
redirect_path = expanded_path
if is_dir && !is_dir_path
# Append / to path if missing
redirect_path = expanded_path.join("")
end
redirect_to context, redirect_path
return
end

unless is_file
file_path = @public_dir.join(Path.posix("/index.html").to_kind(Path::Kind.native))
is_file = File.exists?(file_path)
is_dir = Dir.exists? file_path
end

if is_file
hash = file_hash(file_path)
add_cache_headers(context.response.headers, hash)

if cache_request?(context, hash)
context.response.status = :not_modified
return
end

context.response.content_type = MIME.from_filename(file_path.to_s, "application/octet-stream")
context.response.content_length = File.size(file_path)
File.open(file_path) do |file|
IO.copy(file, context.response)
end
else
call_next(context)
end
end

private def redirect_to(context, url)
context.response.status = :found

url = URI.encode(url.to_s)
context.response.headers.add "Location", url
end

private def add_cache_headers(response_headers : HTTP::Headers, hash : String) : Nil
response_headers["Etag"] = etag(hash)
end

private def cache_request?(context : HTTP::Server::Context, hash : String) : Bool
# According to RFC 7232:
# A recipient must ignore If-Modified-Since if the request contains an If-None-Match header field
if if_none_match = context.request.if_none_match
match = {"*", context.response.headers["Etag"]}
if_none_match.any? { |etag| match.includes?(etag) }
else
false
end
end

private def etag(file_hash)
%{"#{file_hash}"}
end

private def file_hash(file_path)
Digest::MD5.hexdigest(File.read(file_path))
end
end

root = ENV["ROOT"]? || "."
host = ENV["HOST"]? || "127.0.0.1"
port = (ENV["PORT"]? || "3000").to_i

OptionParser.parse do |parser|
parser.banner = "Usage: mantis-explorer-server [arguments]"
parser.on("-r", "--root=ROOT", "directory root (default: #{root})") { |v| root = v }
parser.on("-h", "--host=HOST", "Listening host (default: #{host})") { |v| host = v }
parser.on("-p", "--port=PORT", "Listening port (default: #{port})") { |v| port = v.to_i }
parser.on("--help", "Show this help") do
puts parser
exit
end
parser.invalid_option do |flag|
STDERR.puts "ERROR: #{flag} is not a valid option."
STDERR.puts parser
exit(1)
end
end

server = HTTP::Server.new([
HTTP::ErrorHandler.new,
HTTP::LogHandler.new,
HTTP::CompressHandler.new,
HTTP::ExplorerFileHandler.new(root),
])

server.bind_tcp host, port
puts "Listening at #{host}:#{port}"
server.listen
20 changes: 20 additions & 0 deletions pkgs/mantis-explorer-server.nix
@@ -0,0 +1,20 @@
{ removeReferencesTo, inclusive, pkgconfig, openssl, crystal }:
crystal.buildCrystalPackage {
pname = "mantis-explorer-server";
version = "0.0.1";
format = "crystal";

src = inclusive ./. [ ./mantis-explorer-server.cr ];

nativeBuildInputs = [ removeReferencesTo ];
buildInputs = [ openssl pkgconfig ];

postInstall = ''
remove-references-to -t ${crystal.lib} $out/bin/*
'';

crystalBinaries.mantis-explorer-server = {
src = "mantis-explorer-server.cr";
options = [ "--verbose" "--release" ];
};
}

0 comments on commit b16cd65

Please sign in to comment.