regurgitator.git  about / heads / tags
Read-only Rack endpoints for MogileFS and BARFS
blob 87b0cce1ac45bae06145a32065331427c228c93f 2446 bytes (raw)
$ git show HEAD:lib/regurgitator/local_file.rb	# shows this blob on the CLI

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
 
# -*- encoding: binary -*-
require "time"

# used to serve local files, bypassing HTTP to the backend device
class Regurgitator::LocalFile # :nodoc:

  # may be called by some Rack web servers to use sendfile(2)
  attr_reader :to_path

  # used to provide redirects to clients
  attr_reader :uri

  # Stores the Rack response (a 3-element Array) on success to simulate
  # HTTP_Spew::Request objects
  attr_reader :response

  def initialize(env, path, uri, stat)
    if modified_since = env["HTTP_IF_MODIFIED_SINCE"]
      modified_since?(modified_since, stat) or return
    end
    size = stat.size

    headers = {
      "Content-Type" => "application/octet-stream".freeze, # always ".fid"
      "Content-Length" => size.to_s,
      "Last-Modified" => stat.mtime.httpdate,
      "Accept-Ranges" => "bytes".freeze,
    }
    @response = [ 200, headers ]

    ranges = Rack::Utils.get_byte_ranges(env['HTTP_RANGE'], size)
    if nil == ranges || ranges.size > 1
      @range = nil
    elsif @range = ranges[0]
      @response[0] = 206
      headers["Content-Range"] = "bytes #{@range.begin}-#{@range.end}/#{size}"
      headers["Content-Length"] = (@range.end - @range.begin + 1).to_s
    else
      @response[0] = 416
      headers["Content-Range"] = "bytes */#{size}"
      headers["Content-Length"] = '0'.freeze
      @response << []
      return
    end

    case env["REQUEST_METHOD"]
    when "GET"
      @to_path, @uri = path, uri
      @response << self
    when "HEAD"
      @response << []
    else
      raise "BUG: Unexpected REQUEST_METHOD=#{env['REQUEST_METHOD']}"
    end
  end

  # normal Rack HTTP server endpoint, used if the server can't handle
  # +to_path+
  def each
    File.open(@to_path) do |fp|
      buf = ""
      max = 0x4000
      if @range
        fp.seek(@range.begin)
        len = @range.end - @range.begin + 1
        while len > 0 && fp.read(len > max ? max : len, buf)
          len -= buf.size
          yield buf
        end
      else
        while fp.read(0x4000, buf)
          yield buf
        end
      end
      buf.clear
    end
  end

  def modified_since?(modified_since, stat)
    begin
      modified_since = Time.httpdate(modified_since)
    rescue ArgumentError
      h = { "Content-Type" => "text/plain", "Content-Length" => "0" }
      @response = [ 400, h, [] ]
      return false
    end
    stat.mtime > modified_since and return true
    @response = [ 304, {}, [] ]
    false
  end
end

git clone https://yhbt.net/regurgitator.git