nervion 0.0.1 → 0.0.2
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +4 -2
- data/.travis.yml +9 -0
- data/CHANGELOG.md +9 -0
- data/Gemfile.lock +4 -0
- data/README.md +84 -76
- data/Rakefile +8 -4
- data/features/{streaming.feature → callbacks.feature} +2 -1
- data/features/client_validation.feature +17 -0
- data/features/step_definitions/client_validation_steps.rb +26 -0
- data/features/step_definitions/streaming_steps.rb +12 -2
- data/lib/nervion.rb +1 -0
- data/lib/nervion/client.rb +3 -5
- data/lib/nervion/configuration.rb +24 -0
- data/lib/nervion/facade.rb +99 -10
- data/lib/nervion/request.rb +12 -9
- data/lib/nervion/stream.rb +14 -6
- data/lib/nervion/stream_handler.rb +7 -25
- data/lib/nervion/{http_parser.rb → stream_parser.rb} +10 -8
- data/lib/nervion/version.rb +1 -1
- data/nervion.gemspec +3 -1
- data/spec/nervion/client_spec.rb +3 -1
- data/spec/nervion/configuration_spec.rb +14 -0
- data/spec/nervion/facade_spec.rb +42 -18
- data/spec/nervion/request_spec.rb +0 -1
- data/spec/nervion/stream_handler_spec.rb +5 -13
- data/spec/nervion/stream_parser_spec.rb +44 -0
- data/spec/nervion/stream_spec.rb +7 -4
- metadata +48 -9
- data/spec/nervion/http_parser_spec.rb +0 -26
data/.gitignore
CHANGED
data/.travis.yml
ADDED
data/CHANGELOG.md
ADDED
data/Gemfile.lock
CHANGED
@@ -22,6 +22,7 @@ GEM
|
|
22
22
|
http_parser.rb (0.5.3)
|
23
23
|
json (1.7.3)
|
24
24
|
multi_json (1.3.6)
|
25
|
+
rake (0.9.2.2)
|
25
26
|
rspec (2.9.0)
|
26
27
|
rspec-core (~> 2.9.0)
|
27
28
|
rspec-expectations (~> 2.9.0)
|
@@ -35,6 +36,7 @@ GEM
|
|
35
36
|
simplecov-html (~> 0.5.3)
|
36
37
|
simplecov-html (0.5.3)
|
37
38
|
yajl-ruby (1.1.0)
|
39
|
+
yard (0.8.2.1)
|
38
40
|
|
39
41
|
PLATFORMS
|
40
42
|
ruby
|
@@ -42,5 +44,7 @@ PLATFORMS
|
|
42
44
|
DEPENDENCIES
|
43
45
|
cucumber
|
44
46
|
nervion!
|
47
|
+
rake
|
45
48
|
rspec
|
46
49
|
simplecov
|
50
|
+
yard
|
data/README.md
CHANGED
@@ -1,24 +1,16 @@
|
|
1
|
-
# Nervion
|
1
|
+
# Nervion [![Build Status](https://secure.travis-ci.org/jacegu/nervion.png?branch=master)][travis] [![Dependency Status](https://gemnasium.com/jacegu/nervion.png?travis)][gemnasium]
|
2
2
|
|
3
|
-
**A minimalistic
|
3
|
+
**A minimalistic Ruby client for the
|
4
|
+
[Public Streams](https://dev.twitter.com/docs/streaming-apis/streams/public)
|
5
|
+
of Twitter Streaming API**.
|
4
6
|
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
In our current project we had the need to consume the stream provided by twitter.
|
9
|
-
Although there are a few gems available we had to suffer the pain of poor
|
10
|
-
documentation and error swallowing, which made us lose a lot of time.
|
11
|
-
|
12
|
-
At that point I decided to build one on my own, and that's why you are reading
|
13
|
-
this.
|
7
|
+
[travis]: http://travis-ci.org/jacegu/nervion
|
8
|
+
[gemnasium]: https://gemnasium.com/jacegu/nervion
|
14
9
|
|
15
10
|
|
16
11
|
|
17
12
|
## Installation
|
18
13
|
|
19
|
-
**WARNING: This project hasn't been released as a Gem yet**. This is here for
|
20
|
-
future reference.
|
21
|
-
|
22
14
|
Add this line to your application's Gemfile:
|
23
15
|
|
24
16
|
gem 'nervion'
|
@@ -33,36 +25,29 @@ Or install it yourself as:
|
|
33
25
|
|
34
26
|
|
35
27
|
|
36
|
-
##
|
28
|
+
## Overview
|
37
29
|
|
38
30
|
Nervion mimics the endpoints provided by the
|
39
|
-
[Twitter Stream API](https://dev.twitter.com/docs/streaming-apis)
|
40
|
-
|
41
|
-
[Public Streams](https://dev.twitter.com/docs/streaming-apis/streams/public).
|
42
|
-
In the future we will add support for the
|
43
|
-
[User Streams](https://dev.twitter.com/docs/streaming-apis/streams/user)
|
44
|
-
and the
|
45
|
-
[Site Streams](Use://dev.twitter.com/docs/streaming-apis/streams/site).
|
46
|
-
|
47
|
-
Specifically the two calls that are that are available to the broad audience:
|
31
|
+
[Twitter Stream API](https://dev.twitter.com/docs/streaming-apis)
|
32
|
+
through the following methods:
|
48
33
|
|
49
34
|
- [`follow`](https://dev.twitter.com/docs/api/1/post/statuses/filter)
|
50
35
|
- [`sample`](https://dev.twitter.com/docs/api/1/get/statuses/sample)
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
36
|
+
- [`firehose`](https://dev.twitter.com/docs/api/1/get/statuses/firehose)
|
37
|
+
*notice that the firehose support hasn't been tested against the actual API
|
38
|
+
since it requires a level of access I don't have. If you were able to verify
|
39
|
+
that it works, please, let me know*
|
40
|
+
|
41
|
+
Checkout the docs of the endpoints to know what tweets you can query the
|
42
|
+
Streaming API for and what parameters you have to provide to do so. You can
|
43
|
+
specify any of the parameters supported by the endpoints by passing them
|
59
44
|
as named parameters to the provided methods:
|
60
45
|
|
61
46
|
```ruby
|
62
|
-
|
63
|
-
|
64
|
-
Nervion.filter(
|
65
|
-
#do something with the
|
47
|
+
# This is tracking every tweet that includes the string "madrid" OR any tweet
|
48
|
+
# that is geo-located in Madrid.
|
49
|
+
Nervion.filter(track: 'madrid', locations: '40.364,-3.760,40.365,-3.609') do |message|
|
50
|
+
# do something with the message
|
66
51
|
end
|
67
52
|
```
|
68
53
|
|
@@ -72,7 +57,7 @@ Twitter.
|
|
72
57
|
|
73
58
|
|
74
59
|
|
75
|
-
|
60
|
+
## Authentication
|
76
61
|
|
77
62
|
Since Twitter plans to remove support for basic auth eventually, **Nervion only
|
78
63
|
supports OAuth authentication**.
|
@@ -81,15 +66,16 @@ You can provide the tokens and secrets in a configuration flavour:
|
|
81
66
|
|
82
67
|
```ruby
|
83
68
|
Nervion.configure do |config|
|
84
|
-
config.consumer_key
|
85
|
-
config.consumer_secret
|
86
|
-
config.access_token
|
87
|
-
config.access_token_secret =
|
69
|
+
config.consumer_key = 'xxxxxxxxxxxxxxxxxxxxxxxxxxxx'
|
70
|
+
config.consumer_secret = 'xxxxxxxxxxxxxxxxxxxxxxxxxxxx'
|
71
|
+
config.access_token = 'xxxxxxxxxxxxxxxxxxxxxxxxxxxx'
|
72
|
+
config.access_token_secret = 'xxxxxxxxxxxxxxxxxxxxxxxxxxxx'
|
88
73
|
end
|
89
74
|
```
|
90
75
|
|
91
76
|
|
92
|
-
|
77
|
+
|
78
|
+
## JSON Parsing
|
93
79
|
|
94
80
|
**Nervion will parse the JSON returned by twitter for you**. It uses
|
95
81
|
[Yajl](https://github.com/brianmario/yajl-ruby) as JSON parser for its out of
|
@@ -100,37 +86,42 @@ to use symbols to fetch data in the callbacks.
|
|
100
86
|
|
101
87
|
|
102
88
|
|
103
|
-
|
89
|
+
## Callbacks
|
104
90
|
|
105
|
-
|
106
|
-
**will support callbacks on specific types of tweets and errors** in future
|
107
|
-
versions.
|
91
|
+
Nervion provides three callbacks:
|
108
92
|
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
wrap it in some object that specializes on querying the information that is
|
113
|
-
relevant to you.
|
114
|
-
|
115
|
-
To know what keys to expect you should browse the
|
116
|
-
[*Platform Objects Documentation*](https://dev.twitter.com/docs/platform-objects/tweets).
|
93
|
+
- **Message callback**: called when message is received and parsed
|
94
|
+
- **HTTP error callback**: called when Twitter responds with a status above 200
|
95
|
+
- **Network error callback**: called when the connection to the stream is lost
|
117
96
|
|
118
97
|
|
119
|
-
|
98
|
+
### Message Callback
|
120
99
|
|
121
|
-
You
|
100
|
+
You must setup a callback that **acts on all the received messages** by simply
|
122
101
|
passing in a block to the API call you are making:
|
123
102
|
|
124
103
|
```ruby
|
125
|
-
Nervion.sample { |
|
104
|
+
Nervion.sample { |message| puts message[:text] if message.has_key? :text }
|
126
105
|
```
|
127
106
|
|
128
|
-
Be aware that
|
129
|
-
|
130
|
-
this in mind when querying the hash.
|
107
|
+
Be aware that
|
108
|
+
**[every message type](https://dev.twitter.com/docs/streaming-apis/messages)
|
109
|
+
will trigger this callback**. Keep this in mind when querying the hash.
|
110
|
+
|
111
|
+
The callback receives only one parameter: the hash with the symbolized keys
|
112
|
+
resultant of the JSON parsing. You get to choose what to do with the hash:
|
113
|
+
[mash](https://github.com/intridea/hashie) it before working with it or even
|
114
|
+
wrap it in some object that specializes on querying the information that is
|
115
|
+
relevant to you.
|
116
|
+
|
117
|
+
To know what keys to expect you should browse the
|
118
|
+
[*Platform Objects Documentation*](https://dev.twitter.com/docs/platform-objects/tweets)
|
119
|
+
and know the different
|
120
|
+
[message types](https://dev.twitter.com/docs/streaming-apis/messages)
|
121
|
+
.
|
131
122
|
|
132
123
|
|
133
|
-
|
124
|
+
### HTTP Error Callback
|
134
125
|
|
135
126
|
This callback will be executed when the Streaming API sends a response with a
|
136
127
|
status code above 200. After the callback has been executed a retry will be
|
@@ -142,17 +133,18 @@ You can setup the callback like this:
|
|
142
133
|
|
143
134
|
```ruby
|
144
135
|
Nervion.on_http_error do |status, body|
|
145
|
-
puts "
|
146
|
-
puts "
|
136
|
+
puts "the status of the response was: #{status}"
|
137
|
+
puts "the body of the response body was: #{body}"
|
147
138
|
end
|
148
139
|
```
|
149
140
|
|
150
|
-
|
151
|
-
|
152
|
-
|
141
|
+
Given that most of the HTTP errors are due to client configuration, if no
|
142
|
+
callback is set, Nervion's default behaviour will be to output an error message
|
143
|
+
to `STDERR` that contains both the status and the body of Twitter Streaming
|
144
|
+
API's response.
|
153
145
|
|
154
146
|
|
155
|
-
|
147
|
+
### Network Error callback
|
156
148
|
|
157
149
|
This callback will be executed when the connection with the Twitter Stream API
|
158
150
|
is unexpectedly closed.
|
@@ -163,10 +155,26 @@ Nervion.on_network_error do
|
|
163
155
|
end
|
164
156
|
```
|
165
157
|
|
166
|
-
|
158
|
+
Nervion will do nothing by default when network errors occur because it is
|
167
159
|
unlikely that they are provoked by the client itself.
|
168
160
|
|
169
161
|
|
162
|
+
### Callback chaining
|
163
|
+
|
164
|
+
Callback setup can be chained like this:
|
165
|
+
|
166
|
+
```ruby
|
167
|
+
Nervion.on_network_error do
|
168
|
+
#do something about the error
|
169
|
+
end.on_http_error do |status, body|
|
170
|
+
#do something about the error
|
171
|
+
end.sample do |status|
|
172
|
+
#do something with the status
|
173
|
+
end
|
174
|
+
```
|
175
|
+
|
176
|
+
|
177
|
+
|
170
178
|
## EventMachine Integration
|
171
179
|
|
172
180
|
Nervion runs on the top of EventMachine.
|
@@ -174,6 +182,8 @@ Nervion runs on the top of EventMachine.
|
|
174
182
|
In the near future this `README` will provide a guideline to take advantage of
|
175
183
|
the benefits that EventMachine can provide when used correctly.
|
176
184
|
|
185
|
+
|
186
|
+
|
177
187
|
## Roadmap
|
178
188
|
|
179
189
|
There are some features that are needed and that will be developed before the first
|
@@ -184,14 +194,12 @@ release of the gem:
|
|
184
194
|
- <del>Adhere to the
|
185
195
|
[Twitter Connection guidelines](https://dev.twitter.com/docs/streaming-api/concepts#connecting)</del>
|
186
196
|
*done!*
|
187
|
-
-
|
188
|
-
|
197
|
+
- <del>Improve the DSL provided to setup Nervion to validate the client
|
198
|
+
setup</del> *done!*
|
189
199
|
|
190
|
-
|
191
|
-
interesting to have:
|
200
|
+
Future features will be:
|
192
201
|
|
193
202
|
- Use a gzip compressed stream
|
194
|
-
-
|
195
|
-
|
196
|
-
|
197
|
-
If people start using the client more features will be added.
|
203
|
+
- Be able to configure the client to skip parsing and yield bare Strings with
|
204
|
+
the JSON of the streamed messages. The objective is to improve performance by
|
205
|
+
parsing in other process.
|
data/Rakefile
CHANGED
@@ -1,10 +1,11 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
require "bundler/gem_tasks"
|
1
|
+
require 'bundler/gem_tasks'
|
4
2
|
require 'rspec/core/rake_task'
|
5
3
|
require 'cucumber/rake/task'
|
4
|
+
require 'yard'
|
6
5
|
|
7
6
|
task :default => [:test]
|
7
|
+
|
8
|
+
desc 'Run all the features and specs'
|
8
9
|
task :test => [:rspec, :cucumber]
|
9
10
|
|
10
11
|
RSpec::Core::RakeTask.new(:rspec) do |t|
|
@@ -15,7 +16,10 @@ Cucumber::Rake::Task.new(:cucumber) do |t|
|
|
15
16
|
t.cucumber_opts = '--format progress'
|
16
17
|
end
|
17
18
|
|
18
|
-
|
19
|
+
YARD::Rake::YardocTask.new
|
20
|
+
|
21
|
+
desc 'Generate test coverage report'
|
22
|
+
task :coverage do
|
19
23
|
ENV['COVERAGE'] = 'true'
|
20
24
|
Rake::Task[:rspec].execute
|
21
25
|
Rake::Task[:cucumber].execute
|
@@ -0,0 +1,17 @@
|
|
1
|
+
Feature: Client setup validation
|
2
|
+
|
3
|
+
Scenario: Missing authentication
|
4
|
+
Given I haven't configured Nervion
|
5
|
+
When I try to start streaming
|
6
|
+
Then I get an error pointing me to the readme file
|
7
|
+
|
8
|
+
Scenario Outline: Calling filter without a message callback
|
9
|
+
Given Nervion has been configured
|
10
|
+
When I try to start streaming the <endpoint_name> endpoint
|
11
|
+
Then I get an error pointing me to the readme file
|
12
|
+
|
13
|
+
Examples:
|
14
|
+
| endpoint_name |
|
15
|
+
| filter |
|
16
|
+
| firehose |
|
17
|
+
| sample |
|
@@ -0,0 +1,26 @@
|
|
1
|
+
AUTHENTICATION_README_URL = 'https://github.com/jacegu/nervion'
|
2
|
+
|
3
|
+
def capture_errors_in
|
4
|
+
begin
|
5
|
+
yield
|
6
|
+
rescue Exception => error
|
7
|
+
@error = error
|
8
|
+
end
|
9
|
+
end
|
10
|
+
|
11
|
+
Given /^I haven't configured Nervion$/ do
|
12
|
+
Nervion::Configuration.instance_variable_set '@configured', nil
|
13
|
+
end
|
14
|
+
|
15
|
+
When /^I try to start streaming$/ do
|
16
|
+
capture_errors_in { Nervion.sample { |status| puts status } }
|
17
|
+
end
|
18
|
+
|
19
|
+
When /^I try to start streaming the (.*?) endpoint$/ do |endpoint_name|
|
20
|
+
params = { stall_warnings: true }
|
21
|
+
capture_errors_in { Nervion.send(endpoint_name.to_sym, params) }
|
22
|
+
end
|
23
|
+
|
24
|
+
Then /^I get an error pointing me to the readme file$/ do
|
25
|
+
@error.message.should match /#{AUTHENTICATION_README_URL}/
|
26
|
+
end
|
@@ -1,3 +1,4 @@
|
|
1
|
+
KNOWN_STATUS_COUNT = 100
|
1
2
|
TEST_HOST = '0.0.0.0'
|
2
3
|
TEST_PORT = '9000'
|
3
4
|
|
@@ -12,7 +13,16 @@ def test_client_with(server_version)
|
|
12
13
|
EM.run do
|
13
14
|
EM.start_server(TEST_HOST, TEST_PORT, server_version)
|
14
15
|
EM.add_timer(0) { Nervion.sample { |status| @statuses << status } }
|
15
|
-
EM.add_timer(0.
|
16
|
+
EM.add_timer(0.3) { EM.stop }
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
Given /^Nervion has been configured$/ do
|
21
|
+
Nervion.configure do |config|
|
22
|
+
config.consumer_key = 'xxxxxxxxxxxxxxxxxxxxxxxxxxxx'
|
23
|
+
config.consumer_secret = 'xxxxxxxxxxxxxxxxxxxxxxxxxxxx'
|
24
|
+
config.access_token = 'xxxxxxxxxxxxxxxxxxxxxxxxxxxx'
|
25
|
+
config.access_token_secret = 'xxxxxxxxxxxxxxxxxxxxxxxxxxxx'
|
16
26
|
end
|
17
27
|
end
|
18
28
|
|
@@ -42,7 +52,7 @@ When /^a network error occurs$/ do
|
|
42
52
|
end
|
43
53
|
|
44
54
|
Then /^Nervion calls the status callback with it$/ do
|
45
|
-
@statuses.count.should eq
|
55
|
+
@statuses.count.should eq KNOWN_STATUS_COUNT
|
46
56
|
end
|
47
57
|
|
48
58
|
Then /^Nervion calls the HTTP error callback$/ do
|
data/lib/nervion.rb
CHANGED
data/lib/nervion/client.rb
CHANGED
@@ -1,5 +1,3 @@
|
|
1
|
-
require 'nervion/configuration'
|
2
|
-
require 'nervion/request'
|
3
1
|
require 'nervion/stream'
|
4
2
|
require 'nervion/stream_handler'
|
5
3
|
|
@@ -11,12 +9,12 @@ module Nervion
|
|
11
9
|
end
|
12
10
|
|
13
11
|
def stream(request, callbacks)
|
14
|
-
|
15
|
-
EM.run { EM.connect @host, @port, Stream, request,
|
12
|
+
handler = StreamHandler.new(callbacks)
|
13
|
+
EM.run { @stream = EM.connect @host, @port, Stream, request, handler }
|
16
14
|
end
|
17
15
|
|
18
16
|
def stop
|
19
|
-
@
|
17
|
+
@stream.close
|
20
18
|
EM.stop
|
21
19
|
end
|
22
20
|
end
|
@@ -1,12 +1,19 @@
|
|
1
1
|
module Nervion
|
2
2
|
|
3
|
+
# Allows to configure Nervion.
|
4
|
+
#
|
5
|
+
# @yieldparam [Configuration] config the configuration object.
|
3
6
|
def self.configure
|
7
|
+
Configuration.configured!
|
4
8
|
yield Configuration
|
5
9
|
end
|
6
10
|
|
7
11
|
class Configuration
|
8
12
|
UNCONFIGURED_SETTING = ''
|
9
13
|
|
14
|
+
# Configures the consumer key
|
15
|
+
#
|
16
|
+
# @param [String] consumer_key the consumer key
|
10
17
|
def self.consumer_key=(consumer_key)
|
11
18
|
@consumer_key = consumer_key
|
12
19
|
end
|
@@ -15,6 +22,9 @@ module Nervion
|
|
15
22
|
@consumer_key || UNCONFIGURED_SETTING
|
16
23
|
end
|
17
24
|
|
25
|
+
# Configures the consumer secret
|
26
|
+
#
|
27
|
+
# @param [String] consumer_secret the consumer secret
|
18
28
|
def self.consumer_secret=(consumer_secret)
|
19
29
|
@consumer_secret = consumer_secret
|
20
30
|
end
|
@@ -23,6 +33,9 @@ module Nervion
|
|
23
33
|
@consumer_secret || UNCONFIGURED_SETTING
|
24
34
|
end
|
25
35
|
|
36
|
+
# Configures the access token
|
37
|
+
#
|
38
|
+
# @param [String] access_token the access token
|
26
39
|
def self.access_token=(access_token)
|
27
40
|
@access_token = access_token
|
28
41
|
end
|
@@ -31,6 +44,9 @@ module Nervion
|
|
31
44
|
@access_token || UNCONFIGURED_SETTING
|
32
45
|
end
|
33
46
|
|
47
|
+
# Configures the access token secret
|
48
|
+
#
|
49
|
+
# @param [String] access_token_secret the access token secret
|
34
50
|
def self.access_token_secret=(access_token_secret)
|
35
51
|
@access_token_secret = access_token_secret
|
36
52
|
end
|
@@ -46,5 +62,13 @@ module Nervion
|
|
46
62
|
def self.fetch(setting)
|
47
63
|
send setting.to_sym
|
48
64
|
end
|
65
|
+
|
66
|
+
def self.configured?
|
67
|
+
@configured
|
68
|
+
end
|
69
|
+
|
70
|
+
def self.configured!
|
71
|
+
@configured = true
|
72
|
+
end
|
49
73
|
end
|
50
74
|
end
|
data/lib/nervion/facade.rb
CHANGED
@@ -4,31 +4,91 @@ require 'nervion/request'
|
|
4
4
|
require 'nervion/configuration'
|
5
5
|
|
6
6
|
module Nervion
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
7
|
+
# Sets up the callback to be called upon HTTP errors (when the response from
|
8
|
+
# Twitter's Streaming API has a status above 200).
|
9
|
+
#
|
10
|
+
# @param [Proc] callback the callback
|
11
|
+
# @return [self] to allow callback setup chaining
|
12
12
|
def self.on_http_error(&callback)
|
13
13
|
callback_table[:http_error] = callback
|
14
14
|
self
|
15
15
|
end
|
16
16
|
|
17
|
+
# Sets up the callback to be called upon network errors or unexpected
|
18
|
+
# disconnection.
|
19
|
+
#
|
20
|
+
# @param [Proc] callback the callback
|
21
|
+
# @return [self] to allow callback setup chaining
|
17
22
|
def self.on_network_error(&callback)
|
18
23
|
callback_table[:network_error] = callback
|
19
24
|
self
|
20
25
|
end
|
21
26
|
|
27
|
+
# Sets up the message callback and starts streaming the sample endpoint.
|
28
|
+
#
|
29
|
+
# @see https://dev.twitter.com/docs/api/1/get/statuses/sample
|
30
|
+
# @see https://dev.twitter.com/docs/streaming-apis/parameters#delimited
|
31
|
+
# @see https://dev.twitter.com/docs/streaming-apis/parameters#stall_warnings
|
32
|
+
#
|
33
|
+
# @param [hash] params the parameters submitted to the sample endpoint
|
34
|
+
# @option params [Boolean] :delimited specifies whether messages should be
|
35
|
+
# length-delimited.
|
36
|
+
# @option params [Boolean] :stall_warnings specifies whether stall warnings
|
37
|
+
# should be delivered.
|
38
|
+
# @param [Proc] callback the callback
|
22
39
|
def self.sample(params = {}, &callback)
|
23
|
-
|
24
|
-
new_client.tap { |c| c.stream sample_endpoint(params), callback_table }
|
40
|
+
stream sample_endpoint(params), callback
|
25
41
|
end
|
26
42
|
|
43
|
+
# Sets up the message callback and starts streaming the filter endpoint.
|
44
|
+
#
|
45
|
+
# @note At least one predicate parameter (follow, locations, or track) must be
|
46
|
+
# specified.
|
47
|
+
#
|
48
|
+
# @see https://dev.twitter.com/docs/api/1/get/statuses/filter
|
49
|
+
# @see https://dev.twitter.com/docs/streaming-apis/parameters#follow
|
50
|
+
# @see https://dev.twitter.com/docs/streaming-apis/parameters#track
|
51
|
+
# @see https://dev.twitter.com/docs/streaming-apis/parameters#locations
|
52
|
+
# @see https://dev.twitter.com/docs/streaming-apis/parameters#delimited
|
53
|
+
# @see https://dev.twitter.com/docs/streaming-apis/parameters#stall_warnings
|
54
|
+
#
|
55
|
+
# @param [hash] params the parameters submitted to the sample endpoint
|
56
|
+
# @option params [String] :follow a comma separated list of user IDs,
|
57
|
+
# indicating the users to return statuses for in the stream.
|
58
|
+
# @option params [String] :track keywords to track. Phrases of keywords are
|
59
|
+
# specified by a comma-separated list.
|
60
|
+
# @option params [String] :locations Specifies a set of bounding boxes to track.
|
61
|
+
# @option params [Boolean] :delimited specifies whether messages should be
|
62
|
+
# length-delimited.
|
63
|
+
# @option params [Boolean] :stall_warnings specifies whether stall warnings
|
64
|
+
# should be delivered.
|
65
|
+
# @param [Proc] callback the callback
|
27
66
|
def self.filter(params, &callback)
|
28
|
-
|
29
|
-
|
67
|
+
stream filter_endpoint(params), callback
|
68
|
+
end
|
69
|
+
|
70
|
+
# Sets up the message callback and starts streaming the firehose endpoint.
|
71
|
+
#
|
72
|
+
# @note This endpoint requires a special access level.
|
73
|
+
# @since 0.0.2
|
74
|
+
#
|
75
|
+
# @see https://dev.twitter.com/docs/api/1/get/statuses/firehose
|
76
|
+
# @see https://dev.twitter.com/docs/streaming-apis/parameters#count
|
77
|
+
# @see https://dev.twitter.com/docs/streaming-apis/parameters#delimited
|
78
|
+
# @see https://dev.twitter.com/docs/streaming-apis/parameters#stall_warnings
|
79
|
+
#
|
80
|
+
# @param [hash] params the parameters submitted to the sample endpoint
|
81
|
+
# @option params [Integer] :count the number of messages to backfill.
|
82
|
+
# @option params [Boolean] :delimited specifies whether messages should be
|
83
|
+
# length-delimited.
|
84
|
+
# @option params [Boolean] :stall_warnings specifies whether stall warnings
|
85
|
+
# should be delivered.
|
86
|
+
# @param [Proc] callback the callback
|
87
|
+
def self.firehose(params = {}, &callback)
|
88
|
+
stream firehose_endpoint(params), callback
|
30
89
|
end
|
31
90
|
|
91
|
+
# Stops streaming
|
32
92
|
def self.stop
|
33
93
|
raise 'Nervion is not running' if @client.nil?
|
34
94
|
@client.stop
|
@@ -40,15 +100,44 @@ module Nervion
|
|
40
100
|
@callback_table ||= CallbackTable.new
|
41
101
|
end
|
42
102
|
|
103
|
+
def self.stream(endpoint, callback)
|
104
|
+
raise_not_configured_error unless Configuration.configured?
|
105
|
+
raise_no_message_callback_error if callback.nil?
|
106
|
+
callback_table[:message] = callback
|
107
|
+
new_client.tap { |c| c.stream endpoint, callback_table }
|
108
|
+
end
|
109
|
+
|
43
110
|
def self.new_client
|
44
111
|
@client = Client.new(STREAM_API_HOST, STREAM_API_PORT)
|
45
112
|
end
|
46
113
|
|
47
114
|
def self.sample_endpoint(params)
|
48
|
-
|
115
|
+
get SAMPLE_ENDPOINT, params, Configuration
|
49
116
|
end
|
50
117
|
|
51
118
|
def self.filter_endpoint(params)
|
52
119
|
post FILTER_ENDPOINT, params, Configuration
|
53
120
|
end
|
121
|
+
|
122
|
+
def self.firehose_endpoint(params)
|
123
|
+
get FIREHOSE_ENDPOINT, params, Configuration
|
124
|
+
end
|
125
|
+
|
126
|
+
def self.raise_not_configured_error
|
127
|
+
raise "You need to setup the authentication information for Nervion to work. Please, check out #{AUTHENTICATION_README_URL}"
|
128
|
+
end
|
129
|
+
|
130
|
+
def self.raise_no_message_callback_error
|
131
|
+
raise "You have to setup a message callback. Please, check out #{MSG_CALLBACK_README_URL}"
|
132
|
+
end
|
133
|
+
|
134
|
+
STREAM_API_HOST = 'stream.twitter.com'
|
135
|
+
STREAM_API_PORT = 443
|
136
|
+
SAMPLE_ENDPOINT = "https://#{STREAM_API_HOST}/1/statuses/sample.json"
|
137
|
+
FILTER_ENDPOINT = "https://#{STREAM_API_HOST}/1/statuses/filter.json"
|
138
|
+
FIREHOSE_ENDPOINT = "https://#{STREAM_API_HOST}/1/statuses/firehose.json"
|
139
|
+
|
140
|
+
AUTHENTICATION_README_URL = 'https://github.com/jacegu/nervion#authentication'
|
141
|
+
MSG_CALLBACK_README_URL = 'https://github.com/jacegu/nervion#message-callback'
|
142
|
+
|
54
143
|
end
|
data/lib/nervion/request.rb
CHANGED
@@ -1,14 +1,7 @@
|
|
1
1
|
require_relative 'oauth_header'
|
2
|
+
require_relative 'percent_encoder'
|
2
3
|
|
3
4
|
module Nervion
|
4
|
-
def self.get(uri, params = {}, oauth_params)
|
5
|
-
Get.new uri, params, oauth_params
|
6
|
-
end
|
7
|
-
|
8
|
-
def self.post(uri, params = {}, oauth_params)
|
9
|
-
Post.new uri, params, oauth_params
|
10
|
-
end
|
11
|
-
|
12
5
|
module Request
|
13
6
|
attr_reader :params, :oauth_params
|
14
7
|
|
@@ -73,7 +66,6 @@ module Nervion
|
|
73
66
|
|
74
67
|
class Post
|
75
68
|
include Request
|
76
|
-
include PercentEncoder
|
77
69
|
|
78
70
|
def to_s
|
79
71
|
"#{request_line}\r\n#{headers.join("\r\n")}\r\n\r\n#{body}\r\n"
|
@@ -96,4 +88,15 @@ module Nervion
|
|
96
88
|
percent_encode params
|
97
89
|
end
|
98
90
|
end
|
91
|
+
|
92
|
+
private
|
93
|
+
|
94
|
+
def self.get(uri, params = {}, oauth_params)
|
95
|
+
Get.new uri, params, oauth_params
|
96
|
+
end
|
97
|
+
|
98
|
+
def self.post(uri, params = {}, oauth_params)
|
99
|
+
Post.new uri, params, oauth_params
|
100
|
+
end
|
101
|
+
|
99
102
|
end
|
data/lib/nervion/stream.rb
CHANGED
@@ -1,5 +1,5 @@
|
|
1
1
|
require 'eventmachine'
|
2
|
-
require 'nervion/
|
2
|
+
require 'nervion/stream_parser'
|
3
3
|
require 'nervion/reconnection_scheduler'
|
4
4
|
|
5
5
|
module Nervion
|
@@ -33,29 +33,37 @@ module Nervion
|
|
33
33
|
end
|
34
34
|
|
35
35
|
def unbind
|
36
|
-
handle_closed_stream unless
|
36
|
+
handle_closed_stream unless close_requested?
|
37
37
|
end
|
38
38
|
|
39
39
|
def http_error_occurred?
|
40
40
|
not http_error.nil?
|
41
41
|
end
|
42
42
|
|
43
|
+
def close_requested?
|
44
|
+
@close_stream ||= false
|
45
|
+
end
|
46
|
+
|
47
|
+
def close
|
48
|
+
@close_stream = true
|
49
|
+
end
|
50
|
+
|
43
51
|
private
|
44
52
|
|
45
53
|
def handle_closed_stream
|
46
54
|
if http_error_occurred?
|
47
|
-
|
55
|
+
handle_http_error_and_retry
|
48
56
|
else
|
49
|
-
|
57
|
+
handle_network_error_and_retry
|
50
58
|
end
|
51
59
|
end
|
52
60
|
|
53
|
-
def
|
61
|
+
def handle_http_error_and_retry
|
54
62
|
@handler.handle_http_error http_error
|
55
63
|
@scheduler.reconnect_after_http_error_in self
|
56
64
|
end
|
57
65
|
|
58
|
-
def
|
66
|
+
def handle_network_error_and_retry
|
59
67
|
@handler.handle_network_error
|
60
68
|
@scheduler.reconnect_after_network_error_in self
|
61
69
|
end
|
@@ -1,42 +1,24 @@
|
|
1
|
-
require '
|
2
|
-
require 'nervion/http_parser'
|
1
|
+
require 'nervion/stream_parser'
|
3
2
|
|
4
3
|
module Nervion
|
5
4
|
class StreamHandler
|
6
|
-
|
7
|
-
|
8
|
-
@
|
9
|
-
@http_parser = HttpParser.new(setup_json_parser)
|
5
|
+
def initialize(callbacks, stream_parser = StreamParser.new)
|
6
|
+
@callbacks, @stream_parser = callbacks, stream_parser
|
7
|
+
@stream_parser.on_json_parsed = @callbacks[:message]
|
10
8
|
end
|
11
9
|
|
12
10
|
def <<(data)
|
13
|
-
@
|
11
|
+
@stream_parser << data
|
14
12
|
end
|
15
13
|
|
16
14
|
def handle_http_error(error)
|
17
|
-
@
|
15
|
+
@stream_parser.reset!
|
18
16
|
@callbacks[:http_error].call(error.status, error.body)
|
19
17
|
end
|
20
18
|
|
21
19
|
def handle_network_error
|
22
|
-
@
|
20
|
+
@stream_parser.reset!
|
23
21
|
@callbacks[:network_error].call
|
24
22
|
end
|
25
|
-
|
26
|
-
def stream_close_requested?
|
27
|
-
@close_stream ||= false
|
28
|
-
end
|
29
|
-
|
30
|
-
def close_stream
|
31
|
-
@close_stream = true
|
32
|
-
end
|
33
|
-
|
34
|
-
private
|
35
|
-
|
36
|
-
def setup_json_parser
|
37
|
-
Yajl::Parser.new(symbolize_keys: true).tap do |json_parser|
|
38
|
-
json_parser.on_parse_complete = @callbacks[:status]
|
39
|
-
end
|
40
|
-
end
|
41
23
|
end
|
42
24
|
end
|
@@ -1,12 +1,18 @@
|
|
1
|
+
require 'yajl'
|
1
2
|
require 'http/parser'
|
2
3
|
|
3
4
|
module Nervion
|
4
|
-
class
|
5
|
+
class StreamParser
|
5
6
|
attr_reader :json_parser, :http_parser
|
6
7
|
|
7
|
-
def initialize(
|
8
|
-
@
|
9
|
-
@
|
8
|
+
def initialize(parsers = {})
|
9
|
+
@http_parser = parsers[:http_parser] || Http::Parser.new
|
10
|
+
@json_parser = parsers[:json_parser] || Yajl::Parser.new(symbolize_keys: true)
|
11
|
+
@http_parser.on_body = method(:process)
|
12
|
+
end
|
13
|
+
|
14
|
+
def on_json_parsed=(callback)
|
15
|
+
@json_parser.on_parse_complete = callback
|
10
16
|
end
|
11
17
|
|
12
18
|
def <<(http_stream)
|
@@ -19,10 +25,6 @@ module Nervion
|
|
19
25
|
|
20
26
|
private
|
21
27
|
|
22
|
-
def setup_http_parser
|
23
|
-
Http::Parser.new.tap { |parser| parser.on_body = method(:process) }
|
24
|
-
end
|
25
|
-
|
26
28
|
def process(chunk)
|
27
29
|
if request_successful?
|
28
30
|
parse_json_from chunk
|
data/lib/nervion/version.rb
CHANGED
data/nervion.gemspec
CHANGED
@@ -18,7 +18,9 @@ Gem::Specification.new do |gem|
|
|
18
18
|
gem.add_runtime_dependency 'eventmachine', '~> 1.0.0.beta.4'
|
19
19
|
gem.add_runtime_dependency 'http_parser.rb', '~> 0.5.3'
|
20
20
|
gem.add_runtime_dependency 'yajl-ruby', '~> 1.1.0'
|
21
|
-
gem.add_development_dependency 'rspec'
|
22
21
|
gem.add_development_dependency 'cucumber'
|
22
|
+
gem.add_development_dependency 'rake'
|
23
|
+
gem.add_development_dependency 'rspec'
|
23
24
|
gem.add_development_dependency 'simplecov'
|
25
|
+
gem.add_development_dependency 'yard'
|
24
26
|
end
|
data/spec/nervion/client_spec.rb
CHANGED
@@ -5,6 +5,7 @@ describe Nervion::Client do
|
|
5
5
|
subject { described_class.new('http://twitter.com', 443) }
|
6
6
|
let(:request) { stub :request }
|
7
7
|
let(:callbacks) { stub :callbacks }
|
8
|
+
let(:stream) { mock :stream }
|
8
9
|
let(:stream_handler) { stub :stream_handler }
|
9
10
|
|
10
11
|
before(:all) do
|
@@ -43,7 +44,8 @@ describe Nervion::Client do
|
|
43
44
|
end
|
44
45
|
|
45
46
|
it 'stops streaming' do
|
46
|
-
|
47
|
+
EM.stub(:connect).and_return(stream)
|
48
|
+
stream.should_receive(:close).ordered
|
47
49
|
EM.should_receive(:stop).ordered
|
48
50
|
subject.stream(request, callbacks)
|
49
51
|
subject.stop
|
@@ -2,11 +2,25 @@ require 'nervion/configuration'
|
|
2
2
|
|
3
3
|
describe Nervion::Configuration do
|
4
4
|
|
5
|
+
before do
|
6
|
+
%w{
|
7
|
+
consumer_key consumer_secret access_token access_token_secret configured
|
8
|
+
}.each do |variable|
|
9
|
+
described_class.instance_variable_set "@#{variable}", nil
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
5
13
|
it 'allows configuration from the top level' do
|
6
14
|
Nervion::Configuration.should_receive(:access_key=).with 'access_key'
|
7
15
|
Nervion.configure { |config| config.access_key = 'access_key' }
|
8
16
|
end
|
9
17
|
|
18
|
+
it 'knows if Nervion has been configured' do
|
19
|
+
Nervion::Configuration.should_not be_configured
|
20
|
+
Nervion.configure { |config| }
|
21
|
+
Nervion::Configuration.should be_configured
|
22
|
+
end
|
23
|
+
|
10
24
|
context 'when it has not been configured' do
|
11
25
|
it 'has an empty string as consumer_key' do
|
12
26
|
described_class.consumer_key.should eq ''
|
data/spec/nervion/facade_spec.rb
CHANGED
@@ -1,8 +1,10 @@
|
|
1
1
|
require 'nervion/facade'
|
2
2
|
|
3
|
+
class Callable; def call; end; end;
|
4
|
+
|
3
5
|
describe "Facade that exposes Nervion's API" do
|
4
6
|
let(:callback_table) { mock(:callback_table).as_null_object }
|
5
|
-
let(:
|
7
|
+
let(:message_callback) { lambda { :message_callback } }
|
6
8
|
let(:http_callback) { lambda { :http_error_callback } }
|
7
9
|
let(:network_callback) { lambda { :network_error_callback } }
|
8
10
|
|
@@ -40,43 +42,65 @@ describe "Facade that exposes Nervion's API" do
|
|
40
42
|
let(:request) { stub :request }
|
41
43
|
|
42
44
|
before do
|
45
|
+
Nervion.configure { |config| }
|
43
46
|
Nervion::Client.stub(:new).
|
44
47
|
with(Nervion::STREAM_API_HOST, Nervion::STREAM_API_PORT).
|
45
48
|
and_return(client)
|
46
49
|
end
|
47
50
|
|
48
|
-
|
49
|
-
it 'sets up the
|
50
|
-
callback_table.should_receive(:[]=).with(:
|
51
|
-
Nervion.
|
51
|
+
shared_examples_for 'an endpoint' do
|
52
|
+
it 'sets up the message callback' do
|
53
|
+
callback_table.should_receive(:[]=).with(:message, message_callback)
|
54
|
+
Nervion.send(method_name, params, &message_callback)
|
52
55
|
end
|
53
56
|
|
54
57
|
it 'starts the streaming to the sample endpoint' do
|
55
|
-
Nervion.stub(
|
58
|
+
Nervion.stub(http_method).with(endpoint, params, config).
|
56
59
|
and_return(request)
|
57
60
|
client.should_receive(:stream).with(request, callback_table)
|
58
|
-
Nervion.
|
61
|
+
Nervion.send(method_name, params, &message_callback)
|
59
62
|
end
|
60
|
-
end
|
61
63
|
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
64
|
+
it 'raises an error if Nervion was not configured' do
|
65
|
+
expect do
|
66
|
+
Nervion::Configuration.instance_variable_set(:@configured, nil)
|
67
|
+
Nervion.send(method_name, params, &message_callback)
|
68
|
+
end.to raise_error
|
66
69
|
end
|
67
70
|
|
68
|
-
it '
|
69
|
-
Nervion.
|
70
|
-
and_return(request)
|
71
|
-
client.should_receive(:stream).with(request, callback_table)
|
72
|
-
Nervion.filter(params, &status_callback)
|
71
|
+
it 'raises an error if no message callback was provided' do
|
72
|
+
expect { Nervion.send(method_name, params) }.to raise_error
|
73
73
|
end
|
74
74
|
end
|
75
75
|
|
76
|
+
context 'sample endpoint' do
|
77
|
+
let(:http_method) { :get }
|
78
|
+
let(:endpoint) { Nervion::SAMPLE_ENDPOINT }
|
79
|
+
let(:method_name) { :sample }
|
80
|
+
|
81
|
+
it_behaves_like 'an endpoint'
|
82
|
+
end
|
83
|
+
|
84
|
+
context 'filter endpoint' do
|
85
|
+
let(:http_method) { :post }
|
86
|
+
let(:endpoint) { Nervion::FILTER_ENDPOINT }
|
87
|
+
let(:method_name) { :filter }
|
88
|
+
|
89
|
+
it_behaves_like 'an endpoint'
|
90
|
+
end
|
91
|
+
|
92
|
+
context 'firehose endpoint' do
|
93
|
+
let(:http_method) { :get }
|
94
|
+
let(:endpoint) { Nervion::FIREHOSE_ENDPOINT }
|
95
|
+
let(:method_name) { :firehose }
|
96
|
+
|
97
|
+
it_behaves_like 'an endpoint'
|
98
|
+
end
|
99
|
+
|
76
100
|
context 'stoping' do
|
77
101
|
it 'can stop the streaming' do
|
78
102
|
client.should_receive(:stop)
|
79
|
-
Nervion.sample
|
103
|
+
Nervion.sample{}
|
80
104
|
Nervion.stop
|
81
105
|
end
|
82
106
|
|
@@ -59,7 +59,6 @@ describe Nervion::Request do
|
|
59
59
|
subject.path.should eq '/endpoint?p1=param%20value&p2=%24%26'
|
60
60
|
end
|
61
61
|
|
62
|
-
|
63
62
|
it 'has an string representation' do
|
64
63
|
Nervion::OAuthHeader.stub(:for).with(subject).and_return 'OAuth xxx'
|
65
64
|
subject.to_s.should eq EXPECTED_GET_REQUEST
|
@@ -3,27 +3,25 @@ require 'nervion/stream_handler'
|
|
3
3
|
describe Nervion::StreamHandler do
|
4
4
|
subject { described_class.new callbacks }
|
5
5
|
let(:http_parser) { mock(:http_parser).as_null_object }
|
6
|
-
let(:json_parser) { mock(:json_parser).as_null_object }
|
7
6
|
|
8
7
|
let(:callbacks) do
|
9
8
|
{
|
10
|
-
|
9
|
+
message: message_callback,
|
11
10
|
http_error: http_error_callback,
|
12
11
|
network_error: network_error_callback
|
13
12
|
}
|
14
13
|
end
|
15
14
|
|
16
|
-
let(:
|
15
|
+
let(:message_callback) { mock :message_callback }
|
17
16
|
let(:http_error_callback) { mock(:http_error_callback).as_null_object }
|
18
17
|
let(:network_error_callback) { mock(:network_error_callback).as_null_object }
|
19
18
|
|
20
19
|
before do
|
21
|
-
|
22
|
-
Nervion::HttpParser.stub(:new).with(json_parser).and_return(http_parser)
|
20
|
+
Nervion::StreamParser.stub(:new).and_return(http_parser)
|
23
21
|
end
|
24
22
|
|
25
|
-
it 'sets up the
|
26
|
-
|
23
|
+
it 'sets up the message received callback' do
|
24
|
+
http_parser.should_receive(:on_json_parsed=).with(message_callback)
|
27
25
|
described_class.new callbacks
|
28
26
|
end
|
29
27
|
|
@@ -33,12 +31,6 @@ describe Nervion::StreamHandler do
|
|
33
31
|
subject << data
|
34
32
|
end
|
35
33
|
|
36
|
-
it 'can be told to close the stream' do
|
37
|
-
subject.stream_close_requested?.should be_false
|
38
|
-
subject.close_stream
|
39
|
-
subject.stream_close_requested?.should be_true
|
40
|
-
end
|
41
|
-
|
42
34
|
context 'handling HTTP errors' do
|
43
35
|
let(:http_error) { stub(:http_error, status: 401, body: 'Unauthorized') }
|
44
36
|
|
@@ -0,0 +1,44 @@
|
|
1
|
+
require 'nervion/stream_parser'
|
2
|
+
require 'fixtures/responses'
|
3
|
+
|
4
|
+
describe Nervion::StreamParser do
|
5
|
+
subject { described_class.new(json_parser: json_parser) }
|
6
|
+
let(:json_parser) { stub(:json_parser).as_null_object }
|
7
|
+
|
8
|
+
it 'takes a JSON parser' do
|
9
|
+
subject.json_parser.should be json_parser
|
10
|
+
end
|
11
|
+
|
12
|
+
it 'uses Http::Parser as the default HTTP parser' do
|
13
|
+
http_parser = stub(:http_parser).as_null_object
|
14
|
+
Http::Parser.stub(:new).and_return(http_parser)
|
15
|
+
described_class.new.http_parser.should be http_parser
|
16
|
+
end
|
17
|
+
|
18
|
+
it 'uses Yajl with symbolized keys as the default JSON parser' do
|
19
|
+
yajl_parser = stub(:yajl_parser)
|
20
|
+
Yajl::Parser.stub(:new).with(symbolize_keys: true).and_return(yajl_parser)
|
21
|
+
described_class.new.json_parser.should be yajl_parser
|
22
|
+
end
|
23
|
+
|
24
|
+
it 'can be set up with a JSON parse complete callback' do
|
25
|
+
callback = lambda {}
|
26
|
+
json_parser.should_receive(:on_parse_complete=).with callback
|
27
|
+
subject.on_json_parsed = callback
|
28
|
+
end
|
29
|
+
|
30
|
+
it 'can be reset' do
|
31
|
+
subject << RESPONSE_200
|
32
|
+
subject.reset!
|
33
|
+
expect { subject << RESPONSE_200 }.not_to raise_error Http::Parser::Error
|
34
|
+
end
|
35
|
+
|
36
|
+
it 'parses response body if the response status is 200' do
|
37
|
+
json_parser.should_receive(:<<).with(BODY_200)
|
38
|
+
subject << RESPONSE_200
|
39
|
+
end
|
40
|
+
|
41
|
+
it 'raises an error if the response status is above 200' do
|
42
|
+
expect { subject << RESPONSE_401 }.to raise_error Nervion::HttpError
|
43
|
+
end
|
44
|
+
end
|
data/spec/nervion/stream_spec.rb
CHANGED
@@ -46,10 +46,15 @@ describe Nervion::Stream do
|
|
46
46
|
subject.retry
|
47
47
|
end
|
48
48
|
|
49
|
+
it 'can be closed' do
|
50
|
+
subject.close_requested?.should be_false
|
51
|
+
subject.close
|
52
|
+
subject.close_requested?.should be_true
|
53
|
+
end
|
54
|
+
|
49
55
|
context 'on unbound connections' do
|
50
56
|
context 'due to HTTP errors' do
|
51
57
|
before do
|
52
|
-
handler.stub(:stream_close_requested?).and_return(false)
|
53
58
|
subject.stub(:http_error).and_return(http_error)
|
54
59
|
end
|
55
60
|
|
@@ -67,8 +72,6 @@ describe Nervion::Stream do
|
|
67
72
|
end
|
68
73
|
|
69
74
|
context 'due to network errors' do
|
70
|
-
before { handler.stub(:stream_close_requested?).and_return(false) }
|
71
|
-
|
72
75
|
it 'notifies the error' do
|
73
76
|
handler.should_receive(:handle_network_error)
|
74
77
|
scheduler.stub(:reconnect_after_network_error_in)
|
@@ -84,11 +87,11 @@ describe Nervion::Stream do
|
|
84
87
|
|
85
88
|
context 'due to a request to close the stream' do
|
86
89
|
it 'lets the stream to be closed' do
|
87
|
-
handler.stub(:stream_close_requested?).and_return(true)
|
88
90
|
handler.should_not_receive(:handle_http_error)
|
89
91
|
handler.should_not_receive(:handle_network_error)
|
90
92
|
scheduler.should_not_receive(:reconnect_after_http_error_in)
|
91
93
|
scheduler.should_not_receive(:reconnect_after_network_error_in)
|
94
|
+
subject.close
|
92
95
|
subject.unbind
|
93
96
|
end
|
94
97
|
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: nervion
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0.
|
4
|
+
version: 0.0.2
|
5
5
|
prerelease:
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
@@ -9,7 +9,7 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date: 2012-06-
|
12
|
+
date: 2012-06-27 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: eventmachine
|
@@ -60,7 +60,7 @@ dependencies:
|
|
60
60
|
- !ruby/object:Gem::Version
|
61
61
|
version: 1.1.0
|
62
62
|
- !ruby/object:Gem::Dependency
|
63
|
-
name:
|
63
|
+
name: cucumber
|
64
64
|
requirement: !ruby/object:Gem::Requirement
|
65
65
|
none: false
|
66
66
|
requirements:
|
@@ -76,7 +76,23 @@ dependencies:
|
|
76
76
|
- !ruby/object:Gem::Version
|
77
77
|
version: '0'
|
78
78
|
- !ruby/object:Gem::Dependency
|
79
|
-
name:
|
79
|
+
name: rake
|
80
|
+
requirement: !ruby/object:Gem::Requirement
|
81
|
+
none: false
|
82
|
+
requirements:
|
83
|
+
- - ! '>='
|
84
|
+
- !ruby/object:Gem::Version
|
85
|
+
version: '0'
|
86
|
+
type: :development
|
87
|
+
prerelease: false
|
88
|
+
version_requirements: !ruby/object:Gem::Requirement
|
89
|
+
none: false
|
90
|
+
requirements:
|
91
|
+
- - ! '>='
|
92
|
+
- !ruby/object:Gem::Version
|
93
|
+
version: '0'
|
94
|
+
- !ruby/object:Gem::Dependency
|
95
|
+
name: rspec
|
80
96
|
requirement: !ruby/object:Gem::Requirement
|
81
97
|
none: false
|
82
98
|
requirements:
|
@@ -107,6 +123,22 @@ dependencies:
|
|
107
123
|
- - ! '>='
|
108
124
|
- !ruby/object:Gem::Version
|
109
125
|
version: '0'
|
126
|
+
- !ruby/object:Gem::Dependency
|
127
|
+
name: yard
|
128
|
+
requirement: !ruby/object:Gem::Requirement
|
129
|
+
none: false
|
130
|
+
requirements:
|
131
|
+
- - ! '>='
|
132
|
+
- !ruby/object:Gem::Version
|
133
|
+
version: '0'
|
134
|
+
type: :development
|
135
|
+
prerelease: false
|
136
|
+
version_requirements: !ruby/object:Gem::Requirement
|
137
|
+
none: false
|
138
|
+
requirements:
|
139
|
+
- - ! '>='
|
140
|
+
- !ruby/object:Gem::Version
|
141
|
+
version: '0'
|
110
142
|
description: A minimalistic Twitter Stream API Ruby client
|
111
143
|
email:
|
112
144
|
- j4cegu@gmail.com
|
@@ -117,13 +149,17 @@ files:
|
|
117
149
|
- .gitignore
|
118
150
|
- .rspec
|
119
151
|
- .rvmrc
|
152
|
+
- .travis.yml
|
153
|
+
- CHANGELOG.md
|
120
154
|
- Gemfile
|
121
155
|
- Gemfile.lock
|
122
156
|
- LICENSE
|
123
157
|
- README.md
|
124
158
|
- Rakefile
|
159
|
+
- features/callbacks.feature
|
160
|
+
- features/client_validation.feature
|
161
|
+
- features/step_definitions/client_validation_steps.rb
|
125
162
|
- features/step_definitions/streaming_steps.rb
|
126
|
-
- features/streaming.feature
|
127
163
|
- features/support/env.rb
|
128
164
|
- features/support/streaming_api_double.rb
|
129
165
|
- fixtures/responses.rb
|
@@ -133,7 +169,6 @@ files:
|
|
133
169
|
- lib/nervion/client.rb
|
134
170
|
- lib/nervion/configuration.rb
|
135
171
|
- lib/nervion/facade.rb
|
136
|
-
- lib/nervion/http_parser.rb
|
137
172
|
- lib/nervion/oauth_header.rb
|
138
173
|
- lib/nervion/oauth_signature.rb
|
139
174
|
- lib/nervion/percent_encoder.rb
|
@@ -141,19 +176,20 @@ files:
|
|
141
176
|
- lib/nervion/request.rb
|
142
177
|
- lib/nervion/stream.rb
|
143
178
|
- lib/nervion/stream_handler.rb
|
179
|
+
- lib/nervion/stream_parser.rb
|
144
180
|
- lib/nervion/version.rb
|
145
181
|
- nervion.gemspec
|
146
182
|
- spec/nervion/callback_table_spec.rb
|
147
183
|
- spec/nervion/client_spec.rb
|
148
184
|
- spec/nervion/configuration_spec.rb
|
149
185
|
- spec/nervion/facade_spec.rb
|
150
|
-
- spec/nervion/http_parser_spec.rb
|
151
186
|
- spec/nervion/oauth_header_spec.rb
|
152
187
|
- spec/nervion/oauth_signature_spec.rb
|
153
188
|
- spec/nervion/percent_encoder_spec.rb
|
154
189
|
- spec/nervion/reconnection_scheduler_spec.rb
|
155
190
|
- spec/nervion/request_spec.rb
|
156
191
|
- spec/nervion/stream_handler_spec.rb
|
192
|
+
- spec/nervion/stream_parser_spec.rb
|
157
193
|
- spec/nervion/stream_spec.rb
|
158
194
|
- spec/spec_helper.rb
|
159
195
|
homepage: https://github.com/jacegu/nervion
|
@@ -181,20 +217,23 @@ signing_key:
|
|
181
217
|
specification_version: 3
|
182
218
|
summary: ''
|
183
219
|
test_files:
|
220
|
+
- features/callbacks.feature
|
221
|
+
- features/client_validation.feature
|
222
|
+
- features/step_definitions/client_validation_steps.rb
|
184
223
|
- features/step_definitions/streaming_steps.rb
|
185
|
-
- features/streaming.feature
|
186
224
|
- features/support/env.rb
|
187
225
|
- features/support/streaming_api_double.rb
|
188
226
|
- spec/nervion/callback_table_spec.rb
|
189
227
|
- spec/nervion/client_spec.rb
|
190
228
|
- spec/nervion/configuration_spec.rb
|
191
229
|
- spec/nervion/facade_spec.rb
|
192
|
-
- spec/nervion/http_parser_spec.rb
|
193
230
|
- spec/nervion/oauth_header_spec.rb
|
194
231
|
- spec/nervion/oauth_signature_spec.rb
|
195
232
|
- spec/nervion/percent_encoder_spec.rb
|
196
233
|
- spec/nervion/reconnection_scheduler_spec.rb
|
197
234
|
- spec/nervion/request_spec.rb
|
198
235
|
- spec/nervion/stream_handler_spec.rb
|
236
|
+
- spec/nervion/stream_parser_spec.rb
|
199
237
|
- spec/nervion/stream_spec.rb
|
200
238
|
- spec/spec_helper.rb
|
239
|
+
has_rdoc:
|
@@ -1,26 +0,0 @@
|
|
1
|
-
require 'nervion/http_parser'
|
2
|
-
require 'fixtures/responses'
|
3
|
-
|
4
|
-
describe Nervion::HttpParser do
|
5
|
-
subject { described_class.new(json_parser) }
|
6
|
-
let(:json_parser) { stub(:json_parser).as_null_object }
|
7
|
-
|
8
|
-
it 'takes a JSON parser' do
|
9
|
-
subject.json_parser.should be json_parser
|
10
|
-
end
|
11
|
-
|
12
|
-
it 'can be reset' do
|
13
|
-
subject << RESPONSE_200
|
14
|
-
subject.reset!
|
15
|
-
expect { subject << RESPONSE_200 }.not_to raise_error Http::Parser::Error
|
16
|
-
end
|
17
|
-
|
18
|
-
it 'parses response body if the response status is 200' do
|
19
|
-
json_parser.should_receive(:<<).with(BODY_200)
|
20
|
-
subject << RESPONSE_200
|
21
|
-
end
|
22
|
-
|
23
|
-
it 'raises an error if the response status is above 200' do
|
24
|
-
expect { subject << RESPONSE_401 }.to raise_error Nervion::HttpError
|
25
|
-
end
|
26
|
-
end
|