easy-serve 0.7 → 0.8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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