aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorPaul Duncan <pabs@pablotron.org>2016-03-13 17:09:40 -0400
committerPaul Duncan <pabs@pablotron.org>2016-03-13 17:09:40 -0400
commit909d75e98a2d27b6d831124effbe8f73ee4ce498 (patch)
tree9df0f549f4b89ac6d04c65ffbd2335a42ca13b2f /src
parent64789b507efe482586b2362bc07fa5d59cb26ef9 (diff)
downloadold-guff-909d75e98a2d27b6d831124effbe8f73ee4ce498.tar.bz2
old-guff-909d75e98a2d27b6d831124effbe8f73ee4ce498.zip
add guff-stuff static files handler
Diffstat (limited to 'src')
-rw-r--r--src/guff/config.cr54
-rw-r--r--src/guff/handlers.cr5
-rw-r--r--src/guff/handlers/guff-stuff.cr77
-rw-r--r--src/guff/mime-type.cr14
4 files changed, 140 insertions, 10 deletions
diff --git a/src/guff/config.cr b/src/guff/config.cr
index 1be8d8d..43eaf13 100644
--- a/src/guff/config.cr
+++ b/src/guff/config.cr
@@ -1,4 +1,5 @@
require "option_parser"
+require "yaml"
module Guff
class Config
@@ -8,6 +9,7 @@ module Guff
"data": "./data",
"public": "./public",
"environment": "development",
+ "stuff": "/usr/local/share/guff/stuff",
}
DIRS = %w{
@@ -39,9 +41,20 @@ module Guff
private def parse_args(app, args)
OptionParser.parse(args) do |p|
p.on(
+ "--config PATH",
+ "Read config from YAML file."
+ ) do |path|
+ YAML.parse(File.read(path)).each do |k, v|
+ self[k.as_s] = v.as_s
+ end
+ end
+
+ p.on(
"-h HOST",
"--host HOST",
- "Host (defaults to \"localhost\")."
+ "Host (defaults to \"%s\")." % [
+ DEFAULTS["host"]
+ ]
) do |host|
self["host"] = host
end
@@ -49,7 +62,9 @@ module Guff
p.on(
"-p PORT",
"--port PORT",
- "Port (defaults to 8989)."
+ "Port (defaults to %s)." % [
+ DEFAULTS["port"]
+ ]
) do |port|
self["port"] = port
end
@@ -57,23 +72,37 @@ module Guff
p.on(
"-d PATH",
"--data-dir PATH",
- "Path to data directory (defaults to \"./data\")."
+ "Path to data directory (defaults to \"%s\")." % [
+ DEFAULTS["data"],
+ ]
) do |path|
self["data"] = path
end
-
+
p.on(
- "-u PATH",
"--public-dir PATH",
- "Path to public directory (defaults to \"./public\")."
+ "Path to public directory (defaults to \"%s\")." % [
+ DEFAULTS["public"],
+ ]
) do |path|
self["public"] = path
end
p.on(
+ "--stuff-dir PATH",
+ "Path to Guff static files directory (defaults to \"%s\")." % [
+ DEFAULTS["stuff"],
+ ]
+ ) do |path|
+ self["stuff"] = path
+ end
+
+ p.on(
"-e ENV",
"--environment ENV",
- "Environment (defaults to \"development\")."
+ "Environment (defaults to \"%s\")." % [
+ DEFAULTS["environment"],
+ ]
) do |path|
self["public"] = path
end
@@ -82,7 +111,7 @@ module Guff
p.on(
"--help",
"Print usage"
- ) do
+ ) do
puts p.to_s
exit 0
end
@@ -90,9 +119,14 @@ module Guff
# expand output directory paths and create them
%w{data public}.each do |key|
- self[key] = File.expand_path(File.join(File.dirname(app), self[key]))
+ # expand path (unless it is absolute)
+ self[key] = File.expand_path(File.join(
+ File.dirname(app),
+ self[key]
+ )) unless self[key][0] == '/'
+
# puts "DEBUG: %s: %s" % [key, self[key]]
- Dir.mkdir(self[key], 0o770) unless File.directory?(self[key])
+ Dir.mkdir(self[key], 0o770) unless Dir.exists?(self[key])
end
end
end
diff --git a/src/guff/handlers.cr b/src/guff/handlers.cr
index 834be47..91bc42b 100644
--- a/src/guff/handlers.cr
+++ b/src/guff/handlers.cr
@@ -22,6 +22,11 @@ module Guff
}, {
env: %w{development},
init: ->(models : Models) {
+ GuffStuffHandler.new(models) as HTTP::Handler
+ },
+ }, {
+ env: %w{development},
+ init: ->(models : Models) {
TestBlogHandler.new(models) as HTTP::Handler
},
}, {
diff --git a/src/guff/handlers/guff-stuff.cr b/src/guff/handlers/guff-stuff.cr
new file mode 100644
index 0000000..cc3ac6c
--- /dev/null
+++ b/src/guff/handlers/guff-stuff.cr
@@ -0,0 +1,77 @@
+require "uri"
+require "../handler"
+require "../mime-type"
+require "../views/html/page"
+
+class Guff::Handlers::GuffStuffHandler < Guff::Handler
+ def initialize(models)
+ super(models)
+ @digests = {} of String => String
+ end
+
+ def call(context : HTTP::Server::Context)
+ path = context.request.path.not_nil!
+
+ if matching_request?(context.request.method, path)
+ reply(context)
+ else
+ call_next(context)
+ end
+ end
+
+ private def reply(context : HTTP::Server::Context)
+ # get expanded path to file
+ if path = expand_path(context.request.path.not_nil!)
+ # get file digest
+ file_digest = digest(path)
+
+ # check for cache header
+ if context.request.headers["if-none-match"]? == file_digest
+ # cached, send 304 not modified
+ context.response.status_code = 304
+ else
+ # not cached, set code and send headers
+ context.response.status_code = 200
+ context.response.content_type = Guff::MimeType.mime_type(path)
+ context.response.content_length = File.size(path)
+ context.response.headers["etag"] = file_digest
+
+ if context.request.method == "GET"
+ # send body
+ File.open(path) do |fh|
+ IO.copy(fh, context.response)
+ end
+ end
+ end
+ else
+ # file not found
+ context.response.status_code = 404
+ end
+ end
+
+ VALID_METHODS = %w{GET HEAD}
+ PATH_RE = %r{^/guff-stuff/}
+
+ private def matching_request?(method, path)
+ VALID_METHODS.includes?(method) && PATH_RE.match(path)
+ end
+
+ private def expand_path(req_path : String) : String?
+ # unescape path, check for nil byte
+ path = URI.unescape(req_path)
+ return nil if path.includes?('\0')
+
+ # build absolute path
+ r = File.join(
+ @models.config["stuff"],
+ File.expand_path(path.gsub(PATH_RE, ""), "/")
+ )
+
+ # return path if file exists, or nil otherwise
+ File.file?(r) ? r : nil
+ end
+
+ private def digest(path : String) : String
+ @digests[path] ||= OpenSSL::Digest.new("SHA1").file(path).hexdigest
+ end
+end
diff --git a/src/guff/mime-type.cr b/src/guff/mime-type.cr
new file mode 100644
index 0000000..992521b
--- /dev/null
+++ b/src/guff/mime-type.cr
@@ -0,0 +1,14 @@
+module Guff::MimeType
+ TYPES = {
+ ".js": "text/javascript; charset=utf-8",
+ ".css": "text/css; charset=utf-8",
+ ".html": "text/html; charset=utf-8",
+ ".png": "image/png",
+ ".jpeg": "image/jpeg",
+ ".jpg": "image/jpeg",
+ }
+
+ def self.mime_type(path : String) : String
+ TYPES[File.extname(path)]? || "application/octet-stream"
+ end
+end