em-twitter 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- data/.gemtest +0 -0
- data/.gitignore +40 -0
- data/.rspec +3 -0
- data/.simplecov +1 -0
- data/.travis.yml +8 -0
- data/.yardopts +3 -0
- data/Gemfile +7 -0
- data/Guardfile +6 -0
- data/LICENSE.md +20 -0
- data/README.md +117 -0
- data/Rakefile +15 -0
- data/em-twitter.gemspec +31 -0
- data/examples/stream.rb +61 -0
- data/lib/em-twitter.rb +35 -0
- data/lib/em-twitter/client.rb +111 -0
- data/lib/em-twitter/connection.rb +273 -0
- data/lib/em-twitter/decoders/base_decoder.rb +11 -0
- data/lib/em-twitter/decoders/gzip_decoder.rb +14 -0
- data/lib/em-twitter/proxy.rb +25 -0
- data/lib/em-twitter/reconnectors/application_failure.rb +50 -0
- data/lib/em-twitter/reconnectors/network_failure.rb +51 -0
- data/lib/em-twitter/request.rb +126 -0
- data/lib/em-twitter/response.rb +48 -0
- data/lib/em-twitter/version.rb +5 -0
- data/lib/em_twitter.rb +1 -0
- data/smoke.rb +66 -0
- data/spec/em-twitter/client_spec.rb +55 -0
- data/spec/em-twitter/connection_error_handling_spec.rb +12 -0
- data/spec/em-twitter/connection_reconnect_spec.rb +139 -0
- data/spec/em-twitter/connection_spec.rb +148 -0
- data/spec/em-twitter/decoders/base_decoder_spec.rb +15 -0
- data/spec/em-twitter/decoders/gzip_decoder_spec.rb +20 -0
- data/spec/em-twitter/proxy_spec.rb +23 -0
- data/spec/em-twitter/reconnectors/application_failure_spec.rb +74 -0
- data/spec/em-twitter/reconnectors/network_failure_spec.rb +80 -0
- data/spec/em-twitter/request_spec.rb +77 -0
- data/spec/em-twitter/response_spec.rb +89 -0
- data/spec/em_twitter_spec.rb +19 -0
- data/spec/spec_helper.rb +68 -0
- metadata +207 -0
data/.gemtest
ADDED
File without changes
|
data/.gitignore
ADDED
@@ -0,0 +1,40 @@
|
|
1
|
+
*.gem
|
2
|
+
*.rbc
|
3
|
+
*.sw[a-p]
|
4
|
+
*.tmproj
|
5
|
+
*.tmproject
|
6
|
+
*.un~
|
7
|
+
*~
|
8
|
+
.DS_Store
|
9
|
+
.Spotlight-V100
|
10
|
+
.Trashes
|
11
|
+
._*
|
12
|
+
.bundle
|
13
|
+
.config
|
14
|
+
.directory
|
15
|
+
.elc
|
16
|
+
.emacs.desktop
|
17
|
+
.emacs.desktop.lock
|
18
|
+
.redcar
|
19
|
+
.yardoc
|
20
|
+
Desktop.ini
|
21
|
+
Gemfile.lock
|
22
|
+
Icon?
|
23
|
+
InstalledFiles
|
24
|
+
Session.vim
|
25
|
+
Thumbs.db
|
26
|
+
\#*\#
|
27
|
+
_yardoc
|
28
|
+
auto-save-list
|
29
|
+
coverage
|
30
|
+
doc
|
31
|
+
lib/bundler/man
|
32
|
+
pkg
|
33
|
+
pkg/*
|
34
|
+
rdoc
|
35
|
+
spec/reports
|
36
|
+
test/tmp
|
37
|
+
test/version_tmp
|
38
|
+
tmp
|
39
|
+
tmtags
|
40
|
+
tramp
|
data/.rspec
ADDED
data/.simplecov
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
SimpleCov.start
|
data/.travis.yml
ADDED
data/.yardopts
ADDED
data/Gemfile
ADDED
data/Guardfile
ADDED
data/LICENSE.md
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright (c) 2011 Steve Agalloco
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
4
|
+
a copy of this software and associated documentation files (the
|
5
|
+
"Software"), to deal in the Software without restriction, including
|
6
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
7
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
8
|
+
permit persons to whom the Software is furnished to do so, subject to
|
9
|
+
the following conditions:
|
10
|
+
|
11
|
+
The above copyright notice and this permission notice shall be
|
12
|
+
included in all copies or substantial portions of the Software.
|
13
|
+
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
15
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
16
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
17
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
18
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
19
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
20
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,117 @@
|
|
1
|
+
# EM-Twitter
|
2
|
+
|
3
|
+
EM-Twitter is an EventMachine-based ruby client for the Twitter Streaming API.
|
4
|
+
|
5
|
+
## Usage
|
6
|
+
|
7
|
+
```ruby
|
8
|
+
require 'em-twitter'
|
9
|
+
|
10
|
+
options = {
|
11
|
+
:path => '/1/statuses/filter.json',
|
12
|
+
:params => { :track => 'yankees' },
|
13
|
+
:oauth => {
|
14
|
+
:consumer_key => ENV['CONSUMER_KEY'],
|
15
|
+
:consumer_secret => ENV['CONSUMER_SECRET'],
|
16
|
+
:token => ENV['OAUTH_TOKEN'],
|
17
|
+
:token_secret => ENV['OAUTH_TOKEN_SECRET']
|
18
|
+
}
|
19
|
+
}
|
20
|
+
|
21
|
+
EM.run do
|
22
|
+
client = EM::Twitter::Client.connect(options)
|
23
|
+
|
24
|
+
client.each do |result|
|
25
|
+
puts result
|
26
|
+
end
|
27
|
+
end
|
28
|
+
```
|
29
|
+
|
30
|
+
## SSL
|
31
|
+
|
32
|
+
SSL is used by default (EventMachine defaults to verify_peer => false), and can be configured:
|
33
|
+
|
34
|
+
```ruby
|
35
|
+
options = {
|
36
|
+
:ssl => {
|
37
|
+
:private_key_file => "path/to/key.pem",
|
38
|
+
:cert_chain_file => "path/to/cert.pem",
|
39
|
+
:verify_peer => true
|
40
|
+
}
|
41
|
+
}
|
42
|
+
|
43
|
+
client = EM::Twitter.Client.connect(options)
|
44
|
+
```
|
45
|
+
|
46
|
+
## Proxy Support
|
47
|
+
|
48
|
+
EM-Twitter includes proxy support via a configuration option:
|
49
|
+
|
50
|
+
```ruby
|
51
|
+
options = {
|
52
|
+
:proxy => {
|
53
|
+
:username => 'myusername',
|
54
|
+
:passowrd => 'mypassword',
|
55
|
+
:uri => 'http://my-proxy:8080'
|
56
|
+
}
|
57
|
+
}
|
58
|
+
|
59
|
+
client = EM::Twitter.Client.connect(options)
|
60
|
+
```
|
61
|
+
|
62
|
+
## Error Handling
|
63
|
+
|
64
|
+
EM-Twitter supports the following callbacks for handling errors:
|
65
|
+
|
66
|
+
* on_unauthorized
|
67
|
+
* on_forbidden
|
68
|
+
* on_not_found
|
69
|
+
* on_not_acceptable
|
70
|
+
* on_too_long
|
71
|
+
* on_range_unacceptable
|
72
|
+
* on_enhance_your_calm (aliased as on_rate_limited)
|
73
|
+
|
74
|
+
Errors callbacks are invoked on a Client like so:
|
75
|
+
|
76
|
+
```ruby
|
77
|
+
client = EM::Twitter.Client.connect(options)
|
78
|
+
client.on_forbidden do
|
79
|
+
puts 'oops'
|
80
|
+
end
|
81
|
+
```
|
82
|
+
|
83
|
+
## Reconnections
|
84
|
+
|
85
|
+
EM-Twitter has two callbacks for reconnection handling:
|
86
|
+
|
87
|
+
```ruby
|
88
|
+
client = EM::Twitter.Client.connect(options)
|
89
|
+
client.on_reconnect do |timeout, count|
|
90
|
+
# called each time the client reconnects
|
91
|
+
end
|
92
|
+
|
93
|
+
client.on_max_reconnects do |timeout, count|
|
94
|
+
# called when the client has exceeded either:
|
95
|
+
# 1. the maximum number of reconnect attempts
|
96
|
+
# 2. the maximum timeout limit for reconnections
|
97
|
+
end
|
98
|
+
```
|
99
|
+
|
100
|
+
## Todo
|
101
|
+
|
102
|
+
* Gzip encoding support (see [issue #1](https://github.com/spagalloco/em-twitter/issues/1) for more information)
|
103
|
+
* JSON Parser (see [issue #2](https://github.com/spagalloco/em-twitter/issues/2) for more information)
|
104
|
+
|
105
|
+
## Inspiration
|
106
|
+
|
107
|
+
EM-Twitter is heavily inspired by Vladimir Kolesnikov's [twitter-stream](https://github.com/voloko/twitter-stream). I learned an incredible amount from studying his code and much of the reconnection handling in EM-Twitter is derived/borrowed from his code as are . Eloy Durán's [ssalleyware](https://github.com/alloy/ssalleyware) was very helpful in adding SSL Certificate verification as was David Graham's [vines](https://github.com/negativecode/vines).
|
108
|
+
|
109
|
+
Testing with EM can be a challenge, but was made incredibly easy through the use of Hayes Davis' awesome [mockingbird](https://github.com/hayesdavis/mockingbird) gem.
|
110
|
+
|
111
|
+
## Contributing
|
112
|
+
|
113
|
+
Pull requests welcome: fork, make a topic branch, commit (squash when possible) *with tests* and I'll happily consider.
|
114
|
+
|
115
|
+
## Copyright
|
116
|
+
|
117
|
+
Copyright (c) 2012 Steve Agalloco. See [LICENSE](https://github.com/spagalloco/em-twitter/blob/master/LICENSE.md) for detail
|
data/Rakefile
ADDED
@@ -0,0 +1,15 @@
|
|
1
|
+
require 'bundler/gem_tasks'
|
2
|
+
|
3
|
+
require 'rspec/core/rake_task'
|
4
|
+
RSpec::Core::RakeTask.new(:spec)
|
5
|
+
|
6
|
+
task :default => :spec
|
7
|
+
task :test => :spec
|
8
|
+
|
9
|
+
require 'yard'
|
10
|
+
namespace :doc do
|
11
|
+
YARD::Rake::YardocTask.new do |task|
|
12
|
+
task.files = ['LICENSE.md', 'lib/**/*.rb']
|
13
|
+
task.options = ['--markup', 'markdown']
|
14
|
+
end
|
15
|
+
end
|
data/em-twitter.gemspec
ADDED
@@ -0,0 +1,31 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
require File.expand_path('../lib/em-twitter/version', __FILE__)
|
3
|
+
|
4
|
+
Gem::Specification.new do |gem|
|
5
|
+
gem.name = 'em-twitter'
|
6
|
+
gem.version = EventMachine::Twitter::VERSION
|
7
|
+
gem.homepage = 'https://github.com/spagalloco/em-twitter'
|
8
|
+
|
9
|
+
gem.author = "Steve Agalloco"
|
10
|
+
gem.email = 'steve.agalloco@gmail.com'
|
11
|
+
gem.description = %q{Twitter Streaming API client for EventMachine}
|
12
|
+
gem.summary = %q{Twitter Streaming API client for EventMachine}
|
13
|
+
|
14
|
+
gem.add_dependency "eventmachine", ">= 1.0.0.beta.4"
|
15
|
+
gem.add_dependency "http_parser.rb", "~> 0.5"
|
16
|
+
gem.add_dependency "simple_oauth", "~> 0.1"
|
17
|
+
|
18
|
+
gem.add_development_dependency 'rake'
|
19
|
+
gem.add_development_dependency 'rdiscount'
|
20
|
+
gem.add_development_dependency 'rspec'
|
21
|
+
gem.add_development_dependency 'simplecov'
|
22
|
+
gem.add_development_dependency 'yard'
|
23
|
+
gem.add_development_dependency 'mockingbird', "~> 0.1.1"
|
24
|
+
gem.add_development_dependency 'guard-rspec'
|
25
|
+
|
26
|
+
gem.executables = `git ls-files -- bin/*`.split("\n").map{|f| File.basename(f)}
|
27
|
+
gem.files = `git ls-files`.split("\n")
|
28
|
+
gem.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
|
29
|
+
|
30
|
+
gem.require_paths = ['lib']
|
31
|
+
end
|
data/examples/stream.rb
ADDED
@@ -0,0 +1,61 @@
|
|
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 => 'yankees'
|
10
|
+
},
|
11
|
+
:oauth => {
|
12
|
+
:consumer_key => ENV['CONSUMER_KEY'],
|
13
|
+
:consumer_secret => ENV['CONSUMER_SECRET'],
|
14
|
+
:token => ENV['OAUTH_TOKEN'],
|
15
|
+
:token_secret => ENV['OAUTH_TOKEN_SECRET']
|
16
|
+
}
|
17
|
+
}
|
18
|
+
|
19
|
+
client = EM::Twitter::Client.connect(options)
|
20
|
+
|
21
|
+
client.each do |result|
|
22
|
+
puts result
|
23
|
+
end
|
24
|
+
|
25
|
+
client.on_error do |message|
|
26
|
+
puts "oops: error: #{message}"
|
27
|
+
end
|
28
|
+
|
29
|
+
client.on_unauthorized do
|
30
|
+
puts "oops: unauthorized"
|
31
|
+
end
|
32
|
+
|
33
|
+
client.on_forbidden do
|
34
|
+
puts "oops: unauthorized"
|
35
|
+
end
|
36
|
+
|
37
|
+
client.on_not_found do
|
38
|
+
puts "oops: not_found"
|
39
|
+
end
|
40
|
+
|
41
|
+
client.on_not_acceptable do
|
42
|
+
puts "oops: not_acceptable"
|
43
|
+
end
|
44
|
+
|
45
|
+
client.on_too_long do
|
46
|
+
puts "oops: too_long"
|
47
|
+
end
|
48
|
+
|
49
|
+
client.on_range_unacceptable do
|
50
|
+
puts "oops: range_unacceptable"
|
51
|
+
end
|
52
|
+
|
53
|
+
client.on_enhance_your_calm do
|
54
|
+
puts "oops: enhance_your_calm"
|
55
|
+
end
|
56
|
+
|
57
|
+
EM.add_timer(10) do
|
58
|
+
EM.stop
|
59
|
+
end
|
60
|
+
|
61
|
+
end
|
data/lib/em-twitter.rb
ADDED
@@ -0,0 +1,35 @@
|
|
1
|
+
require 'em-twitter/client'
|
2
|
+
require 'em-twitter/version'
|
3
|
+
require 'logger'
|
4
|
+
|
5
|
+
module EventMachine
|
6
|
+
module Twitter
|
7
|
+
DEFAULT_CONNECTION_OPTIONS = {
|
8
|
+
:host => 'stream.twitter.com',
|
9
|
+
:port => 443,
|
10
|
+
:method => 'POST',
|
11
|
+
:content_type => "application/x-www-form-urlencoded",
|
12
|
+
:path => '/',
|
13
|
+
:params => {},
|
14
|
+
:headers => {},
|
15
|
+
:user_agent => "EM::Twitter Ruby Gem #{EM::Twitter::VERSION}",
|
16
|
+
:proxy => nil,
|
17
|
+
:ssl => {},
|
18
|
+
:timeout => 0,
|
19
|
+
:auth => {},
|
20
|
+
:reconnect_options => {},
|
21
|
+
:encoding => nil,
|
22
|
+
:auto_reconnect => true
|
23
|
+
}
|
24
|
+
|
25
|
+
class ReconnectLimitError < StandardError; end
|
26
|
+
|
27
|
+
def self.logger
|
28
|
+
@logger ||= Logger.new(STDOUT)
|
29
|
+
end
|
30
|
+
|
31
|
+
def self.logger=(new_logger)
|
32
|
+
@logger = new_logger
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
@@ -0,0 +1,111 @@
|
|
1
|
+
require 'em-twitter/connection'
|
2
|
+
|
3
|
+
module EventMachine
|
4
|
+
module Twitter
|
5
|
+
class Client
|
6
|
+
|
7
|
+
CALLBACKS = [
|
8
|
+
:each_item_callback,
|
9
|
+
:error_callback,
|
10
|
+
:unauthorized_callback,
|
11
|
+
:forbidden_callback,
|
12
|
+
:not_found_callback,
|
13
|
+
:not_acceptable_callback,
|
14
|
+
:too_long_callback,
|
15
|
+
:range_unacceptable_callback,
|
16
|
+
:enhance_your_calm_callback,
|
17
|
+
:reconnect_callback,
|
18
|
+
:max_reconnects_callback,
|
19
|
+
:close_callback
|
20
|
+
].freeze unless defined?(CALLBACKS)
|
21
|
+
|
22
|
+
attr_accessor :connection, :options, :host, :port
|
23
|
+
attr_accessor *CALLBACKS
|
24
|
+
|
25
|
+
# A convenience method for creating and connecting.
|
26
|
+
def self.connect(options = {})
|
27
|
+
new(options).tap do |client|
|
28
|
+
client.connect
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
def initialize(options = {})
|
33
|
+
@options = DEFAULT_CONNECTION_OPTIONS.merge(options)
|
34
|
+
|
35
|
+
@host = @options[:host]
|
36
|
+
@port = @options[:port]
|
37
|
+
|
38
|
+
if @options[:proxy] && @options[:proxy][:uri]
|
39
|
+
proxy_uri = URI.parse(@options[:proxy][:uri])
|
40
|
+
@host = proxy_uri.host
|
41
|
+
@port = proxy_uri.port
|
42
|
+
end
|
43
|
+
@connection = nil
|
44
|
+
end
|
45
|
+
|
46
|
+
def connect
|
47
|
+
@connection = EM.connect(@host, @port, Connection, self, @host, @port)
|
48
|
+
end
|
49
|
+
|
50
|
+
def each(&block)
|
51
|
+
@each_item_callback = block
|
52
|
+
end
|
53
|
+
|
54
|
+
def on_error(&block)
|
55
|
+
@error_callback = block
|
56
|
+
end
|
57
|
+
|
58
|
+
def on_unauthorized(&block)
|
59
|
+
@unauthorized_callback = block
|
60
|
+
end
|
61
|
+
|
62
|
+
def on_forbidden(&block)
|
63
|
+
@forbidden_callback = block
|
64
|
+
end
|
65
|
+
|
66
|
+
def on_not_found(&block)
|
67
|
+
@not_found_callback = block
|
68
|
+
end
|
69
|
+
|
70
|
+
def on_not_acceptable(&block)
|
71
|
+
@not_acceptable_callback = block
|
72
|
+
end
|
73
|
+
|
74
|
+
def on_too_long(&block)
|
75
|
+
@too_long_callback = block
|
76
|
+
end
|
77
|
+
|
78
|
+
def on_range_unacceptable(&block)
|
79
|
+
@range_unacceptable_callback = block
|
80
|
+
end
|
81
|
+
|
82
|
+
def on_enhance_your_calm(&block)
|
83
|
+
@enhance_your_calm_callback = block
|
84
|
+
end
|
85
|
+
alias :on_rate_limited :on_enhance_your_calm
|
86
|
+
|
87
|
+
def on_reconnect(&block)
|
88
|
+
@reconnect_callback = block
|
89
|
+
end
|
90
|
+
|
91
|
+
def on_max_reconnects(&block)
|
92
|
+
@max_reconnects_callback = block
|
93
|
+
end
|
94
|
+
|
95
|
+
def on_close(&block)
|
96
|
+
@close_callback = block
|
97
|
+
end
|
98
|
+
|
99
|
+
# Delegate to EM::Twitter::Connection
|
100
|
+
def method_missing(method, *args, &block)
|
101
|
+
return super unless @connection.respond_to?(method)
|
102
|
+
@connection.send(method, *args, &block)
|
103
|
+
end
|
104
|
+
|
105
|
+
def respond_to?(method, include_private=false)
|
106
|
+
@connection.respond_to?(method, include_private) || super(method, include_private)
|
107
|
+
end
|
108
|
+
|
109
|
+
end
|
110
|
+
end
|
111
|
+
end
|