nervion 0.0.1 → 0.0.2
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/.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 [][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
|