Class: IntervalResponse::Sequence
- Inherits:
-
Object
- Object
- IntervalResponse::Sequence
- Defined in:
- lib/interval_response/sequence.rb
Overview
Represents a linear sequence of non-overlapping, joined intervals. For example, an HTTP response which consists of multiple edge included segments, or a timeline with clips joined together. Every interval contains a segment - an arbitrary object which responds to ‘#size` at time of adding to the IntervalSequence.
Instance Attribute Summary collapse
-
#size ⇒ Integer
readonly
The sum of sizes of all the segments of the sequence.
Instance Method Summary collapse
-
#<<(segment) ⇒ Object
Adds a segment to the sequence.
-
#add_segment(segment, size:, etag: size) ⇒ Object
Adds a segment to the sequence with specifying the size and optionally the ETag value of the segment.
-
#each_in_range(from_range_in_resource) ⇒ Object
Yields every segment which is touched by the given Range in resource in sequence, together with a Range object which defines the necessary part of the segment.
-
#empty? ⇒ Boolean
Tells whether the size of the entire sequence is 0.
-
#etag ⇒ String
For IE resumes to work, a strong ETag must be set in the response, and a strong comparison must be performed on it.
-
#first_interval_only?(*ranges) ⇒ Boolean
Tells whether all of the given ‘ranges` will be satisfied from the first interval only.
-
#initialize(*segments) ⇒ Sequence
constructor
Creates a new Sequence with given segments.
Constructor Details
#initialize(*segments) ⇒ Sequence
Creates a new Sequence with given segments.
18 19 20 21 22 |
# File 'lib/interval_response/sequence.rb', line 18 def initialize(*segments) @intervals = [] @size = 0 segments.each { |s| self << s } end |
Instance Attribute Details
#size ⇒ Integer (readonly)
Returns the sum of sizes of all the segments of the sequence.
13 14 15 |
# File 'lib/interval_response/sequence.rb', line 13 def size @size end |
Instance Method Details
#<<(segment) ⇒ Object
Adds a segment to the sequence. The segment gets added at the end of the sequence.
28 29 30 31 |
# File 'lib/interval_response/sequence.rb', line 28 def <<(segment) segment_size_or_bytesize = segment.respond_to?(:bytesize) ? segment.bytesize : segment.size add_segment(segment, size: segment_size_or_bytesize) end |
#add_segment(segment, size:, etag: size) ⇒ Object
Adds a segment to the sequence with specifying the size and optionally the ETag value of the segment. ETag defaults to the size of the segment. Segment can be any object as the size gets passed as a keyword argument
42 43 44 45 46 47 48 49 50 51 |
# File 'lib/interval_response/sequence.rb', line 42 def add_segment(segment, size:, etag: size) if size > 0 etag_quoted = '"%s"' % etag # We save the index of the interval inside the Struct so that we can # use `bsearch` later instead of requiring `bsearch_index` to be available @intervals << Interval.new(segment, size, @size, @intervals.length, etag_quoted) @size += size end self end |
#each_in_range(from_range_in_resource) ⇒ Object
Yields every segment which is touched by the given Range in resource in sequence, together with a Range object which defines the necessary part of the segment. For example, calling ‘each_in_range(0..2)` with 2 segments of size 1 and 2 will successively yield [segment1, 0..0] then [segment2, 0..1]
Interval sequences can be nested - you can place a Sequence inside another Sequence as a segment. In that case when you call ‘each_in_range` on the outer Sequence and you need to retrieve data from the inner Sequence which is one of the segments, the call will yield the segments from the inner Sequence, “drilling down” as deep as is appropriate.
Three arguments will be yielded to the block - the segment (the “meat” of an interval, which is the object given when the interval was added to the Sequence), the range within the interval (which is always going to be an inclusive ‘Range` of integers) and a boolean flag indicating whether this interval is the very first interval in the requested subset of the sequence. This flag honors nesting (if you have arbitrarily nested interval Sequences and you request something from the first interval of several Sequences deep it will still indicate `true`).
72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 |
# File 'lib/interval_response/sequence.rb', line 72 def each_in_range(from_range_in_resource) # Skip empty ranges requested_range_size = (from_range_in_resource.end - from_range_in_resource.begin) + 1 return if requested_range_size < 1 # Then walk through included intervals. If the range misses # our intervals completely included_intervals will be empty. included_intervals = intervals_within_range(from_range_in_resource) included_intervals.each do |interval| int_start = interval.offset int_end = interval.offset + interval.size - 1 req_start = from_range_in_resource.begin req_end = from_range_in_resource.end range_within_interval = (max(int_start, req_start) - int_start)..(min(int_end, req_end) - int_start) is_first_interval = interval.position == 0 # Allow Sequences to be composed together if interval.segment.respond_to?(:each_in_range) interval.segment.each_in_range(range_within_interval) do |sub_segment, sub_range, is_first_nested_interval| yield(sub_segment, sub_range, is_first_interval && is_first_nested_interval) end else yield(interval.segment, range_within_interval, is_first_interval) end end end |
#empty? ⇒ Boolean
Tells whether the size of the entire sequence is 0
100 101 102 |
# File 'lib/interval_response/sequence.rb', line 100 def empty? @size == 0 end |
#etag ⇒ String
For IE resumes to work, a strong ETag must be set in the response, and a strong comparison must be performed on it.
ETags have meaning with Range: requests, because when a client requests a range it will send the ETag back in the If-Range header. That header tells the server that “I want to have the ranges as emitted by the response representation that has output this etag”. This is done so that there is a guarantee that the same resource being requested has the same resource length (off of which the ranges get computed), and the ranges can be safely combined by the client. In practice this means that the ETag must contain some “version handle” which stays unchanged as long as the code responsible for generating the response does not change. In our case the response can change due to the following things:
-
The lengths of the segments change
-
The contents of the segments changes
-
Code that outputs the ranges themselves changes, and outputs different offsets of differently-sized resources. A resource can be differently sized since the MIME multiplart-byte-range response can have its boundary or per-part headers change, which affects the size of the MIME part headers. Even though the boundary is not a part of the resource itself, the sizes of the part headers do contribute to the envelope size - that should stay the same as long as the ETag holds.
It is important that the returned ETag is a strong ETag (not prefixed with ‘W/’) and must be enclosed in double-quotes.
See for more blogs.msdn.microsoft.com/ieinternals/2011/06/03/download-resumption-in-internet-explorer/
The ETag value gets derived from the ETags of the segments, which will be Marshal.dump’ed together and then added to the hash digest to produce the final ETag value.
135 136 137 138 139 140 141 142 |
# File 'lib/interval_response/sequence.rb', line 135 def etag d = Digest::SHA1.new d << IntervalResponse::VERSION @intervals.each do |interval| d << interval.etag end '"%s"' % d.hexdigest end |
#first_interval_only?(*ranges) ⇒ Boolean
Tells whether all of the given ‘ranges` will be satisfied from the first interval only. This can be used to redirect to the resource at that interval instead of proxying it through, since the `Range` header won’t need to be adjusted
147 148 149 150 151 152 153 154 155 |
# File 'lib/interval_response/sequence.rb', line 147 def first_interval_only?(*ranges) ranges.map do |range| each_in_range(range) do |_, _, is_first_interval| return false unless is_first_interval end end true end |