em-twitter 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (40) hide show
  1. data/.gemtest +0 -0
  2. data/.gitignore +40 -0
  3. data/.rspec +3 -0
  4. data/.simplecov +1 -0
  5. data/.travis.yml +8 -0
  6. data/.yardopts +3 -0
  7. data/Gemfile +7 -0
  8. data/Guardfile +6 -0
  9. data/LICENSE.md +20 -0
  10. data/README.md +117 -0
  11. data/Rakefile +15 -0
  12. data/em-twitter.gemspec +31 -0
  13. data/examples/stream.rb +61 -0
  14. data/lib/em-twitter.rb +35 -0
  15. data/lib/em-twitter/client.rb +111 -0
  16. data/lib/em-twitter/connection.rb +273 -0
  17. data/lib/em-twitter/decoders/base_decoder.rb +11 -0
  18. data/lib/em-twitter/decoders/gzip_decoder.rb +14 -0
  19. data/lib/em-twitter/proxy.rb +25 -0
  20. data/lib/em-twitter/reconnectors/application_failure.rb +50 -0
  21. data/lib/em-twitter/reconnectors/network_failure.rb +51 -0
  22. data/lib/em-twitter/request.rb +126 -0
  23. data/lib/em-twitter/response.rb +48 -0
  24. data/lib/em-twitter/version.rb +5 -0
  25. data/lib/em_twitter.rb +1 -0
  26. data/smoke.rb +66 -0
  27. data/spec/em-twitter/client_spec.rb +55 -0
  28. data/spec/em-twitter/connection_error_handling_spec.rb +12 -0
  29. data/spec/em-twitter/connection_reconnect_spec.rb +139 -0
  30. data/spec/em-twitter/connection_spec.rb +148 -0
  31. data/spec/em-twitter/decoders/base_decoder_spec.rb +15 -0
  32. data/spec/em-twitter/decoders/gzip_decoder_spec.rb +20 -0
  33. data/spec/em-twitter/proxy_spec.rb +23 -0
  34. data/spec/em-twitter/reconnectors/application_failure_spec.rb +74 -0
  35. data/spec/em-twitter/reconnectors/network_failure_spec.rb +80 -0
  36. data/spec/em-twitter/request_spec.rb +77 -0
  37. data/spec/em-twitter/response_spec.rb +89 -0
  38. data/spec/em_twitter_spec.rb +19 -0
  39. data/spec/spec_helper.rb +68 -0
  40. metadata +207 -0
@@ -0,0 +1,48 @@
1
+ module EventMachine
2
+ module Twitter
3
+ class Response
4
+
5
+ attr_reader :body
6
+
7
+ def initialize(body = '')
8
+ @body = body
9
+ end
10
+
11
+ def concat(data)
12
+ return unless data && data.size > 0
13
+
14
+ data.strip!
15
+
16
+ return if data.empty?
17
+
18
+ if json_start?(data) || json_end?(data)
19
+ @body << data
20
+ end
21
+ end
22
+ alias :<< :concat
23
+
24
+ def complete?
25
+ json_start?(@body) && json_end?(@body)
26
+ end
27
+
28
+ def empty?
29
+ @body == ''
30
+ end
31
+
32
+ def reset
33
+ @body = ''
34
+ end
35
+
36
+ private
37
+
38
+ def json_start?(data)
39
+ data[0,1] == '{'
40
+ end
41
+
42
+ def json_end?(data)
43
+ data[data.length-1,1] == '}'
44
+ end
45
+
46
+ end
47
+ end
48
+ end
@@ -0,0 +1,5 @@
1
+ module EventMachine
2
+ module Twitter
3
+ VERSION = "0.1.0" unless defined?(EventMachine::Twitter::VERSION)
4
+ end
5
+ end
@@ -0,0 +1 @@
1
+ require 'em-twitter'
@@ -0,0 +1,66 @@
1
+ require 'bundler/setup'
2
+ require 'em-twitter'
3
+
4
+ EM::run do
5
+
6
+ options = {
7
+ :path => '/1/statuses/filter.json',
8
+ :params => {
9
+ :track => 'you,Obama,eli,bachelor,Romney'
10
+ },
11
+ :ssl => {
12
+ :verify_peer => true,
13
+ :cert_chain_file => '/etc/ssl/certs/cacert.pem'
14
+ },
15
+ :oauth => {
16
+ :consumer_key => 'czveJ6LqlTuVHsbDBuRQ',
17
+ :consumer_secret => 'wGY6NCptPerGog4lPvAKu450jtUaT3bz7wSQdzDiYaY',
18
+ :token => '4618-01z5ihmeVYuc343JAYzaejVTuV0VU5CSF7gUWzsB8',
19
+ :token_secret => 'HVQrPKjfQAyNPkQCCej10dIaLUaFzIIEAkwsI1hQ0'
20
+ }
21
+ # , :encoding => 'gzip'
22
+ }
23
+
24
+ client = EM::Twitter::Client.connect(options)
25
+
26
+ client.each do |item|
27
+ puts item
28
+ end
29
+
30
+ client.on_error do |message|
31
+ puts "oops: error: #{message}"
32
+ end
33
+
34
+ client.on_unauthorized do
35
+ puts "oops: unauthorized"
36
+ end
37
+
38
+ client.on_forbidden do
39
+ puts "oops: unauthorized"
40
+ end
41
+
42
+ client.on_not_found do
43
+ puts "oops: not_found"
44
+ end
45
+
46
+ client.on_not_acceptable do
47
+ puts "oops: not_acceptable"
48
+ end
49
+
50
+ client.on_too_long do
51
+ puts "oops: too_long"
52
+ end
53
+
54
+ client.on_range_unacceptable do
55
+ puts "oops: range_unacceptable"
56
+ end
57
+
58
+ client.on_enhance_your_calm do
59
+ puts "oops: enhance_your_calm"
60
+ end
61
+
62
+ EM.add_timer(25) do
63
+ EM.stop
64
+ end
65
+
66
+ end
@@ -0,0 +1,55 @@
1
+ require 'spec_helper'
2
+
3
+ describe EM::Twitter::Client do
4
+
5
+ describe '.connect' do
6
+ before do
7
+ conn = stub('EventMachine::Connection')
8
+ conn.stub(:start_tls).and_return(nil)
9
+ EM.stub(:connect).and_return(conn)
10
+ end
11
+
12
+ context 'without a proxy' do
13
+ it 'connects to the configured host/port' do
14
+ EventMachine.should_receive(:connect)
15
+ .with(test_options[:host],
16
+ test_options[:port],
17
+ EventMachine::Twitter::Connection,
18
+ kind_of(EM::Twitter::Client),
19
+ test_options[:host],
20
+ test_options[:port])
21
+ EM::Twitter::Client.connect(default_options)
22
+ end
23
+ end
24
+
25
+ context 'when using a proxy' do
26
+ it 'connects to the proxy server' do
27
+ EventMachine.should_receive(:connect)
28
+ .with("my-proxy",
29
+ 8080,
30
+ EventMachine::Twitter::Connection,
31
+ kind_of(EM::Twitter::Client),
32
+ 'my-proxy',
33
+ 8080)
34
+ EM::Twitter::Client.connect(default_options.merge(proxy_options))
35
+ end
36
+ end
37
+
38
+ it "should not trigger SSL until connection is established" do
39
+ connection = stub('EventMachine::Connection')
40
+ EM.should_receive(:connect).and_return(connection)
41
+ EM.should_not_receive(:start_tls)
42
+ client = EM::Twitter::Client.connect(:ssl => { :key => "/path/to/key.pem", :cert => "/path/to/cert.pem" })
43
+ end
44
+ end
45
+
46
+ describe '#respond_to?' do
47
+ it 'delegate to the connection' do
48
+ EM.run_block do
49
+ client = EM::Twitter::Client.connect(default_options)
50
+ client.respond_to?(:immediate_reconnect).should be_true
51
+ end
52
+ end
53
+ end
54
+
55
+ end
@@ -0,0 +1,12 @@
1
+ require 'spec_helper'
2
+
3
+ describe 'EM::Twitter::Connection error handling' do
4
+ error_callback_invoked('on_unauthorized', 401, 'Unauthorized')
5
+ error_callback_invoked('on_forbidden', 403, 'Forbidden')
6
+ error_callback_invoked('on_not_found', 404, 'Not Found')
7
+ error_callback_invoked('on_not_acceptable', 406, 'Not Acceptable')
8
+ error_callback_invoked('on_too_long', 413, 'Too Long')
9
+ error_callback_invoked('on_range_unacceptable', 416, 'Range Unacceptable')
10
+ error_callback_invoked('on_enhance_your_calm', 420, 'Enhance Your Calm')
11
+ error_callback_invoked('on_error', 500, 'Internal Server Error', 'An error occurred.')
12
+ end
@@ -0,0 +1,139 @@
1
+ require 'spec_helper'
2
+
3
+ include EM::Twitter
4
+
5
+ describe 'EM::Twitter::Connection reconnections' do
6
+
7
+ describe '#reconnect' do
8
+ before do
9
+ Mockingbird.setup(test_options) do
10
+ on_connection(1) do
11
+ disconnect!
12
+ end
13
+ end
14
+ end
15
+
16
+ after { Mockingbird.teardown }
17
+
18
+ it 'calls the on_reconnect callback on reconnects' do
19
+ called = false
20
+
21
+ EM.run do
22
+ client = Client.connect(default_options)
23
+ client.on_reconnect { called = true; EM.stop }
24
+ end
25
+
26
+ called.should be_true
27
+ end
28
+
29
+ it 'does not reconnect when auto_reconnect is false' do
30
+ EM.run do
31
+ client = Client.connect(default_options.merge(:auto_reconnect => false))
32
+ client.should_not_receive(:reconnect)
33
+ client.on_close { EM.stop }
34
+ end
35
+ end
36
+ end
37
+
38
+ describe '#immediate_reconnect' do
39
+ before do
40
+ Mockingbird.setup(test_options) do
41
+ 100.times do
42
+ send '{"foo":"bar"}'
43
+ end
44
+ end
45
+ end
46
+
47
+ after { Mockingbird.teardown }
48
+
49
+ it 'reconnects immediately' do
50
+ called = false
51
+
52
+ EM.run_block do
53
+ client = Client.connect(default_options)
54
+ client.on_reconnect { called = true; EM.stop }
55
+ client.immediate_reconnect
56
+ end
57
+
58
+ called.should be_true
59
+ end
60
+
61
+ it 'reconnects the current connection' do
62
+ EM.run_block do
63
+ client = Client.connect(default_options)
64
+ client.connection.should_receive(:reconnect).once
65
+ client.reconnect
66
+ end
67
+ end
68
+ end
69
+
70
+ describe '#on_max_reconnects' do
71
+ context 'application failure' do
72
+ before do
73
+ Mockingbird.setup(test_options) do
74
+ status 200, "Success"
75
+
76
+ on_connection('*') do
77
+ disconnect!
78
+ end
79
+ end
80
+ end
81
+
82
+ after { Mockingbird.teardown }
83
+
84
+ it "should notify after reconnect limit is reached" do
85
+ timeout, retries = nil, nil
86
+
87
+ EM.run do
88
+ client = Client.new(default_options)
89
+ client.on_max_reconnects do |t, r|
90
+ timeout, retries = t, r
91
+ EM.stop
92
+ end
93
+ client.connect
94
+
95
+ # do this so that EM doesn't create timers that grind
96
+ # this test to a halt
97
+ client.connection.reconnector = Reconnectors::ApplicationFailure.new(:reconnect_count => 320)
98
+ end
99
+
100
+ timeout.should eq(20)
101
+ retries.should eq(321)
102
+ end
103
+ end
104
+
105
+ context 'network failure' do
106
+ before do
107
+ Mockingbird.setup(test_options) do
108
+ status 200, "Success"
109
+
110
+ on_connection('*') do
111
+ disconnect!
112
+ end
113
+ end
114
+ end
115
+
116
+ after { Mockingbird.teardown }
117
+
118
+ it "should notify after reconnect limit is reached" do
119
+ timeout, retries = nil, nil
120
+
121
+ EM.run do
122
+ client = Client.new(default_options)
123
+ client.on_max_reconnects do |t, r|
124
+ timeout, retries = t, r
125
+ EM.stop
126
+ end
127
+ client.connect
128
+
129
+ # do this so that EM doesn't create timers that grind
130
+ # this test to a halt
131
+ client.connection.reconnector = Reconnectors::NetworkFailure.new(:reconnect_count => 320)
132
+ end
133
+
134
+ timeout.should eq(0.5)
135
+ retries.should eq(321)
136
+ end
137
+ end
138
+ end
139
+ end
@@ -0,0 +1,148 @@
1
+ require 'spec_helper'
2
+
3
+ describe EM::Twitter::Connection do
4
+
5
+ describe '#post_init' do
6
+ it 'calls the on_inited callback' do
7
+ called = false
8
+
9
+ EM.run_block do
10
+ client = EM::Twitter::Client.connect(default_options.merge(:on_inited => lambda { called = true}))
11
+ end
12
+
13
+ called.should be_true
14
+ end
15
+
16
+ it 'sets the inactivity timeout' do
17
+ EM.should_receive(:set_comm_inactivity_timeout)
18
+ EM.run_block do
19
+ client = EM::Twitter::Client.connect(default_options.merge(:timeout => 2))
20
+ end
21
+ end
22
+ end
23
+
24
+ describe 'streaming' do
25
+ describe '#receive_data' do
26
+ before do
27
+ Mockingbird.setup(test_options) do
28
+ 100.times do
29
+ send '{"foo":"ba'
30
+ send 'r"}'
31
+ end
32
+ end
33
+ end
34
+
35
+ after { Mockingbird.teardown }
36
+
37
+ it 'converts response data into complete buffers' do
38
+ count = 0
39
+
40
+ EM.run do
41
+ client = EM::Twitter::Client.connect(default_options)
42
+ client.each do |message|
43
+ count = count + 1
44
+ EM.stop if count == 100
45
+ end
46
+ end
47
+
48
+ count.should == 100
49
+ end
50
+ end
51
+
52
+ describe '#each' do
53
+ before do
54
+ Mockingbird.setup(test_options) do
55
+ 100.times do
56
+ send '{"foo":"bar"}'
57
+ end
58
+ end
59
+ end
60
+
61
+ after { Mockingbird.teardown }
62
+
63
+ it 'emits each complete response chunk' do
64
+ count = 0
65
+
66
+ EM.run do
67
+ client = EM::Twitter::Client.connect(default_options)
68
+ client.each do |message|
69
+ count = count + 1
70
+ EM.stop if count == 100
71
+ end
72
+ end
73
+
74
+ count.should == 100
75
+ end
76
+ end
77
+ end
78
+
79
+ describe "#on_close" do
80
+ it "sets a callback that is invoked when the connection closes" do
81
+ called = false
82
+
83
+ EM.run_block do
84
+ client = EM::Twitter::Client.connect(default_options)
85
+ client.on_close { called = true }
86
+ end
87
+
88
+ called.should be_true
89
+ end
90
+ end
91
+
92
+ describe '#stop' do
93
+ it 'closes the connection' do
94
+ EM.run_block do
95
+ client = EM::Twitter::Client.connect(default_options)
96
+ client.connection.should_receive(:close_connection).once
97
+ client.stop
98
+ end
99
+ end
100
+
101
+ it 'gracefully closes' do
102
+ EM.run_block do
103
+ client = EM::Twitter::Client.connect(default_options)
104
+ client.stop
105
+ client.should be_gracefully_closed
106
+ end
107
+ end
108
+ end
109
+
110
+ describe '#update' do
111
+ before do
112
+ Mockingbird.setup(test_options) do
113
+ 100.times do
114
+ send '{"foo":"bar"}'
115
+ end
116
+ end
117
+ end
118
+
119
+ after { Mockingbird.teardown }
120
+
121
+ it 'updates the options hash' do
122
+ EM.run_block do
123
+ client = EM::Twitter::Client.connect(default_options)
124
+ client.connection.update(:params => { :track => 'rangers' })
125
+ client.connection.options[:params].should eq({:track => 'rangers'})
126
+ end
127
+ end
128
+
129
+ it 'reconnects after updating' do
130
+ EM.run_block do
131
+ client = EM::Twitter::Client.connect(default_options)
132
+ client.connection.should_receive(:immediate_reconnect).once
133
+ client.connection.update(:params => { :track => 'rangers' })
134
+ end
135
+ end
136
+
137
+ it 'uses the new options when reconnecting' do
138
+ EM.run_block do
139
+ client = EM::Twitter::Client.connect(default_options)
140
+ client.connection.should_receive(:send_data) do |request|
141
+ request.to_s.should include('track=rangers')
142
+ end
143
+ client.connection.update(:params => { :track => 'rangers' })
144
+ end
145
+ end
146
+ end
147
+
148
+ end