cocaine-framework-ruby 0.11.2

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,76 @@
1
+ require 'eventmachine'
2
+
3
+
4
+ $log = Logger.new(STDERR)
5
+ $log.level = Logger::DEBUG
6
+
7
+
8
+ class Timer
9
+ def initialize(timeout)
10
+ @timeout = timeout
11
+ end
12
+ end
13
+
14
+
15
+ class DisownTimer < Timer
16
+ def initialize(timeout = 10.0)
17
+ super timeout
18
+ end
19
+
20
+ def start(&block)
21
+ @timer = EM::Timer.new @timeout do
22
+ block.call
23
+ end
24
+ end
25
+
26
+ def cancel
27
+ @timer.cancel if @timer
28
+ end
29
+ end
30
+
31
+
32
+ class HeartbeatTimer < Timer
33
+ def initialize(timeout = 30.0)
34
+ super timeout
35
+ end
36
+
37
+ def start(&block)
38
+ @timer = EM::PeriodicTimer.new @timeout do
39
+ block.call
40
+ end
41
+ end
42
+
43
+ def cancel
44
+ @timer.cancel if @timer
45
+ end
46
+ end
47
+
48
+
49
+ class Cocaine::HealthManager
50
+ def initialize(dispatcher, options={})
51
+ @dispatcher = dispatcher
52
+ options = {disown: 2.0, heartbeat: 10.0}.merge options
53
+ @disown = DisownTimer.new(options[:disown])
54
+ @heartbeat = HeartbeatTimer.new(options[:heartbeat])
55
+ end
56
+
57
+ def start
58
+ $log.debug 'health manager has been started'
59
+ @heartbeat.start { exhale }
60
+ end
61
+
62
+ def breath
63
+ $log.debug '[->] doing breath'
64
+ @disown.cancel
65
+ end
66
+
67
+ private
68
+ def exhale
69
+ $log.debug '[<-] doing exhale'
70
+ @disown.start {
71
+ $log.error 'worker has been disowned'
72
+ EM.next_tick { EM.stop }
73
+ }
74
+ @dispatcher.send_heartbeat 0
75
+ end
76
+ end
@@ -0,0 +1,63 @@
1
+ require 'cgi'
2
+ require 'net/http'
3
+
4
+ require 'cocaine/namespace'
5
+
6
+ $log = Logger.new(STDERR)
7
+ $log.level = Logger::DEBUG
8
+
9
+ module Cocaine::Http
10
+ class Request
11
+ attr_reader :method, :version, :query, :headers, :body
12
+
13
+ def initialize(rq)
14
+ @ch = rq.read
15
+ end
16
+
17
+ def read
18
+ ch = Cocaine::Channel.new
19
+ @ch.callback { |msg|
20
+ @method, url, @version, headers, @body = MessagePack::unpack msg
21
+ @headers = Hash[headers]
22
+ tmp_query = URI.parse(url).query
23
+ @query = tmp_query == nil ? {} : CGI.parse(tmp_query)
24
+ ch.trigger self
25
+ }.errback { |err|
26
+ ch.error err
27
+ }
28
+ ch
29
+ end
30
+ end
31
+
32
+ class Response
33
+ def initialize(response)
34
+ @response = response
35
+ end
36
+
37
+ def write_headers(code, headers)
38
+ @response.write [code, headers]
39
+ end
40
+
41
+ def body=(body)
42
+ @response.write body
43
+ end
44
+
45
+ def error(errno, reason)
46
+ @response.error errno, reason
47
+ end
48
+
49
+ def close
50
+ @response.close
51
+ end
52
+ end
53
+
54
+ def http(method_name)
55
+ old_method_name = "#{method_name}_old".to_sym
56
+ alias_method(old_method_name, method_name)
57
+ define_method(method_name) do |rq, rs|
58
+ request = Request.new rq
59
+ response = Response.new rs
60
+ send(old_method_name, request, response)
61
+ end
62
+ end
63
+ end
@@ -0,0 +1,9 @@
1
+ class Cocaine::Request
2
+ def initialize(channel)
3
+ @ch = channel
4
+ end
5
+
6
+ def read
7
+ @ch
8
+ end
9
+ end
@@ -0,0 +1,31 @@
1
+ require 'cocaine/error'
2
+
3
+
4
+ class Cocaine::Response
5
+ def initialize(session, dispatcher)
6
+ @session = session
7
+ @dispatcher = dispatcher
8
+ @closed = false
9
+ end
10
+
11
+ def write(data)
12
+ check_closed
13
+ @dispatcher.send_chunk @session, data.to_msgpack
14
+ end
15
+
16
+ def error(errno, reason)
17
+ check_closed
18
+ @dispatcher.send_error @session, errno, reason
19
+ end
20
+
21
+ def close
22
+ check_closed
23
+ @closed = true
24
+ @dispatcher.send_choke @session
25
+ end
26
+
27
+ private
28
+ def check_closed
29
+ raise IllegalStateError.new 'Response is already closed' if @closed
30
+ end
31
+ end
@@ -0,0 +1,38 @@
1
+ $log = Logger.new(STDERR)
2
+ $log.level = Logger::DEBUG
3
+
4
+
5
+ class Cocaine::Sandbox
6
+ module Errno
7
+ NO_SUCH_EVENT = 1
8
+ end
9
+
10
+ def initialize
11
+ @handlers = {}
12
+ end
13
+
14
+ def on(event, handler)
15
+ @handlers[event] = handler
16
+ end
17
+
18
+ def invoke(event, request, response)
19
+ if @handlers.has_key? event
20
+ invoke_handler event, request, response
21
+ else
22
+ write_error Errno::NO_SUCH_EVENT, "application has no event '#{event}'", response
23
+ end
24
+ end
25
+
26
+ private
27
+ def invoke_handler(event, request, response)
28
+ $log.debug "invoking '#{event}'"
29
+ handler = @handlers[event]
30
+ handler.execute(request, response)
31
+ end
32
+
33
+ private
34
+ def write_error(errno, reason, response)
35
+ response.error errno, reason
36
+ response.close
37
+ end
38
+ end
@@ -0,0 +1,73 @@
1
+ require 'optparse'
2
+
3
+ require 'cocaine/asio/connection'
4
+ require 'cocaine/server/dispatcher'
5
+ require 'cocaine/server/sandbox'
6
+
7
+ $log = Logger.new(STDERR)
8
+ $log.level = Logger::DEBUG
9
+
10
+
11
+ class Cocaine::Worker
12
+ def initialize(options={})
13
+ options = {
14
+ endpoint: '',
15
+ uuid: '',
16
+ }.merge(options)
17
+
18
+ @endpoint = options[:endpoint]
19
+ @uuid = options[:uuid]
20
+
21
+ @sandbox = Cocaine::Sandbox.new
22
+ end
23
+
24
+ def on(event, handler)
25
+ @sandbox.on event, handler
26
+ end
27
+
28
+ def run
29
+ EM.error_handler { |error|
30
+ short_reason = error.inspect
31
+ traceback = error.backtrace.join("\n")
32
+ $log.warn "error caught at the top of event loop:\n#{short_reason}\n#{traceback}"
33
+ }
34
+
35
+ EM.run do
36
+ $log.debug 'starting worker'
37
+ $log.debug "connecting to the #{@endpoint}"
38
+ EM.connect @endpoint, nil, Cocaine::Connection do |conn|
39
+ $log.debug "connection established with #{@endpoint}"
40
+ @dispatcher = Cocaine::WorkerDispatcher.new self, conn
41
+ @dispatcher.send_handshake 0, @uuid
42
+ @dispatcher.send_heartbeat 0
43
+ end
44
+ end
45
+ end
46
+
47
+ def invoke(event, request, response)
48
+ @sandbox.invoke(event, request, response)
49
+ end
50
+
51
+ def terminate(errno, reason)
52
+ $log.debug "terminating with: [#{errno}] #{reason}"
53
+ @dispatcher.send_terminate 0, errno, reason
54
+ EM.stop
55
+ exit(errno)
56
+ end
57
+ end
58
+
59
+
60
+ class Cocaine::WorkerFactory
61
+ def self.create
62
+ options = {}
63
+ OptionParser.new do |opts|
64
+ opts.banner = 'Usage: <your_worker.rb> --app NAME --locator ADDRESS --uuid UUID --endpoint ENDPOINT'
65
+
66
+ opts.on('--app NAME', 'Worker name') { |a| options[:app] = a }
67
+ opts.on('--locator ADDRESS', 'Locator address') { |a| options[:locator] = a }
68
+ opts.on('--uuid UUID', 'Worker uuid') { |a| options[:uuid] = a }
69
+ opts.on('--endpoint ENDPOINT', 'Worker endpoint') { |a| options[:endpoint] = a }
70
+ end.parse!
71
+ return Cocaine::Worker.new options
72
+ end
73
+ end
@@ -0,0 +1,72 @@
1
+ require 'fiber'
2
+ require 'em-synchrony'
3
+
4
+ require 'cocaine/namespace'
5
+
6
+ class Cocaine::Synchrony::Channel
7
+ def initialize(channel)
8
+ @pending = []
9
+ register_fiber channel
10
+ end
11
+
12
+ def read
13
+ if @pending.empty?
14
+ future = Fiber.yield
15
+ else
16
+ future = @pending.pop
17
+ end
18
+ future.get
19
+ end
20
+
21
+ def each
22
+ loop do
23
+ begin
24
+ yield Fiber.yield.get
25
+ rescue ChokeEvent
26
+ return
27
+ end
28
+ end
29
+ end
30
+
31
+ def collect(count=0)
32
+ if count == 0
33
+ collect_until_choke
34
+ else
35
+ collect_until_count count
36
+ end
37
+ end
38
+
39
+ private
40
+ def register_fiber(channel)
41
+ fb = Fiber.current
42
+ channel.callback { |future|
43
+ if fb.alive?
44
+ fb.resume future
45
+ else
46
+ @pending.push future
47
+ end
48
+ }
49
+ end
50
+
51
+ private
52
+ def collect_until_count(count)
53
+ results = []
54
+ each do |chunk|
55
+ results.push chunk
56
+ count -= 1
57
+ if count == 0
58
+ break
59
+ end
60
+ end
61
+ results
62
+ end
63
+
64
+ private
65
+ def collect_until_choke
66
+ results = []
67
+ each do |chunk|
68
+ results.push chunk
69
+ end
70
+ results
71
+ end
72
+ end
@@ -0,0 +1,53 @@
1
+ require 'cocaine/client/service'
2
+ require 'cocaine/synchrony/channel'
3
+
4
+
5
+ module Cocaine::Synchrony
6
+ def self.sync(df)
7
+ fb = Fiber.current
8
+ df.callback do |result|
9
+ if fb == Fiber.current
10
+ return result
11
+ else
12
+ fb.resume result
13
+ end
14
+ end
15
+
16
+ df.errback do |err|
17
+ if fb == Fiber.current
18
+ raise err
19
+ else
20
+ fb.resume err
21
+ end
22
+ end
23
+
24
+ result = Fiber.yield
25
+ if result.is_a? Exception
26
+ raise result
27
+ else
28
+ return result
29
+ end
30
+ end
31
+ end
32
+
33
+
34
+ class Cocaine::Synchrony::Service
35
+ def initialize(name, host='localhost', port=10053)
36
+ @service = Cocaine::Service.new name, host, port
37
+ connect
38
+ create_proxy_methods
39
+ end
40
+
41
+ def connect
42
+ Cocaine::Synchrony.sync @service.connect
43
+ end
44
+
45
+ private
46
+ def create_proxy_methods
47
+ @service.api.each do |id, name|
48
+ self.metaclass.send(:define_method, name) do |*args|
49
+ Cocaine::Synchrony::Channel.new @service.send name, *args
50
+ end
51
+ end
52
+ end
53
+ end
@@ -0,0 +1,123 @@
1
+ require 'em-synchrony'
2
+ require 'logger'
3
+
4
+ require 'cocaine/synchrony/service'
5
+
6
+ $log = Logger.new(STDOUT)
7
+ $log.level = Logger::DEBUG
8
+
9
+
10
+ class Hook
11
+ attr_reader :callbacks
12
+ def initialize
13
+ @callbacks = {
14
+ :connected => lambda {},
15
+ :message => {}
16
+ }
17
+ end
18
+
19
+ def connected(&block)
20
+ @callbacks[:connected] = block
21
+ end
22
+
23
+ def message(request, &block)
24
+ @callbacks[:message][request] = block
25
+ end
26
+ end
27
+
28
+
29
+ class CocaineRuntimeMock
30
+ module Server
31
+ def initialize(name, responses=nil, hook=nil, options={})
32
+ $log.debug "new connection for '#{name}'"
33
+ @name = name
34
+ @responses = responses || {}
35
+ @hook = hook || Hook.new
36
+ @options = options
37
+ end
38
+
39
+ def post_init
40
+ @hook.callbacks[:connected].call
41
+ end
42
+
43
+ def receive_data(data)
44
+ unpacker ||= MessagePack::Unpacker.new
45
+ unpacker.feed_each data do |chunk|
46
+ id, session, data = chunk
47
+ $log.debug "received message: [#{id}, #{session}, #{data}]"
48
+
49
+ if @hook.callbacks[:message].has_key? chunk
50
+ response = @hook.callbacks[:message][chunk].call
51
+ send_response session, response
52
+ elsif @responses.has_key? chunk
53
+ response = @responses[chunk]
54
+ send_response session, response
55
+ end
56
+ end
57
+ end
58
+
59
+ private
60
+ def send_response(session, response)
61
+ $log.debug "iterating over response: #{response}"
62
+ response.each do |chunk|
63
+ if chunk.is_a? Error
64
+ send_data chunk.pack session
65
+ else
66
+ send_data (Chunk.new chunk.to_msgpack).pack session
67
+ end
68
+ end
69
+ send_data Choke.new.pack session
70
+ end
71
+ end
72
+
73
+ def initialize(options = {})
74
+ options = {:host => 'localhost', :port => 10053}.merge(options)
75
+
76
+ @host = options[:host]
77
+ @port = options[:port]
78
+
79
+ @services = {}
80
+ @responses = {}
81
+ @hooks = {}
82
+
83
+ @servers = []
84
+
85
+ register 'locator', 0, endpoint: [@host, @port], version: 1, api: {}
86
+ end
87
+
88
+ def register(name, session, options={})
89
+ raise "service '#{name}' already registered" if @services.has_key? name
90
+
91
+ $log.debug "registering '#{name}' service"
92
+
93
+ options = {endpoint: ['localhost', 0], version: 1, api: {}}.merge options
94
+ @services[name] = options
95
+
96
+ on 'locator',
97
+ [0, session, [name]],
98
+ [[options[:endpoint], options[:version], options[:api]]]
99
+ end
100
+
101
+ def on(name, request, response)
102
+ @responses[name] ||= {}
103
+ @responses[name][request] = response
104
+ end
105
+
106
+ def when(name)
107
+ @hooks[name] ||= Hook.new
108
+ end
109
+
110
+ def run
111
+ @services.each do |name, options|
112
+ $log.debug "starting '#{name}' service at #{options[:endpoint]}"
113
+ sig = EM::start_server *options[:endpoint], Server, name, @responses[name], @hooks[name], options
114
+ @servers.push sig
115
+ end
116
+ end
117
+
118
+ def stop
119
+ @servers.each do |sig|
120
+ EM.stop_server sig
121
+ end
122
+ end
123
+ end