cocaine-framework-ruby 0.11.2
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 +7 -0
- data/.gitignore +3 -0
- data/LICENSE +165 -0
- data/README.md +16 -0
- data/cocaine.gemspec +27 -0
- data/examples/echo/echo.rb +56 -0
- data/examples/echo/manifest.json +3 -0
- data/lib/cocaine.rb +6 -0
- data/lib/cocaine/asio/channel.rb +88 -0
- data/lib/cocaine/asio/channel/combiner.rb +16 -0
- data/lib/cocaine/asio/channel/manager.rb +19 -0
- data/lib/cocaine/asio/connection.rb +58 -0
- data/lib/cocaine/client/dispatcher.rb +46 -0
- data/lib/cocaine/client/error.rb +7 -0
- data/lib/cocaine/client/service.rb +130 -0
- data/lib/cocaine/decoder.rb +11 -0
- data/lib/cocaine/dispatcher.rb +19 -0
- data/lib/cocaine/error.rb +2 -0
- data/lib/cocaine/future.rb +26 -0
- data/lib/cocaine/namespace.rb +4 -0
- data/lib/cocaine/protocol.rb +155 -0
- data/lib/cocaine/server/dispatcher.rb +72 -0
- data/lib/cocaine/server/health.rb +76 -0
- data/lib/cocaine/server/http.rb +63 -0
- data/lib/cocaine/server/request.rb +9 -0
- data/lib/cocaine/server/response.rb +31 -0
- data/lib/cocaine/server/sandbox.rb +38 -0
- data/lib/cocaine/server/worker.rb +73 -0
- data/lib/cocaine/synchrony/channel.rb +72 -0
- data/lib/cocaine/synchrony/service.rb +53 -0
- data/lib/cocaine/testing/mock_server.rb +123 -0
- data/lib/version.rb +3 -0
- data/spec/channel_spec.rb +160 -0
- data/spec/connection_spec.rb +31 -0
- data/spec/health_spec.rb +19 -0
- data/spec/service_spec.rb +322 -0
- data/spec/stub_server.rb +60 -0
- data/spec/worker_spec.rb +37 -0
- metadata +126 -0
@@ -0,0 +1,46 @@
|
|
1
|
+
require 'cocaine/asio/channel/combiner'
|
2
|
+
require 'cocaine/asio/channel/manager'
|
3
|
+
require 'cocaine/client/error'
|
4
|
+
require 'cocaine/dispatcher'
|
5
|
+
|
6
|
+
$log = Logger.new(STDERR)
|
7
|
+
$log.level = Logger::DEBUG
|
8
|
+
|
9
|
+
|
10
|
+
class Cocaine::ClientDispatcher < Cocaine::Dispatcher
|
11
|
+
def initialize(conn)
|
12
|
+
super conn
|
13
|
+
@channels = Cocaine::ChannelManager.new
|
14
|
+
end
|
15
|
+
|
16
|
+
def invoke(method_id, *data)
|
17
|
+
session, channel = @channels.create
|
18
|
+
message = MessagePack::pack([method_id, session, data])
|
19
|
+
@conn.send_data message
|
20
|
+
Cocaine::ChannelCombiner.new channel
|
21
|
+
end
|
22
|
+
|
23
|
+
protected
|
24
|
+
def process(session, message)
|
25
|
+
channel = @channels[session]
|
26
|
+
case message.id
|
27
|
+
when RPC::CHUNK
|
28
|
+
channel.trigger unpack message.data
|
29
|
+
when RPC::ERROR
|
30
|
+
channel.error Cocaine::ServiceError.new "[#{message.errno}] #{message.reason}"
|
31
|
+
when RPC::CHOKE
|
32
|
+
channel.error ChokeEvent.new
|
33
|
+
channel.close
|
34
|
+
else
|
35
|
+
raise "unexpected message id: #{message.id}"
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
private
|
40
|
+
def unpack(data)
|
41
|
+
if data.kind_of?(Array)
|
42
|
+
data = data.join(',')
|
43
|
+
end
|
44
|
+
MessagePack.unpack(data)
|
45
|
+
end
|
46
|
+
end
|
@@ -0,0 +1,130 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'logger'
|
3
|
+
|
4
|
+
require 'msgpack'
|
5
|
+
require 'eventmachine'
|
6
|
+
|
7
|
+
require 'cocaine/asio/channel'
|
8
|
+
require 'cocaine/asio/connection'
|
9
|
+
require 'cocaine/client/dispatcher'
|
10
|
+
require 'cocaine/client/error'
|
11
|
+
require 'cocaine/protocol'
|
12
|
+
|
13
|
+
|
14
|
+
class Object
|
15
|
+
def metaclass
|
16
|
+
class << self
|
17
|
+
self
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
|
23
|
+
$log = Logger.new(STDERR)
|
24
|
+
$log.level = Logger::DEBUG
|
25
|
+
|
26
|
+
|
27
|
+
class Cocaine::AbstractService
|
28
|
+
attr_reader :api
|
29
|
+
|
30
|
+
def initialize(name)
|
31
|
+
@name = name
|
32
|
+
@api = {}
|
33
|
+
end
|
34
|
+
|
35
|
+
def invoke(method_id, *args)
|
36
|
+
$log.debug "invoking '#{@name}' method #{method_id} with #{args}"
|
37
|
+
@dispatcher.invoke method_id, *args
|
38
|
+
end
|
39
|
+
|
40
|
+
private
|
41
|
+
def connect_to_endpoint(*endpoint)
|
42
|
+
df = EM::DefaultDeferrable.new
|
43
|
+
$log.debug "connecting to the service '#{@name}' at #{endpoint} ..."
|
44
|
+
EM.connect *endpoint, Cocaine::Connection do |conn|
|
45
|
+
conn.hooks.on :connected do
|
46
|
+
conn.hooks.clear
|
47
|
+
$log.debug "connection established with service '#{@name}' at #{endpoint}"
|
48
|
+
@dispatcher = Cocaine::ClientDispatcher.new conn
|
49
|
+
df.succeed
|
50
|
+
end
|
51
|
+
|
52
|
+
conn.hooks.on :disconnected do
|
53
|
+
conn.hooks.clear
|
54
|
+
$log.debug "failed to connect to the '#{@name}' service at #{endpoint}"
|
55
|
+
df.fail Cocaine::ConnectionError.new
|
56
|
+
end
|
57
|
+
end
|
58
|
+
df
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
|
63
|
+
class Cocaine::Locator < Cocaine::AbstractService
|
64
|
+
@default_host = 'localhost'
|
65
|
+
@default_port = 10053
|
66
|
+
class << self
|
67
|
+
attr_accessor :default_host, :default_port
|
68
|
+
end
|
69
|
+
|
70
|
+
def initialize(host=self.class.default_host, port=self.class.default_port)
|
71
|
+
@name = 'locator'
|
72
|
+
@host = host
|
73
|
+
@port = port
|
74
|
+
end
|
75
|
+
|
76
|
+
def resolve(name)
|
77
|
+
df = EventMachine::DefaultDeferrable.new
|
78
|
+
connect_df = connect_to_endpoint @host, @port
|
79
|
+
connect_df.callback { do_resolve name, df }
|
80
|
+
connect_df.errback { |err| df.fail err }
|
81
|
+
df
|
82
|
+
end
|
83
|
+
|
84
|
+
private
|
85
|
+
def do_resolve(name, df)
|
86
|
+
$log.debug "resolving service '#{name}'"
|
87
|
+
channel = invoke 0, name
|
88
|
+
channel.callback do |future|
|
89
|
+
begin
|
90
|
+
df.succeed future.get
|
91
|
+
rescue Exception => err
|
92
|
+
df.fail err
|
93
|
+
end
|
94
|
+
end
|
95
|
+
end
|
96
|
+
end
|
97
|
+
|
98
|
+
|
99
|
+
class Cocaine::Service < Cocaine::AbstractService
|
100
|
+
def initialize(name, host='localhost', port=10053)
|
101
|
+
super name
|
102
|
+
@settings = {:host => host, :port => port}
|
103
|
+
end
|
104
|
+
|
105
|
+
def connect
|
106
|
+
df = EventMachine::DefaultDeferrable.new
|
107
|
+
locator = Cocaine::Locator.new @settings[:host], @settings[:port]
|
108
|
+
d = locator.resolve @name
|
109
|
+
d.callback { |result| on_connect result, df }
|
110
|
+
d.errback { |err| df.fail err }
|
111
|
+
df
|
112
|
+
end
|
113
|
+
|
114
|
+
private
|
115
|
+
def on_connect(result, df)
|
116
|
+
$log.debug "service '#{@name}' resolved: #{result}"
|
117
|
+
|
118
|
+
@endpoint, @version, @api = result
|
119
|
+
@api.each do |id, name|
|
120
|
+
self.metaclass.send(:define_method, name) do |*args|
|
121
|
+
invoke id, *args
|
122
|
+
end
|
123
|
+
end
|
124
|
+
|
125
|
+
#todo: No ipv6 support yet
|
126
|
+
connect_df = connect_to_endpoint *@endpoint
|
127
|
+
connect_df.callback { df.succeed }
|
128
|
+
connect_df.errback { |err| df.fail err }
|
129
|
+
end
|
130
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
require 'logger'
|
2
|
+
|
3
|
+
$log = Logger.new(STDERR)
|
4
|
+
$log.level = Logger::DEBUG
|
5
|
+
|
6
|
+
|
7
|
+
class Cocaine::Dispatcher
|
8
|
+
def initialize(conn)
|
9
|
+
@conn = conn
|
10
|
+
@conn.hooks.on :message do |session, message|
|
11
|
+
process session, message
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
protected
|
16
|
+
def process(session, message)
|
17
|
+
raise NotImplementedError
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
class Cocaine::Future
|
2
|
+
def set_value(value)
|
3
|
+
@value = value
|
4
|
+
end
|
5
|
+
|
6
|
+
def set_error(error)
|
7
|
+
@error = error
|
8
|
+
end
|
9
|
+
|
10
|
+
def get
|
11
|
+
raise @error if @error
|
12
|
+
@value
|
13
|
+
end
|
14
|
+
|
15
|
+
def self.value(value)
|
16
|
+
f = Cocaine::Future.new
|
17
|
+
f.set_value value
|
18
|
+
f
|
19
|
+
end
|
20
|
+
|
21
|
+
def self.error(error)
|
22
|
+
f = Cocaine::Future.new
|
23
|
+
f.set_error error
|
24
|
+
f
|
25
|
+
end
|
26
|
+
end
|
@@ -0,0 +1,155 @@
|
|
1
|
+
require 'msgpack'
|
2
|
+
|
3
|
+
require 'cocaine/namespace'
|
4
|
+
|
5
|
+
module RPC
|
6
|
+
HANDSHAKE = 0
|
7
|
+
HEARTBEAT = 1
|
8
|
+
TERMINATE = 2
|
9
|
+
INVOKE = 3
|
10
|
+
CHUNK = 4
|
11
|
+
ERROR = 5
|
12
|
+
CHOKE = 6
|
13
|
+
end
|
14
|
+
|
15
|
+
|
16
|
+
class Protocol
|
17
|
+
attr_reader :id
|
18
|
+
|
19
|
+
def pack(session)
|
20
|
+
[@id, session, content].to_msgpack
|
21
|
+
end
|
22
|
+
|
23
|
+
def to_s
|
24
|
+
"#{self.class.name}(#{content})"
|
25
|
+
end
|
26
|
+
|
27
|
+
protected
|
28
|
+
def initialize(id)
|
29
|
+
@id = id
|
30
|
+
end
|
31
|
+
|
32
|
+
protected
|
33
|
+
def content
|
34
|
+
[]
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
|
39
|
+
class Handshake < Protocol
|
40
|
+
def initialize(uuid)
|
41
|
+
super RPC::HANDSHAKE
|
42
|
+
@uuid = uuid
|
43
|
+
end
|
44
|
+
|
45
|
+
protected
|
46
|
+
def content
|
47
|
+
[@uuid]
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
|
52
|
+
class Heartbeat < Protocol
|
53
|
+
def initialize
|
54
|
+
super RPC::HEARTBEAT
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
|
59
|
+
class Terminate < Protocol
|
60
|
+
attr_reader :errno
|
61
|
+
attr_reader :reason
|
62
|
+
|
63
|
+
def initialize(errno, reason)
|
64
|
+
super RPC::TERMINATE
|
65
|
+
@errno = errno
|
66
|
+
@reason = reason
|
67
|
+
end
|
68
|
+
|
69
|
+
protected
|
70
|
+
def content
|
71
|
+
[@errno, @reason]
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
|
76
|
+
class Invoke < Protocol
|
77
|
+
attr_reader :event
|
78
|
+
|
79
|
+
def initialize(event)
|
80
|
+
super RPC::INVOKE
|
81
|
+
@event = event
|
82
|
+
end
|
83
|
+
|
84
|
+
protected
|
85
|
+
def content
|
86
|
+
[@event]
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
|
91
|
+
class Chunk < Protocol
|
92
|
+
attr_reader :data
|
93
|
+
|
94
|
+
def initialize(data)
|
95
|
+
super RPC::CHUNK
|
96
|
+
@data = data
|
97
|
+
end
|
98
|
+
|
99
|
+
protected
|
100
|
+
def content
|
101
|
+
[@data]
|
102
|
+
end
|
103
|
+
end
|
104
|
+
|
105
|
+
|
106
|
+
class Error < Protocol
|
107
|
+
attr_reader :errno
|
108
|
+
attr_reader :reason
|
109
|
+
|
110
|
+
def initialize(errno, reason)
|
111
|
+
super RPC::ERROR
|
112
|
+
@errno = errno
|
113
|
+
@reason = reason
|
114
|
+
end
|
115
|
+
|
116
|
+
protected
|
117
|
+
def content
|
118
|
+
[@errno, @reason]
|
119
|
+
end
|
120
|
+
end
|
121
|
+
|
122
|
+
|
123
|
+
class Choke < Protocol
|
124
|
+
def initialize
|
125
|
+
super RPC::CHOKE
|
126
|
+
end
|
127
|
+
end
|
128
|
+
|
129
|
+
|
130
|
+
class Cocaine::ProtocolFactory
|
131
|
+
def self.create(id, data)
|
132
|
+
case id
|
133
|
+
when RPC::HANDSHAKE
|
134
|
+
Handshake.new *data
|
135
|
+
when RPC::HEARTBEAT
|
136
|
+
Heartbeat.new *data
|
137
|
+
when RPC::TERMINATE
|
138
|
+
Terminate.new *data
|
139
|
+
when RPC::INVOKE
|
140
|
+
Invoke.new *data
|
141
|
+
when RPC::CHUNK
|
142
|
+
Chunk.new *data
|
143
|
+
when RPC::ERROR
|
144
|
+
Error.new *data
|
145
|
+
when RPC::CHOKE
|
146
|
+
Choke.new *data
|
147
|
+
else
|
148
|
+
raise "unexpected message id: #{id}"
|
149
|
+
end
|
150
|
+
end
|
151
|
+
end
|
152
|
+
|
153
|
+
|
154
|
+
class ChokeEvent < Exception
|
155
|
+
end
|
@@ -0,0 +1,72 @@
|
|
1
|
+
require 'cocaine/asio/channel'
|
2
|
+
require 'cocaine/protocol'
|
3
|
+
require 'cocaine/server/health'
|
4
|
+
require 'cocaine/server/request'
|
5
|
+
require 'cocaine/server/response'
|
6
|
+
|
7
|
+
|
8
|
+
class Cocaine::WorkerDispatcher < Cocaine::Dispatcher
|
9
|
+
def initialize(worker, conn)
|
10
|
+
super conn
|
11
|
+
@worker = worker
|
12
|
+
@health = Cocaine::HealthManager.new self
|
13
|
+
@health.start
|
14
|
+
@channels = {}
|
15
|
+
end
|
16
|
+
|
17
|
+
def send_handshake(session, uuid)
|
18
|
+
send Handshake.new(uuid), session
|
19
|
+
end
|
20
|
+
|
21
|
+
def send_heartbeat(session)
|
22
|
+
send Heartbeat.new, session
|
23
|
+
end
|
24
|
+
|
25
|
+
def send_terminate(session, errno, reason)
|
26
|
+
send Terminate.new(errno, reason), session
|
27
|
+
end
|
28
|
+
|
29
|
+
def send_chunk(session, data)
|
30
|
+
send Chunk.new(data), session
|
31
|
+
end
|
32
|
+
|
33
|
+
def send_error(session, errno, reason)
|
34
|
+
send Error.new(errno, reason), session
|
35
|
+
end
|
36
|
+
|
37
|
+
def send_choke(session)
|
38
|
+
send Choke.new, session
|
39
|
+
end
|
40
|
+
|
41
|
+
protected
|
42
|
+
def process(session, message)
|
43
|
+
case message.id
|
44
|
+
when RPC::HEARTBEAT
|
45
|
+
@health.breath()
|
46
|
+
when RPC::TERMINATE
|
47
|
+
@worker.terminate message.errno, message.reason
|
48
|
+
when RPC::INVOKE
|
49
|
+
channel = Cocaine::Channel.new
|
50
|
+
request = Cocaine::Request.new channel
|
51
|
+
response = Cocaine::Response.new session, self
|
52
|
+
@channels[session] = channel
|
53
|
+
@worker.invoke(message.event, request, response)
|
54
|
+
when RPC::CHUNK
|
55
|
+
df = @channels[session]
|
56
|
+
df.trigger message.data
|
57
|
+
when RPC::ERROR
|
58
|
+
df = @channels[session]
|
59
|
+
df.error message.reason
|
60
|
+
when RPC::CHOKE
|
61
|
+
df = @channels.delete(session)
|
62
|
+
df.close
|
63
|
+
else
|
64
|
+
raise "unexpected message id: #{id}"
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
private
|
69
|
+
def send(message, session)
|
70
|
+
@conn.send_data message.pack(session)
|
71
|
+
end
|
72
|
+
end
|