easy-serve 0.7 → 0.8

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: df13d8d57aa30fecfea71c7f1c0b3415e3384aaf
4
- data.tar.gz: fad37a85f05878d484889783e6c49387380300fe
3
+ metadata.gz: 03c328ab82c8bb6aebbc3b9e6e5c9a50a3895a5c
4
+ data.tar.gz: be6c2705dc58ee78cf2ce725f0bdb885dac96378
5
5
  SHA512:
6
- metadata.gz: 65b1b16bb7305ef869cc00ad481f621a104c680d0940e1ca2351e5b0ec19fbb6cc423dea5037d57f87f4ab7ccadf228ba526fb6188723be4062f3c67d3506e75
7
- data.tar.gz: 820382890ef5249a837f5a1c71faf2650c1925c3d4f282a5cc24ee70a9d37f7091d7579adbcd63f4d4dea551ad32b69731e45b0aa29f5b16e0966ad345125d64
6
+ metadata.gz: d91ad208ac5237e50d2e03ca0d1524a2a8005753f78fa1ccf192b7486baa8ce70abca4c2b8ca7ecfaa25c2d364f1d0f5a6dcf6759fb2916557a3b9e9ca31b64f
7
+ data.tar.gz: 22d93a3d940daf5e2ecf4810896f9f13fbef3d49e8cad7f7f2b9a1a07991f1d4bb106163e55dfaf9d70f7a8dec8ed0bbc0a6b5a76b3856c74fbafa50e7e76491
data/README.md CHANGED
@@ -1,4 +1,4 @@
1
1
  easy-serve
2
2
  ==========
3
3
 
4
- Framework for starting tcp/unix servers and connected clients under one parent process and on remote hosts.
4
+ Framework for starting tcp/unix services and connected clients under one parent process and on remote hosts.
data/examples/multi.rb CHANGED
@@ -1,18 +1,24 @@
1
1
  require 'easy-serve'
2
2
 
3
- servers_file = ARGV.shift
4
- unless servers_file
5
- abort "Usage: #$0 servers_file # Run this in two or more shells"
3
+ if ARGV.delete("--tcp")
4
+ proto = :tcp
5
+ else
6
+ proto = :unix
6
7
  end
7
8
 
8
- EasyServe.start servers_file: servers_file do |ez|
9
+ services_file = ARGV.shift
10
+ unless services_file
11
+ abort "Usage: #$0 services_file # Run this in two or more shells"
12
+ end
13
+
14
+ EasyServe.start services_file: services_file do |ez|
9
15
  log = ez.log
10
16
  log.level = Logger::DEBUG
11
17
  log.formatter = nil if $VERBOSE
12
18
 
13
- ez.start_servers do
14
- ez.server "simple-server", :unix do |svr|
15
- log.debug {"starting server"}
19
+ ez.start_services do
20
+ ez.service "simple-service", proto do |svr|
21
+ log.debug {"starting service"}
16
22
  Thread.new do
17
23
  loop do
18
24
  Thread.new(svr.accept) do |conn|
@@ -26,13 +32,13 @@ EasyServe.start servers_file: servers_file do |ez|
26
32
  end
27
33
  end
28
34
 
29
- ez.child "simple-server" do |conn|
35
+ ez.child "simple-service" do |conn|
30
36
  log.progname = "client with pid=#$$"
31
37
  log.info conn.read
32
38
  conn.write "hello from #{log.progname}"
33
39
  end
34
40
 
35
- ez.local "simple-server" do |conn|
41
+ ez.local "simple-service" do |conn|
36
42
  log.progname = "parent process"
37
43
  log.info "PRESS RETURN TO STOP"
38
44
  gets
data/examples/passive.rb CHANGED
@@ -1,14 +1,20 @@
1
1
  require 'easy-serve'
2
2
  Thread.abort_on_exception = true
3
3
 
4
+ if ARGV.delete("--tcp")
5
+ proto = :tcp
6
+ else
7
+ proto = :unix
8
+ end
9
+
4
10
  EasyServe.start do |ez|
5
11
  log = ez.log
6
12
  log.level = Logger::DEBUG
7
13
  log.formatter = nil if $VERBOSE
8
- log.debug {"starting servers"}
14
+ log.debug {"starting services"}
9
15
 
10
- ez.start_servers do
11
- ez.server "simple-server", :unix do |svr|
16
+ ez.start_services do
17
+ ez.service "simple-service", proto do |svr|
12
18
  Thread.new do
13
19
  loop do
14
20
  conn = svr.accept
@@ -21,7 +27,7 @@ EasyServe.start do |ez|
21
27
  end
22
28
  end
23
29
 
24
- ez.child "simple-server", passive: true do |conn|
30
+ ez.child "simple-service", passive: true do |conn|
25
31
  log.progname = "client 1"
26
32
  log.info conn.read
27
33
  conn.write "hello from #{log.progname}, pid = #$$; sleeping..."
@@ -31,7 +37,7 @@ EasyServe.start do |ez|
31
37
 
32
38
  sleep 0.1
33
39
 
34
- ez.child "simple-server" do |conn|
40
+ ez.child "simple-service" do |conn|
35
41
  log.progname = "client 2"
36
42
  log.info conn.read
37
43
  conn.write "hello from #{log.progname}, pid = #$$"
@@ -23,8 +23,8 @@ EasyServe.start do |ez|
23
23
  log.level = Logger::INFO
24
24
  log.formatter = nil if $VERBOSE
25
25
 
26
- ez.start_servers do
27
- ez.server "simple-server", :tcp, nil, 0 do |svr|
26
+ ez.start_services do
27
+ ez.service "simple-service", :tcp do |svr|
28
28
  Thread.new do
29
29
  loop do
30
30
  Thread.new(svr.accept) do |conn|
@@ -41,7 +41,7 @@ EasyServe.start do |ez|
41
41
  end
42
42
  end
43
43
 
44
- ez.remote "simple-server", host: addr_there do |conn|
44
+ ez.remote "simple-service", host: addr_there do |conn|
45
45
  # this block runs locally, but calls methods on the remote using drb
46
46
  log.progname = "druby remote on #{addr_there}"
47
47
  log.info "trying to read from #{conn.inspect}"
@@ -22,8 +22,8 @@ EasyServe.start do |ez|
22
22
  log.level = Logger::INFO
23
23
  log.formatter = nil if $VERBOSE
24
24
 
25
- ez.start_servers do
26
- ez.server "simple-server", :tcp, nil, 0 do |svr|
25
+ ez.start_services do
26
+ ez.service "simple-service", :tcp do |svr|
27
27
  Thread.new do
28
28
  loop do
29
29
  Thread.new(svr.accept) do |conn|
@@ -40,7 +40,7 @@ EasyServe.start do |ez|
40
40
  end
41
41
  end
42
42
 
43
- ez.remote "simple-server", host: addr_there, log: true, passive: true,
43
+ ez.remote "simple-service", host: addr_there, log: true, passive: true,
44
44
  eval: %{
45
45
  conn = conns[0]
46
46
  # this code is executed on the remote host, connected by conn, not drb
@@ -52,17 +52,17 @@ EasyServe.start do |ez|
52
52
 
53
53
  sleep
54
54
  # Without passive, this sleep would prevent the distributed app from
55
- # exiting -- the simple-server above could not be safely stopped, since
55
+ # exiting -- the simple-service above could not be safely stopped, since
56
56
  # there's no guarantee that it is no longer needed. The passive
57
57
  # declaration make it clear that this process can be stopped after all
58
- # non-passive clients have finished, and then the server can be stopped.
58
+ # non-passive clients have finished, and then the service can be stopped.
59
59
  # Of course, this also means that if all other clients execute very
60
60
  # quickly, this client might never get a chance to run.
61
61
  }
62
62
 
63
63
  sleep 1 # Ensure (for testing) that the above client runs.
64
64
 
65
- ez.remote "simple-server", host: addr_there, log: true, eval: %{
65
+ ez.remote "simple-service", host: addr_there, log: true, eval: %{
66
66
  conn = conns[0]
67
67
  # this code is executed on the remote host, connected by conn, not drb
68
68
  log.progname = "eval remote 2 on \#{host}"
@@ -15,7 +15,7 @@ unless addr_there
15
15
 
16
16
  The 'hostname' may by any valid hostname or ssh alias.
17
17
 
18
- If --tunnel is specified, use the ssh connection to tunnel the tupelo
18
+ If --tunnel is specified, use the ssh connection to tunnel the data
19
19
  traffic. Otherwise, just use tcp. (Always use ssh to start the remote
20
20
  process.)
21
21
 
@@ -27,9 +27,9 @@ EasyServe.start do |ez|
27
27
  log.level = Logger::INFO
28
28
  log.formatter = nil if $VERBOSE
29
29
 
30
- ez.start_servers do
30
+ ez.start_services do
31
31
  host = tunnel ? "localhost" : nil # no need to expose port if tunnelled
32
- ez.server "simple-server", :tcp, host, 0 do |svr|
32
+ ez.service "simple-service", :tcp, bind_host: host do |svr|
33
33
  Thread.new do
34
34
  loop do
35
35
  Thread.new(svr.accept) do |conn|
@@ -46,7 +46,7 @@ EasyServe.start do |ez|
46
46
  end
47
47
  end
48
48
 
49
- ez.remote "simple-server", host: addr_there, tunnel: tunnel, log: true,
49
+ ez.remote "simple-service", host: addr_there, tunnel: tunnel, log: true,
50
50
  eval: %{
51
51
  conn = conns[0]
52
52
  # this code is executed on the remote host, connected by conn, not drb
@@ -1,22 +1,22 @@
1
1
  require 'easy-serve'
2
2
 
3
- servers_file = ARGV.shift
4
- unless servers_file
3
+ services_file = ARGV.shift
4
+ unless services_file
5
5
  abort <<-END
6
- Usage: #$0 servers_file
7
- For the client, copy the generated servers_file to the client host, and
6
+ Usage: #$0 services_file
7
+ For the client, copy the generated services_file to the client host, and
8
8
  run with the same command.
9
9
  END
10
10
  end
11
11
 
12
- EasyServe.start servers_file: servers_file do |ez|
12
+ EasyServe.start services_file: services_file do |ez|
13
13
  log = ez.log
14
14
  log.level = Logger::DEBUG
15
15
  log.formatter = nil if $VERBOSE
16
16
 
17
- ez.start_servers do
18
- ez.server "simple-server", :tcp, '0.0.0.0', 0 do |svr|
19
- log.debug {"starting server"}
17
+ ez.start_services do
18
+ ez.service "simple-service", :tcp do |svr|
19
+ log.debug {"starting service"}
20
20
  Thread.new do
21
21
  loop do
22
22
  Thread.new(svr.accept) do |conn|
@@ -30,7 +30,7 @@ EasyServe.start servers_file: servers_file do |ez|
30
30
  end
31
31
  end
32
32
 
33
- ez.local "simple-server" do |conn|
33
+ ez.local "simple-service" do |conn|
34
34
  log.progname = "parent process"
35
35
  log.info conn.read
36
36
  conn.write "hello from #{log.progname}"
@@ -15,7 +15,7 @@ unless address_there
15
15
 
16
16
  The 'hostname' may by any valid hostname or ssh alias.
17
17
 
18
- If --tunnel is specified, use the ssh connection to tunnel the tupelo
18
+ If --tunnel is specified, use the ssh connection to tunnel the data
19
19
  traffic. Otherwise, just use tcp. (Always use ssh to start the remote
20
20
  process.)
21
21
 
@@ -27,10 +27,10 @@ EasyServe.start do |ez|
27
27
  log.level = Logger::INFO
28
28
  log.formatter = nil if $VERBOSE
29
29
 
30
- ez.start_servers do
30
+ ez.start_services do
31
31
  host = tunnel ? "localhost" : nil # no need to expose port if tunnelled
32
32
 
33
- ez.server "adder", :tcp, host, 0 do |svr|
33
+ ez.service "adder", :tcp, bind_host: host do |svr|
34
34
  Thread.new do
35
35
  loop do
36
36
  Thread.new(svr.accept) do |conn|
@@ -56,7 +56,7 @@ EasyServe.start do |ez|
56
56
  end
57
57
  end
58
58
 
59
- ez.server "multiplier", :tcp, host, 0 do |svr|
59
+ ez.service "multiplier", :tcp, bind_host: host do |svr|
60
60
  Thread.new do
61
61
  loop do
62
62
  Thread.new(svr.accept) do |conn|
@@ -15,7 +15,7 @@ unless addr_there
15
15
 
16
16
  The 'hostname' may by any valid hostname or ssh alias.
17
17
 
18
- If --tunnel is specified, use the ssh connection to tunnel the tupelo
18
+ If --tunnel is specified, use the ssh connection to tunnel the data
19
19
  traffic. Otherwise, just use tcp. (Always use ssh to start the remote
20
20
  process.)
21
21
 
@@ -31,9 +31,9 @@ EasyServe.start do |ez|
31
31
  log.level = Logger::INFO
32
32
  log.formatter = nil if $VERBOSE
33
33
 
34
- ez.start_servers do
34
+ ez.start_services do
35
35
  host = tunnel ? "localhost" : nil # no need to expose port if tunnelled
36
- ez.server "simple-server", :tcp, host, 0 do |svr|
36
+ ez.service "simple-service", :tcp, bind_host: host do |svr|
37
37
  Thread.new do
38
38
  loop do
39
39
  Thread.new(svr.accept) do |conn|
@@ -50,7 +50,7 @@ EasyServe.start do |ez|
50
50
  end
51
51
  end
52
52
 
53
- ez.remote "simple-server", host: addr_there, tunnel: tunnel,
53
+ ez.remote "simple-service", host: addr_there, tunnel: tunnel,
54
54
  dir: "/tmp",
55
55
  file: "remote-run-script.rb",
56
56
  # 'file' passed to load, so can be rel to dir or ruby's $LOAD_PATH
data/examples/simple.rb CHANGED
@@ -1,13 +1,19 @@
1
1
  require 'easy-serve'
2
2
 
3
+ if ARGV.delete("--tcp")
4
+ proto = :tcp
5
+ else
6
+ proto = :unix
7
+ end
8
+
3
9
  EasyServe.start do |ez|
4
10
  log = ez.log
5
11
  log.level = Logger::DEBUG
6
12
  log.formatter = nil if $VERBOSE
7
- log.debug {"starting servers"}
13
+ log.debug {"starting services"}
8
14
 
9
- ez.start_servers do
10
- ez.server "simple-server", :unix do |svr|
15
+ ez.start_services do
16
+ ez.service "simple-service", proto do |svr|
11
17
  Thread.new do
12
18
  loop do
13
19
  conn = svr.accept
@@ -20,13 +26,13 @@ EasyServe.start do |ez|
20
26
  end
21
27
  end
22
28
 
23
- ez.child "simple-server" do |conn|
29
+ ez.child "simple-service" do |conn|
24
30
  log.progname = "client 1"
25
31
  log.info conn.read
26
32
  conn.write "hello from #{log.progname}"
27
33
  end
28
34
 
29
- ez.local "simple-server" do |conn|
35
+ ez.local "simple-service" do |conn|
30
36
  log.progname = "parent process"
31
37
  log.info conn.read
32
38
  conn.write "hello from #{log.progname}"
data/lib/easy-serve.rb CHANGED
@@ -3,16 +3,10 @@ require 'socket'
3
3
  require 'yaml'
4
4
  require 'fileutils'
5
5
 
6
- class EasyServe
7
- VERSION = "0.7"
6
+ require 'easy-serve/service'
8
7
 
9
- class Server
10
- attr_reader :name, :pid, :addr
11
-
12
- def initialize name, pid, addr
13
- @name, @pid, @addr = name, pid, addr
14
- end
15
- end
8
+ class EasyServe
9
+ VERSION = "0.8"
16
10
 
17
11
  class EasyFormatter < Logger::Formatter
18
12
  Format = "%s: %s: %s\n"
@@ -35,10 +29,10 @@ class EasyServe
35
29
  end
36
30
 
37
31
  attr_accessor :log
38
- attr_accessor :servers
32
+ attr_accessor :services
39
33
  attr_reader :children
40
34
  attr_reader :passive_children
41
- attr_reader :servers_file
35
+ attr_reader :services_file
42
36
  attr_reader :interactive
43
37
 
44
38
  def self.start(log: default_logger, **opts)
@@ -52,37 +46,37 @@ class EasyServe
52
46
  end
53
47
 
54
48
  def initialize **opts
55
- @servers_file = opts[:servers_file]
49
+ @services_file = opts[:services_file]
56
50
  @interactive = opts[:interactive]
57
51
  @log = opts[:log] || self.class.null_logger
58
52
  @children = [] # pid
59
53
  @passive_children = [] # pid
60
54
  @owner = false
61
55
  @tmpdir = nil
62
- @servers = opts[:servers] # name => Server
56
+ @services = opts[:services] # name => service
63
57
 
64
- unless servers
65
- if servers_file
66
- @servers =
58
+ unless services
59
+ if services_file
60
+ @services =
67
61
  begin
68
- load_server_table
62
+ load_service_table
69
63
  rescue Errno::ENOENT
70
- init_server_table
64
+ init_service_table
71
65
  end
72
66
  else
73
- init_server_table
67
+ init_service_table
74
68
  end
75
69
  end
76
70
  end
77
71
 
78
- def load_server_table
79
- File.open(servers_file) do |f|
72
+ def load_service_table
73
+ File.open(services_file) do |f|
80
74
  YAML.load(f)
81
75
  end
82
76
  end
83
77
 
84
- def init_server_table
85
- @servers ||= begin
78
+ def init_service_table
79
+ @services ||= begin
86
80
  @owner = true
87
81
  {}
88
82
  end
@@ -113,20 +107,16 @@ class EasyServe
113
107
  end
114
108
 
115
109
  if @owner
116
- servers.each do |name, server|
110
+ services.each do |name, service|
117
111
  log.info "stopping #{name}"
118
- Process.kill "TERM", server.pid
119
- Process.waitpid server.pid
120
- if server.addr.kind_of? String
121
- FileUtils.remove_entry server.addr
122
- end
112
+ service.cleanup
123
113
  end
124
114
 
125
- if servers_file
115
+ if services_file
126
116
  begin
127
- FileUtils.rm servers_file
117
+ FileUtils.rm services_file
128
118
  rescue Errno::ENOENT
129
- log.warn "servers file at #{servers_file.inspect} was deleted already"
119
+ log.warn "services file #{services_file.inspect} was deleted already"
130
120
  end
131
121
  end
132
122
  end
@@ -134,14 +124,14 @@ class EasyServe
134
124
  clean_tmpdir
135
125
  end
136
126
 
137
- def start_servers
127
+ def start_services
138
128
  if @owner
139
- log.debug {"starting servers"}
129
+ log.debug {"starting services"}
140
130
  yield
141
131
 
142
- if servers_file
143
- File.open(servers_file, "w") do |f|
144
- YAML.dump(servers, f)
132
+ if services_file
133
+ File.open(services_file, "w") do |f|
134
+ YAML.dump(services, f)
145
135
  end
146
136
  end
147
137
  end
@@ -166,7 +156,7 @@ class EasyServe
166
156
  end
167
157
  end
168
158
 
169
- def inc_socket_filename name
159
+ def self.bump_socket_filename name
170
160
  name =~ /-\d+\z/ ? name.succ : name + "-0"
171
161
  end
172
162
 
@@ -186,83 +176,49 @@ class EasyServe
186
176
  end
187
177
  end
188
178
 
189
- def server name, proto = :unix, host = nil, port = nil
190
- server_class, *server_addr =
191
- case proto
192
- when /unix/i; [UNIXServer, choose_socket_filename(name, base: host)]
193
- when /tcp/i; [TCPServer, host || host_name, port || 0]
194
- else raise ArgumentError, "Unknown socket protocol: #{proto.inspect}"
195
- end
179
+ MAX_TRIES = 10
196
180
 
197
- rd, wr = IO.pipe
181
+ def service name, proto = nil, **opts
182
+ proto ||= opts.delete(:proto) || :unix
183
+ case proto
184
+ when :unix
185
+ opts[:path] ||= choose_socket_filename(name, base: opts[:base])
186
+
187
+ when :tcp
188
+ opts[:connect_host] ||=
189
+ case opts[:bind_host]
190
+ when nil, "0.0.0.0", /\A<any>\z/i
191
+ host_name ## maybe local connectors should use "localhost" ?
192
+ when "localhost", "127.0.0.1"
193
+ "localhost"
194
+ end
195
+ end
198
196
 
197
+ service = Service.for(name, proto, **opts)
198
+ rd, wr = IO.pipe
199
199
  pid = fork do
200
200
  rd.close
201
201
  log.progname = name
202
202
  log.info "starting"
203
-
204
- svr = server_for(server_class, *server_addr)
203
+
204
+ svr = service.serve(max_tries: MAX_TRIES, log: log)
205
205
  yield svr if block_given?
206
206
  no_interrupt_if_interactive
207
207
 
208
- addr =
209
- case proto
210
- when /unix/i; svr.addr[1]
211
- when /tcp/i; svr.addr(false).values_at(2,1)
212
- end
213
- Marshal.dump addr, wr
208
+ Marshal.dump service, wr
214
209
  wr.close
215
210
  sleep
216
211
  end
217
212
 
218
213
  wr.close
219
- addr = Marshal.load rd
214
+ services[name] = Marshal.load rd
220
215
  rd.close
221
- servers[name] = Server.new(name, pid, addr)
222
- end
223
-
224
- MAX_TRIES = 10
225
-
226
- def server_for server_class, *server_addr
227
- tries = 0
228
- begin
229
- server_class.new(*server_addr)
230
- rescue Errno::EADDRINUSE => ex
231
- if server_class == UNIXServer
232
- if tries < MAX_TRIES
233
- tries += 1
234
- server_addr[0] = inc_socket_filename(server_addr[0])
235
- log.warn {
236
- "#{ex} -- trying again at path #{server_addr}, #{tries}/#{MAX_TRIES} times."
237
- }
238
- retry
239
- end
240
- elsif server_class == TCPServer
241
- port = Integer(server_addr[1])
242
- if port and tries < MAX_TRIES
243
- tries += 1
244
- port += 1
245
- server_addr[1] = port
246
- log.warn {
247
- "#{ex} -- trying again at port #{port}, #{tries}/#{MAX_TRIES} times."
248
- }
249
- retry
250
- end
251
- else
252
- raise ArgumentError, "unknown server_class: #{server_class.inspect}"
253
- end
254
- raise
255
-
256
- rescue => ex
257
- ex.message << "; addr=#{server_addr.inspect}"
258
- raise
259
- end
260
216
  end
261
217
 
262
218
  # A passive client child may be stopped after all active clients exit.
263
- def child *server_names, passive: false
219
+ def child *service_names, passive: false
264
220
  c = fork do
265
- conns = server_names.map {|sn| socket_for(*servers[sn].addr)}
221
+ conns = service_names.map {|sn| services[sn].connect}
266
222
  yield(*conns) if block_given?
267
223
  no_interrupt_if_interactive
268
224
  end
@@ -275,8 +231,8 @@ class EasyServe
275
231
  child *args, &block
276
232
  end
277
233
 
278
- def local *server_names
279
- conns = server_names.map {|sn| socket_for(*servers[sn].addr)}
234
+ def local *service_names
235
+ conns = service_names.map {|sn| services[sn].connect}
280
236
  yield(*conns) if block_given?
281
237
  ensure
282
238
  conns and conns.each do |conn|
@@ -285,20 +241,8 @@ class EasyServe
285
241
  log.info "stopped local client"
286
242
  end
287
243
 
288
- def socket_for *addr
289
- socket_class =
290
- case addr.size
291
- when 1; UNIXSocket
292
- else TCPSocket
293
- end
294
- socket_class.new(*addr)
295
- rescue => ex
296
- ex.message << "; addr=#{addr.inspect}"
297
- raise
298
- end
299
-
300
244
  # ^C in the irb session (parent process) should not kill the
301
- # server (child process)
245
+ # service (child process)
302
246
  def no_interrupt_if_interactive
303
247
  trap("INT") {} if interactive
304
248
  end
@@ -0,0 +1,34 @@
1
+ class EasyServe
2
+ # Returns list of services that are accessible from +host+, setting
3
+ # up an ssh tunnel if specified. Note that OpenSSH 6.0 or later is required
4
+ # for the tunnel option.
5
+ def accessible_services host, tunnel: false
6
+ tcp_svs = services.values.grep(TCPService)
7
+ return tcp_svs unless tunnel and host != "localhost" and host != "127.0.0.1"
8
+
9
+ tcp_svs.map do |service|
10
+ service_host =
11
+ case service.bind_host
12
+ when nil, "localhost", "127.0.0.1", "0.0.0.0", /\A<any>\z/i
13
+ "localhost"
14
+ else
15
+ service.bind_host
16
+ end
17
+
18
+ fwd = "0:#{service_host}:#{service.port}"
19
+ out = `ssh -O forward -R #{fwd} #{host}`
20
+
21
+ begin
22
+ remote_port = Integer(out)
23
+ rescue
24
+ log.error "Unable to set up dynamic ssh port forwarding. " +
25
+ "Please check if ssh -v is at least 6.0."
26
+ raise
27
+ end
28
+
29
+ at_exit {system "ssh -O cancel -R #{fwd} #{host}"}
30
+
31
+ TCPService.new service.name, connect_host: "localhost", port: remote_port
32
+ end
33
+ end
34
+ end
@@ -6,7 +6,7 @@ class EasyServe
6
6
  # It's up to you to start another thread inside the code block if you
7
7
  # want more concurrency. This is for convenience when testing (cases in which
8
8
  # concurrency needs to be controlled explicitly).
9
- def remote_drb *server_names, host: nil
9
+ def remote_drb *service_names, host: nil
10
10
  ## remote logfile option?
11
11
 
12
12
  DRb.start_service("druby://#{host_name}:0", nil)
@@ -24,17 +24,17 @@ class EasyServe
24
24
  require 'yaml'
25
25
  require 'easy-serve'
26
26
 
27
- server_names = #{server_names.inspect}
28
- servers = YAML.load(#{YAML.dump(servers).inspect})
27
+ service_names = #{service_names.inspect}
28
+ services = YAML.load(#{YAML.dump(services).inspect})
29
29
  log_level = #{log.level}
30
30
  host_uri = #{host_uri.inspect}
31
31
 
32
- EasyServe.start servers: servers do |ez|
32
+ EasyServe.start services: services do |ez|
33
33
  log = ez.log
34
34
  log.level = log_level
35
35
  log.formatter = nil if $VERBOSE
36
36
 
37
- ez.local *server_names do |*conns|
37
+ ez.local *service_names do |*conns|
38
38
  begin
39
39
  DRb.start_service(host_uri, {conns: conns})
40
40
  puts DRb.uri
@@ -3,12 +3,14 @@ require 'easy-serve'
3
3
 
4
4
  def EasyServe.manage_remote_eval_client msg
5
5
  $VERBOSE = msg["verbose"]
6
- server_names, servers_list, log_level, eval_string, host =
7
- msg.values_at(*%w{server_names servers_list log_level eval_string host})
6
+ service_names, services_list, log_level, eval_string, host =
7
+ msg.values_at(*%w{service_names services_list log_level eval_string host})
8
8
 
9
- servers = {}
10
- servers_list.each do |name, pid, addr|
11
- servers[name] = EasyServe::Server.new(name, pid, addr)
9
+ services = {}
10
+ service_names = Marshal.load(service_names)
11
+ services_list = Marshal.load(services_list)
12
+ services_list.each do |service|
13
+ services[service.name] = service
12
14
  end
13
15
 
14
16
  log_args = msg["log"]
@@ -22,12 +24,12 @@ def EasyServe.manage_remote_eval_client msg
22
24
  EasyServe.null_logger
23
25
  end
24
26
 
25
- EasyServe.start servers: servers, log: log do |ez|
27
+ EasyServe.start services: services, log: log do |ez|
26
28
  log = ez.log
27
29
  log.level = log_level
28
30
  log.formatter = nil if $VERBOSE
29
31
 
30
- ez.local *server_names do |*conns|
32
+ ez.local *service_names do |*conns|
31
33
  begin
32
34
  pr = eval "proc do |conns, host, log| #{eval_string}; end"
33
35
  pr[conns, host, log]
@@ -48,7 +50,7 @@ def EasyServe.handle_remote_eval_messages
48
50
  unpacker = MessagePack::Unpacker.new($stdin)
49
51
  unpacker.each do |msg|
50
52
  case
51
- when msg["server_names"]
53
+ when msg["service_names"]
52
54
  Thread.new {manage_remote_eval_client(msg); exit}
53
55
  when msg["exit"]
54
56
  puts "exiting"
@@ -1,5 +1,5 @@
1
1
  require 'msgpack'
2
- require 'easy-serve/accessible-servers'
2
+ require 'easy-serve/accessible-services'
3
3
 
4
4
  class EasyServe
5
5
  # useful simple cases in testing and in production, but long eval strings
@@ -13,7 +13,7 @@ class EasyServe
13
13
  #
14
14
  # 2. Log back over ssh: pass log: true.
15
15
  #
16
- def remote_eval *server_names,
16
+ def remote_eval *service_names,
17
17
  host: nil, passive: false, tunnel: false, **opts
18
18
  child_pid = fork do
19
19
  log.progname = "remote_eval #{host}"
@@ -26,17 +26,17 @@ class EasyServe
26
26
  "w+" do |ssh|
27
27
 
28
28
  ssh.sync = true
29
- servers_list = accessible_servers(host, tunnel: tunnel)
29
+ services_list = accessible_services(host, tunnel: tunnel)
30
30
 
31
31
  MessagePack.pack(
32
32
  {
33
- verbose: $VERBOSE,
34
- server_names: server_names,
35
- servers_list: servers_list,
36
- log_level: log.level,
37
- eval_string: opts[:eval],
38
- host: host,
39
- log: opts[:log]
33
+ verbose: $VERBOSE,
34
+ service_names: Marshal.dump(service_names),
35
+ services_list: Marshal.dump(services_list),
36
+ log_level: log.level,
37
+ eval_string: opts[:eval],
38
+ host: host,
39
+ log: opts[:log]
40
40
  },
41
41
  ssh)
42
42
 
@@ -3,15 +3,17 @@ require 'easy-serve'
3
3
 
4
4
  def EasyServe.manage_remote_run_client msg
5
5
  $VERBOSE = msg["verbose"]
6
- server_names, servers_list, log_level, host, dir, file, class_name, args =
6
+ service_names, services_list, log_level, host, dir, file, class_name, args =
7
7
  msg.values_at(*%w{
8
- server_names servers_list log_level host
8
+ service_names services_list log_level host
9
9
  dir file class_name args
10
10
  })
11
11
 
12
- servers = {}
13
- servers_list.each do |name, pid, addr|
14
- servers[name] = EasyServe::Server.new(name, pid, addr)
12
+ services = {}
13
+ service_names = Marshal.load(service_names)
14
+ services_list = Marshal.load(services_list)
15
+ services_list.each do |service|
16
+ services[service.name] = service
15
17
  end
16
18
 
17
19
  ### opt for tmpdir and send files to it via ssh
@@ -29,12 +31,12 @@ def EasyServe.manage_remote_run_client msg
29
31
  EasyServe.null_logger
30
32
  end
31
33
 
32
- EasyServe.start servers: servers, log: log do |ez|
34
+ EasyServe.start services: services, log: log do |ez|
33
35
  log = ez.log
34
36
  log.level = log_level
35
37
  log.formatter = nil if $VERBOSE
36
38
 
37
- ez.local *server_names do |*conns|
39
+ ez.local *service_names do |*conns|
38
40
  begin
39
41
  cl = Object.const_get(class_name)
40
42
  ro = cl.new(conns, host, log, *args)
@@ -56,7 +58,7 @@ def EasyServe.handle_remote_run_messages
56
58
  unpacker = MessagePack::Unpacker.new($stdin)
57
59
  unpacker.each do |msg|
58
60
  case
59
- when msg["server_names"]
61
+ when msg["service_names"]
60
62
  Thread.new {manage_remote_run_client(msg); exit}
61
63
  when msg["exit"]
62
64
  puts "exiting"
@@ -1,5 +1,5 @@
1
1
  require 'msgpack'
2
- require 'easy-serve/accessible-servers'
2
+ require 'easy-serve/accessible-services'
3
3
 
4
4
  class EasyServe
5
5
  # useful in production, though it requires remote lib files to be set up.
@@ -12,7 +12,7 @@ class EasyServe
12
12
  #
13
13
  # 2. Log back over ssh: pass log: true.
14
14
  #
15
- def remote_run *server_names, host: nil, passive: false, tunnel: false, **opts
15
+ def remote_run *service_names, host: nil, passive: false, tunnel: false, **opts
16
16
  child_pid = fork do
17
17
  log.progname = "remote_run #{host}"
18
18
 
@@ -24,20 +24,20 @@ class EasyServe
24
24
  "w+" do |ssh|
25
25
 
26
26
  ssh.sync = true
27
- servers_list = accessible_servers(host, tunnel: tunnel)
27
+ services_list = accessible_services(host, tunnel: tunnel)
28
28
 
29
29
  MessagePack.pack(
30
30
  {
31
- verbose: $VERBOSE,
32
- server_names: server_names,
33
- servers_list: servers_list,
34
- log_level: log.level,
35
- host: host,
36
- dir: opts[:dir],
37
- file: opts[:file],
38
- class_name: opts[:class_name],
39
- args: opts[:args],
40
- log: opts[:log]
31
+ verbose: $VERBOSE,
32
+ service_names: Marshal.dump(service_names),
33
+ services_list: Marshal.dump(services_list),
34
+ log_level: log.level,
35
+ host: host,
36
+ dir: opts[:dir],
37
+ file: opts[:file],
38
+ class_name: opts[:class_name],
39
+ args: opts[:args],
40
+ log: opts[:log]
41
41
  },
42
42
  ssh)
43
43
 
@@ -3,20 +3,20 @@ require 'easy-serve'
3
3
  class EasyServe
4
4
  class RemoteError < RuntimeError; end
5
5
 
6
- def remote *server_names, host: nil, **opts
6
+ def remote *service_names, host: nil, **opts
7
7
  raise ArgumentError, "no host specified" unless host
8
8
 
9
9
  if opts[:eval]
10
10
  require 'easy-serve/remote-eval'
11
- remote_eval(*server_names, host: host, **opts)
11
+ remote_eval(*service_names, host: host, **opts)
12
12
 
13
13
  elsif opts[:file]
14
14
  require 'easy-serve/remote-run'
15
- remote_run(*server_names, host: host, **opts)
15
+ remote_run(*service_names, host: host, **opts)
16
16
 
17
17
  elsif block_given?
18
18
  require 'easy-serve/remote-drb'
19
- remote_drb(*server_names, host: host, **opts, &Proc.new)
19
+ remote_drb(*service_names, host: host, **opts, &Proc.new)
20
20
 
21
21
  else
22
22
  raise ArgumentError, "cannot select remote mode based on arguments"
@@ -0,0 +1,125 @@
1
+ class EasyServe
2
+ # Refers to a named service. Use the #serve method to start the service.
3
+ # Encapsulates current location and identity, including pid and address. A
4
+ # Service object can be serialized to a remote process so it can #connect to
5
+ # the service.
6
+ class Service
7
+ attr_reader :name
8
+ attr_reader :pid
9
+
10
+ SERVICE_CLASS = {}
11
+
12
+ def self.for name, proto, **opts
13
+ sc = SERVICE_CLASS[proto] or
14
+ raise ArgumentError, "Unknown socket protocol: #{proto.inspect}"
15
+ sc.new name, **opts
16
+ end
17
+
18
+ def initialize name
19
+ @name = name
20
+ end
21
+
22
+ # Returns the client socket.
23
+ def connect
24
+ try_connect
25
+ rescue => ex
26
+ ex.message << "; #{inspect}"
27
+ raise
28
+ end
29
+
30
+ # Returns the server socket.
31
+ def serve max_tries: 1, log: (raise ArgumentError, "missing log")
32
+ tries = 1
33
+ begin
34
+ try_serve.tap do
35
+ @pid = Process.pid
36
+ end
37
+
38
+ rescue Errno::EADDRINUSE => ex
39
+ raise if tries >= max_tries
40
+ log.warn {"#{ex}; #{inspect} (#{tries}/#{max_tries} tries)"}
41
+ tries += 1; bump!
42
+ log.info {"Trying: #{inspect}"}
43
+ retry
44
+ end
45
+ rescue => ex
46
+ ex.message << "; #{inspect}"
47
+ raise
48
+ end
49
+
50
+ def cleanup
51
+ Process.kill "TERM", pid
52
+ Process.waitpid pid
53
+ end
54
+ end
55
+
56
+ class UNIXService < Service
57
+ SERVICE_CLASS[:unix] = self
58
+
59
+ attr_reader :path
60
+
61
+ def initialize name, path: nil
62
+ super name
63
+ @path = path
64
+ end
65
+
66
+ def serve max_tries: 1, log: log
67
+ super.tap do |svr|
68
+ found_path = svr.addr[1]
69
+ log.debug "#{inspect} is listening at #{found_path}"
70
+
71
+ if found_path != path
72
+ log.error "Unexpected path: #{found_path} != #{path}"
73
+ end
74
+ end
75
+ end
76
+
77
+ def cleanup
78
+ super
79
+ FileUtils.remove_entry path if path
80
+ end
81
+
82
+ def try_connect
83
+ UNIXSocket.new(path)
84
+ end
85
+
86
+ def try_serve
87
+ UNIXServer.new(path)
88
+ end
89
+
90
+ def bump!
91
+ @path = EasyServe.bump_socket_filename(path)
92
+ end
93
+ end
94
+
95
+ class TCPService < Service
96
+ SERVICE_CLASS[:tcp] = self
97
+
98
+ attr_reader :bind_host, :connect_host, :port
99
+
100
+ def initialize name, bind_host: nil, connect_host: nil, port: 0
101
+ super name
102
+ @bind_host, @connect_host, @port = bind_host, connect_host, port
103
+ end
104
+
105
+ def serve max_tries: 1, log: log
106
+ super.tap do |svr|
107
+ found_addr = svr.addr(false).values_at(2,1)
108
+ log.debug "#{inspect} is listening at #{found_addr.join(":")}"
109
+ @port = found_addr[1]
110
+ end
111
+ end
112
+
113
+ def try_connect
114
+ TCPSocket.new(connect_host, port)
115
+ end
116
+
117
+ def try_serve
118
+ TCPServer.new(bind_host, port)
119
+ end
120
+
121
+ def bump!
122
+ @port += 1 unless port == 0 # should not happen
123
+ end
124
+ end
125
+ end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: easy-serve
3
3
  version: !ruby/object:Gem::Version
4
- version: '0.7'
4
+ version: '0.8'
5
5
  platform: ruby
6
6
  authors:
7
7
  - Joel VanderWerf
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2013-12-15 00:00:00.000000000 Z
11
+ date: 2013-12-18 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: msgpack
@@ -24,8 +24,8 @@ dependencies:
24
24
  - - ">="
25
25
  - !ruby/object:Gem::Version
26
26
  version: '0'
27
- description: Framework for starting tcp/unix servers and connected clients under one
28
- parent process and on remote hosts.
27
+ description: Framework for starting tcp/unix services and connected clients under
28
+ one parent process and on remote hosts.
29
29
  email: vjoel@users.sourceforge.net
30
30
  executables: []
31
31
  extensions: []
@@ -35,24 +35,25 @@ extra_rdoc_files:
35
35
  files:
36
36
  - README.md
37
37
  - COPYING
38
- - lib/easy-serve/remote.rb
38
+ - lib/easy-serve.rb
39
+ - lib/easy-serve/remote-eval-mgr.rb
39
40
  - lib/easy-serve/remote-run.rb
40
41
  - lib/easy-serve/remote-drb.rb
41
- - lib/easy-serve/accessible-servers.rb
42
- - lib/easy-serve/remote-run-mgr.rb
43
- - lib/easy-serve/remote-eval-mgr.rb
42
+ - lib/easy-serve/remote.rb
43
+ - lib/easy-serve/accessible-services.rb
44
44
  - lib/easy-serve/remote-eval.rb
45
- - lib/easy-serve.rb
46
- - examples/remote-run.rb
47
- - examples/remote-multi-server.rb
48
- - examples/passive.rb
45
+ - lib/easy-serve/remote-run-mgr.rb
46
+ - lib/easy-serve/service.rb
47
+ - examples/simple.rb
48
+ - examples/remote-manual.rb
49
+ - examples/multi.rb
49
50
  - examples/remote-eval-passive.rb
51
+ - examples/remote-run.rb
50
52
  - examples/remote-drb.rb
53
+ - examples/remote-multi-server.rb
51
54
  - examples/remote-run-script.rb
52
- - examples/multi.rb
53
- - examples/remote-manual.rb
54
- - examples/simple.rb
55
55
  - examples/remote-eval.rb
56
+ - examples/passive.rb
56
57
  homepage: https://github.com/vjoel/easy-serve
57
58
  licenses:
58
59
  - BSD
@@ -83,7 +84,7 @@ rubyforge_project:
83
84
  rubygems_version: 2.1.11
84
85
  signing_key:
85
86
  specification_version: 4
86
- summary: Framework for starting tcp/unix servers and connected clients under one parent
87
- process and on remote hosts
87
+ summary: Framework for starting tcp/unix services and connected clients under one
88
+ parent process and on remote hosts
88
89
  test_files: []
89
90
  has_rdoc:
@@ -1,29 +0,0 @@
1
- class EasyServe
2
- # Returns list of [name, pid, addr] that are accessible from host, setting
3
- # up ssh tunnel if specified. Note that OpenSSH 6.0 or later is required
4
- # for the tunnel option.
5
- def accessible_servers host, tunnel: false
6
- if tunnel and host != "localhost" and host != "127.0.0.1"
7
- servers.map do |n, s|
8
- _, local_port = s.addr
9
- fwd = "0:localhost:#{local_port}"
10
- out = `ssh -O forward -R #{fwd} #{host}`
11
-
12
- begin
13
- remote_port = Integer(out)
14
- rescue
15
- log.error "Unable to set up dynamic ssh port forwarding. " +
16
- "Please check if ssh -v is at least 6.0."
17
- raise
18
- end
19
-
20
- at_exit {system "ssh -O cancel -R #{fwd} #{host}"}
21
-
22
- [s.name, s.pid, ["localhost", remote_port]]
23
- end
24
-
25
- else
26
- servers.map {|n, s| [s.name, s.pid, s.addr]}
27
- end
28
- end
29
- end