iproto 0.2 → 0.3.3
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.
- data/iproto.gemspec +3 -2
- data/lib/iproto/connection_api.rb +3 -7
- data/lib/iproto/core-ext.rb +3 -0
- data/lib/iproto/em.rb +148 -72
- data/lib/iproto/tcp_socket.rb +36 -7
- data/lib/iproto.rb +9 -5
- metadata +4 -3
data/iproto.gemspec
CHANGED
@@ -4,8 +4,8 @@ Gem::Specification.new do |s|
|
|
4
4
|
s.rubygems_version = '1.3.5'
|
5
5
|
|
6
6
|
s.name = 'iproto'
|
7
|
-
s.version = '0.
|
8
|
-
s.date = '2012-
|
7
|
+
s.version = '0.3.3'
|
8
|
+
s.date = '2012-07-06'
|
9
9
|
s.rubyforge_project = 'iproto'
|
10
10
|
|
11
11
|
s.summary = "Mail.Ru simple network protocol"
|
@@ -28,6 +28,7 @@ Gem::Specification.new do |s|
|
|
28
28
|
iproto.gemspec
|
29
29
|
lib/iproto.rb
|
30
30
|
lib/iproto/connection_api.rb
|
31
|
+
lib/iproto/core-ext.rb
|
31
32
|
lib/iproto/em.rb
|
32
33
|
lib/iproto/tcp_socket.rb
|
33
34
|
]
|
@@ -1,16 +1,12 @@
|
|
1
1
|
module IProto
|
2
2
|
module ConnectionAPI
|
3
|
+
PACK = 'VVV'.freeze
|
3
4
|
def next_request_id
|
4
|
-
@next_request_id ||= 0
|
5
|
-
@next_request_id += 1
|
6
|
-
if @next_request_id > 0xffffffff
|
7
|
-
@next_request_id = 0
|
8
|
-
end
|
9
|
-
@next_request_id
|
5
|
+
@next_request_id = ((@next_request_id ||= 0) + 1) & 0x7fffffff
|
10
6
|
end
|
11
7
|
|
12
8
|
def send_request(request_id, data)
|
13
9
|
# for override
|
14
10
|
end
|
15
11
|
end
|
16
|
-
end
|
12
|
+
end
|
data/lib/iproto/em.rb
CHANGED
@@ -1,118 +1,194 @@
|
|
1
1
|
require 'eventmachine'
|
2
2
|
require 'fiber'
|
3
|
+
require 'iproto/core-ext'
|
4
|
+
|
3
5
|
module IProto
|
4
6
|
module EM
|
5
|
-
module
|
6
|
-
def
|
7
|
-
|
7
|
+
module FixedLengthProtocol
|
8
|
+
def post_init
|
9
|
+
raise "you should set @_needed_size" unless @_needed_size
|
8
10
|
end
|
9
11
|
|
10
|
-
module ClassMethods
|
11
|
-
def header_size(size = nil)
|
12
|
-
if size
|
13
|
-
@_header_size = size
|
14
|
-
else
|
15
|
-
@_header_size
|
16
|
-
end
|
17
|
-
end
|
18
|
-
end
|
19
|
-
|
20
|
-
attr_accessor :header, :body
|
21
|
-
|
22
12
|
def receive_data(data)
|
23
13
|
@buffer ||= ''
|
24
14
|
offset = 0
|
25
|
-
while (chunk = data[offset, _needed_size - @buffer.size]).size > 0
|
26
|
-
@buffer
|
15
|
+
while (chunk = data[offset, _needed_size - @buffer.size]).size > 0
|
16
|
+
@buffer << chunk
|
27
17
|
offset += chunk.size
|
28
18
|
if @buffer.size == _needed_size
|
29
|
-
|
30
|
-
when :receive_header
|
31
|
-
@_state = :receive_body
|
32
|
-
receive_header @buffer
|
33
|
-
when :receive_body
|
34
|
-
@_state = :receive_header
|
35
|
-
receive_body @buffer
|
36
|
-
end
|
19
|
+
chunk = @buffer
|
37
20
|
@buffer = ''
|
21
|
+
receive_chunk chunk
|
38
22
|
end
|
39
23
|
end
|
40
24
|
end
|
41
25
|
|
42
|
-
def
|
26
|
+
def receive_chunk(chunk)
|
43
27
|
# for override
|
44
28
|
end
|
45
|
-
|
46
|
-
def body_size
|
47
|
-
# for override
|
48
|
-
end
|
49
|
-
|
50
|
-
def receive_body(body)
|
51
|
-
# for override
|
52
|
-
end
|
53
|
-
|
54
|
-
def _needed_size
|
55
|
-
case _state
|
56
|
-
when :receive_header
|
57
|
-
self.class.header_size
|
58
|
-
when :receive_body
|
59
|
-
body_size
|
60
|
-
end
|
61
|
-
end
|
62
|
-
|
63
|
-
def _state
|
64
|
-
@_state ||= :receive_header
|
65
|
-
end
|
66
29
|
end
|
67
30
|
|
68
31
|
class Connection < ::EM::Connection
|
69
32
|
include IProto::ConnectionAPI
|
70
|
-
include
|
33
|
+
include FixedLengthProtocol
|
34
|
+
HEADER_SIZE = 12
|
35
|
+
|
36
|
+
def initialize(host, port, reconnect = true)
|
37
|
+
@host = host
|
38
|
+
@port = port
|
39
|
+
@should_reconnect = !!reconnect
|
40
|
+
@reconnect_timer = nil
|
41
|
+
@connected = false
|
42
|
+
@waiting_requests = {}
|
43
|
+
@waiting_for_connect = []
|
44
|
+
init_protocol
|
45
|
+
end
|
71
46
|
|
72
|
-
|
47
|
+
def shutdown_hook
|
48
|
+
::EM.add_shutdown_hook {
|
49
|
+
@connected = false
|
50
|
+
if @reconnect_timer && !(Symbol === @reconnect_timer)
|
51
|
+
::EM.cancel_timer @reconnect_timer
|
52
|
+
end
|
53
|
+
@reconnect_timer = @should_reconnect ? :force : nil
|
54
|
+
}
|
55
|
+
end
|
73
56
|
|
74
57
|
def connection_completed
|
58
|
+
@reconnect_timer = nil
|
75
59
|
@connected = true
|
60
|
+
shutdown_hook
|
61
|
+
_perform_waiting_for_connect(true)
|
76
62
|
end
|
77
63
|
|
78
|
-
|
79
|
-
def
|
80
|
-
@
|
64
|
+
attr_reader :_needed_size
|
65
|
+
def init_protocol
|
66
|
+
@_needed_size = HEADER_SIZE
|
67
|
+
@_state = :receive_header
|
81
68
|
end
|
82
69
|
|
83
|
-
def
|
84
|
-
|
70
|
+
def receive_chunk(chunk)
|
71
|
+
if @_state == :receive_header
|
72
|
+
@type, body_size, @request_id = chunk.unpack(PACK)
|
73
|
+
@_needed_size = body_size
|
74
|
+
@_state = :receive_body
|
75
|
+
else
|
76
|
+
request = @waiting_requests.delete @request_id
|
77
|
+
raise IProto::UnexpectedResponse.new("For request id #{@request_id}") unless request
|
78
|
+
@_needed_size = HEADER_SIZE
|
79
|
+
@_state = :receive_header
|
80
|
+
do_response(request, chunk)
|
81
|
+
end
|
85
82
|
end
|
86
83
|
|
87
|
-
def
|
88
|
-
|
89
|
-
raise IProto::UnexpectedResponse.new("For request id #{@request_id}") unless fiber
|
90
|
-
fiber.resume data
|
84
|
+
def do_response(request, data)
|
85
|
+
request.call data
|
91
86
|
end
|
92
|
-
# end FixedHeaderAndBody API
|
93
87
|
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
88
|
+
def _setup_reconnect_timer(timeout)
|
89
|
+
if @reconnect_timer.nil? || @reconnect_timer == :force
|
90
|
+
@reconnect_timer = :waiting
|
91
|
+
if @timeout == 0
|
92
|
+
reconnect @host, @port
|
93
|
+
else
|
94
|
+
@reconnect_timer = ::EM.add_timer(timeout) do
|
95
|
+
reconnect @host, @port
|
96
|
+
end
|
97
|
+
end
|
98
|
+
end
|
101
99
|
end
|
102
|
-
# end
|
103
100
|
|
104
|
-
def
|
105
|
-
@
|
101
|
+
def _send_request(request_type, body, request)
|
102
|
+
unless @connected
|
103
|
+
unless @should_reconnect
|
104
|
+
raise IProto::Disconnected.new("connection is closed") if @should_reconnect.nil?
|
105
|
+
else
|
106
|
+
@waiting_for_connect << [request_type, body, request]
|
107
|
+
_setup_reconnect_timer(0)
|
108
|
+
end
|
109
|
+
else
|
110
|
+
_do_send_request(request_type, body, request)
|
111
|
+
end
|
112
|
+
end
|
113
|
+
|
114
|
+
def _perform_waiting_for_connect(real)
|
115
|
+
if real
|
116
|
+
@waiting_for_connect.each do |request_type, body, request|
|
117
|
+
_do_send_request(request_type, body, request)
|
118
|
+
end
|
119
|
+
else
|
120
|
+
i = -1
|
121
|
+
@waiting_for_connect.each do |request_type, body, request|
|
122
|
+
@waiting_requests[i] = request
|
123
|
+
i -= 1
|
124
|
+
end
|
125
|
+
end
|
126
|
+
@waiting_for_connect.clear
|
127
|
+
end
|
128
|
+
|
129
|
+
def _do_send_request(request_type, body, request)
|
130
|
+
while @waiting_requests.include?(request_id = next_request_id); end
|
131
|
+
send_data [request_type, body.size, request_id].pack(PACK) + body
|
132
|
+
@waiting_requests[request_id] = request
|
133
|
+
end
|
134
|
+
|
135
|
+
def close
|
136
|
+
close_connection(false)
|
106
137
|
end
|
107
138
|
|
108
139
|
def close_connection(*args)
|
109
|
-
|
140
|
+
@should_reconnect = nil
|
141
|
+
if @reconnect_timer
|
142
|
+
::EM.cancel_timer @reconnect_timer unless Symbol === @reconnect_timer
|
143
|
+
@reconnect_timer = nil
|
144
|
+
end
|
145
|
+
if @connected
|
146
|
+
super(*args)
|
147
|
+
end
|
148
|
+
discard_requests
|
149
|
+
end
|
150
|
+
|
151
|
+
def discard_requests
|
152
|
+
exc = IProto::Disconnected.new("discarded cause of disconnect")
|
153
|
+
_perform_waiting_for_connect(false)
|
154
|
+
@waiting_requests.keys.each do |req|
|
155
|
+
request = @waiting_requests.delete req
|
156
|
+
do_response request, exc
|
157
|
+
end
|
110
158
|
end
|
111
159
|
|
112
160
|
def unbind
|
113
|
-
|
161
|
+
discard_requests
|
162
|
+
case @should_reconnect
|
163
|
+
when true
|
164
|
+
@connected = false
|
165
|
+
_setup_reconnect_timer(0.03) unless @reconnect_timer == :force
|
166
|
+
when false
|
167
|
+
if @connected
|
168
|
+
raise IProto::Disconnected
|
169
|
+
else
|
170
|
+
raise IProto::CouldNotConnect
|
171
|
+
end
|
172
|
+
when nil
|
173
|
+
# do nothing cause we explicitely disconnected
|
174
|
+
end
|
175
|
+
end
|
176
|
+
end
|
177
|
+
|
178
|
+
class FiberedConnection < Connection
|
179
|
+
def send_request(request_type, body)
|
180
|
+
_send_request(request_type, body, Fiber.current)
|
181
|
+
result = Fiber.yield
|
182
|
+
raise result if Exception === result
|
183
|
+
result
|
184
|
+
end
|
185
|
+
end
|
186
|
+
|
187
|
+
class CallbackConnection < Connection
|
188
|
+
def send_request(request_type, body, cb = nil, &block)
|
189
|
+
_send_request(request_type, body, cb || block)
|
114
190
|
end
|
115
191
|
end
|
116
192
|
|
117
193
|
end
|
118
|
-
end
|
194
|
+
end
|
data/lib/iproto/tcp_socket.rb
CHANGED
@@ -1,27 +1,56 @@
|
|
1
1
|
require 'socket'
|
2
2
|
module IProto
|
3
3
|
# TODO: timeouts
|
4
|
-
class TCPSocket
|
4
|
+
class TCPSocket
|
5
5
|
include IProto::ConnectionAPI
|
6
|
+
def initialize(host, port, reconnect = true)
|
7
|
+
@host = host
|
8
|
+
@port = port
|
9
|
+
@reconnect = true
|
10
|
+
end
|
11
|
+
|
12
|
+
def close
|
13
|
+
@reconnect = false
|
14
|
+
if @socket
|
15
|
+
@socket.close rescue nil
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
def socket
|
20
|
+
@socket ||= ::TCPSocket.new(@host, @port)
|
21
|
+
rescue Errno::ECONNREFUSED => e
|
22
|
+
raise CouldNotConnect, e
|
23
|
+
end
|
6
24
|
|
7
25
|
# begin ConnectionAPI
|
8
26
|
def send_request(request_type, body)
|
9
27
|
request_id = next_request_id
|
10
|
-
r = send [request_type, body.bytesize, request_id].pack(
|
28
|
+
r = socket.send ([request_type, body.bytesize, request_id].pack(PACK) << body), 0
|
11
29
|
response_size = recv_header request_id
|
12
30
|
recv_response response_size
|
31
|
+
rescue Errno::EPIPE => e
|
32
|
+
@socket = nil if @reconnect
|
33
|
+
raise Disconnected, e
|
13
34
|
end
|
14
35
|
# end ConnectionAPI
|
15
36
|
|
16
37
|
def recv_header(request_id)
|
17
|
-
header = read(12)
|
18
|
-
|
19
|
-
|
38
|
+
header = socket.read(12) or begin
|
39
|
+
@socket = nil if @reconnect
|
40
|
+
raise Disconnected, 'disconnected while read'
|
41
|
+
end
|
42
|
+
type, response_size, recv_request_id = header.unpack(PACK)
|
43
|
+
unless request_id == recv_request_id
|
44
|
+
raise UnexpectedResponse.new("Waiting response for request_id #{request_id}, but received for #{recv_request_id}")
|
45
|
+
end
|
20
46
|
response_size
|
21
47
|
end
|
22
48
|
|
23
49
|
def recv_response(response_size)
|
24
|
-
read(response_size)
|
50
|
+
socket.read(response_size) or begin
|
51
|
+
@socket = nil if @reconnect
|
52
|
+
raise Disconnected, 'disconnected while read'
|
53
|
+
end
|
25
54
|
end
|
26
55
|
end
|
27
|
-
end
|
56
|
+
end
|
data/lib/iproto.rb
CHANGED
@@ -1,24 +1,28 @@
|
|
1
1
|
module IProto
|
2
|
-
VERSION = '0.
|
2
|
+
VERSION = '0.3.3'
|
3
3
|
class IProtoError < StandardError; end
|
4
4
|
class CouldNotConnect < IProtoError; end
|
5
5
|
class UnexpectedResponse < IProtoError; end
|
6
|
+
class Disconnected < IProtoError; end
|
6
7
|
|
7
8
|
require 'iproto/connection_api'
|
8
9
|
|
9
10
|
# types:
|
10
11
|
# :em
|
11
12
|
# :block
|
12
|
-
def self.get_connection(host, port, type = :block)
|
13
|
+
def self.get_connection(host, port, type = :block, reconnect = true)
|
13
14
|
case type
|
14
15
|
when :em
|
15
16
|
require 'iproto/em'
|
16
|
-
::EM.connect host, port, IProto::EM::
|
17
|
+
::EM.connect host, port, IProto::EM::FiberedConnection, host, port, reconnect
|
18
|
+
when :em_callback
|
19
|
+
require 'iproto/em'
|
20
|
+
::EM.connect host, port, IProto::EM::CallbackConnection, host, port, reconnect
|
17
21
|
when :block
|
18
22
|
require 'iproto/tcp_socket'
|
19
|
-
IProto::TCPSocket.new(host, port)
|
23
|
+
IProto::TCPSocket.new(host, port, reconnect)
|
20
24
|
else
|
21
25
|
raise "Undefined type #{type}"
|
22
26
|
end
|
23
27
|
end
|
24
|
-
end
|
28
|
+
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: iproto
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version:
|
4
|
+
version: 0.3.3
|
5
5
|
prerelease:
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
@@ -9,7 +9,7 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date: 2012-
|
12
|
+
date: 2012-07-06 00:00:00.000000000 Z
|
13
13
|
dependencies: []
|
14
14
|
description: Mail.Ru simple network protocol
|
15
15
|
email: ceo@prepor.ru
|
@@ -25,6 +25,7 @@ files:
|
|
25
25
|
- iproto.gemspec
|
26
26
|
- lib/iproto.rb
|
27
27
|
- lib/iproto/connection_api.rb
|
28
|
+
- lib/iproto/core-ext.rb
|
28
29
|
- lib/iproto/em.rb
|
29
30
|
- lib/iproto/tcp_socket.rb
|
30
31
|
homepage: http://github.com/mailru/iproto-ruby
|
@@ -48,7 +49,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
48
49
|
version: '0'
|
49
50
|
requirements: []
|
50
51
|
rubyforge_project: iproto
|
51
|
-
rubygems_version: 1.8.
|
52
|
+
rubygems_version: 1.8.24
|
52
53
|
signing_key:
|
53
54
|
specification_version: 2
|
54
55
|
summary: Mail.Ru simple network protocol
|