module IntervalResponse

Constants

ENTIRE_RESOURCE_RANGE
VERSION

Public Class Methods

new(interval_sequence, rack_env_headers) click to toggle source

Creates a new IntervalResponse object. The object returned does not have a specific class, but is one of the following objects, which all support the same interface:

@param interval_sequence the sequence of segments @param rack_env_headers the Rack env, or a Hash containing 'HTTP_RANGE' and 'HTTP_IF_RANGE' headers @return [Empty, Single, Full, Multi, Invalid]

# File lib/interval_response.rb, line 33
def self.new(interval_sequence, rack_env_headers)
  http_range_header_value = rack_env_headers['HTTP_RANGE']
  http_if_range_header_value = rack_env_headers['HTTP_IF_RANGE']

  # If the 'If-Range' header is provided but does not match, discard the Range header. It means
  # that the client is requesting a certain representation of the resource and wants a range
  # _within_ that representation, but the representation has since changed and the offsets
  # no longer make sense. In that case we are supposed to answer with a 200 and the full
  # monty.
  if http_if_range_header_value && http_if_range_header_value != interval_sequence.etag
    Measurometer.increment_counter('interval_response.if_range_mismatch', 1)
    return new(interval_sequence, 'HTTP_RANGE' => ENTIRE_RESOURCE_RANGE)
  end

  if http_if_range_header_value
    Measurometer.increment_counter('interval_response.if_range_match', 1)
  elsif http_range_header_value
    Measurometer.increment_counter('interval_response.if_range_not_provided', 1)
  end

  prepare_response(interval_sequence, http_range_header_value).tap do |res|
    response_type_name_for_metric = res.class.to_s.split('::').last.downcase # Some::Module::Empty => empty
    Measurometer.increment_counter('interval_response.resp_%s' % response_type_name_for_metric, 1)
  end
end

Private Class Methods

prepare_response(interval_sequence, http_range_header_value) click to toggle source
# File lib/interval_response.rb, line 59
def self.prepare_response(interval_sequence, http_range_header_value)
  # Case 1 - response of 0 bytes (empty resource).
  # We don't even have to parse the Range header for this since
  # the response will be the same, always.
  return Empty.new(interval_sequence) if interval_sequence.empty?

  # Parse the HTTP Range: header
  range_request_header = http_range_header_value || ENTIRE_RESOURCE_RANGE
  http_ranges = Rack::Utils.get_byte_ranges(range_request_header, interval_sequence.size)

  # Case 2 - Client did send us a Range header, but Rack discarded
  # it because it is invalid and cannot be satisfied
  return Invalid.new(interval_sequence) if http_range_header_value && (http_ranges.nil? || http_ranges.empty?)

  # Case 3 - entire resource
  return Full.new(interval_sequence) if http_ranges.length == 1 && http_ranges.first == (0..(interval_sequence.size - 1))

  # Case 4 - one content range
  return Single.new(interval_sequence, http_ranges[0]) if http_ranges.length == 1

  # Case 5 - MIME multipart with multiple content ranges
  Multi.new(interval_sequence, http_ranges)
end