net-http-follow_tail 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/.rspec +1 -0
- data/Gemfile +12 -0
- data/Gemfile.lock +38 -0
- data/README.md +45 -0
- data/Rakefile +7 -0
- data/lib/net/http/follow_tail.rb +156 -0
- data/spec/follow_tail_spec.rb +95 -0
- data/spec/spec_helper.rb +8 -0
- data/spec/tailer_spec.rb +182 -0
- metadata +104 -0
data/.rspec
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
-f doc
|
data/Gemfile
ADDED
data/Gemfile.lock
ADDED
@@ -0,0 +1,38 @@
|
|
1
|
+
GEM
|
2
|
+
remote: https://rubygems.org/
|
3
|
+
specs:
|
4
|
+
addressable (2.3.4)
|
5
|
+
coderay (1.0.7)
|
6
|
+
crack (0.3.2)
|
7
|
+
diff-lcs (1.2.3)
|
8
|
+
exponential-backoff (0.0.2)
|
9
|
+
method_source (0.8)
|
10
|
+
pry (0.9.12.1)
|
11
|
+
coderay (~> 1.0.5)
|
12
|
+
method_source (~> 0.8)
|
13
|
+
slop (~> 3.4)
|
14
|
+
rake (10.0.4)
|
15
|
+
reindeer (0.0.1)
|
16
|
+
rspec (2.13.0)
|
17
|
+
rspec-core (~> 2.13.0)
|
18
|
+
rspec-expectations (~> 2.13.0)
|
19
|
+
rspec-mocks (~> 2.13.0)
|
20
|
+
rspec-core (2.13.1)
|
21
|
+
rspec-expectations (2.13.0)
|
22
|
+
diff-lcs (>= 1.1.3, < 2.0)
|
23
|
+
rspec-mocks (2.13.1)
|
24
|
+
slop (3.4.4)
|
25
|
+
webmock (1.11.0)
|
26
|
+
addressable (>= 2.2.7)
|
27
|
+
crack (>= 0.3.2)
|
28
|
+
|
29
|
+
PLATFORMS
|
30
|
+
ruby
|
31
|
+
|
32
|
+
DEPENDENCIES
|
33
|
+
exponential-backoff
|
34
|
+
pry
|
35
|
+
rake
|
36
|
+
reindeer
|
37
|
+
rspec
|
38
|
+
webmock
|
data/README.md
ADDED
@@ -0,0 +1,45 @@
|
|
1
|
+
# Net::HTTP::FollowTail
|
2
|
+
|
3
|
+
Fulfils the same role as `tail -f` but for files over HTTP. That is to
|
4
|
+
say if you have log files (e.g IRC logs) available at a URL you could
|
5
|
+
follow them with this module.
|
6
|
+
|
7
|
+
# Usage
|
8
|
+
|
9
|
+
```ruby
|
10
|
+
require 'net/http/follow_tail'
|
11
|
+
|
12
|
+
Net::HTTP::FollowTail.follow(uri: 'http://example.com/irc.log') do |result, tailer|
|
13
|
+
puts "Someone on IRC said: ", result.content
|
14
|
+
end
|
15
|
+
```
|
16
|
+
|
17
|
+
# Interface
|
18
|
+
|
19
|
+
If you're desiring of a URI's tail then the simplest way of using this
|
20
|
+
module is to use the `follow` class method on `Net::HTTP::FollowTail`.
|
21
|
+
It's first argument should be a hash, or array of hashes, containing
|
22
|
+
at least `uri` key with a value that's either a `URI::HTTP` instance
|
23
|
+
or something that would `URI.parse` to one. It also expects a block
|
24
|
+
which gets executed whenever new data appears at the tail at any of
|
25
|
+
the URIs. That's demonstrated in the *Usage* example above.
|
26
|
+
|
27
|
+
Other data that can be passed in the hash argument(s) are:
|
28
|
+
|
29
|
+
- `wait_in_seconds`: How long to wait in seconds between polls.
|
30
|
+
- `offset`: An offset in `Fixnum` bytes to start at.
|
31
|
+
- `max_retries`: The number of times to retry in the face of failure
|
32
|
+
before giving up.
|
33
|
+
- `always_callback`: A boolean indicating that the callback should be
|
34
|
+
called even the tail request wasn't successful.
|
35
|
+
|
36
|
+
The callback is called with two arguments - a
|
37
|
+
`Net::HTTP::FollowTail::Result` instance and a
|
38
|
+
`Net::HTTP::FollowTail::Tailer` instance respectively. The former
|
39
|
+
exposing the result of the most recent tail request at the latter the
|
40
|
+
current tailing state. By default it is only called when the tail
|
41
|
+
request was successful.
|
42
|
+
|
43
|
+
# Author
|
44
|
+
|
45
|
+
Dan Brook `<dan@broquaint.com>`
|
data/Rakefile
ADDED
@@ -0,0 +1,156 @@
|
|
1
|
+
require 'net/http'
|
2
|
+
require 'uri'
|
3
|
+
require 'exponential_backoff'
|
4
|
+
require 'reindeer'
|
5
|
+
|
6
|
+
class Net::HTTP::FollowTail
|
7
|
+
class Result < Reindeer
|
8
|
+
has :state, is_a: Symbol
|
9
|
+
has :method, is_a: Symbol
|
10
|
+
has :response, is_a: Net::HTTPSuccess
|
11
|
+
|
12
|
+
def has_response?
|
13
|
+
not @response.nil?
|
14
|
+
end
|
15
|
+
|
16
|
+
def is_success?
|
17
|
+
@state == :success
|
18
|
+
end
|
19
|
+
def is_error?
|
20
|
+
@state == :error
|
21
|
+
end
|
22
|
+
|
23
|
+
def content
|
24
|
+
response.body
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
class Tailer < Reindeer
|
29
|
+
has :uri, required: true
|
30
|
+
has :offset, is_a: Fixnum, default: -> { 0 }
|
31
|
+
has :wait_in_seconds, is_a: Fixnum, default: -> { 60 }
|
32
|
+
has :exponential_backoff, lazy_build: true
|
33
|
+
has :max_retries, is_a: Fixnum, default: -> { 5 }
|
34
|
+
has :retries_so_far, is_a: Fixnum, default: -> { 0 }
|
35
|
+
has :still_following, is: :rw, default: -> { true }
|
36
|
+
|
37
|
+
def build(opts)
|
38
|
+
@uri = opts[:uri].kind_of?(URI::HTTP) ? opts[:uri] : URI.parse(opts[:uri])
|
39
|
+
end
|
40
|
+
|
41
|
+
def still_following?
|
42
|
+
@still_following
|
43
|
+
end
|
44
|
+
|
45
|
+
# This and regular_wait need new names!
|
46
|
+
def error_wait
|
47
|
+
if exponential_backoff.length > 1
|
48
|
+
exponential_backoff.shift
|
49
|
+
else
|
50
|
+
exponential_backoff.first
|
51
|
+
end
|
52
|
+
end
|
53
|
+
def regular_wait
|
54
|
+
@exponential_backoff = get_backoff_list
|
55
|
+
@retries_so_far = 0
|
56
|
+
wait_in_seconds
|
57
|
+
end
|
58
|
+
|
59
|
+
def update_offset(offset_increment)
|
60
|
+
@offset += offset_increment.to_i
|
61
|
+
end
|
62
|
+
|
63
|
+
def head_request
|
64
|
+
http = Net::HTTP.new(uri.host, uri.port)
|
65
|
+
http.request( Net::HTTP::Head.new(uri.to_s) )
|
66
|
+
end
|
67
|
+
|
68
|
+
def get_request(size_now)
|
69
|
+
http = Net::HTTP.new(uri.host, uri.port)
|
70
|
+
req = Net::HTTP::Get.new(uri.to_s)
|
71
|
+
req.initialize_http_header('Range' => "bytes=#{offset}-#{size_now}")
|
72
|
+
http.request(req)
|
73
|
+
end
|
74
|
+
|
75
|
+
def tail
|
76
|
+
@retries_so_far += 1
|
77
|
+
|
78
|
+
begin
|
79
|
+
head_response = head_request
|
80
|
+
rescue Timeout::Error, SocketError, EOFError, Errno::ETIMEDOUT
|
81
|
+
return Result.new(state: :error, method: :head)
|
82
|
+
end
|
83
|
+
|
84
|
+
# TODO invoke head_response.value to check for non 200s.
|
85
|
+
|
86
|
+
size_now = head_response.content_length
|
87
|
+
if size_now == offset
|
88
|
+
return Result.new(state: :no_change, method: :head, response: head_response)
|
89
|
+
end
|
90
|
+
|
91
|
+
begin
|
92
|
+
get_response = get_request(size_now)
|
93
|
+
rescue Timeout::Error, SocketError, EOFError
|
94
|
+
return Result.new(state: :error, method: :get)
|
95
|
+
end
|
96
|
+
|
97
|
+
update_offset get_response.content_length
|
98
|
+
|
99
|
+
# yield get_response, offset_for(uri)
|
100
|
+
return Result.new(state: :success, method: :get, response: get_response)
|
101
|
+
end
|
102
|
+
|
103
|
+
private
|
104
|
+
|
105
|
+
def get_backoff_list
|
106
|
+
ExponentialBackoff.new(
|
107
|
+
wait_in_seconds, wait_in_seconds ** 2
|
108
|
+
).intervals_for(0 .. max_retries)
|
109
|
+
end
|
110
|
+
|
111
|
+
def build_exponential_backoff
|
112
|
+
get_backoff_list
|
113
|
+
end
|
114
|
+
end
|
115
|
+
|
116
|
+
def self.follow(opts, &block)
|
117
|
+
tailers = normalize_options(opts).collect do |o|
|
118
|
+
{t: Tailer.new(o), ac: o[:always_callback]}
|
119
|
+
end
|
120
|
+
|
121
|
+
while tailers.any? {|h| h[:t].still_following?}
|
122
|
+
for tailer in tailers.select{|h| h[:t].still_following?}
|
123
|
+
get_tail tailer[:t], tailer[:ac], block
|
124
|
+
end
|
125
|
+
end
|
126
|
+
end
|
127
|
+
|
128
|
+
def self.normalize_options(opts)
|
129
|
+
return [opts] if opts.is_a?(Hash)
|
130
|
+
|
131
|
+
raise ArgumentError, "Expected a Hash or Array not a #{opts}" unless opts.is_a? Array
|
132
|
+
|
133
|
+
opts
|
134
|
+
end
|
135
|
+
|
136
|
+
def self.get_tail(tailer, always_callback, block)
|
137
|
+
result = tailer.tail
|
138
|
+
|
139
|
+
while result.is_error?
|
140
|
+
block.call(result, tailer) if always_callback
|
141
|
+
return unless tailer.still_following?
|
142
|
+
|
143
|
+
if tailer.retries_so_far >= tailer.max_retries
|
144
|
+
# Would throw an exception but that breaks out of the #follow loop too.
|
145
|
+
tailer.still_following = false
|
146
|
+
return
|
147
|
+
end
|
148
|
+
sleep tailer.error_wait
|
149
|
+
result = tailer.tail
|
150
|
+
end
|
151
|
+
|
152
|
+
block.call(result, tailer) if result.is_success? or always_callback
|
153
|
+
|
154
|
+
sleep tailer.regular_wait
|
155
|
+
end
|
156
|
+
end
|
@@ -0,0 +1,95 @@
|
|
1
|
+
require 'net/http/follow_tail'
|
2
|
+
require 'spec_helper'
|
3
|
+
|
4
|
+
describe Net::HTTP::FollowTail do
|
5
|
+
# Not sure if this is appropriate but it does the job.
|
6
|
+
before(:each, simple_stub_request: true) do
|
7
|
+
stub_request(:head, 'example.com')
|
8
|
+
.to_return(headers: { 'Content-Length' => 321 })
|
9
|
+
stub_request(:get, 'example.com')
|
10
|
+
.with(headers: {'Range' => 'bytes=0-321'})
|
11
|
+
.to_return(headers: { 'Content-Length' => 321 })
|
12
|
+
Net::HTTP::FollowTail.should_receive(:sleep).with(60)
|
13
|
+
end
|
14
|
+
|
15
|
+
describe '#follow' do
|
16
|
+
it 'calls a block with a result and a tailer', simple_stub_request: true do
|
17
|
+
Net::HTTP::FollowTail.follow(uri: 'http://example.com/') do |result, tailer|
|
18
|
+
expect(result).to be_an_instance_of(Net::HTTP::FollowTail::Result)
|
19
|
+
expect(result.is_success?).to be_true
|
20
|
+
tailer.still_following = false
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
it 'should accept an Array of options', simple_stub_request: true do
|
25
|
+
# Would use multiple items but failing to stub sleep correctly :/
|
26
|
+
opts = [{uri: 'http://example.com/'}]
|
27
|
+
Net::HTTP::FollowTail.follow(opts) do |result, tailer|
|
28
|
+
expect(result).to be_an_instance_of(Net::HTTP::FollowTail::Result)
|
29
|
+
expect(result.is_success?).to be_true
|
30
|
+
tailer.still_following = false
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
it 'raises an error for weird input' do
|
35
|
+
expect {
|
36
|
+
Net::HTTP::FollowTail.follow(:boom)
|
37
|
+
}.to raise_error(ArgumentError)
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
describe '#get_tail' do
|
42
|
+
it 'should make a request and call a block', simple_stub_request: true do
|
43
|
+
a_tailer = Net::HTTP::FollowTail::Tailer.new(uri: 'http://example.com')
|
44
|
+
Net::HTTP::FollowTail.get_tail(a_tailer, false, Proc.new{ |result, tailer|
|
45
|
+
expect(tailer).to eql(a_tailer)
|
46
|
+
expect(result.is_success?).to be_true
|
47
|
+
})
|
48
|
+
end
|
49
|
+
|
50
|
+
# Incidentally tests always_callback which was introduced to allow
|
51
|
+
# this testing to work.
|
52
|
+
it 'should retry when an error is received' do
|
53
|
+
stub_request(:head, 'example.com').to_timeout
|
54
|
+
Net::HTTP::FollowTail.stub(:sleep) { }
|
55
|
+
|
56
|
+
# A bit gross but effective.
|
57
|
+
call_count = 0
|
58
|
+
|
59
|
+
a_tailer = Net::HTTP::FollowTail::Tailer.new(uri: 'http://example.com')
|
60
|
+
Net::HTTP::FollowTail.get_tail(a_tailer, true, Proc.new{ |result, tailer|
|
61
|
+
if call_count == 1
|
62
|
+
expect(result.is_success?).to be_true
|
63
|
+
call_count += 1
|
64
|
+
else
|
65
|
+
expect(result.is_success?).to be_false
|
66
|
+
call_count += 1
|
67
|
+
|
68
|
+
stub_request(:head, 'example.com')
|
69
|
+
.to_return(headers: { 'Content-Length' => 321 })
|
70
|
+
stub_request(:get, 'example.com')
|
71
|
+
.with(headers: {'Range' => 'bytes=0-321'})
|
72
|
+
.to_return(headers: { 'Content-Length' => 321 })
|
73
|
+
end
|
74
|
+
})
|
75
|
+
expect(call_count).to eq(2)
|
76
|
+
end
|
77
|
+
|
78
|
+
it 'exit loop when max_retries is hit' do
|
79
|
+
stub_request(:head, 'example.com').to_timeout
|
80
|
+
Net::HTTP::FollowTail.stub(:sleep) { }
|
81
|
+
|
82
|
+
a_tailer = Net::HTTP::FollowTail::Tailer.new(
|
83
|
+
uri: 'http://example.com',
|
84
|
+
max_retries: 2
|
85
|
+
)
|
86
|
+
|
87
|
+
Net::HTTP::FollowTail.get_tail(a_tailer, true, Proc.new{ |result, tailer|
|
88
|
+
expect(result.is_success?).to be_false
|
89
|
+
})
|
90
|
+
|
91
|
+
expect(a_tailer.still_following?).to be_false
|
92
|
+
expect(a_tailer.retries_so_far).to eq(2)
|
93
|
+
end
|
94
|
+
end
|
95
|
+
end
|
data/spec/spec_helper.rb
ADDED
data/spec/tailer_spec.rb
ADDED
@@ -0,0 +1,182 @@
|
|
1
|
+
require 'net/http/follow_tail'
|
2
|
+
require 'spec_helper'
|
3
|
+
|
4
|
+
describe Net::HTTP::FollowTail::Tailer do
|
5
|
+
let(:example_uri) { 'http://example.com/' }
|
6
|
+
let(:example_host) { 'example.com' }
|
7
|
+
let(:simple_tailer) { Net::HTTP::FollowTail::Tailer.new(uri: example_uri) }
|
8
|
+
let(:default_wait) { 60 }
|
9
|
+
|
10
|
+
describe '#new' do
|
11
|
+
it 'has sensible defaults' do
|
12
|
+
tailer = simple_tailer
|
13
|
+
expect(tailer.uri).to be_an_instance_of(URI::HTTP)
|
14
|
+
expect(tailer.offset).to eq(0)
|
15
|
+
expect(tailer.wait_in_seconds).to eq(default_wait)
|
16
|
+
expect(tailer.max_retries).to eq(5)
|
17
|
+
expect(tailer.retries_so_far).to eq(0)
|
18
|
+
end
|
19
|
+
|
20
|
+
it 'requires a uri to be specified' do
|
21
|
+
expect {
|
22
|
+
Net::HTTP::FollowTail::Tailer.new
|
23
|
+
}.to raise_error(Reindeer::Meta::Attribute::AttributeError)
|
24
|
+
end
|
25
|
+
|
26
|
+
it 'accepts a URI instance' do
|
27
|
+
uri = URI.parse(example_uri)
|
28
|
+
tailer = Net::HTTP::FollowTail::Tailer.new(uri: uri)
|
29
|
+
expect(tailer.uri).to eql(uri)
|
30
|
+
end
|
31
|
+
|
32
|
+
it 'accepts offset, wait & max_retries options' do
|
33
|
+
tailer = Net::HTTP::FollowTail::Tailer.new(
|
34
|
+
uri: example_uri,
|
35
|
+
offset: 1234,
|
36
|
+
wait_in_seconds: 20,
|
37
|
+
max_retries: 2,
|
38
|
+
)
|
39
|
+
expect(tailer.uri).to be_an_instance_of(URI::HTTP)
|
40
|
+
expect(tailer.offset).to eq(1234)
|
41
|
+
expect(tailer.wait_in_seconds).to eq(20)
|
42
|
+
expect(tailer.max_retries).to eq(2)
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
describe '.regular_wait' do
|
47
|
+
it 'always returns wait_in_seconds' do
|
48
|
+
tailer = Net::HTTP::FollowTail::Tailer.new(
|
49
|
+
uri: example_uri,
|
50
|
+
wait_in_seconds: 66,
|
51
|
+
)
|
52
|
+
|
53
|
+
expect(tailer.wait_in_seconds).to eq(66)
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
describe '.error_wait' do
|
58
|
+
it 'munges state appropriately' do
|
59
|
+
tailer = simple_tailer
|
60
|
+
|
61
|
+
expect(tailer.wait_in_seconds).to eq(default_wait)
|
62
|
+
expect(tailer.error_wait).to eq(default_wait)
|
63
|
+
expect(tailer.error_wait).to be > default_wait
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
describe '.head_request' do
|
68
|
+
it 'provides a response' do
|
69
|
+
stub_request :head, "example.com"
|
70
|
+
expect(simple_tailer.head_request).to be_an_instance_of(Net::HTTPOK)
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
describe '.get_request' do
|
75
|
+
it 'should make ranged requests' do
|
76
|
+
stub_request(:get, example_host).with(headers: {'Range' => 'bytes=0-200'})
|
77
|
+
expect(simple_tailer.get_request(200)).to be_an_instance_of(Net::HTTPOK)
|
78
|
+
end
|
79
|
+
|
80
|
+
it 'makes range requests against an offset' do
|
81
|
+
tailer = simple_tailer
|
82
|
+
tailer.update_offset 200
|
83
|
+
stub_request(:get, example_host).with(headers: {'Range' => 'bytes=200-400'})
|
84
|
+
expect(tailer.get_request(400)).to be_an_instance_of(Net::HTTPOK)
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
describe '.tail' do
|
89
|
+
it 'returns a result object' do
|
90
|
+
stub_request(:head, example_host)
|
91
|
+
.to_return(headers: { 'Content-Length' => 321 })
|
92
|
+
stub_request(:get, example_host)
|
93
|
+
.with(headers: {'Range' => 'bytes=0-321'})
|
94
|
+
.to_return(headers: { 'Content-Length' => 321 })
|
95
|
+
|
96
|
+
result = simple_tailer.tail
|
97
|
+
expect(result).to be_an_instance_of(Net::HTTP::FollowTail::Result)
|
98
|
+
expect(result.is_success?).to be_true
|
99
|
+
end
|
100
|
+
|
101
|
+
it 'updates offset state with multiple tail calls' do
|
102
|
+
stub_request(:head, example_host)
|
103
|
+
.to_return(headers: { 'Content-Length' => 5 })
|
104
|
+
stub_request(:get, example_host)
|
105
|
+
.with(headers: {'Range' => 'bytes=0-5'})
|
106
|
+
.to_return(headers: { 'Content-Length' => 5 })
|
107
|
+
|
108
|
+
tailer = simple_tailer
|
109
|
+
tailer.tail
|
110
|
+
|
111
|
+
stub_request(:head, example_host)
|
112
|
+
.to_return(headers: { 'Content-Length' => 10 })
|
113
|
+
stub_request(:get, example_host)
|
114
|
+
.with(headers: {'Range' => 'bytes=5-10'})
|
115
|
+
.to_return(headers: { 'Content-Length' => 5 })
|
116
|
+
|
117
|
+
result = tailer.tail
|
118
|
+
expect(result).to be_an_instance_of(Net::HTTP::FollowTail::Result)
|
119
|
+
expect(result.is_success?).to be_true
|
120
|
+
expect(result.method).to be(:get)
|
121
|
+
expect(tailer.offset).to eq(10)
|
122
|
+
|
123
|
+
stub_request(:head, example_host)
|
124
|
+
.to_return(headers: { 'Content-Length' => 15 })
|
125
|
+
stub_request(:get, example_host)
|
126
|
+
.with(headers: {'Range' => 'bytes=10-15'})
|
127
|
+
.to_return(headers: { 'Content-Length' => 5 })
|
128
|
+
|
129
|
+
result = tailer.tail
|
130
|
+
expect(tailer.offset).to eq(15)
|
131
|
+
end
|
132
|
+
|
133
|
+
it 'correctly updates against existing offset' do
|
134
|
+
stub_request(:head, example_host)
|
135
|
+
.to_return(headers: { 'Content-Length' => 66 })
|
136
|
+
stub_request(:get, example_host)
|
137
|
+
.with(headers: {'Range' => 'bytes=50-66'})
|
138
|
+
.to_return(headers: { 'Content-Length' => 16 })
|
139
|
+
|
140
|
+
tailer = Net::HTTP::FollowTail::Tailer.new(uri: example_uri, offset: 50)
|
141
|
+
tailer.tail
|
142
|
+
expect(tailer.offset).to eq(66)
|
143
|
+
end
|
144
|
+
|
145
|
+
it 'to handle HEAD errors' do
|
146
|
+
stub_request(:head, example_host).to_timeout
|
147
|
+
|
148
|
+
result = simple_tailer.tail
|
149
|
+
expect(result.is_error?).to be_true
|
150
|
+
expect(result.method).to be(:head)
|
151
|
+
expect(result.has_response?).to be_false
|
152
|
+
end
|
153
|
+
|
154
|
+
it 'to handle GET errors' do
|
155
|
+
stub_request(:head, example_host)
|
156
|
+
.to_return(headers: { 'Content-Length' => 10 })
|
157
|
+
stub_request(:get, example_host).to_timeout
|
158
|
+
|
159
|
+
result = simple_tailer.tail
|
160
|
+
expect(result.is_error?).to be_true
|
161
|
+
expect(result.method).to be(:get)
|
162
|
+
expect(result.has_response?).to be_false
|
163
|
+
end
|
164
|
+
|
165
|
+
it 'returns a no change Result when appropriate' do
|
166
|
+
stub_request(:head, example_host)
|
167
|
+
.to_return(headers: { 'Content-Length' => 25 })
|
168
|
+
|
169
|
+
tailer = Net::HTTP::FollowTail::Tailer.new(
|
170
|
+
uri: example_uri,
|
171
|
+
offset: 25
|
172
|
+
)
|
173
|
+
|
174
|
+
result = tailer.tail
|
175
|
+
expect(result.state).to eq(:no_change)
|
176
|
+
expect(result.method).to eq(:head)
|
177
|
+
expect(result.has_response?).to be_true
|
178
|
+
expect(result.is_success?).to be_false
|
179
|
+
expect(result.is_error?).to be_false
|
180
|
+
end
|
181
|
+
end
|
182
|
+
end
|
metadata
ADDED
@@ -0,0 +1,104 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: net-http-follow_tail
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.1
|
5
|
+
prerelease:
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Dan Brook
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2013-05-04 00:00:00.000000000 Z
|
13
|
+
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
15
|
+
name: exponential-backoff
|
16
|
+
requirement: !ruby/object:Gem::Requirement
|
17
|
+
none: false
|
18
|
+
requirements:
|
19
|
+
- - ~>
|
20
|
+
- !ruby/object:Gem::Version
|
21
|
+
version: 0.0.2
|
22
|
+
type: :runtime
|
23
|
+
prerelease: false
|
24
|
+
version_requirements: !ruby/object:Gem::Requirement
|
25
|
+
none: false
|
26
|
+
requirements:
|
27
|
+
- - ~>
|
28
|
+
- !ruby/object:Gem::Version
|
29
|
+
version: 0.0.2
|
30
|
+
- !ruby/object:Gem::Dependency
|
31
|
+
name: rake
|
32
|
+
requirement: !ruby/object:Gem::Requirement
|
33
|
+
none: false
|
34
|
+
requirements:
|
35
|
+
- - ~>
|
36
|
+
- !ruby/object:Gem::Version
|
37
|
+
version: 0.9.2
|
38
|
+
type: :development
|
39
|
+
prerelease: false
|
40
|
+
version_requirements: !ruby/object:Gem::Requirement
|
41
|
+
none: false
|
42
|
+
requirements:
|
43
|
+
- - ~>
|
44
|
+
- !ruby/object:Gem::Version
|
45
|
+
version: 0.9.2
|
46
|
+
- !ruby/object:Gem::Dependency
|
47
|
+
name: rspec
|
48
|
+
requirement: !ruby/object:Gem::Requirement
|
49
|
+
none: false
|
50
|
+
requirements:
|
51
|
+
- - ~>
|
52
|
+
- !ruby/object:Gem::Version
|
53
|
+
version: '2'
|
54
|
+
type: :development
|
55
|
+
prerelease: false
|
56
|
+
version_requirements: !ruby/object:Gem::Requirement
|
57
|
+
none: false
|
58
|
+
requirements:
|
59
|
+
- - ~>
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '2'
|
62
|
+
description: Watch multiple URIs for appended content e.g log files
|
63
|
+
email: dan@broquaint.com
|
64
|
+
executables: []
|
65
|
+
extensions: []
|
66
|
+
extra_rdoc_files: []
|
67
|
+
files:
|
68
|
+
- .rspec
|
69
|
+
- Gemfile
|
70
|
+
- Gemfile.lock
|
71
|
+
- README.md
|
72
|
+
- Rakefile
|
73
|
+
- lib/net/http/follow_tail.rb
|
74
|
+
- spec/follow_tail_spec.rb
|
75
|
+
- spec/spec_helper.rb
|
76
|
+
- spec/tailer_spec.rb
|
77
|
+
homepage: http://github.com/broquaint/net-http-follow_tail
|
78
|
+
licenses: []
|
79
|
+
post_install_message:
|
80
|
+
rdoc_options: []
|
81
|
+
require_paths:
|
82
|
+
- lib
|
83
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
84
|
+
none: false
|
85
|
+
requirements:
|
86
|
+
- - ! '>='
|
87
|
+
- !ruby/object:Gem::Version
|
88
|
+
version: '1.9'
|
89
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
90
|
+
none: false
|
91
|
+
requirements:
|
92
|
+
- - ! '>='
|
93
|
+
- !ruby/object:Gem::Version
|
94
|
+
version: '0'
|
95
|
+
requirements: []
|
96
|
+
rubyforge_project:
|
97
|
+
rubygems_version: 1.8.24
|
98
|
+
signing_key:
|
99
|
+
specification_version: 3
|
100
|
+
summary: Like tail -f for the web
|
101
|
+
test_files:
|
102
|
+
- spec/follow_tail_spec.rb
|
103
|
+
- spec/spec_helper.rb
|
104
|
+
- spec/tailer_spec.rb
|