Class: ApnsGatling::Client

Inherits:
Object
  • Object
show all
Defined in:
lib/apns_gatling/apns_client.rb

Constant Summary collapse

DRAFT =
'h2'.freeze

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(team_id, auth_key_id, ecdsa_key, sandbox = false) ⇒ Client

Returns a new instance of Client.



14
15
16
17
18
19
20
# File 'lib/apns_gatling/apns_client.rb', line 14

def initialize(team_id, auth_key_id, ecdsa_key, sandbox = false)
  @token_maker = Token.new(team_id, auth_key_id, ecdsa_key)
  @sandbox = sandbox
  @mutex = Mutex.new
  @cv = ConditionVariable.new
  init_vars
end

Instance Attribute Details

#sandboxObject (readonly)

Returns the value of attribute sandbox.



12
13
14
# File 'lib/apns_gatling/apns_client.rb', line 12

def sandbox
  @sandbox
end

#tokenObject (readonly)

Returns the value of attribute token.



12
13
14
# File 'lib/apns_gatling/apns_client.rb', line 12

def token
  @token
end

#token_makerObject (readonly)

Returns the value of attribute token_maker.



12
13
14
# File 'lib/apns_gatling/apns_client.rb', line 12

def token_maker
  @token_maker
end

Instance Method Details

#closeObject



169
170
171
172
# File 'lib/apns_gatling/apns_client.rb', line 169

def close
  exit_thread(@socket_thread)
  init_vars
end

#connectionObject

connection



93
94
95
96
97
98
99
100
101
102
103
# File 'lib/apns_gatling/apns_client.rb', line 93

def connection
  @connection ||= HTTP2::Client.new.tap do |conn|
    conn.on(:frame) do |bytes|
      @mutex.synchronize do
        @socket.write bytes
        @socket.flush
        @first_data_sent = true
      end
    end
  end
end

#ensure_sent_before_receivingObject



147
148
149
150
151
# File 'lib/apns_gatling/apns_client.rb', line 147

def ensure_sent_before_receiving
  while !@first_data_sent
    sleep 0.01
  end
end

#ensure_socket_openObject

scoket



106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
# File 'lib/apns_gatling/apns_client.rb', line 106

def ensure_socket_open
  @mutex.synchronize do 
    return if @socket_thread
    @socket = new_socket
    @socket_thread = Thread.new do 
      begin 
        socket_loop
      rescue EOFError
        init_vars
        raise SocketError.new('Socket was remotely closed')
      rescue Exception => e
        init_vars
        raise e
      end
    end.tap { |t| t.abort_on_exception = true }
  end
end

#exit_thread(thread) ⇒ Object



174
175
176
177
178
# File 'lib/apns_gatling/apns_client.rb', line 174

def exit_thread(thread)
  return unless thread
  thread.exit
  thread.join
end

#hostObject



46
47
48
49
50
51
52
# File 'lib/apns_gatling/apns_client.rb', line 46

def host
  if sandbox
    APPLE_DEVELOPMENT_SERVER
  else
    APPLE_PRODUCTION_SERVER
  end
end

#init_varsObject



22
23
24
25
26
27
28
29
30
31
# File 'lib/apns_gatling/apns_client.rb', line 22

def init_vars
  @mutex.synchronize do 
    @socket.close if @socket && !@socket.closed?
    @socket = nil
    @socket_thread = nil
    @first_data_sent = false
    @token_generated_at = 0
    @blocking = true
  end
end

#joinObject



180
181
182
# File 'lib/apns_gatling/apns_client.rb', line 180

def join
  @socket_thread.join if @socket_thread
end

#new_socketObject



124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
# File 'lib/apns_gatling/apns_client.rb', line 124

def new_socket
  ctx = OpenSSL::SSL::SSLContext.new
  ctx.verify_mode = OpenSSL::SSL::VERIFY_NONE

  # For ALPN support, Ruby >= 2.3 and OpenSSL >= 1.0.2 are required
  ctx.alpn_protocols = [DRAFT]
  ctx.alpn_select_cb = lambda do |protocols|
    DRAFT if protocols.include? DRAFT
  end

  tcp = TCPSocket.new(host, 443)
  socket = OpenSSL::SSL::SSLSocket.new(tcp, ctx)
  socket.sync_close = true
  socket.hostname = host
  socket.connect

  if socket.alpn_protocol != DRAFT
    puts "Failed to negotiate #{DRAFT} via ALPN"
    exit
  end
  socket
end

#provider_tokenObject



33
34
35
36
37
38
39
40
41
42
43
44
# File 'lib/apns_gatling/apns_client.rb', line 33

def provider_token
  timestamp = Time.new.to_i
  if timestamp - @token_generated_at > 3550
    @mutex.synchronize do 
      @token_generated_at = timestamp
      @token = @token_maker.new_token
    end
    @token
  else
    @token
  end
end

#push(message) ⇒ Object

push message



55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
# File 'lib/apns_gatling/apns_client.rb', line 55

def push(message)
  request = Request.new(message, provider_token, host)
  response = Response.new
  ensure_socket_open

  begin
    stream = connection.new_stream
  rescue StandardError => e
    close
    raise e
  end

  stream.on(:close) do
    @mutex.synchronize do 
      @token_generated_at = 0 if response.status == '403' && response.error[:reason] == 'ExpiredProviderToken' 
      if @blocking 
        @blocking = false
        @cv.signal
      end
    end
    yield response
  end

  stream.on(:headers) do |h|
    hs = Hash[*h.flatten]
    response.headers.merge!(hs)
  end

  stream.on(:data) do |d|
    response.data << d
  end

  stream.headers(request.headers, end_stream: false)
  stream.data(request.data)
  @mutex.synchronize { @cv.wait(@mutex, 60) } if @blocking
end

#socket_loopObject



153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
# File 'lib/apns_gatling/apns_client.rb', line 153

def socket_loop
  ensure_sent_before_receiving
  loop do
    begin
      data = @socket.read_nonblock(1024)
      connection << data # in
    rescue IO::WaitReadable
      IO.select([@socket])
      retry
    rescue IO::WaitWritable
      IO.select(nil, [@socket])
      retry
    end
  end
end