client_for_poslynx 0.9.0 → 1.0.0.pre
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 +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
|