client_for_poslynx 0.9.0 → 1.0.0.pre
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +8 -8
- data/README.md +172 -147
- data/examples/em_protocol_example.rb +82 -0
- data/examples/em_session_basic_example.rb +85 -0
- data/examples/em_session_interrupt_example.rb +155 -0
- data/lib/client_for_poslynx/data/requests/abstract_request.rb +6 -0
- data/lib/client_for_poslynx/net.rb +2 -1
- data/lib/client_for_poslynx/net/em_connector.rb +344 -0
- data/lib/client_for_poslynx/net/em_connector/callback_map.rb +50 -0
- data/lib/client_for_poslynx/net/em_connector/connection_handler.rb +14 -0
- data/lib/client_for_poslynx/net/em_connector/connector_state.rb +24 -0
- data/lib/client_for_poslynx/net/em_connector/event_dispatcher.rb +66 -0
- data/lib/client_for_poslynx/net/em_connector/handles_connection.rb +57 -0
- data/lib/client_for_poslynx/net/em_connector/request_call.rb +27 -0
- data/lib/client_for_poslynx/net/em_protocol.rb +1 -1
- data/lib/client_for_poslynx/net/em_session.rb +250 -0
- data/lib/client_for_poslynx/version.rb +1 -1
- data/spec/client_for_poslynx/net/em_connector_spec.rb +624 -0
- data/spec/client_for_poslynx/net/em_session_spec.rb +313 -0
- metadata +19 -8
- data/lib/client_for_poslynx/net/structured_client.rb +0 -104
- data/lib/client_for_poslynx/net/structured_client/em_connection.rb +0 -72
- data/spec/client_for_poslynx/net/structured_client_spec.rb +0 -118
@@ -0,0 +1,50 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
|
3
|
+
module ClientForPoslynx
|
4
|
+
module Net
|
5
|
+
class EM_Connector
|
6
|
+
|
7
|
+
def self.CallbackMap(*args)
|
8
|
+
if args.length == 1 && EMC::CallbackMap === args.first
|
9
|
+
return args.first
|
10
|
+
else
|
11
|
+
EMC::CallbackMap.new( *args )
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
class CallbackMap
|
16
|
+
def initialize(callable_map={})
|
17
|
+
@callable_map = callable_map
|
18
|
+
end
|
19
|
+
|
20
|
+
def ==(other)
|
21
|
+
callable_map == other.callable_map
|
22
|
+
end
|
23
|
+
|
24
|
+
def [](callback_key)
|
25
|
+
callable_map[callback_key]
|
26
|
+
end
|
27
|
+
|
28
|
+
def to_hash
|
29
|
+
callable_map.dup
|
30
|
+
end
|
31
|
+
|
32
|
+
def merge(other)
|
33
|
+
self.class.new(
|
34
|
+
callable_map.merge( other.to_hash )
|
35
|
+
)
|
36
|
+
end
|
37
|
+
|
38
|
+
def call(callback_key, *args)
|
39
|
+
callback = callable_map[callback_key]
|
40
|
+
callback.call *args if callback
|
41
|
+
end
|
42
|
+
|
43
|
+
protected
|
44
|
+
|
45
|
+
attr_reader :callable_map
|
46
|
+
end
|
47
|
+
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
|
3
|
+
module ClientForPoslynx
|
4
|
+
module Net
|
5
|
+
class EM_Connector
|
6
|
+
|
7
|
+
class State < Struct.new( :encryption, :connection, :connection_status, :status_of_request )
|
8
|
+
|
9
|
+
def connection_initial? ; connection_status == :initial ; end
|
10
|
+
def connecting? ; connection_status == :connecting ; end
|
11
|
+
def connected? ; connection_status == :connected ; end
|
12
|
+
def disconnecting? ; connection_status == :disconnecting ; end
|
13
|
+
def disconnecting? ; connection_status == :disconnected ; end
|
14
|
+
|
15
|
+
def request_initial? ; status_of_request == :initial ; end
|
16
|
+
def request_pending? ; status_of_request == :pending ; end
|
17
|
+
def got_response? ; status_of_request == :got_response ; end
|
18
|
+
def request_failed? ; status_of_request == :failed ; end
|
19
|
+
|
20
|
+
end
|
21
|
+
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
@@ -0,0 +1,66 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
|
3
|
+
module ClientForPoslynx
|
4
|
+
module Net
|
5
|
+
class EM_Connector
|
6
|
+
|
7
|
+
class EventDispatcher
|
8
|
+
NULL_LISTENER = ->(*) { }
|
9
|
+
|
10
|
+
class << self
|
11
|
+
private :new
|
12
|
+
|
13
|
+
def null(connection)
|
14
|
+
new( connection )
|
15
|
+
end
|
16
|
+
|
17
|
+
def for_connect(connection, opts)
|
18
|
+
original_dispatcher = opts[:original_dispatcher]
|
19
|
+
callback_map = EMC.CallbackMap(
|
20
|
+
connection_completed: opts[:on_success],
|
21
|
+
unbind: opts[:on_failure],
|
22
|
+
)
|
23
|
+
new( connection, original_dispatcher, callback_map )
|
24
|
+
end
|
25
|
+
|
26
|
+
def for_disconnect(connection, opts)
|
27
|
+
callback_map = EMC.CallbackMap(
|
28
|
+
unbind: opts[:on_completed]
|
29
|
+
)
|
30
|
+
new( connection, nil, callback_map )
|
31
|
+
end
|
32
|
+
|
33
|
+
def for_send_request(connection, opts)
|
34
|
+
callback_map = EMC.CallbackMap(
|
35
|
+
receive_response: opts[:on_response],
|
36
|
+
unbind: opts[:on_failure],
|
37
|
+
)
|
38
|
+
new( connection, nil, callback_map )
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
def initialize(connection, original_dispatcher = nil, callback_map = EMC.CallbackMap())
|
43
|
+
@connection = connection
|
44
|
+
@original_dispatcher = original_dispatcher
|
45
|
+
@callback_map = callback_map
|
46
|
+
end
|
47
|
+
|
48
|
+
def []=(event_type, callback)
|
49
|
+
callback_map[event_type] = callback
|
50
|
+
end
|
51
|
+
|
52
|
+
def event_occurred(event_type, *args)
|
53
|
+
connection.reset_event_dispatcher
|
54
|
+
original_dispatcher.event_occurred( event_type, *args ) if original_dispatcher
|
55
|
+
callback_map.call event_type, *args
|
56
|
+
end
|
57
|
+
|
58
|
+
private
|
59
|
+
|
60
|
+
attr_reader :connection, :original_dispatcher, :callback_map
|
61
|
+
|
62
|
+
end
|
63
|
+
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
@@ -0,0 +1,57 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
|
3
|
+
module ClientForPoslynx
|
4
|
+
module Net
|
5
|
+
class EM_Connector
|
6
|
+
|
7
|
+
module HandlesConnection
|
8
|
+
def initialize(connector_state)
|
9
|
+
@connector_state = connector_state
|
10
|
+
connector_state.connection = self
|
11
|
+
connector_state.connection_status = :connecting
|
12
|
+
end
|
13
|
+
|
14
|
+
attr_writer :event_dispatcher
|
15
|
+
|
16
|
+
def event_dispatcher
|
17
|
+
@event_dispatcher ||= EMC::EventDispatcher.null( self )
|
18
|
+
end
|
19
|
+
|
20
|
+
def reset_event_dispatcher
|
21
|
+
self.event_dispatcher = nil
|
22
|
+
end
|
23
|
+
|
24
|
+
def connection_completed
|
25
|
+
if connector_state.encryption == :use_ssl
|
26
|
+
start_tls
|
27
|
+
else
|
28
|
+
connector_state.connection_status = :connected
|
29
|
+
event_dispatcher.event_occurred :connection_completed
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
def ssl_handshake_completed
|
34
|
+
connector_state.connection_status = :connected
|
35
|
+
event_dispatcher.event_occurred :connection_completed
|
36
|
+
end
|
37
|
+
|
38
|
+
def unbind
|
39
|
+
connector_state.connection_status = :disconnected
|
40
|
+
connector_state.status_of_request = :failed if connector_state.request_pending?
|
41
|
+
event_dispatcher.event_occurred :unbind
|
42
|
+
end
|
43
|
+
|
44
|
+
def receive_response(response_data)
|
45
|
+
connector_state.status_of_request = :got_response if connector_state.request_pending?
|
46
|
+
event_dispatcher.event_occurred :receive_response, response_data
|
47
|
+
end
|
48
|
+
|
49
|
+
private
|
50
|
+
|
51
|
+
attr_reader :connector_state
|
52
|
+
|
53
|
+
end
|
54
|
+
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
|
3
|
+
module ClientForPoslynx
|
4
|
+
module Net
|
5
|
+
class EM_Connector
|
6
|
+
|
7
|
+
def self.RequestCall(*args)
|
8
|
+
if args.length == 1 && self::RequestCall === args.first
|
9
|
+
args.first
|
10
|
+
elsif args.first.nil?
|
11
|
+
RequestCall.new(nil, {})
|
12
|
+
else
|
13
|
+
RequestCall.new( *args )
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
class RequestCall < Struct.new(:request_data, :result_callbacks)
|
18
|
+
def initialize(request_data, result_callbacks={})
|
19
|
+
result_callbacks = EMC.CallbackMap( result_callbacks )
|
20
|
+
super request_data, result_callbacks
|
21
|
+
freeze
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,250 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
|
3
|
+
module ClientForPoslynx
|
4
|
+
module Net
|
5
|
+
|
6
|
+
# Provides a synchronous, structured API for making
|
7
|
+
# requests to POSLynx host using Event Machine and returning
|
8
|
+
# responses when received.
|
9
|
+
#
|
10
|
+
# Eliminates the need for complicated and messy event
|
11
|
+
# callback chains in POSLynx client code.
|
12
|
+
#
|
13
|
+
# Multiple sessions may be in effect at the same time,
|
14
|
+
# interacting with the same POSLyx host by using the same
|
15
|
+
# Event Machine connector. This is important for performing
|
16
|
+
# actions such as initiating a new payment interaction when
|
17
|
+
# a previous interaction was abandoned.
|
18
|
+
class EM_Session
|
19
|
+
|
20
|
+
# Base class for errors raised by the session to code that
|
21
|
+
# is running in the context of a sesssion.
|
22
|
+
class Error < StandardError ; end
|
23
|
+
|
24
|
+
# Raised when an error condition is detected while making
|
25
|
+
# a request or while waiting for the response.
|
26
|
+
class RequestError < Error ; end
|
27
|
+
|
28
|
+
# Raised when attempting to make a request while another
|
29
|
+
# session is waiting for a response to a request of the
|
30
|
+
# same type (other than PinPadReset).
|
31
|
+
class ConflictingRequestError < Error ; end
|
32
|
+
|
33
|
+
# Executes the given block in the context of a session
|
34
|
+
# attached to the given Event Machine connector. The
|
35
|
+
# session is passed as the argument to the block.
|
36
|
+
def self.execute(connector)
|
37
|
+
new( connector ).execute { |s| yield s }
|
38
|
+
end
|
39
|
+
|
40
|
+
attr_reader :connector
|
41
|
+
|
42
|
+
attr_reader :status
|
43
|
+
|
44
|
+
attr_reader :em_system
|
45
|
+
|
46
|
+
# Builds a new EM_Session instance attached to the given
|
47
|
+
# Event Machine connector.
|
48
|
+
#
|
49
|
+
# ==== Options
|
50
|
+
# * <tt>:em_system</tt> - The event machine system that
|
51
|
+
# will be called on for operations such as adding timers
|
52
|
+
# and deferring actions.
|
53
|
+
def initialize(connector, opts={})
|
54
|
+
self.connector = connector
|
55
|
+
@em_system = opts.fetch( :em_system, ::EM )
|
56
|
+
self.status = :initialized
|
57
|
+
end
|
58
|
+
|
59
|
+
# Executes the given block in the context of the session
|
60
|
+
# The session is passed as the argument to the block.
|
61
|
+
def execute
|
62
|
+
@fiber = Fiber.new do
|
63
|
+
yield self
|
64
|
+
:done
|
65
|
+
end
|
66
|
+
self.status = :engaged
|
67
|
+
dispatch fiber.resume
|
68
|
+
end
|
69
|
+
|
70
|
+
# Called from within an executed block of code for the
|
71
|
+
# session to send request data to the POSLynx and return
|
72
|
+
# the response to the request once it is received.
|
73
|
+
# If a connection could not be established or is lost
|
74
|
+
# before a response can be received, then a
|
75
|
+
# <tt>RequestError</tt> exception will be raised.
|
76
|
+
#
|
77
|
+
# If another session attempts to supplant this request, but
|
78
|
+
# the response to this request is subsequently received,
|
79
|
+
# then the response is returned as normal, but this
|
80
|
+
# session's status is changed to detached, and any
|
81
|
+
# subsequent request attempts made in this session will
|
82
|
+
# result in <tt>RequestError</tt> being raised.
|
83
|
+
#
|
84
|
+
# If another session is already waiting for a response,
|
85
|
+
# then this will attempt to usurp or supplant the other
|
86
|
+
# request. If the new request is of the same type as the
|
87
|
+
# existing request, and the type is not PinPadReset, then
|
88
|
+
# this call will raise a <tt>ConflictingRequestError</tt>
|
89
|
+
# exception.
|
90
|
+
def request(data)
|
91
|
+
if status == :detached
|
92
|
+
raise RequestError, "Session cannot make requests because it is detached"
|
93
|
+
end
|
94
|
+
if connector.request_pending?
|
95
|
+
pending_request_data = connector.latest_request.request_data
|
96
|
+
pending_callbacks = connector.latest_request.result_callbacks
|
97
|
+
if Data::Requests::PinPadReset === data && Data::Requests::PinPadReset === pending_request_data
|
98
|
+
pending_callbacks.call :on_failure
|
99
|
+
was_successful, resp_data_or_ex = Fiber.yield( [:_get_response] )
|
100
|
+
elsif data.class == pending_request_data.class
|
101
|
+
raise ConflictingRequestError, "Attempted a request while another request of the same type is in progress"
|
102
|
+
else
|
103
|
+
was_successful, resp_data_or_ex = Fiber.yield( [:_request, data, connector.latest_request] )
|
104
|
+
end
|
105
|
+
else
|
106
|
+
was_successful, resp_data_or_ex = Fiber.yield( [:_request, data] )
|
107
|
+
end
|
108
|
+
raise resp_data_or_ex unless was_successful
|
109
|
+
resp_data_or_ex
|
110
|
+
end
|
111
|
+
|
112
|
+
# Given a block argument, returns control to EventMachine,
|
113
|
+
# executes the block in a separate thread, waits for the
|
114
|
+
# thread to complete, and then returns the value returned
|
115
|
+
# by the block.
|
116
|
+
#
|
117
|
+
# This is implemented behind the scenes using
|
118
|
+
# EventMachine::defer and has the same considerations and
|
119
|
+
# caveats.
|
120
|
+
#
|
121
|
+
# Note that methods of the session should not be called by
|
122
|
+
# code within the block since those methods should only be
|
123
|
+
# called from code running in the main event loop thread.
|
124
|
+
def exec_dissociated(&block)
|
125
|
+
was_successful, resp_data_or_ex = Fiber.yield( [:_exec_dissociated, block] )
|
126
|
+
raise resp_data_or_ex unless was_successful
|
127
|
+
resp_data_or_ex
|
128
|
+
end
|
129
|
+
|
130
|
+
# Returns control to EventMachine, and returns control to
|
131
|
+
# the session code after the given delay-time in seconds.
|
132
|
+
def sleep(delay_time)
|
133
|
+
Fiber.yield [:_sleep, delay_time]
|
134
|
+
end
|
135
|
+
|
136
|
+
private
|
137
|
+
|
138
|
+
attr_writer :connector, :status
|
139
|
+
attr_reader :fiber
|
140
|
+
|
141
|
+
def dispatch(fiber_callback)
|
142
|
+
return if fiber_callback == :done
|
143
|
+
send *fiber_callback
|
144
|
+
end
|
145
|
+
|
146
|
+
def _request(data, overlaps_request=nil)
|
147
|
+
connector.connect(
|
148
|
+
on_success: ->() {
|
149
|
+
send_request_callbacks = response_handlers( overlaps_request )
|
150
|
+
connector.send_request data, send_request_callbacks
|
151
|
+
},
|
152
|
+
on_failure: ->() {
|
153
|
+
dispatch fiber.resume( [false, RequestError.new] )
|
154
|
+
}
|
155
|
+
)
|
156
|
+
rescue => ex
|
157
|
+
dispatch fiber.resume( [false, ex] )
|
158
|
+
end
|
159
|
+
|
160
|
+
def _get_response
|
161
|
+
connector.get_response response_handlers
|
162
|
+
end
|
163
|
+
|
164
|
+
def response_handlers(overlaps_request=nil)
|
165
|
+
overlapped_request_data = overlaps_request && overlaps_request.request_data
|
166
|
+
overlapped_request_callbacks = overlaps_request && overlaps_request.result_callbacks
|
167
|
+
{
|
168
|
+
on_response: on_response_handler( overlaps_request ),
|
169
|
+
on_failure: on_failure_handler( overlaps_request ),
|
170
|
+
on_detached: ->(){ detach! },
|
171
|
+
}
|
172
|
+
end
|
173
|
+
|
174
|
+
def on_response_handler(overlaps_request)
|
175
|
+
overlaps_request ?
|
176
|
+
on_response_handler_with_overlap( overlaps_request ) :
|
177
|
+
simple_on_response_handler
|
178
|
+
end
|
179
|
+
|
180
|
+
def on_response_handler_with_overlap(overlaps_request)
|
181
|
+
overlapped_request_data = overlaps_request && overlaps_request.request_data
|
182
|
+
overlapped_request_callbacks = overlaps_request && overlaps_request.result_callbacks
|
183
|
+
->(response_data) {
|
184
|
+
if overlapped_request_data.potential_response?( response_data )
|
185
|
+
overlapped_request_callbacks.call :on_detached
|
186
|
+
overlapped_request_callbacks.call :on_response, response_data
|
187
|
+
_get_response
|
188
|
+
else
|
189
|
+
overlapped_request_callbacks.call :on_failure
|
190
|
+
dispatch fiber.resume( [true, response_data] )
|
191
|
+
end
|
192
|
+
}
|
193
|
+
end
|
194
|
+
|
195
|
+
def simple_on_response_handler
|
196
|
+
->(response_data) {
|
197
|
+
dispatch fiber.resume( [true, response_data] )
|
198
|
+
}
|
199
|
+
end
|
200
|
+
|
201
|
+
def on_failure_handler(overlaps_request)
|
202
|
+
overlaps_request ?
|
203
|
+
on_failure_handler_with_overlap( overlaps_request ) :
|
204
|
+
simple_on_failure_handler
|
205
|
+
end
|
206
|
+
|
207
|
+
def on_failure_handler_with_overlap(overlaps_request)
|
208
|
+
overlapped_request_callbacks = overlaps_request && overlaps_request.result_callbacks
|
209
|
+
->() {
|
210
|
+
overlapped_request_callbacks.call :on_failure if overlaps_request
|
211
|
+
dispatch fiber.resume( [false, RequestError.new] )
|
212
|
+
}
|
213
|
+
end
|
214
|
+
|
215
|
+
def simple_on_failure_handler
|
216
|
+
->() {
|
217
|
+
dispatch fiber.resume( [false, RequestError.new] )
|
218
|
+
}
|
219
|
+
end
|
220
|
+
|
221
|
+
def detach!
|
222
|
+
self.connector = nil
|
223
|
+
self.status = :detached
|
224
|
+
end
|
225
|
+
|
226
|
+
def _exec_dissociated op
|
227
|
+
wrapped_op = ->() {
|
228
|
+
begin
|
229
|
+
result = op.call
|
230
|
+
[true, result]
|
231
|
+
rescue => ex
|
232
|
+
[false, ex]
|
233
|
+
end
|
234
|
+
}
|
235
|
+
callback = ->(result) do
|
236
|
+
dispatch fiber.resume result
|
237
|
+
end
|
238
|
+
em_system.defer wrapped_op, callback
|
239
|
+
end
|
240
|
+
|
241
|
+
def _sleep(delay_time)
|
242
|
+
callback = ->() {
|
243
|
+
dispatch fiber.resume
|
244
|
+
}
|
245
|
+
em_system.add_timer delay_time, callback
|
246
|
+
end
|
247
|
+
end
|
248
|
+
|
249
|
+
end
|
250
|
+
end
|