Last active
October 12, 2019 16:43
-
-
Save mwgamera/cf6ccda7eee305fe58b9e942f7ce009e to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/usr/bin/env ruby | |
# Like <https://gist.github.com/jimfoltz/ee791c1bdd30ce137bc23cce826096da> | |
# but cooler. Keep only 10 last revisions, gzipped. | |
# klg, Jun 2019 | |
require 'securerandom' | |
require 'webrick' | |
require 'zlib' | |
class Tw5 < WEBrick::HTTPServlet::AbstractServlet | |
def do_OPTIONS req, res | |
res['allow'] = 'GET,HEAD,OPTIONS,PUT' | |
res['dav'] = 'tw5/put' # has DAV but not compatible | |
end | |
def do_GET req, res | |
f = File.open "./#{req.path}.gz" | |
st = f.stat | |
make_validators res, st | |
check_validators req, res | |
res['content-type'] = 'text/html;charset=utf-8' | |
if a = req['accept-encoding'] and | |
WEBrick::HTTPUtils.parse_qvalues(a).member?('gzip') | |
res['content-encoding'] = 'gzip' | |
res['content-length'] = st.size | |
res.body = f | |
else | |
Zlib::GzipReader.open f do |gz| | |
res.body = gz.read | |
end | |
f.close | |
end | |
res.status = 200 | |
rescue Errno::ENOENT | |
begin | |
f = File.open "./#{req.path}" | |
st = f.stat | |
unless st.file? | |
raise WEBrick::HTTPStatus::NotFound.new "#{req.path} not found." | |
end | |
make_validators res, st | |
check_validators req, res | |
res['content-length'] = st.size | |
res.body = f | |
res.status = 200 | |
rescue Errno::ENOENT | |
raise WEBrick::HTTPStatus::NotFound.new "#{req.path} not found." | |
end | |
end | |
def do_PUT req, res | |
if req['content-range'] || req['content-encoding'] | |
raise WEBrick::HTTPStatus::NotImplemented | |
end | |
path = "./#{req.path}.gz".gsub /\/\.*(?=\/|$)/, '' | |
tmpn = "./tmp.#{$$}-#{SecureRandom.hex(6)}" | |
Zlib::GzipWriter.open tmpn do |gz| | |
req.body { |chunk| gz.write chunk } | |
end | |
res.status = 204 | |
rev = 0 | |
File.open '.' do |dir| | |
dir.flock File::LOCK_EX | |
begin | |
make_validators res, File.stat(path) | |
check_validators req, res | |
rev = File.readlink(path).match(/;([0-9]+)$/)[1].to_i | |
rescue Errno::EINVAL | |
File.link path, "#{path};0" | |
rescue Errno::ENOENT | |
res.status = 201 | |
end | |
rev += 1 | |
File.rename tmpn, "#{path};#{rev}" | |
rpath = path.gsub /.*\//, '' | |
File.symlink "#{rpath};#{rev}", tmpn | |
File.rename tmpn, path | |
make_validators res, File.stat(path) | |
end | |
if rev >= 10 | |
begin | |
File.unlink "#{path};#{rev - 10}" | |
rescue Errno::ENOENT | |
end | |
end | |
ensure | |
begin | |
File.unlink tmpn | |
rescue Errno::ENOENT | |
end | |
end | |
def make_validators res, stat | |
mtime = stat.mtime | |
res['last-modified'] = mtime.httpdate | |
res['etag'] = etag = sprintf '%x-%x-%x', stat.ino, stat.size, mtime.to_i | |
end | |
def check_validators req, res | |
mtime = Time.parse(res['last-modified']) | |
etag = res['etag'] | |
if x = req['if-match'] and etag.nil? || | |
x != '*' && !WEBrick::HTTPUtils::split_header_value(x).member?(etag) | |
raise WEBrick::HTTPStatus::PreconditionFailed | |
end | |
if x = req['if-none-match'] and | |
x == '*' || WEBrick::HTTPUtils::split_header_value(x).member?(etag) | |
if %w(GET HEAD).member?(req.request_method) | |
raise WEBrick::HTTPStatus::NotModified | |
else | |
raise WEBrick::HTTPStatus::PreconditionFailed | |
end | |
end | |
begin | |
if x = req['if-modified-since'] and | |
Time.parse(x) >= mtime | |
raise WEBrick::HTTPStatus::NotModified | |
end | |
if x = req['if-unmodified-since'] and | |
!req['if-match'] && Time.parse(x) < mtime | |
raise WEBrick::HTTPStatus::PreconditionFailed | |
end | |
rescue ArgumentError | |
# ignore malformed dates | |
end | |
end | |
end | |
if $0 == __FILE__ | |
server = WEBrick::HTTPServer.new :Port => ENV['PORT'] || 8080 | |
server.mount '/', Tw5 | |
trap 'INT' do | |
server.shutdown | |
end | |
server.start | |
end |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment