cocaine-framework 0.12.0.pre.rc4
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/lib/cocaine/cocaine.rb +472 -0
- data/lib/cocaine/version.rb +3 -0
- data/lib/cocaine.rb +11 -0
- metadata +106 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: a2b30cb410a7cdae36692be845443f20e70f6fb1
|
4
|
+
data.tar.gz: 8e2f6823087e7161aeeb049387d561eb8e1742a3
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: a43592c3b4d2abf4077730fa553d8736046403f2e4ff3e78a52286cffacad5e732bec030a475db361c8c0b9f688c1630812d74b1fbf3141910307a2e2c07432e
|
7
|
+
data.tar.gz: 64fb788a2a46339f7e551e21c5e2eaaab1b582a6783ef36b3536e234de0e70fc1abf28a5a384508ddea1f253c4bdc022a4ea95e7530002ba0096974a026f1a44
|
@@ -0,0 +1,472 @@
|
|
1
|
+
require 'logger'
|
2
|
+
require 'msgpack'
|
3
|
+
require 'optparse'
|
4
|
+
require 'uri'
|
5
|
+
|
6
|
+
require 'celluloid'
|
7
|
+
require 'celluloid/io'
|
8
|
+
|
9
|
+
module Cocaine
|
10
|
+
# For dynamic method creation. [Detail].
|
11
|
+
class Meta
|
12
|
+
def metaclass
|
13
|
+
class << self
|
14
|
+
self
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
METHOD_ID = 0
|
20
|
+
TX_TREE_ID = 1
|
21
|
+
RX_TREE_ID = 2
|
22
|
+
|
23
|
+
module Default
|
24
|
+
module Locator
|
25
|
+
$host = '::'
|
26
|
+
$port = 10053
|
27
|
+
API = {
|
28
|
+
0 => [
|
29
|
+
'resolve',
|
30
|
+
{},
|
31
|
+
{
|
32
|
+
0 => ['write', nil, {}],
|
33
|
+
1 => ['error', {}, {}],
|
34
|
+
2 => ['close', {}, {}]
|
35
|
+
}
|
36
|
+
]
|
37
|
+
}
|
38
|
+
ERROR_CODE = 1
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
module RPC
|
43
|
+
HANDSHAKE, HEARTBEAT, TERMINATE, INVOKE, CHUNK, ERROR, CHOKE = (0..6).to_a
|
44
|
+
|
45
|
+
RXTREE = {
|
46
|
+
CHUNK => ['write', nil, {}],
|
47
|
+
ERROR => ['error', {}, {}],
|
48
|
+
CHOKE => ['close', {}, {}]
|
49
|
+
}
|
50
|
+
TXTREE = RXTREE
|
51
|
+
end
|
52
|
+
|
53
|
+
# [Detail]
|
54
|
+
# Base class for shared read channel state.
|
55
|
+
class Mailbox
|
56
|
+
def initialize(queue)
|
57
|
+
@queue = queue
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
# [API]
|
62
|
+
# Read-only part for shared reader state.
|
63
|
+
class RxMailbox < Mailbox
|
64
|
+
def recv(timeout=30.0)
|
65
|
+
@queue.receive timeout
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
# [Detail]
|
70
|
+
# Write-only part for shared reader state.
|
71
|
+
class TxMailbox < Mailbox
|
72
|
+
def initialize(queue, tree)
|
73
|
+
super queue
|
74
|
+
|
75
|
+
@tree = Hash.new
|
76
|
+
tree.each do |id, (method, txtree, rxtree)|
|
77
|
+
@tree[id] = txtree
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
def push(id, payload)
|
82
|
+
txtree = @tree[id]
|
83
|
+
if txtree && txtree.empty?
|
84
|
+
# Todo: Close.
|
85
|
+
LOG.debug "Closing RX channel #{self}"
|
86
|
+
end
|
87
|
+
|
88
|
+
@queue << [id, payload]
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
92
|
+
# [Detail]
|
93
|
+
# Shared reader state, that acts like channel. Need for channel splitting between the library and a user.
|
94
|
+
class RxChannel
|
95
|
+
attr_reader :tx, :rx
|
96
|
+
|
97
|
+
def initialize(tree)
|
98
|
+
queue = Celluloid::Mailbox.new
|
99
|
+
@tx = TxMailbox.new queue, tree
|
100
|
+
@rx = RxMailbox.new queue
|
101
|
+
end
|
102
|
+
end
|
103
|
+
|
104
|
+
# [API]
|
105
|
+
# Writer channel. Patches itself with current state, providing methods described in tx tree.
|
106
|
+
class TxChannel < Meta
|
107
|
+
def initialize(tree, session, socket)
|
108
|
+
@session = session
|
109
|
+
@socket = socket
|
110
|
+
@tree = nil
|
111
|
+
rebind tree
|
112
|
+
end
|
113
|
+
|
114
|
+
private
|
115
|
+
def push(id, *args)
|
116
|
+
LOG.debug "<- [#{@session}, #{id}, #{args}]"
|
117
|
+
@socket.write MessagePack.pack [@session, id, args]
|
118
|
+
rebind @tree[id][Cocaine::TX_TREE_ID]
|
119
|
+
end
|
120
|
+
|
121
|
+
def rebind(new)
|
122
|
+
if new.nil?
|
123
|
+
LOG.debug 'Found recursive leaf - doing nothing with tx channel'
|
124
|
+
return
|
125
|
+
end
|
126
|
+
|
127
|
+
old = @tree || Hash.new
|
128
|
+
old.each do |id, (method, txtree, rxtree)|
|
129
|
+
LOG.debug "Removed '#{method}' method for tx channel"
|
130
|
+
self.metaclass.send(:define_method, method) do |*|
|
131
|
+
raise Exception.new "Method '#{method}' is removed"
|
132
|
+
end
|
133
|
+
end
|
134
|
+
|
135
|
+
new ||= Hash.new
|
136
|
+
new.each do |id, (method, txtree, rxtree)|
|
137
|
+
LOG.debug "Defined '#{method}' method for tx channel"
|
138
|
+
self.metaclass.send(:define_method, method) do |*args|
|
139
|
+
push id, *args
|
140
|
+
end
|
141
|
+
end
|
142
|
+
|
143
|
+
@tree = new
|
144
|
+
end
|
145
|
+
end
|
146
|
+
|
147
|
+
# [Detail]
|
148
|
+
# Service actor, which can define itself via its dispatch tree.
|
149
|
+
class DefinedService < Meta
|
150
|
+
include Celluloid::IO
|
151
|
+
|
152
|
+
def initialize(name, endpoints, dispatch)
|
153
|
+
@name = name
|
154
|
+
@dispatch = dispatch
|
155
|
+
|
156
|
+
@counter = 1
|
157
|
+
@sessions = Hash.new
|
158
|
+
|
159
|
+
LOG.debug "Initializing '#{name}' service - with possible endpoints: #{endpoints}"
|
160
|
+
endpoints.each do |host, port|
|
161
|
+
LOG.debug "Trying to connect to '#{name}' at '[#{host}]:#{port}'"
|
162
|
+
begin
|
163
|
+
@endpoint = [host, port]
|
164
|
+
@socket = TCPSocket.new(host, port)
|
165
|
+
break
|
166
|
+
rescue IOError => err
|
167
|
+
LOG.warn "Failed: #{err}"
|
168
|
+
end
|
169
|
+
end
|
170
|
+
|
171
|
+
dispatch.each do |id, (method, txtree, rxtree)|
|
172
|
+
LOG.debug "Defined '#{method}' method for service #{self}"
|
173
|
+
self.metaclass.send(:define_method, method) do |*args|
|
174
|
+
LOG.debug "Invoking #{@name}.#{method}(#{args})"
|
175
|
+
return invoke(id, *args)
|
176
|
+
end
|
177
|
+
end
|
178
|
+
|
179
|
+
async.run
|
180
|
+
end
|
181
|
+
|
182
|
+
private
|
183
|
+
def run
|
184
|
+
LOG.debug "Service '#{@name}' is running"
|
185
|
+
unpacker = MessagePack::Unpacker.new
|
186
|
+
loop do
|
187
|
+
data = @socket.readpartial(4096)
|
188
|
+
unpacker.feed_each(data) do |decoded|
|
189
|
+
async.received *decoded
|
190
|
+
end
|
191
|
+
end
|
192
|
+
end
|
193
|
+
|
194
|
+
def received(session, id, payload)
|
195
|
+
LOG.debug "-> [#{session}, #{id}, #{payload}]"
|
196
|
+
tx, rx = @sessions[session]
|
197
|
+
if rx
|
198
|
+
rx.push id, payload
|
199
|
+
else
|
200
|
+
LOG.warn "Received message to closed session: [#{session}, #{id}, #{payload}]"
|
201
|
+
end
|
202
|
+
end
|
203
|
+
|
204
|
+
def invoke(id, *args)
|
205
|
+
method, txtree, rxtree = @dispatch[id]
|
206
|
+
LOG.debug "Invoking #{@name}[#{id}=#{method}] with #{args}"
|
207
|
+
|
208
|
+
txchan = TxChannel.new txtree, @counter, @socket
|
209
|
+
rxchan = RxChannel.new rxtree
|
210
|
+
@sessions[@counter] = [txchan, rxchan.tx]
|
211
|
+
|
212
|
+
LOG.debug "<- [#{@counter}, #{id}, #{args}]"
|
213
|
+
message = MessagePack.pack([@counter, id, args])
|
214
|
+
@counter += 1
|
215
|
+
|
216
|
+
@socket.write message
|
217
|
+
return txchan, rxchan.rx
|
218
|
+
end
|
219
|
+
end
|
220
|
+
|
221
|
+
# [API]
|
222
|
+
class Locator < DefinedService
|
223
|
+
def initialize(host=nil, port=nil)
|
224
|
+
super :locator, [[host || Default::Locator::host, port || Default::Locator::port]], Default::Locator::API
|
225
|
+
end
|
226
|
+
end
|
227
|
+
|
228
|
+
class ServiceError < IOError
|
229
|
+
end
|
230
|
+
|
231
|
+
# [API]
|
232
|
+
# Service class. All you need is name and (optionally) locator endpoint.
|
233
|
+
class Service < DefinedService
|
234
|
+
def initialize(name, host=nil, port=nil)
|
235
|
+
locator = Locator.new host, port
|
236
|
+
tx, rx = locator.resolve name
|
237
|
+
id, payload = rx.recv
|
238
|
+
if id == Default::Locator::ERROR_CODE
|
239
|
+
raise ServiceError.new payload
|
240
|
+
end
|
241
|
+
|
242
|
+
endpoints, version, dispatch = payload
|
243
|
+
super name, endpoints, dispatch
|
244
|
+
end
|
245
|
+
end
|
246
|
+
|
247
|
+
# [Detail]
|
248
|
+
# Special worker actor for RAII.
|
249
|
+
class WorkerActor
|
250
|
+
include Celluloid
|
251
|
+
|
252
|
+
def initialize(block)
|
253
|
+
@block = block
|
254
|
+
end
|
255
|
+
|
256
|
+
def execute(tx, rx)
|
257
|
+
@block.call tx, rx
|
258
|
+
yield
|
259
|
+
end
|
260
|
+
end
|
261
|
+
|
262
|
+
# [API]
|
263
|
+
# Worker class.
|
264
|
+
class Worker
|
265
|
+
include Celluloid
|
266
|
+
include Celluloid::IO
|
267
|
+
|
268
|
+
execute_block_on_receiver :on
|
269
|
+
finalizer :finalize
|
270
|
+
|
271
|
+
def initialize(app, uuid, endpoint)
|
272
|
+
@app = app
|
273
|
+
@uuid = uuid
|
274
|
+
@endpoint = endpoint
|
275
|
+
@actors = Hash.new
|
276
|
+
@sessions = Hash.new
|
277
|
+
end
|
278
|
+
|
279
|
+
def on(event, &block)
|
280
|
+
@actors[event.to_s] = WorkerActor.new block
|
281
|
+
end
|
282
|
+
|
283
|
+
def run
|
284
|
+
LOG.debug "Starting worker '#{@app}' with uuid '#{@uuid}' at '#{@endpoint}'"
|
285
|
+
@socket = UNIXSocket.open(@endpoint)
|
286
|
+
async.handshake
|
287
|
+
async.health
|
288
|
+
async.serve
|
289
|
+
end
|
290
|
+
|
291
|
+
private
|
292
|
+
def handshake
|
293
|
+
LOG.debug '<- Handshake'
|
294
|
+
@socket.write MessagePack::pack([1, RPC::HANDSHAKE, [@uuid]])
|
295
|
+
end
|
296
|
+
|
297
|
+
def health
|
298
|
+
heartbeat = MessagePack::pack([1, RPC::HEARTBEAT, []])
|
299
|
+
loop do
|
300
|
+
LOG.debug '<- Heartbeat'
|
301
|
+
@socket.write heartbeat
|
302
|
+
sleep 5.0
|
303
|
+
end
|
304
|
+
end
|
305
|
+
|
306
|
+
def serve
|
307
|
+
unpacker = MessagePack::Unpacker.new
|
308
|
+
loop do
|
309
|
+
data = @socket.readpartial 4096
|
310
|
+
unpacker.feed_each data do |decoded|
|
311
|
+
async.received *decoded
|
312
|
+
end
|
313
|
+
end
|
314
|
+
end
|
315
|
+
|
316
|
+
def received(session, id, payload)
|
317
|
+
LOG.debug "-> Message(#{session}, #{id}, #{payload})"
|
318
|
+
case id
|
319
|
+
when RPC::HANDSHAKE
|
320
|
+
when RPC::HEARTBEAT
|
321
|
+
when RPC::TERMINATE
|
322
|
+
terminate *payload
|
323
|
+
when RPC::INVOKE
|
324
|
+
invoke session, *payload
|
325
|
+
when RPC::CHUNK, RPC::ERROR
|
326
|
+
push session, id, *payload
|
327
|
+
when RPC::CHOKE
|
328
|
+
LOG.debug "Closing #{session} session"
|
329
|
+
@sessions.delete session
|
330
|
+
else
|
331
|
+
LOG.warn "Received unknown message: [#{session}, #{id}, #{payload}]"
|
332
|
+
end
|
333
|
+
end
|
334
|
+
|
335
|
+
def invoke(session, event)
|
336
|
+
actor = @actors[event]
|
337
|
+
txchan = TxChannel.new RPC::TXTREE, session, @socket
|
338
|
+
rxchan = RxChannel.new RPC::RXTREE
|
339
|
+
|
340
|
+
if actor
|
341
|
+
@sessions[session] = [txchan, rxchan.tx]
|
342
|
+
actor.execute txchan, rxchan.rx do
|
343
|
+
LOG.debug '<- Choke'
|
344
|
+
txchan.close
|
345
|
+
end
|
346
|
+
else
|
347
|
+
LOG.warn "Event '#{event}' is not registered"
|
348
|
+
txchan.error -1, "event '#{event}' is not registered"
|
349
|
+
end
|
350
|
+
end
|
351
|
+
|
352
|
+
def push(session, id, *payload)
|
353
|
+
tx, rx = @sessions[session]
|
354
|
+
if rx
|
355
|
+
rx.push id, *payload
|
356
|
+
else
|
357
|
+
raise Exception.new "received push event on unknown #{session} session"
|
358
|
+
end
|
359
|
+
end
|
360
|
+
|
361
|
+
def terminate(errno, reason)
|
362
|
+
LOG.warn "Terminating [#{errno}]: #{reason}"
|
363
|
+
exit errno
|
364
|
+
end
|
365
|
+
|
366
|
+
def finalize
|
367
|
+
if @socket
|
368
|
+
@socket.close
|
369
|
+
end
|
370
|
+
end
|
371
|
+
end
|
372
|
+
|
373
|
+
# [API].
|
374
|
+
class WorkerFactory
|
375
|
+
def self.create
|
376
|
+
options = {}
|
377
|
+
OptionParser.new do |opts|
|
378
|
+
opts.banner = 'Usage: <worker.rb> --app NAME --locator ADDRESS --uuid UUID --endpoint ENDPOINT'
|
379
|
+
|
380
|
+
opts.on('--app NAME', 'Worker name') do |app|
|
381
|
+
options[:app] = app
|
382
|
+
end
|
383
|
+
|
384
|
+
opts.on('--locator ADDRESS', 'Locator address') do |endpoint|
|
385
|
+
options[:locator] = endpoint
|
386
|
+
end
|
387
|
+
|
388
|
+
opts.on('--uuid UUID', 'Worker uuid') do |uuid|
|
389
|
+
options[:uuid] = uuid
|
390
|
+
end
|
391
|
+
|
392
|
+
opts.on('--endpoint ENDPOINT', 'Worker endpoint') do |endpoint|
|
393
|
+
options[:endpoint] = endpoint
|
394
|
+
end
|
395
|
+
end.parse!
|
396
|
+
|
397
|
+
Cocaine::LOG.debug "Options: #{options}"
|
398
|
+
Default::Locator::host, sep, Default::Locator::port = options[:locator].rpartition(':')
|
399
|
+
Default::Locator::port = Default::Locator::port.to_i
|
400
|
+
|
401
|
+
Cocaine::LOG.debug "Setting default Locator endpoint to: #{Default::Locator::host}:#{Default::Locator::port}"
|
402
|
+
return Worker.new(options[:app], options[:uuid], options[:endpoint])
|
403
|
+
end
|
404
|
+
end
|
405
|
+
|
406
|
+
class Rack
|
407
|
+
def self.on(event)
|
408
|
+
worker = Cocaine::WorkerFactory.create
|
409
|
+
|
410
|
+
worker.on :http do |res, req|
|
411
|
+
id, payload = req.recv
|
412
|
+
Cocaine::LOG.debug "After receive: '#{{:id => id, :payload => payload}}'"
|
413
|
+
|
414
|
+
case id
|
415
|
+
when Cocaine::RPC::CHUNK
|
416
|
+
method, url, version, headers, body = MessagePack::unpack payload
|
417
|
+
Cocaine::LOG.debug "After unpack: '#{id}, #{[method, url, version, headers, body]}'"
|
418
|
+
|
419
|
+
env = Hash[*headers.flatten]
|
420
|
+
parsed_url = URI.parse("http://#{env['Host']}#{url}")
|
421
|
+
default_host = parsed_url.hostname || 'localhost'
|
422
|
+
default_port = parsed_url.port || '80'
|
423
|
+
|
424
|
+
# noinspection RubyStringKeysInHashInspection
|
425
|
+
env.update(
|
426
|
+
{
|
427
|
+
'GATEWAY_INTERFACE' => 'CGI/1.1',
|
428
|
+
'PATH_INFO' => parsed_url.path || '',
|
429
|
+
'QUERY_STRING' => parsed_url.query || '',
|
430
|
+
'REMOTE_ADDR' => '::1',
|
431
|
+
'REMOTE_HOST' => 'localhost',
|
432
|
+
'REQUEST_METHOD' => method,
|
433
|
+
'REQUEST_URI' => url,
|
434
|
+
'SCRIPT_NAME' => '',
|
435
|
+
'SERVER_NAME' => default_host,
|
436
|
+
'SERVER_PORT' => default_port.to_s,
|
437
|
+
'SERVER_PROTOCOL' => "HTTP/#{version}",
|
438
|
+
'rack.version' => [1, 5],
|
439
|
+
'rack.input' => body,
|
440
|
+
'rack.errors' => $stderr,
|
441
|
+
'rack.multithread' => true,
|
442
|
+
'rack.multiprocess' => false,
|
443
|
+
'rack.run_once' => false,
|
444
|
+
'rack.url_scheme' => 'http',
|
445
|
+
'HTTP_VERSION' => "HTTP/#{version}",
|
446
|
+
'REQUEST_PATH' => parsed_url.path,
|
447
|
+
}
|
448
|
+
)
|
449
|
+
|
450
|
+
Cocaine::LOG.debug "ENV: #{env}"
|
451
|
+
|
452
|
+
now = Time.now
|
453
|
+
code, headers, body = yield env
|
454
|
+
headers['X-Response-Took'] = Time.now - now
|
455
|
+
res.write MessagePack.pack [code, headers.to_a]
|
456
|
+
body.each do |item|
|
457
|
+
res.write(item)
|
458
|
+
end
|
459
|
+
|
460
|
+
body.close if body.respond_to?(:close)
|
461
|
+
when Cocaine::RPC::ERROR
|
462
|
+
when Cocaine::RPC::CHOKE
|
463
|
+
else
|
464
|
+
# Type code here.
|
465
|
+
end
|
466
|
+
end
|
467
|
+
|
468
|
+
worker.run
|
469
|
+
sleep
|
470
|
+
end
|
471
|
+
end
|
472
|
+
end
|
data/lib/cocaine.rb
ADDED
metadata
ADDED
@@ -0,0 +1,106 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: cocaine-framework
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.12.0.pre.rc4
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Evgeny Safronov
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2014-10-15 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: rspec
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '3.1'
|
20
|
+
type: :development
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - "~>"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '3.1'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: msgpack
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - "~>"
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '0.5'
|
34
|
+
type: :runtime
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - "~>"
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '0.5'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: celluloid
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - "~>"
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '0.16'
|
48
|
+
type: :runtime
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - "~>"
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '0.16'
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: celluloid-io
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - "~>"
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '0.16'
|
62
|
+
type: :runtime
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - "~>"
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: '0.16'
|
69
|
+
description: |-
|
70
|
+
Cocaine Framework is a framework for simplifying development both server-side and client-side
|
71
|
+
applications.
|
72
|
+
email:
|
73
|
+
- division494@gmail.com
|
74
|
+
executables: []
|
75
|
+
extensions: []
|
76
|
+
extra_rdoc_files: []
|
77
|
+
files:
|
78
|
+
- lib/cocaine.rb
|
79
|
+
- lib/cocaine/cocaine.rb
|
80
|
+
- lib/cocaine/version.rb
|
81
|
+
homepage: https://github.com/cocaine/cocaine-framework-ruby
|
82
|
+
licenses:
|
83
|
+
- Ruby
|
84
|
+
- LGPLv3
|
85
|
+
metadata: {}
|
86
|
+
post_install_message:
|
87
|
+
rdoc_options: []
|
88
|
+
require_paths:
|
89
|
+
- lib
|
90
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
91
|
+
requirements:
|
92
|
+
- - ">="
|
93
|
+
- !ruby/object:Gem::Version
|
94
|
+
version: '0'
|
95
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
96
|
+
requirements:
|
97
|
+
- - ">"
|
98
|
+
- !ruby/object:Gem::Version
|
99
|
+
version: 1.3.1
|
100
|
+
requirements: []
|
101
|
+
rubyforge_project:
|
102
|
+
rubygems_version: 2.2.2
|
103
|
+
signing_key:
|
104
|
+
specification_version: 4
|
105
|
+
summary: Ruby/Cocaine library
|
106
|
+
test_files: []
|