# -*- 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", # always ".fid" "Content-Length" => size.to_s, "Last-Modified" => stat.mtime.httpdate, "Accept-Ranges" => "bytes", } @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' @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 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