pipeline_toolkit 1.0.4 → 1.0.6
Sign up to get free protection for your applications and to get access to all the features.
- data/LICENSE.markdown +20 -0
- data/README.markdown +98 -0
- data/bin/msg_generator +13 -0
- data/bin/msg_probe +13 -0
- data/bin/msg_push +13 -0
- data/bin/msg_sink +14 -0
- data/bin/msg_subscribe +13 -0
- data/lib/pipeline_toolkit.rb +17 -0
- data/lib/pipeline_toolkit/amqp/abstract.rb +95 -0
- data/lib/pipeline_toolkit/amqp/reader.rb +64 -0
- data/lib/pipeline_toolkit/amqp/writer.rb +54 -0
- data/lib/pipeline_toolkit/commands/msg_generator/cli.rb +45 -0
- data/lib/pipeline_toolkit/commands/msg_probe/cli.rb +46 -0
- data/lib/pipeline_toolkit/commands/msg_push/cli.rb +58 -0
- data/lib/pipeline_toolkit/commands/msg_sink/cli.rb +41 -0
- data/lib/pipeline_toolkit/commands/msg_subscribe/cli.rb +58 -0
- data/lib/pipeline_toolkit/default_logger.rb +126 -11
- data/lib/pipeline_toolkit/handlers/message_handler.rb +38 -0
- data/lib/pipeline_toolkit/message_coder.rb +18 -8
- data/lib/pipeline_toolkit/message_command.rb +138 -61
- data/lib/pipeline_toolkit/message_generator.rb +21 -0
- data/lib/pipeline_toolkit/message_probe.rb +6 -6
- data/lib/pipeline_toolkit/message_pusher.rb +51 -54
- data/lib/pipeline_toolkit/message_sink.rb +1 -1
- data/lib/pipeline_toolkit/message_subscriber.rb +182 -201
- data/lib/pipeline_toolkit/monitoring/monitor_server.rb +124 -0
- data/spec/eventmachine_helper.rb +44 -0
- data/spec/message_subscriber_spec.rb +64 -0
- data/spec/spec_helper.rb +15 -0
- metadata +202 -47
- data/.gitignore +0 -5
- data/README.rdoc +0 -70
- data/Rakefile +0 -40
- data/VERSION +0 -1
- data/bin/msg_generator.rb +0 -0
- data/bin/msg_probe.rb +0 -15
- data/bin/msg_push.rb +0 -25
- data/bin/msg_sink.rb +0 -11
- data/bin/msg_subscribe.rb +0 -27
- data/monitor/munin.rb +0 -91
- data/pipeline_toolkit.gemspec +0 -72
@@ -0,0 +1,21 @@
|
|
1
|
+
require 'json'
|
2
|
+
|
3
|
+
class MessageGenerator
|
4
|
+
|
5
|
+
def initialize(opts)
|
6
|
+
@delay = opts.delay
|
7
|
+
@msg = JSON.parse(opts.msg)
|
8
|
+
end
|
9
|
+
|
10
|
+
def start
|
11
|
+
loop do
|
12
|
+
self.send_msg(@msg)
|
13
|
+
sleep(@delay) unless @delay.nil?
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
def send_msg(msg)
|
18
|
+
# puts MessageCoder.encode(@msg)
|
19
|
+
$stdout.flush
|
20
|
+
end
|
21
|
+
end
|
@@ -13,16 +13,16 @@ class MessageProbe
|
|
13
13
|
self.init_dnssd if opts.dnssd
|
14
14
|
end
|
15
15
|
|
16
|
-
def init_loop
|
17
|
-
EM.start_server('0.0.0.0', @http_port, ProbeHttpRequest, self)
|
18
|
-
EM.add_periodic_timer(@interval) { self.tick }
|
19
|
-
end
|
20
|
-
|
21
16
|
def init_dnssd
|
22
17
|
require 'dnssd'
|
23
18
|
DNSSD.register!("#{@name} probe", "_http._tcp", nil, @http_port)
|
24
19
|
end
|
25
20
|
|
21
|
+
def initialize_machine
|
22
|
+
EM.start_server('0.0.0.0', @http_port, ProbeHttpRequest, self)
|
23
|
+
EM.add_periodic_timer(@interval) { self.tick }
|
24
|
+
end
|
25
|
+
|
26
26
|
def tick
|
27
27
|
@time_delta = Time.now - @prev_time
|
28
28
|
@mps = @count / @time_delta
|
@@ -40,7 +40,7 @@ class MessageProbe
|
|
40
40
|
|
41
41
|
# OPTIMIZE. can improve performance by overriding base process_line method
|
42
42
|
# saving us the unnecessary marshal step.
|
43
|
-
def
|
43
|
+
def process_standard(msg)
|
44
44
|
@count += 1
|
45
45
|
msg
|
46
46
|
end
|
@@ -1,61 +1,58 @@
|
|
1
1
|
require "mq"
|
2
2
|
|
3
|
+
##
|
4
|
+
# A Message Queue machine to handle messages that has to be published
|
5
|
+
# back into a message queue.
|
6
|
+
#
|
3
7
|
class MessagePusher
|
4
8
|
include MessageCommand
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
#
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
@exchanges.each do |exchange|
|
53
|
-
key = route_key(msg)
|
54
|
-
exchange.publish(msg.to_yaml)
|
55
|
-
# OPTIMIZE. Using MessageCoder.encode(msg) instead of to_yaml is 2x faster. But won't be easy to
|
56
|
-
# debug. Worth it?
|
57
|
-
end
|
58
|
-
:ack
|
9
|
+
include Amqp::Writer
|
10
|
+
|
11
|
+
##
|
12
|
+
# Initializes a new intance
|
13
|
+
#
|
14
|
+
# @param options<Hash> Options hash for the message pusher.
|
15
|
+
#
|
16
|
+
def initialize(options = {})
|
17
|
+
super(options)
|
18
|
+
DefaultLogger.debug("MessagePusher#initialize(options = {})") if options[:env] == "development"
|
19
|
+
|
20
|
+
queues_string = options[:queues].map { |name, type| "#{name} (key:#{type})" }.join(",")
|
21
|
+
DefaultLogger.info "================================================================"
|
22
|
+
DefaultLogger.info "Booting #{self.class.name} (#{options[:env]})"
|
23
|
+
DefaultLogger.info "Exchange: #{options[:exchange]} (#{options[:type]} passive:#{options[:passive]} durable:#{options[:durable]})"
|
24
|
+
DefaultLogger.info "Queues: #{queues_string}"
|
25
|
+
DefaultLogger.info "amqp://#{options[:user]}:#{options[:pass]}@#{options[:host]}:#{options[:port]}#{options[:vhost]}"
|
26
|
+
DefaultLogger.info ""
|
27
|
+
end
|
28
|
+
|
29
|
+
##
|
30
|
+
# Initialize the AMQP server connection and exchange so we can write messages to the queue.
|
31
|
+
#
|
32
|
+
def initialize_machine
|
33
|
+
DefaultLogger.debug("MessagePusher#initialize_machine") if options[:env] == "development"
|
34
|
+
initialize_writer
|
35
|
+
initialize_queues
|
36
|
+
end
|
37
|
+
|
38
|
+
##
|
39
|
+
# Notify back down the pipeline information about this machine.
|
40
|
+
#
|
41
|
+
def report_back
|
42
|
+
DefaultLogger.debug("MessagePusher#open_acknowledgement_pipe") if options[:env] == "development"
|
43
|
+
message = { :msg_type => :pipe_desc, :queues => queue_names.join(",") }
|
44
|
+
write_to_pipe(message, @ack_pipe)
|
45
|
+
end
|
46
|
+
|
47
|
+
##
|
48
|
+
# Handle the messages by writing them into the AMQP server exchange.
|
49
|
+
#
|
50
|
+
# @param message<Hash> The message to write to the exchange.
|
51
|
+
#
|
52
|
+
def process_standard(message)
|
53
|
+
DefaultLogger.debug("MessagePusher#process_standard(message)") if options[:env] == "development"
|
54
|
+
publish(message)
|
55
|
+
acknowledge(message) if options[:ack]
|
59
56
|
end
|
60
57
|
|
61
58
|
end
|
@@ -3,246 +3,227 @@ require "mq"
|
|
3
3
|
require "time"
|
4
4
|
require "socket"
|
5
5
|
|
6
|
+
##
|
7
|
+
# The message subscriber is used to subscribe to a AMQP server queue
|
8
|
+
# and handle a single message at a time.
|
9
|
+
#
|
6
10
|
class MessageSubscriber
|
7
|
-
include DefaultLogger
|
8
11
|
|
9
|
-
|
10
|
-
|
12
|
+
include Amqp::Reader
|
13
|
+
|
14
|
+
attr_reader :start_time
|
15
|
+
attr_reader :mps
|
16
|
+
attr_reader :options
|
17
|
+
attr_accessor :queues_out
|
11
18
|
|
12
19
|
PIPE_PATH = "/tmp"
|
13
20
|
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
@
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
21
|
+
##
|
22
|
+
# Initializes a new instance
|
23
|
+
#
|
24
|
+
# @param options<Hash> An options hash, see command line interface.
|
25
|
+
#
|
26
|
+
def initialize(options = {})
|
27
|
+
|
28
|
+
DefaultLogger.init_logger(options)
|
29
|
+
|
30
|
+
@options = options
|
31
|
+
|
32
|
+
@options[:http_port] ||= Socket.select_random_port(10_000, 11_000)
|
33
|
+
@options[:name] ||= (Socket.gethostname + '_' + Process.pid.to_s)
|
34
|
+
|
35
|
+
DefaultLogger.info "================================================================"
|
36
|
+
DefaultLogger.info "Booting #{self.class.name} (#{options[:env]})"
|
37
|
+
DefaultLogger.info "Exchange: #{options[:exchange]} (#{options[:type]} passive:#{options[:passive]} durable:#{options[:durable]})"
|
38
|
+
DefaultLogger.info "Queue: #{options[:queue]} (ack:#{options[:ack]})"
|
39
|
+
DefaultLogger.info "amqp://#{options[:user]}:#{options[:pass]}@#{options[:host]}:#{options[:port]}#{options[:vhost]}"
|
40
|
+
DefaultLogger.info "Monitoring: http://localhost:#{options[:http_port]}/ (#{options[:content_type]} name:#{options[:name]} DNS-SD:#{options[:dnssd]})"
|
41
|
+
DefaultLogger.info ""
|
42
|
+
|
43
|
+
@queues_out = ''
|
44
|
+
|
45
|
+
initialize_dnssd
|
46
|
+
|
47
|
+
reset_message_statistics
|
35
48
|
end
|
36
49
|
|
50
|
+
##
|
51
|
+
# Starts the subscriber reactor loop
|
52
|
+
#
|
37
53
|
def start
|
38
|
-
# FIXME. Need to do more investigation on if/when messages are lost with shutdowns
|
39
|
-
Signal.trap('INT') { AMQP.stop{ EM.stop } }
|
40
|
-
Signal.trap('TERM') { AMQP.stop{ EM.stop } }
|
41
54
|
|
55
|
+
Signal.trap('INT') { EM.stop }
|
56
|
+
Signal.trap('TERM') { EM.stop }
|
57
|
+
|
58
|
+
DefaultLogger.debug("MessageSubscriber#start")
|
42
59
|
begin
|
43
|
-
|
44
|
-
|
60
|
+
create_ack_pipe
|
61
|
+
|
62
|
+
EM.run do
|
45
63
|
@start_time = Time.now
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
conn = EM.watch(@sys_pipe, HandleCtlMessages, @sys_pipe, self)
|
64
|
+
|
65
|
+
initialize_reader
|
66
|
+
queue_subscribe
|
67
|
+
|
68
|
+
conn = EM.watch(@ack_pipe, Handlers::MessageHandler, self, options)
|
69
|
+
# must call this to setup callback to notify_readable
|
54
70
|
conn.notify_readable = true
|
55
|
-
|
56
|
-
EM.start_server('0.0.0.0',
|
57
|
-
EM.add_periodic_timer(5) {
|
71
|
+
|
72
|
+
EM.start_server('0.0.0.0', options[:http_port], Monitoring::MonitorServer, self, options)
|
73
|
+
EM.add_periodic_timer(5) { calculate_message_statistics }
|
58
74
|
end
|
59
75
|
rescue StandardError => e
|
60
|
-
|
76
|
+
DefaultLogger.error "#{e.class.name}: #{e.message}\n" << e.backtrace.join("\n")
|
61
77
|
raise e
|
62
78
|
ensure
|
63
|
-
|
79
|
+
shutdown
|
64
80
|
end
|
65
81
|
end
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
self.reset
|
71
|
-
end
|
72
|
-
|
73
|
-
def reset
|
74
|
-
@count = 0
|
75
|
-
@prev_time = Time.now
|
76
|
-
end
|
77
|
-
|
82
|
+
|
83
|
+
##
|
84
|
+
# Stop the subscriber
|
85
|
+
#
|
78
86
|
def shutdown
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
87
|
+
DefaultLogger.info("Shutting down #{self.class.name}")
|
88
|
+
DefaultLogger.info "^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^"
|
89
|
+
destroy_ack_pipe
|
90
|
+
end
|
91
|
+
|
92
|
+
##
|
93
|
+
# Callback for the Handlers::MessageHandler when it receives a message
|
94
|
+
#
|
95
|
+
# @param message<Hash> The decoded message
|
96
|
+
#
|
97
|
+
def process(message)
|
98
|
+
case message[:msg_type]
|
99
|
+
when :ack
|
100
|
+
perform_acknowledgement(message[:ack_id])
|
101
|
+
when :pipe_desc
|
102
|
+
DefaultLogger.debug("AcknowledgementHandler#queues_out")
|
103
|
+
@queues_out = message[:queues]
|
104
|
+
else
|
105
|
+
raise Exception.new("Unknown control message received: #{message}")
|
91
106
|
end
|
92
107
|
end
|
93
|
-
|
94
|
-
def create_exchange(name, type)
|
95
|
-
MQ::Exchange.new(MQ.default, type.to_sym, @exchange_name, :durable => true, :passive => false)
|
96
|
-
end
|
97
|
-
|
98
|
-
def create_sys_pipe
|
99
|
-
log.debug("creating sys-pipe")
|
100
|
-
name = File.join(PIPE_PATH, "sys_pipe_#{self.generate_guid}")
|
101
|
-
`mkfifo #{name}`
|
102
|
-
@sys_pipe = File.new(name, "r+")
|
103
|
-
$stdout.puts(MessageCoder.encode({:msg_type => :system,
|
104
|
-
:sys_pipe => name,
|
105
|
-
:use_ack => @use_ack,
|
106
|
-
:max_unackd => @max_unackd}))
|
107
|
-
$stdout.flush
|
108
|
-
end
|
109
|
-
|
110
|
-
def destroy_sys_pipe
|
111
|
-
`rm #{@sys_pipe.path}`
|
112
|
-
end
|
113
108
|
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
109
|
+
##
|
110
|
+
# Support templating of member data.
|
111
|
+
#
|
112
|
+
def get_binding
|
113
|
+
binding
|
118
114
|
end
|
119
115
|
|
120
|
-
#
|
121
|
-
def
|
122
|
-
|
123
|
-
if @ix and @ix >= 10_000
|
124
|
-
@ix = nil
|
125
|
-
@seed = nil
|
126
|
-
end
|
127
|
-
|
128
|
-
# NB. This will only work on *nix platforms
|
129
|
-
@seed ||= `uuidgen`.chomp.gsub(/-/,"")
|
130
|
-
@ix ||= 0
|
131
|
-
|
132
|
-
"#{@seed}#{@ix += 1}"
|
133
|
-
end
|
134
|
-
|
135
|
-
def process_message(header, body)
|
136
|
-
msg = YAML.load(body)
|
137
|
-
store_ack(msg, header) if @use_ack
|
138
|
-
write_msg(msg)
|
116
|
+
# :nodoc:
|
117
|
+
def name
|
118
|
+
options[:name] || ''
|
139
119
|
end
|
140
120
|
|
141
|
-
|
142
|
-
|
143
|
-
|
121
|
+
# :nodoc:
|
122
|
+
def queue_name
|
123
|
+
options[:name] || ''
|
144
124
|
end
|
145
125
|
|
146
|
-
|
147
|
-
header = @unackd_msgs.delete(ack_id)
|
148
|
-
header.ack
|
149
|
-
end
|
126
|
+
protected
|
150
127
|
|
151
|
-
|
152
|
-
|
153
|
-
|
128
|
+
##
|
129
|
+
# Process the message received from AMQP server
|
130
|
+
#
|
131
|
+
# @param header<MQ::Header> The header of the message
|
132
|
+
# @param body Description
|
133
|
+
#
|
134
|
+
def process_queue_message(header, body)
|
135
|
+
DefaultLogger.debug("MessageSubscriber#process_queue_message(header, body)")
|
136
|
+
unless body.is_a?(Hash)
|
137
|
+
message = { :msg_type => :standard, :raw => body }
|
138
|
+
else
|
139
|
+
message = { :msg_type => :standard }.merge(body)
|
140
|
+
end
|
141
|
+
store_acknowledgement(message, header) if options[:ack]
|
142
|
+
write(message)
|
154
143
|
end
|
144
|
+
|
145
|
+
private
|
146
|
+
|
147
|
+
##
|
148
|
+
# Reset the instance variables used to store message statistics
|
149
|
+
#
|
150
|
+
def reset_message_statistics
|
151
|
+
@count = 0
|
152
|
+
@prev_time = Time.now
|
153
|
+
end
|
154
|
+
|
155
|
+
##
|
156
|
+
# Calculate the statistics on message handling
|
157
|
+
#
|
158
|
+
def calculate_message_statistics
|
159
|
+
@time_delta = Time.now - @prev_time
|
160
|
+
@mps = @count / @time_delta
|
161
|
+
reset_message_statistics
|
162
|
+
end
|
155
163
|
|
156
|
-
|
157
|
-
|
158
|
-
|
159
|
-
|
160
|
-
|
161
|
-
|
162
|
-
@
|
164
|
+
##
|
165
|
+
# Create the acknowledgement named pipe
|
166
|
+
#
|
167
|
+
def create_ack_pipe
|
168
|
+
name = File.join(PIPE_PATH, "sys_pipe_#{generate_guid}")
|
169
|
+
`mkfifo #{name}`
|
170
|
+
@ack_pipe = File.new(name, "r+")
|
171
|
+
message = { :msg_type => :system,
|
172
|
+
:sys_pipe => name,
|
173
|
+
:ack => options[:ack] }
|
174
|
+
write_to_pipe(message)
|
175
|
+
end
|
176
|
+
|
177
|
+
##
|
178
|
+
# Destroy the acknowledgement named pipe
|
179
|
+
#
|
180
|
+
def destroy_ack_pipe
|
181
|
+
`rm #{@ack_pipe.path}`
|
163
182
|
end
|
164
183
|
|
165
|
-
|
166
|
-
|
167
|
-
|
168
|
-
|
169
|
-
|
170
|
-
|
171
|
-
|
172
|
-
|
173
|
-
|
184
|
+
##
|
185
|
+
# Generates a unique guid to use as part of the acknowledgement named pipe name.
|
186
|
+
# Stolen from EM.
|
187
|
+
#
|
188
|
+
def generate_guid
|
189
|
+
# Cache uuidgen seed for better performance
|
190
|
+
if @ix and @ix >= 10_000
|
191
|
+
@ix = nil
|
192
|
+
@seed = nil
|
174
193
|
end
|
175
|
-
end
|
176
|
-
end
|
177
194
|
|
178
|
-
|
179
|
-
|
195
|
+
# NB. This will only work on *nix platforms
|
196
|
+
@seed ||= `uuidgen`.chomp.gsub(/-/,"")
|
197
|
+
@ix ||= 0
|
180
198
|
|
181
|
-
|
182
|
-
@msg_sub = msg_sub
|
199
|
+
"#{@seed}#{@ix += 1}"
|
183
200
|
end
|
184
201
|
|
185
|
-
|
186
|
-
|
187
|
-
|
202
|
+
##
|
203
|
+
# Write the message to STDOUT
|
204
|
+
#
|
205
|
+
# @param message<Hash> The message to write
|
206
|
+
#
|
207
|
+
def write(message)
|
208
|
+
DefaultLogger.debug("MessageSubscriber#write(message)")
|
209
|
+
@count += 1
|
210
|
+
write_to_pipe(message)
|
188
211
|
end
|
189
212
|
|
190
|
-
|
191
|
-
|
192
|
-
|
193
|
-
|
194
|
-
|
195
|
-
|
196
|
-
|
197
|
-
|
198
|
-
|
199
|
-
|
200
|
-
background: black;
|
201
|
-
color: #80c0c0;
|
202
|
-
}
|
203
|
-
h1 {
|
204
|
-
font: 12pt Monospace;
|
205
|
-
text-align:center;
|
206
|
-
}
|
207
|
-
table {
|
208
|
-
font: 10pt Monospace;
|
209
|
-
margin-left:auto;
|
210
|
-
margin-right:auto;
|
211
|
-
text-align:right;
|
212
|
-
}
|
213
|
-
.page {
|
214
|
-
position:relative;
|
215
|
-
top: 20%;
|
216
|
-
# border-style:solid;
|
217
|
-
# border-width:5px;
|
218
|
-
width: 30%;
|
219
|
-
margin-left:auto;
|
220
|
-
margin-right:auto;
|
221
|
-
}
|
222
|
-
</style>
|
223
|
-
</head>
|
224
|
-
<body>
|
225
|
-
<div class=page>
|
226
|
-
<h1><span class="name">#{@msg_sub.name}</span></h1>
|
227
|
-
<table>
|
228
|
-
<tr>
|
229
|
-
<td>Structure:</td><td><span class="queue_in">#{@msg_sub.queue_name}</span> -> <span class="exchanges_out">#{@msg_sub.exchanges_out}</span></td>
|
230
|
-
</tr>
|
231
|
-
<tr>
|
232
|
-
<td>Throughput:</td><td><span class="mps">#{@msg_sub.mps.to_i}</span></td>
|
233
|
-
</tr>
|
234
|
-
<tr>
|
235
|
-
<td>Uptime:</td><td><span class="uptime">#{(Time.now - @msg_sub.start_time).to_i / 60}mins</span></td>
|
236
|
-
</tr>
|
237
|
-
</table>
|
238
|
-
</div>
|
239
|
-
</body>
|
240
|
-
</html>
|
241
|
-
EOL
|
242
|
-
|
243
|
-
response.send_response
|
213
|
+
##
|
214
|
+
# Initialize the DNS Service Discovery (aka Bonjour, MDNS).
|
215
|
+
#
|
216
|
+
def initialize_dnssd
|
217
|
+
return unless options[:dnssd]
|
218
|
+
# FIXME. DNS-SD breaks when under load. Not sure what problem is yet, have raised it with
|
219
|
+
# gem author:
|
220
|
+
# http://github.com/tenderlove/dnssd/issues#issue/3
|
221
|
+
require 'dnssd'
|
222
|
+
DNSSD.register!("#{options[:name]}_pipe", "_http._tcp", nil, options[:http_port])
|
244
223
|
end
|
245
|
-
end
|
246
|
-
|
247
|
-
end
|
248
224
|
|
225
|
+
def write_to_pipe(message, pipe=$stdout)
|
226
|
+
pipe.syswrite(MessageCoder.encode(message) << "\n")
|
227
|
+
end
|
228
|
+
|
229
|
+
end
|