cocaine-framework-ruby 0.11.2
Sign up to get free protection for your applications and to get access to all the features.
- 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
|