distributor 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,2 @@
1
+ module Distributor
2
+ end
@@ -0,0 +1,88 @@
1
+ require "distributor"
2
+ require "distributor/connector"
3
+ require "distributor/multiplexer"
4
+ require "json"
5
+
6
+ class Distributor::Client
7
+
8
+ def initialize(input, output=input)
9
+ @connector = Distributor::Connector.new
10
+ @multiplexer = Distributor::Multiplexer.new(output)
11
+ @handlers = {}
12
+ @processes = []
13
+ @on_close = Hash.new([])
14
+
15
+ # reserve a command channel
16
+ @multiplexer.reserve(0)
17
+
18
+ # feed data from the input channel into the multiplexer
19
+ @connector.handle(input) do |io|
20
+ @multiplexer.input io
21
+ end
22
+
23
+ @connector.on_close(input) do |io|
24
+ @multiplexer.output 0, JSON.dump({ "command" => "close", "ch" => ch })
25
+ end
26
+
27
+ # handle the command channel of the multiplexer
28
+ @connector.handle(@multiplexer.reader(0)) do |io|
29
+ data = JSON.parse(io.readpartial(4096))
30
+
31
+ case command = data["command"]
32
+ when "close" then
33
+ ch = data["ch"]
34
+ @on_close[ch].each { |c| c.call(ch) }
35
+ when "launch" then
36
+ ch = data["ch"]
37
+ @multiplexer.reserve ch
38
+ @handlers[data["id"]].call(ch)
39
+ @handlers.delete(data["id"])
40
+ @processes << ch
41
+ else
42
+ raise "no such command: #{command}"
43
+ end
44
+ end
45
+ end
46
+
47
+ def output(ch, data)
48
+ @multiplexer.output ch, data
49
+ end
50
+
51
+ def run(command, &handler)
52
+ id = "#{Time.now.to_f}-#{rand(10000)}"
53
+ @multiplexer.output 0, JSON.dump({ "id" => id, "command" => "run", "args" => command })
54
+ @handlers[id] = handler
55
+ end
56
+
57
+ def hookup(ch, input, output)
58
+ # handle data incoming on the multiplexer
59
+ @connector.handle(@multiplexer.reader(ch)) do |io|
60
+ begin
61
+ data = io.readpartial(4096)
62
+ # output.write "#{ch}: #{data}"
63
+ output.write data
64
+ rescue EOFError
65
+ @multiplexer.output 0, JSON.dump({ "command" => "close", "ch" => ch })
66
+ end
67
+ end
68
+
69
+ # handle data incoming from the input channel
70
+ @connector.handle(input) do |io|
71
+ begin
72
+ data = io.readpartial(4096)
73
+ @multiplexer.output ch, data
74
+ rescue EOFError
75
+ @processes.each { |ch| @multiplexer.close(ch) }
76
+ end
77
+ end
78
+ end
79
+
80
+ def on_close(ch, &blk)
81
+ @on_close[ch] << blk
82
+ end
83
+
84
+ def start
85
+ loop { @connector.listen }
86
+ end
87
+
88
+ end
@@ -0,0 +1,28 @@
1
+ require "distributor"
2
+
3
+ class Distributor::Connector
4
+
5
+ attr_reader :connections
6
+
7
+ def initialize
8
+ @connections = {}
9
+ @on_close = Hash.new([])
10
+ end
11
+
12
+ def handle(from, &handler)
13
+ @connections[from] = handler
14
+ end
15
+
16
+ def on_close(from, &handler)
17
+ @on_close[from] << handler
18
+ end
19
+
20
+ def listen
21
+ rs, ws = IO.select(@connections.keys)
22
+ rs.each do |from|
23
+ @on_close.each { |c| c.call } if from.eof?
24
+ self.connections[from].call(from)
25
+ end
26
+ end
27
+
28
+ end
@@ -0,0 +1,47 @@
1
+ require "distributor"
2
+ require "distributor/packet"
3
+
4
+ class Distributor::Multiplexer
5
+
6
+ def initialize(output)
7
+ @output = output
8
+ @readers = {}
9
+ @writers = {}
10
+
11
+ @output.sync = true
12
+ end
13
+
14
+ def reserve(ch=nil)
15
+ ch ||= @readers.keys.length
16
+ raise "channel already taken: #{ch}" if @readers.has_key?(ch)
17
+ @readers[ch], @writers[ch] = IO.pipe
18
+ ch
19
+ end
20
+
21
+ def reader(ch)
22
+ @readers[ch] || raise("no such channel: #{ch}")
23
+ end
24
+
25
+ def writer(ch)
26
+ @writers[ch] || raise("no such channel: #{ch}")
27
+ end
28
+
29
+ def input(io)
30
+ ch, data = Distributor::Packet.parse(io)
31
+ return if ch.nil?
32
+ writer(ch).write data
33
+ rescue IOError
34
+ @output.write Distributor::Packet.create(0, JSON.dump({ "command" => "close", "ch" => ch }))
35
+ end
36
+
37
+ def output(ch, data)
38
+ @output.write Distributor::Packet.create(ch, data)
39
+ rescue Errno::EPIPE
40
+ end
41
+
42
+ def close(ch)
43
+ writer(ch).close
44
+ rescue IOError
45
+ end
46
+
47
+ end
@@ -0,0 +1,36 @@
1
+ require "distributor"
2
+
3
+ class Distributor::Packet
4
+
5
+ PROTOCOL_VERSION = 1
6
+
7
+ def self.create(channel, data)
8
+ packet = "DIST"
9
+ packet += pack(PROTOCOL_VERSION)
10
+ packet += pack(channel)
11
+ packet += pack(data.length)
12
+ packet += data
13
+ end
14
+
15
+ def self.parse(io)
16
+ header = io.read(4)
17
+ return if header.nil?
18
+ raise "invalid header" unless header == "DIST"
19
+ version = unpack(io.read(4))
20
+ channel = unpack(io.read(4))
21
+ length = unpack(io.read(4))
22
+ data = ""
23
+ data += io.readpartial([4096,length-data.length].min) while data.length < length
24
+
25
+ [ channel, data ]
26
+ end
27
+
28
+ def self.pack(num)
29
+ [num.to_s(16).rjust(8,"0")].pack("H8")
30
+ end
31
+
32
+ def self.unpack(string)
33
+ string.unpack("N").first
34
+ end
35
+
36
+ end
@@ -0,0 +1,66 @@
1
+ require "distributor"
2
+ require "distributor/connector"
3
+ require "distributor/multiplexer"
4
+ require "pty"
5
+
6
+ class Distributor::Server
7
+
8
+ def initialize(input, output=input)
9
+ @connector = Distributor::Connector.new
10
+ @multiplexer = Distributor::Multiplexer.new(output)
11
+
12
+ # reserve a command channel
13
+ @multiplexer.reserve(0)
14
+
15
+ # feed data from the input channel into the multiplexer
16
+ @connector.handle(input) do |io|
17
+ @multiplexer.input io
18
+ end
19
+
20
+ # handle the command channel of the multiplexer
21
+ @connector.handle(@multiplexer.reader(0)) do |io|
22
+ data = JSON.parse(io.readpartial(4096))
23
+
24
+ case command = data["command"]
25
+ when "close" then
26
+ @multiplexer.close data["ch"]
27
+ when "run" then
28
+ ch = run(data["args"])
29
+ @multiplexer.output 0, JSON.dump({ "id" => data["id"], "command" => "launch", "ch" => ch })
30
+ else
31
+ raise "no such command: #{command}"
32
+ end
33
+ end
34
+ end
35
+
36
+ def run(command)
37
+ ch = @multiplexer.reserve
38
+
39
+ rd, wr, pid = PTY.spawn(command)
40
+
41
+ # handle data incoming from process
42
+ @connector.handle(rd) do |io|
43
+ begin
44
+ @multiplexer.output(ch, io.readpartial(4096))
45
+ rescue EOFError
46
+ @multiplexer.output 0, JSON.dump({ "command" => "close", "ch" => ch })
47
+ end
48
+ end
49
+
50
+ # handle data incoming on the multiplexer
51
+ @connector.handle(@multiplexer.reader(ch)) do |input_io|
52
+ data = input_io.readpartial(4096)
53
+ wr.write data
54
+ end
55
+
56
+ ch
57
+ end
58
+
59
+ def close(ch)
60
+ end
61
+
62
+ def start
63
+ loop { @connector.listen }
64
+ end
65
+
66
+ end
@@ -0,0 +1,5 @@
1
+ module Distributor
2
+
3
+ VERSION = "0.0.1"
4
+
5
+ end
metadata ADDED
@@ -0,0 +1,62 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: distributor
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - David Dollar
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2012-06-14 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: thor
16
+ requirement: &70336779621500 !ruby/object:Gem::Requirement
17
+ none: false
18
+ requirements:
19
+ - - ! '>='
20
+ - !ruby/object:Gem::Version
21
+ version: 0.13.6
22
+ type: :runtime
23
+ prerelease: false
24
+ version_requirements: *70336779621500
25
+ description: TCP Multiplexer
26
+ email: ddollar@gmail.com
27
+ executables: []
28
+ extensions: []
29
+ extra_rdoc_files: []
30
+ files:
31
+ - lib/distributor/client.rb
32
+ - lib/distributor/connector.rb
33
+ - lib/distributor/multiplexer.rb
34
+ - lib/distributor/packet.rb
35
+ - lib/distributor/server.rb
36
+ - lib/distributor/version.rb
37
+ - lib/distributor.rb
38
+ homepage: http://github.com/ddollar/distributor
39
+ licenses: []
40
+ post_install_message:
41
+ rdoc_options: []
42
+ require_paths:
43
+ - lib
44
+ required_ruby_version: !ruby/object:Gem::Requirement
45
+ none: false
46
+ requirements:
47
+ - - ! '>='
48
+ - !ruby/object:Gem::Version
49
+ version: '0'
50
+ required_rubygems_version: !ruby/object:Gem::Requirement
51
+ none: false
52
+ requirements:
53
+ - - ! '>='
54
+ - !ruby/object:Gem::Version
55
+ version: '0'
56
+ requirements: []
57
+ rubyforge_project:
58
+ rubygems_version: 1.8.11
59
+ signing_key:
60
+ specification_version: 3
61
+ summary: TCP Multiplexer
62
+ test_files: []