em-twitter 0.1.0

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.
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