http 0.8.14 → 0.9.0.pre

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,13 +0,0 @@
1
- module HTTP
2
- class Cache
3
- # NoOp cache. Always makes the request. Allows avoiding
4
- # conditionals in the request flow.
5
- class NullCache
6
- # @return [Response] the result of the provided block
7
- # @yield [request, options] so that the request can actually be made
8
- def perform(request, options)
9
- yield(request, options)
10
- end
11
- end
12
- end
13
- end
@@ -1,95 +0,0 @@
1
- require "http/headers"
2
- require "http/cache/headers"
3
-
4
- module HTTP
5
- class Request
6
- # Decorator for requests to provide convenience methods related to caching.
7
- class Caching < DelegateClass(HTTP::Request)
8
- INVALIDATING_METHODS = [:post, :put, :delete, :patch].freeze
9
- CACHEABLE_METHODS = [:get, :head].freeze
10
-
11
- # When was this request sent to the server
12
- #
13
- # @api public
14
- attr_accessor :sent_at
15
-
16
- # Inits a new instance
17
- # @api private
18
- def initialize(obj)
19
- super
20
- @requested_at = nil
21
- @received_at = nil
22
- end
23
-
24
- # @return [HTTP::Request::Caching]
25
- def caching
26
- self
27
- end
28
-
29
- # @return [Boolean] true iff request demands the resources cache entry be invalidated
30
- #
31
- # @api public
32
- def invalidates_cache?
33
- INVALIDATING_METHODS.include?(verb) ||
34
- cache_headers.no_store?
35
- end
36
-
37
- # @return [Boolean] true if request is cacheable
38
- #
39
- # @api public
40
- def cacheable?
41
- CACHEABLE_METHODS.include?(verb) &&
42
- !cache_headers.no_store?
43
- end
44
-
45
- # @return [Boolean] true iff the cache control info of this
46
- # request demands that the response be revalidated by the origin
47
- # server.
48
- #
49
- # @api public
50
- def skips_cache?
51
- 0 == cache_headers.max_age ||
52
- cache_headers.must_revalidate? ||
53
- cache_headers.no_cache?
54
- end
55
-
56
- # @return [HTTP::Request::Caching] new request based on this
57
- # one but conditional on the resource having changed since
58
- # `cached_response`
59
- #
60
- # @api public
61
- def conditional_on_changes_to(cached_response)
62
- self.class.new HTTP::Request.new(
63
- verb, uri, headers.merge(conditional_headers_for(cached_response)),
64
- proxy, body, version).caching
65
- end
66
-
67
- # @return [HTTP::Cache::Headers] cache control helper for this request
68
- # @api public
69
- def cache_headers
70
- @cache_headers ||= HTTP::Cache::Headers.new headers
71
- end
72
-
73
- def env
74
- {"rack-cache.cache_key" => lambda { |r| r.uri.to_s }}
75
- end
76
-
77
- private
78
-
79
- # @return [Headers] conditional request headers
80
- def conditional_headers_for(cached_response)
81
- headers = HTTP::Headers.new
82
-
83
- cached_response.headers.get(HTTP::Headers::ETAG).
84
- each { |etag| headers.add(HTTP::Headers::IF_NONE_MATCH, etag) }
85
-
86
- cached_response.headers.get(HTTP::Headers::LAST_MODIFIED).
87
- each { |last_mod| headers.add(HTTP::Headers::IF_MODIFIED_SINCE, last_mod) }
88
-
89
- headers.add(HTTP::Headers::CACHE_CONTROL, "max-age=0") if cache_headers.forces_revalidation?
90
-
91
- headers
92
- end
93
- end
94
- end
95
- end
@@ -1,143 +0,0 @@
1
- require "http/headers"
2
- require "http/cache/headers"
3
- require "http/response/string_body"
4
- require "http/response/io_body"
5
-
6
- module HTTP
7
- class Response
8
- # Decorator class for responses to provide convenience methods
9
- # related to caching.
10
- class Caching < DelegateClass(HTTP::Response)
11
- CACHEABLE_RESPONSE_CODES = [200, 203, 300, 301, 410].freeze
12
-
13
- def initialize(obj)
14
- super
15
- @requested_at = nil
16
- @received_at = nil
17
- end
18
-
19
- # @return [HTTP::Response::Caching]
20
- def caching
21
- self
22
- end
23
-
24
- # @return [Boolean] true iff this response is stale
25
- def stale?
26
- expired? || cache_headers.must_revalidate?
27
- end
28
-
29
- # @return [Boolean] true iff this response has expired
30
- def expired?
31
- current_age >= cache_headers.max_age
32
- end
33
-
34
- # @return [Boolean] true iff this response is cacheable
35
- #
36
- # ---
37
- # A Vary header field-value of "*" always fails to match and
38
- # subsequent requests on that resource can only be properly
39
- # interpreted by the
40
- def cacheable?
41
- @cacheable ||=
42
- begin
43
- CACHEABLE_RESPONSE_CODES.include?(code) \
44
- && !(cache_headers.vary_star? ||
45
- cache_headers.no_store? ||
46
- cache_headers.no_cache?)
47
- end
48
- end
49
-
50
- # @return [Numeric] the current age (in seconds) of this response
51
- #
52
- # ---
53
- # Algo from https://tools.ietf.org/html/rfc2616#section-13.2.3
54
- def current_age
55
- now = Time.now
56
- age_value = headers.get(HTTP::Headers::AGE).map(&:to_i).max || 0
57
-
58
- apparent_age = [0, received_at - server_response_time].max
59
- corrected_received_age = [apparent_age, age_value].max
60
- response_delay = [0, received_at - requested_at].max
61
- corrected_initial_age = corrected_received_age + response_delay
62
- resident_time = [0, now - received_at].max
63
-
64
- corrected_initial_age + resident_time
65
- end
66
-
67
- # @return [Time] the time at which this response was requested
68
- def requested_at
69
- @requested_at ||= received_at
70
- end
71
- attr_writer :requested_at
72
-
73
- # @return [Time] the time at which this response was received
74
- def received_at
75
- @received_at ||= Time.now
76
- end
77
- attr_writer :received_at
78
-
79
- # Update self based on this response being revalidated by the
80
- # server.
81
- def validated!(validating_response)
82
- headers.merge!(validating_response.headers)
83
- self.requested_at = validating_response.requested_at
84
- self.received_at = validating_response.received_at
85
- end
86
-
87
- # @return [HTTP::Cache::Headers] cache control headers helper object.
88
- def cache_headers
89
- @cache_headers ||= HTTP::Cache::Headers.new headers
90
- end
91
-
92
- def body
93
- @body ||= if __getobj__.body.respond_to? :each
94
- __getobj__.body
95
- else
96
- StringBody.new(__getobj__.body.to_s)
97
- end
98
- end
99
-
100
- def body=(new_body)
101
- @body = if new_body.respond_to?(:readpartial) && new_body.respond_to?(:read)
102
- # IO-ish, probably a rack cache response body
103
- IoBody.new(new_body)
104
-
105
- elsif new_body.respond_to? :join
106
- # probably an array of body parts (rack cache does this sometimes)
107
- StringBody.new(new_body.join(""))
108
-
109
- elsif new_body.respond_to? :readpartial
110
- # normal body, just use it.
111
- new_body
112
-
113
- else
114
- # backstop, just to_s it
115
- StringBody.new(new_body.to_s)
116
- end
117
- end
118
-
119
- def vary
120
- headers.get(HTTP::Headers::VARY).first
121
- end
122
-
123
- protected
124
-
125
- # @return [Time] the time at which the server generated this response.
126
- def server_response_time
127
- headers.get(HTTP::Headers::DATE).
128
- map(&method(:to_time_or_epoch)).
129
- max || begin
130
- # set it if it is not already set
131
- headers[HTTP::Headers::DATE] = received_at.httpdate
132
- received_at
133
- end
134
- end
135
-
136
- def to_time_or_epoch(t_str)
137
- Time.httpdate(t_str)
138
- rescue ArgumentError
139
- Time.at(0)
140
- end
141
- end
142
- end
143
- end
@@ -1,63 +0,0 @@
1
- module HTTP
2
- class Response
3
- # A Body class that wraps an IO, rather than a the client
4
- # object.
5
- class IoBody
6
- include Enumerable
7
- extend Forwardable
8
-
9
- # @return [String,nil] the next `size` octets part of the
10
- # body, or nil if whole body has already been read.
11
- def readpartial(size = HTTP::Connection::BUFFER_SIZE)
12
- stream!
13
- return nil if stream.eof?
14
-
15
- stream.readpartial(size)
16
- end
17
-
18
- # Iterate over the body, allowing it to be enumerable
19
- def each
20
- while (part = readpartial)
21
- yield part
22
- end
23
- end
24
-
25
- # @return [String] eagerly consume the entire body as a string
26
- def to_s
27
- @contents ||= readall
28
- end
29
- alias_method :to_str, :to_s
30
-
31
- def_delegator :to_s, :empty?
32
-
33
- # Assert that the body is actively being streamed
34
- def stream!
35
- fail StateError, "body has already been consumed" if @streaming == false
36
- @streaming = true
37
- end
38
-
39
- # Easier to interpret string inspect
40
- def inspect
41
- "#<#{self.class}:#{object_id.to_s(16)} @streaming=#{!!@streaming}>"
42
- end
43
-
44
- protected
45
-
46
- def initialize(an_io)
47
- @streaming = nil
48
- @stream = an_io
49
- end
50
-
51
- attr_reader :contents, :stream
52
-
53
- def readall
54
- fail StateError, "body is being streamed" unless @streaming.nil?
55
-
56
- @streaming = false
57
- "".tap do |buf|
58
- buf << stream.read until stream.eof?
59
- end
60
- end
61
- end
62
- end
63
- end
@@ -1,53 +0,0 @@
1
- module HTTP
2
- class Response
3
- # A Body class that wraps a String, rather than a the client
4
- # object.
5
- class StringBody
6
- include Enumerable
7
- extend Forwardable
8
-
9
- # @return [String,nil] the next `size` octets part of the
10
- # body, or nil if whole body has already been read.
11
- def readpartial(size = @contents.length)
12
- stream!
13
- return nil if @streaming_offset >= @contents.length
14
-
15
- @contents[@streaming_offset, size].tap do |part|
16
- @streaming_offset += (part.length + 1)
17
- end
18
- end
19
-
20
- # Iterate over the body, allowing it to be enumerable
21
- def each
22
- yield @contents
23
- end
24
-
25
- # @return [String] eagerly consume the entire body as a string
26
- def to_s
27
- @contents
28
- end
29
- alias_method :to_str, :to_s
30
-
31
- def_delegator :@contents, :empty?
32
-
33
- # Assert that the body is actively being streamed
34
- def stream!
35
- fail StateError, "body has already been consumed" if @streaming == false
36
- @streaming = true
37
- end
38
-
39
- # Easier to interpret string inspect
40
- def inspect
41
- "#<#{self.class}:#{object_id.to_s(16)}>"
42
- end
43
-
44
- protected
45
-
46
- def initialize(contents)
47
- @contents = contents
48
- @streaming = nil
49
- @streaming_offset = 0
50
- end
51
- end
52
- end
53
- end
@@ -1,77 +0,0 @@
1
- RSpec.describe HTTP::Cache::Headers do
2
- subject(:cache_headers) { described_class.new headers }
3
-
4
- describe ".new" do
5
- it "accepts instance of HTTP::Headers" do
6
- expect { described_class.new HTTP::Headers.new }.not_to raise_error
7
- end
8
-
9
- it "it rejects any object that does not respond to #headers" do
10
- expect { described_class.new double }.to raise_error HTTP::Error
11
- end
12
- end
13
-
14
- context "with <Cache-Control: private>" do
15
- let(:headers) { {"Cache-Control" => "private"} }
16
- it { is_expected.to be_private }
17
- end
18
-
19
- context "with <Cache-Control: public>" do
20
- let(:headers) { {"Cache-Control" => "public"} }
21
- it { is_expected.to be_public }
22
- end
23
-
24
- context "with <Cache-Control: no-cache>" do
25
- let(:headers) { {"Cache-Control" => "no-cache"} }
26
- it { is_expected.to be_no_cache }
27
- end
28
-
29
- context "with <Cache-Control: no-store>" do
30
- let(:headers) { {"Cache-Control" => "no-store"} }
31
- it { is_expected.to be_no_store }
32
- end
33
-
34
- describe "#max_age" do
35
- subject { cache_headers.max_age }
36
-
37
- context "with <Cache-Control: max-age=100>" do
38
- let(:headers) { {"Cache-Control" => "max-age=100"} }
39
- it { is_expected.to eq 100 }
40
- end
41
-
42
- context "with <Expires: {100 seconds from now}>" do
43
- let(:headers) { {"Expires" => (Time.now + 100).httpdate} }
44
- it { is_expected.to be_within(1).of(100) }
45
- end
46
-
47
- context "with <Expires: {100 seconds before now}>" do
48
- let(:headers) { {"Expires" => (Time.now - 100).httpdate} }
49
- it { is_expected.to eq 0 }
50
- end
51
-
52
- context "with <Expires: -1>" do
53
- let(:headers) { {"Expires" => "-1"} }
54
- it { is_expected.to eq 0 }
55
- end
56
- end
57
-
58
- context "with <Vary: *>" do
59
- let(:headers) { {"Vary" => "*"} }
60
- it { is_expected.to be_vary_star }
61
- end
62
-
63
- context "with no cache related headers" do
64
- let(:headers) { {} }
65
-
66
- it { is_expected.not_to be_private }
67
- it { is_expected.not_to be_public }
68
- it { is_expected.not_to be_no_cache }
69
- it { is_expected.not_to be_no_store }
70
- it { is_expected.not_to be_vary_star }
71
-
72
- describe "#max_age" do
73
- subject { cache_headers.max_age }
74
- it { is_expected.to eq Float::INFINITY }
75
- end
76
- end
77
- end