lowdown 0.3.0 → 0.3.1
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.
- checksums.yaml +4 -4
- data/.rubocop.yml +6 -0
- data/README.md +38 -2
- data/examples/long-running.rb +4 -4
- data/examples/simple.rb +26 -16
- data/lib/lowdown/client.rb +7 -3
- data/lib/lowdown/connection.rb +24 -9
- data/lib/lowdown/mock.rb +4 -0
- data/lib/lowdown/version.rb +1 -1
- metadata +3 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 658a05f8102498326e20e21fff2c403ff597bbf7
|
4
|
+
data.tar.gz: 2274644492c37bdfe02681faccf3128b6297b0d3
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 610fe096be572adaaf73453bb1a12fcbd6b4b64da59a0110fbe876f4a4d18e10174cc9ecc2fd50912577a0b98cf6c94cf241b605acbbf11e100ed89183bcaf8a
|
7
|
+
data.tar.gz: 53b1305c9bccf953d3cfe174cd785f77a3000b3cc37e7a1c7a3c7bffd3b64d6c5e8318bf925c0610e4b62aab01ece0c43c1089c35b9955bc1ff9f614cc8444e0
|
data/.rubocop.yml
CHANGED
@@ -110,6 +110,12 @@ Style/ParallelAssignment:
|
|
110
110
|
Style/StringLiterals:
|
111
111
|
EnforcedStyle: double_quotes
|
112
112
|
|
113
|
+
Style/StringLiteralsInInterpolation:
|
114
|
+
EnforcedStyle: double_quotes
|
115
|
+
|
116
|
+
Style/FormatString:
|
117
|
+
EnforcedStyle: percent
|
118
|
+
|
113
119
|
Style/StructInheritance:
|
114
120
|
Enabled: false
|
115
121
|
|
data/README.md
CHANGED
@@ -4,7 +4,7 @@
|
|
4
4
|
|
5
5
|
[](https://travis-ci.org/alloy/lowdown)
|
6
6
|
|
7
|
-
NOTE: _This is not battle-tested yet
|
7
|
+
⚠︎ NOTE: _This is not battle-tested yet, which will follow over the next few weeks. A v1 will be released at that time._
|
8
8
|
|
9
9
|
Lowdown is a Ruby client for the HTTP/2 version of the Apple Push Notification Service.
|
10
10
|
|
@@ -74,6 +74,8 @@ end
|
|
74
74
|
|
75
75
|
### Persistent connection
|
76
76
|
|
77
|
+
⚠︎ NOTE: _See the ‘Gotchas’ section, specifically about process forking._
|
78
|
+
|
77
79
|
The trick to creating a persistent connection is to specify the `keep_alive: true` option when creating the client:
|
78
80
|
|
79
81
|
```ruby
|
@@ -134,7 +136,7 @@ Keep in mind that, like with the block version, this message is sent on the grou
|
|
134
136
|
|
135
137
|
While we’re on the topic of threading anyways, here’s an important thing to keep in mind; each set of `group` callbacks
|
136
138
|
is performed on its own thread. It is thus _your_ responsibility to take this into account. E.g. if you are planning to
|
137
|
-
update a DB model with the status of a notification delivery, be sure to respect the
|
139
|
+
update a DB model with the status of a notification delivery, be sure to respect the threading rules of your DB client,
|
138
140
|
which usually means to not re-use models that were loaded on a different thread.
|
139
141
|
|
140
142
|
A simple approach to this is by passing the data you need to be able to update the DB as a `context`, which can be any
|
@@ -161,6 +163,37 @@ increase this with the `pool_size` option:
|
|
161
163
|
Lowdown::Client.production(true, File.read("path/to/certificate.pem"), pool_size: 3)
|
162
164
|
```
|
163
165
|
|
166
|
+
## Gotchas
|
167
|
+
|
168
|
+
* If you’re forking your process, be sure to **not** load lowdown before forking (because this [does not work well with
|
169
|
+
Celluloid](https://github.com/celluloid/celluloid/wiki/Gotchas#do-not-create-actors-prior-to-forking-the-process), or
|
170
|
+
with threading and Ruby in general).
|
171
|
+
|
172
|
+
Forking is done by, e.g. [Spring][spring] and [DelayedJob][delayed_job], when daemonizing workers. In practice, this
|
173
|
+
means that e.g. you should not initialize a client from a Rails initializer, but rather do it lazily when it’s really
|
174
|
+
required. E.g.:
|
175
|
+
|
176
|
+
```ruby
|
177
|
+
class PushNotificationService
|
178
|
+
def initialize(certificate_path)
|
179
|
+
@certificate_path = certificate_path
|
180
|
+
@client_mutex = Mutex.new
|
181
|
+
end
|
182
|
+
|
183
|
+
def client
|
184
|
+
client = nil
|
185
|
+
@client_mutex.synchronize do
|
186
|
+
@client ||= Lowdown::Client.production(true, File.read(certificate_path), keep_alive: true)
|
187
|
+
client = @client
|
188
|
+
end
|
189
|
+
client
|
190
|
+
end
|
191
|
+
end
|
192
|
+
|
193
|
+
# In your initializer:
|
194
|
+
PUSH_NOTIFICATION_SERVICE = PushNotificationService.new("path/to/certificate.pem")
|
195
|
+
```
|
196
|
+
|
164
197
|
## Related tool ☞
|
165
198
|
|
166
199
|
Also checkout [this library](https://github.com/alloy/time_zone_scheduler) for scheduling across time zones.
|
@@ -177,3 +210,6 @@ The gem is available as open source under the terms of the [MIT License](http://
|
|
177
210
|
[notification]: http://www.rubydoc.info/gems/lowdown/Lowdown/Notification
|
178
211
|
[group]: http://www.rubydoc.info/gems/lowdown/Lowdown/RequestGroup
|
179
212
|
[delegate]: http://www.rubydoc.info/gems/lowdown/Lowdown/Connection/DelegateProtocol
|
213
|
+
|
214
|
+
[spring]: https://github.com/rails/spring
|
215
|
+
[delayed_job]: https://github.com/collectiveidea/delayed_job
|
data/examples/long-running.rb
CHANGED
@@ -44,9 +44,6 @@ loop do
|
|
44
44
|
end
|
45
45
|
end.each(&:join)
|
46
46
|
|
47
|
-
logger.info "Sleep for 5 seconds"
|
48
|
-
sleep(5)
|
49
|
-
|
50
47
|
rescue Interrupt
|
51
48
|
logger.info "[!] Interrupt, exiting"
|
52
49
|
break
|
@@ -54,8 +51,11 @@ loop do
|
|
54
51
|
rescue Exception => e
|
55
52
|
logger.error "[!] Exception occurred, re-trying in 1 second: #{e.inspect}\n\t#{e.backtrace.join("\n\t")}"
|
56
53
|
sleep 1
|
57
|
-
redo
|
58
54
|
end
|
55
|
+
|
56
|
+
GC.start
|
57
|
+
logger.info "Sleep for 5 seconds"
|
58
|
+
sleep(5)
|
59
59
|
end
|
60
60
|
|
61
61
|
client.disconnect
|
data/examples/simple.rb
CHANGED
@@ -11,27 +11,37 @@ production = environment == "production"
|
|
11
11
|
|
12
12
|
# $CELLULOID_DEBUG = true
|
13
13
|
# Celluloid.logger.level = Logger::DEBUG
|
14
|
-
|
15
|
-
Celluloid.logger.level = Logger::ERROR
|
14
|
+
Celluloid.logger.level = Logger::INFO
|
15
|
+
# Celluloid.logger.level = Logger::ERROR
|
16
16
|
|
17
|
-
|
18
|
-
start = nil
|
17
|
+
client = Lowdown::Client.production(production, certificate: File.read(cert_file), pool_size: 2)
|
19
18
|
|
20
19
|
# The block form of Client#connect flushes and closes the connection at the end of the block.
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
20
|
+
loop do
|
21
|
+
begin
|
22
|
+
client.connect do |group|
|
23
|
+
600.times do
|
24
|
+
notification = Lowdown::Notification.new(:token => device_token)
|
25
|
+
notification.payload = { :alert => "Hello HTTP/2! ID=#{notification.id}" }
|
26
|
+
group.send_notification(notification) do |response|
|
27
|
+
if response.success?
|
28
|
+
puts "Sent notification with ID: #{notification.id}"
|
29
|
+
else
|
30
|
+
puts "[!] (##{response.id}): #{response}"
|
31
|
+
end
|
32
|
+
end
|
31
33
|
end
|
32
34
|
end
|
35
|
+
rescue Interrupt
|
36
|
+
puts "[!] Interrupt, exiting"
|
37
|
+
break
|
38
|
+
|
39
|
+
rescue Exception => e
|
40
|
+
puts "[!] Error occurred: #{e.message}"
|
33
41
|
end
|
34
|
-
end
|
35
42
|
|
36
|
-
|
43
|
+
GC.start
|
44
|
+
puts "Sleep for 5 seconds"
|
45
|
+
sleep(5)
|
46
|
+
end
|
37
47
|
|
data/lib/lowdown/client.rb
CHANGED
@@ -181,9 +181,13 @@ module Lowdown
|
|
181
181
|
# @return [void]
|
182
182
|
#
|
183
183
|
def disconnect
|
184
|
-
@connection.
|
185
|
-
|
186
|
-
|
184
|
+
if @connection.respond_to?(:actors)
|
185
|
+
@connection.actors.each do |connection|
|
186
|
+
connection.async.disconnect if connection.alive?
|
187
|
+
end
|
188
|
+
else
|
189
|
+
@connection.async.disconnect if @connection.alive?
|
190
|
+
end
|
187
191
|
end
|
188
192
|
|
189
193
|
# Use this to group a batch of requests and halt the caller thread until all of the requests in the group have been
|
data/lib/lowdown/connection.rb
CHANGED
@@ -63,9 +63,15 @@ module Lowdown
|
|
63
63
|
HEARTBEAT_TIMEOUT = CONNECT_TIMEOUT
|
64
64
|
|
65
65
|
include Celluloid::IO
|
66
|
-
include Celluloid::Internals::Logger
|
67
66
|
finalizer :disconnect
|
68
67
|
|
68
|
+
include Celluloid::Internals::Logger
|
69
|
+
%w(debug info warn error fatal unknown).each do |level|
|
70
|
+
define_method(level) do |message|
|
71
|
+
super("[APNS Connection##{"0x%x" % (object_id << 1)}] #{message}")
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
69
75
|
# @param [URI, String] uri
|
70
76
|
# the details to connect to the APN service.
|
71
77
|
#
|
@@ -109,7 +115,10 @@ module Lowdown
|
|
109
115
|
# @return [void]
|
110
116
|
#
|
111
117
|
def disconnect
|
112
|
-
|
118
|
+
if @connection
|
119
|
+
info "Closing..."
|
120
|
+
@connection.close
|
121
|
+
end
|
113
122
|
@heartbeat.cancel if @heartbeat
|
114
123
|
reset_state!
|
115
124
|
end
|
@@ -146,7 +155,7 @@ module Lowdown
|
|
146
155
|
return if @connection
|
147
156
|
@connecting = true
|
148
157
|
|
149
|
-
info "
|
158
|
+
info "Connecting..."
|
150
159
|
|
151
160
|
# Celluloid::IO::DNSResolver bug. In case there is no connection at all:
|
152
161
|
# 1. This results in `nil`:
|
@@ -232,17 +241,18 @@ module Lowdown
|
|
232
241
|
@max_stream_count = @http.remote_settings[:settings_max_concurrent_streams]
|
233
242
|
@connected = true
|
234
243
|
|
235
|
-
|
236
|
-
|
244
|
+
info "Connection established."
|
245
|
+
debug "Maximum number of streams: #{@max_stream_count}. Flushing #{@request_queue.size} enqueued requests."
|
237
246
|
|
238
247
|
@request_queue.size.times do
|
239
248
|
async.try_to_perform_request!
|
240
249
|
end
|
241
250
|
|
242
251
|
@heartbeat = every(HEARTBEAT_INTERVAL) do
|
243
|
-
debug "Sending heartbeat ping"
|
252
|
+
debug "Sending heartbeat ping..."
|
244
253
|
begin
|
245
254
|
future.ping.call(HEARTBEAT_TIMEOUT)
|
255
|
+
debug "Got heartbeat reply."
|
246
256
|
rescue Celluloid::TimedOut
|
247
257
|
raise TimedOut, "Heartbeat ping timed-out."
|
248
258
|
end
|
@@ -316,17 +326,22 @@ module Lowdown
|
|
316
326
|
|
317
327
|
def try_to_perform_request!
|
318
328
|
unless @connected
|
319
|
-
|
329
|
+
unless @warned_about_not_connected
|
330
|
+
warn "Defer performing request, because the connection has not been established yet."
|
331
|
+
@warned_about_not_connected = true
|
332
|
+
end
|
320
333
|
return
|
334
|
+
else
|
335
|
+
@warned_about_not_connected = false
|
321
336
|
end
|
322
337
|
|
323
338
|
unless @http.active_stream_count < @max_stream_count
|
324
|
-
debug "Defer performing request, because the maximum concurren stream count has been reached"
|
339
|
+
debug "Defer performing request, because the maximum concurren stream count has been reached."
|
325
340
|
return
|
326
341
|
end
|
327
342
|
|
328
343
|
unless request = @request_queue.shift
|
329
|
-
debug "Defer performing request, because the request queue is empty"
|
344
|
+
debug "Defer performing request, because the request queue is empty."
|
330
345
|
return
|
331
346
|
end
|
332
347
|
|
data/lib/lowdown/mock.rb
CHANGED
data/lib/lowdown/version.rb
CHANGED
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: lowdown
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.3.
|
4
|
+
version: 0.3.1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Eloy Durán
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2016-
|
11
|
+
date: 2016-02-09 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: http-2
|
@@ -132,7 +132,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
132
132
|
version: '0'
|
133
133
|
requirements: []
|
134
134
|
rubyforge_project:
|
135
|
-
rubygems_version: 2.5.1
|
135
|
+
rubygems_version: 2.4.5.1
|
136
136
|
signing_key:
|
137
137
|
specification_version: 4
|
138
138
|
summary: A Ruby client for the HTTP/2 version of the Apple Push Notification Service.
|