Class: Lowdown::Client

Inherits:
Object
  • Object
show all
Defined in:
lib/lowdown/client.rb,
lib/lowdown/client/request_group.rb

Overview

The main class to use for interactions with the Apple Push Notification HTTP/2 service.

Important connection configuration options are pool_size and keep_alive. The former specifies the number of simultaneous connections the client should make and the latter is key for long running processes.

Defined Under Namespace

Classes: RequestGroup

Constant Summary collapse

DEVELOPMENT_URI =

The details to connect to the development (sandbox) environment version of the APN service.

URI.parse("https://api.development.push.apple.com:443")
PRODUCTION_URI =

The details to connect to the production environment version of the APN service.

URI.parse("https://api.push.apple.com:443")
DEFAULT_GROUP_TIMEOUT =

The default timeout for #group.

3600

Instance Attribute Summary collapse

Constructor Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(connection:, default_topic: nil) ⇒ Client

You should normally use any of the other constructors to create a Client object.

Parameters:

  • connection (Connection, Celluloid::Supervision::Container::Pool<Connection>)

    a single Connection or a pool of Connection actors configured to connect to the remote service.

  • default_topic (String) (defaults to: nil)

    the ‘topic’ to use if the Certificate is a Universal Certificate and a Notification doesn’t explicitely provide one.



128
129
130
# File 'lib/lowdown/client.rb', line 128

def initialize(connection:, default_topic: nil)
  @connection, @default_topic = connection, default_topic
end

Instance Attribute Details

#connectionConnection, Celluloid::Supervision::Container::Pool<Connection> (readonly)

Returns a single Connection or a pool of Connection actors configured to connect to the remote service.

Returns:

  • (Connection, Celluloid::Supervision::Container::Pool<Connection>)

    a single Connection or a pool of Connection actors configured to connect to the remote service.



137
138
139
# File 'lib/lowdown/client.rb', line 137

def connection
  @connection
end

#default_topicString? (readonly)

Returns the ‘topic’ to use if the Certificate is a Universal Certificate and a Notification doesn’t explicitely provide one.

Returns:

  • (String, nil)

    the ‘topic’ to use if the Certificate is a Universal Certificate and a Notification doesn’t explicitely provide one.



143
144
145
# File 'lib/lowdown/client.rb', line 143

def default_topic
  @default_topic
end

Class Method Details

.client(uri:, certificate:, pool_size: 1, keep_alive: false, connection_class: Connection) ⇒ Client

Creates a connection pool that connects to the specified uri.

Parameters:

  • uri (URI)

    the endpoint details of the service to connect to.

  • certificate (Certificate, String)

    a configured Certificate or PEM data to construct a Certificate from.

  • pool_size (Fixnum) (defaults to: 1)

    the number of connections to make.

  • keep_alive (Boolean) (defaults to: false)

    when true this will make connections, new and restarted, immediately connect to the remote service. Use this if you want to keep connections open indefinitely.

  • connection_class (Class) (defaults to: Connection)

    the connection class to instantiate, this can for instan be Mock::Connection during testing.

Returns:

  • (Client)

    a new instance of Client.



94
95
96
97
98
99
# File 'lib/lowdown/client.rb', line 94

def self.client(uri:, certificate:, pool_size: 1, keep_alive: false, connection_class: Connection)
  certificate = Certificate.certificate(certificate)
  connection_class ||= Connection
  connection_pool = connection_class.pool(size: pool_size, args: [uri, certificate.ssl_context, keep_alive])
  client_with_connection(connection_pool, certificate: certificate)
end

.client_with_connection(connection, certificate:) ⇒ Client

Creates a Client configured with the app_bundle_id as its default_topic, in case the Certificate represents a Universal Certificate.

Parameters:

  • connection (Connection, Celluloid::Supervision::Container::Pool<Connection>)

    a single Connection or a pool of Connection actors configured to connect to the remote service.

  • certificate (Certificate)

    a configured Certificate.

Returns:

  • (Client)

    a new instance of Client.



112
113
114
# File 'lib/lowdown/client.rb', line 112

def self.client_with_connection(connection, certificate:)
  new(connection: connection, default_topic: certificate.universal? ? certificate.topics.first : nil)
end

.production(production, certificate:, pool_size: 1, keep_alive: false, connection_class: Connection) ⇒ Client

This is the most convenient constructor for regular use.

Parameters:

  • production (Boolean)

    whether to use the production or the development environment.

  • certificate (Certificate, String)

    a configured Certificate or PEM data to construct a Certificate from.

  • pool_size (Fixnum) (defaults to: 1)

    the number of connections to make.

  • keep_alive (Boolean) (defaults to: false)

    when true this will make connections, new and restarted, immediately connect to the remote service. Use this if you want to keep connections open indefinitely.

  • connection_class (Class) (defaults to: Connection)

    the connection class to instantiate, this can for instan be Mock::Connection during testing.

Returns:

  • (Client)

    a new instance of Client.

Raises:

  • (ArgumentError)

    raised if the provided Certificate does not support the requested environment.



56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
# File 'lib/lowdown/client.rb', line 56

def self.production(production, certificate:, pool_size: 1, keep_alive: false, connection_class: Connection)
  certificate = Certificate.certificate(certificate)
  if production
    unless certificate.production?
      raise ArgumentError, "The specified certificate is not usable with the production environment."
    end
  else
    unless certificate.development?
      raise ArgumentError, "The specified certificate is not usable with the development environment."
    end
  end
  client(uri: production ? PRODUCTION_URI : DEVELOPMENT_URI,
         certificate: certificate,
         pool_size: pool_size,
         keep_alive: keep_alive,
         connection_class: connection_class)
end

Instance Method Details

#connect(group_timeout: nil) {|group| ... } ⇒ void

Note:

Don’t use this if you opted to keep a pool of connections alive.

This method returns an undefined value.

Opens the connection to the service, yields a request group, and automatically closes the connection by the end of the block.

Parameters:

  • group_timeout (Numeric) (defaults to: nil)

    the maximum amount of time to wait for a request group to halt the caller thread. Defaults to 1 hour.

Yield Parameters:

See Also:



162
163
164
165
166
167
168
169
170
171
172
173
174
175
# File 'lib/lowdown/client.rb', line 162

def connect(group_timeout: nil, &block)
  if @connection.respond_to?(:actors)
    @connection.actors.each { |connection| connection.async.connect }
  else
    @connection.async.connect
  end
  if block
    begin
      group(timeout: group_timeout, &block)
    ensure
      disconnect
    end
  end
end

#disconnectvoid

This method returns an undefined value.

Closes the connection to the service.



183
184
185
186
187
188
189
190
191
# File 'lib/lowdown/client.rb', line 183

def disconnect
  if @connection.respond_to?(:actors)
    @connection.actors.each do |connection|
      connection.async.disconnect if connection.alive?
    end
  else
    @connection.async.disconnect if @connection.alive?
  end
end

#group(timeout: nil) {|group| ... } ⇒ void

Note:

Do not share the yielded group across threads.

This method returns an undefined value.

Use this to group a batch of requests and halt the caller thread until all of the requests in the group have been performed.

It proxies Lowdown::Client::RequestGroup#send_notification to #send_notification, but, unlike the latter, the request callbacks are provided in the form of a block.

Parameters:

  • timeout (Numeric) (defaults to: nil)

    the maximum amount of time to wait for a request group to halt the caller thread. Defaults to 1 hour.

Yield Parameters:

Raises:

  • (Exception)

    if a connection in the pool has died during the execution of this group, the reason for its death will be raised.

See Also:



216
217
218
219
220
221
222
223
224
225
226
227
# File 'lib/lowdown/client.rb', line 216

def group(timeout: nil)
  group = nil
  monitor do |condition|
    group = RequestGroup.new(self, condition)
    yield group
    if !group.empty? && exception = condition.wait(timeout || DEFAULT_GROUP_TIMEOUT)
      raise exception
    end
  end
ensure
  group.terminate
end

#monitor {|condition| ... } ⇒ void

This method returns an undefined value.

Registers a condition object with the connection pool, for the duration of the given block. It either returns an exception that caused a connection to die, or whatever value you signal to it.

This is automatically used by #group.

Yield Parameters:



239
240
241
242
243
244
245
246
247
248
249
250
251
# File 'lib/lowdown/client.rb', line 239

def monitor
  condition = Connection::Monitor::Condition.new
  if defined?(Mock::Connection) && @connection.class == Mock::Connection
    yield condition
  else
    begin
      @connection.__register_lowdown_crash_condition__(condition)
      yield condition
    ensure
      @connection.__deregister_lowdown_crash_condition__(condition)
    end
  end
end

#send_notification(notification, delegate:, context: nil) ⇒ void

Note:

In general, you will probably want to use #group to be able to use Lowdown::Client::RequestGroup#send_notification, which takes a traditional blocks-based callback approach.

This method returns an undefined value.

Verifies the notification is valid and then sends it to the remote service. Response feedback is provided via a delegate mechanism.

Parameters:

  • notification (Notification)

    the notification object whose data to send to the service.

  • delegate (Connection::DelegateProtocol)

    an object that implements the connection delegate protocol.

  • context (Object, nil) (defaults to: nil)

    any object that you want to be passed to the delegate once the response is back.

Raises:

See Also:



275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
# File 'lib/lowdown/client.rb', line 275

def send_notification(notification, delegate:, context: nil)
  raise ArgumentError, "Invalid notification: #{notification.inspect}" unless notification.valid?

  topic = notification.topic || @default_topic
  headers = {}
  headers["apns-expiration"] = (notification.expiration || 0).to_i
  headers["apns-id"]         = notification.formatted_id
  headers["apns-priority"]   = notification.priority     if notification.priority
  headers["apns-topic"]      = topic                     if topic

  body = notification.formatted_payload.to_json

  @connection.async.post(path: "/3/device/#{notification.token}",
                         headers: headers,
                         body: body,
                         delegate: delegate,
                         context: context)
end