easy-serve 0.1

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 2989b3ccae0056e24b4e2c097ce9c15b0db1f5ee
4
+ data.tar.gz: 2f0b15aad79aad9ab35d467ce1a77ef2cf60ccef
5
+ SHA512:
6
+ metadata.gz: 47c813e39f2b4f4824f20cdcf8036fb7a3c91f273eea99bcf3c6ad6af14a3d8be62511671aca3bdcd2800ff3b8c152221f43f2337298d0325091e21a341ce940
7
+ data.tar.gz: b7d53a73b898c9ba8b379146c67a7d49144d3a3e72a6cb01f375ceb182e0d0279e9b3e2071f282b9bfe5a123760d02f2ab3d797b74994fc67a6cb2aabb72d871
data/COPYING ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2013, Joel VanderWerf, vjoel@users.sourceforge.net
2
+ All rights reserved.
3
+
4
+ Redistribution and use in source and binary forms, with or without
5
+ modification, are permitted provided that the following conditions are met:
6
+
7
+ 1. Redistributions of source code must retain the above copyright notice, this
8
+ list of conditions and the following disclaimer.
9
+ 2. Redistributions in binary form must reproduce the above copyright notice,
10
+ this list of conditions and the following disclaimer in the documentation
11
+ and/or other materials provided with the distribution.
12
+
13
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
14
+ ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
15
+ WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
16
+ DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
17
+ ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
18
+ (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
19
+ LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
20
+ ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
21
+ (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
22
+ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
data/README.md ADDED
@@ -0,0 +1,4 @@
1
+ easy-serve
2
+ ==========
3
+
4
+ Framework for starting tcp/unix servers and connected clients under one parent process.
data/examples/multi.rb ADDED
@@ -0,0 +1,40 @@
1
+ require 'easy-serve'
2
+
3
+ servers_file = ARGV.shift
4
+ unless servers_file
5
+ abort "Usage: #$0 servers_file # Run this in two or more shells"
6
+ end
7
+
8
+ EasyServe.start servers_file: servers_file do |ez|
9
+ log = ez.log
10
+ log.level = Logger::DEBUG
11
+ log.formatter = nil if $VERBOSE
12
+
13
+ ez.start_servers do
14
+ ez.server "simple-server", :unix do |svr|
15
+ log.debug {"starting server"}
16
+ Thread.new do
17
+ loop do
18
+ Thread.new(svr.accept) do |conn|
19
+ conn.write "hello from #{log.progname}"
20
+ conn.close_write
21
+ log.info conn.read
22
+ conn.close
23
+ end
24
+ end
25
+ end
26
+ end
27
+ end
28
+
29
+ ez.client "simple-server" do |conn|
30
+ log.progname = "client with pid=#$$"
31
+ log.info conn.read
32
+ conn.write "hello from #{log.progname}"
33
+ end
34
+
35
+ ez.local "simple-server" do |conn|
36
+ log.progname = "parent process"
37
+ log.info "PRESS RETURN TO STOP"
38
+ gets
39
+ end
40
+ end
@@ -0,0 +1,39 @@
1
+ require 'easy-serve'
2
+
3
+ servers_file = ARGV.shift
4
+ unless servers_file
5
+ abort <<-END
6
+ Usage: #$0 servers_file
7
+ For the client, copy the generated servers_file to the client host.
8
+ END
9
+ end
10
+
11
+ EasyServe.start servers_file: servers_file do |ez|
12
+ log = ez.log
13
+ log.level = Logger::DEBUG
14
+ log.formatter = nil if $VERBOSE
15
+
16
+ ez.start_servers do
17
+ ez.server "simple-server", :tcp, '0.0.0.0', 0 do |svr|
18
+ log.debug {"starting server"}
19
+ Thread.new do
20
+ loop do
21
+ Thread.new(svr.accept) do |conn|
22
+ conn.write "hello from #{log.progname}"
23
+ conn.close_write
24
+ log.info conn.read
25
+ conn.close
26
+ end
27
+ end
28
+ end
29
+ end
30
+ end
31
+
32
+ ez.local "simple-server" do |conn|
33
+ log.progname = "parent process"
34
+ log.info conn.read
35
+ conn.write "hello from #{log.progname}"
36
+ log.info "PRESS RETURN TO STOP"
37
+ gets
38
+ end
39
+ end
@@ -0,0 +1,34 @@
1
+ require 'easy-serve'
2
+
3
+ EasyServe.start do |ez|
4
+ log = ez.log
5
+ log.level = Logger::DEBUG
6
+ log.formatter = nil if $VERBOSE
7
+ log.debug {"starting servers"}
8
+
9
+ ez.start_servers do
10
+ ez.server "simple-server", :unix do |svr|
11
+ Thread.new do
12
+ loop do
13
+ conn = svr.accept
14
+ conn.write "hello from #{log.progname}"
15
+ conn.close_write
16
+ log.info conn.read
17
+ conn.close
18
+ end
19
+ end
20
+ end
21
+ end
22
+
23
+ ez.client "simple-server" do |conn|
24
+ log.progname = "client 1"
25
+ log.info conn.read
26
+ conn.write "hello from #{log.progname}"
27
+ end
28
+
29
+ ez.local "simple-server" do |conn|
30
+ log.progname = "parent process"
31
+ log.info conn.read
32
+ conn.write "hello from #{log.progname}"
33
+ end
34
+ end
data/lib/easy-serve.rb ADDED
@@ -0,0 +1,253 @@
1
+ require 'logger'
2
+ require 'socket'
3
+ require 'yaml'
4
+ require 'fileutils'
5
+
6
+ class EasyServe
7
+ class Server
8
+ attr_reader :name, :pid, :addr
9
+
10
+ def initialize name, pid, addr
11
+ @name, @pid, @addr = name, pid, addr
12
+ end
13
+ end
14
+
15
+ class EasyFormatter < Logger::Formatter
16
+ Format = "%s: %s: %s\n"
17
+
18
+ def call(severity, time, progname, msg)
19
+ Format % [severity[0..0], progname, msg2str(msg)]
20
+ end
21
+ end
22
+
23
+ def self.default_logger
24
+ log = Logger.new($stderr)
25
+ log.formatter = EasyFormatter.new
26
+ log
27
+ end
28
+
29
+ def self.null_logger
30
+ log = Logger.new('/dev/null')
31
+ log.level = Logger::FATAL
32
+ log
33
+ end
34
+
35
+ attr_accessor :log
36
+ attr_accessor :servers
37
+ attr_reader :clients
38
+ attr_reader :servers_file
39
+ attr_reader :interactive
40
+
41
+ def self.start(log: default_logger, **opts)
42
+ ez = new(**opts, log: log)
43
+ yield ez
44
+ rescue => ex
45
+ log.error ex
46
+ raise
47
+ ensure
48
+ ez.cleanup if ez
49
+ end
50
+
51
+ def initialize **opts
52
+ @servers_file = opts[:servers_file]
53
+ @interactive = opts[:interactive]
54
+ @log = opts[:log] || self.class.null_logger
55
+ @clients = [] # pid
56
+ @owner = false
57
+ @servers = nil # name => Server
58
+
59
+ if servers_file
60
+ @servers =
61
+ begin
62
+ load_server_table
63
+ rescue Errno::ENOENT
64
+ init_server_table
65
+ end
66
+ else
67
+ init_server_table
68
+ end
69
+ end
70
+
71
+ def load_server_table
72
+ File.open(servers_file) do |f|
73
+ YAML.load(f)
74
+ end
75
+ end
76
+
77
+ def init_server_table
78
+ @servers ||= begin
79
+ @owner = true
80
+ {}
81
+ end
82
+ end
83
+
84
+ def cleanup
85
+ handler = trap("INT") do
86
+ trap("INT", handler)
87
+ end
88
+
89
+ clients.each do |pid|
90
+ log.debug {"waiting for client pid=#{pid} to stop"}
91
+ Process.waitpid pid
92
+ end
93
+
94
+ if @owner
95
+ servers.each do |name, server|
96
+ log.info "stopping #{name}"
97
+ Process.kill "TERM", server.pid
98
+ Process.waitpid server.pid
99
+ if server.addr.kind_of? String
100
+ FileUtils.remove_entry server.addr
101
+ end
102
+ end
103
+
104
+ if servers_file
105
+ begin
106
+ FileUtils.rm servers_file
107
+ rescue Errno::ENOENT
108
+ log.warn "servers file at #{servers_file.inspect} was deleted already"
109
+ end
110
+ end
111
+ end
112
+
113
+ clean_tmpdir
114
+ end
115
+
116
+ def start_servers
117
+ if @owner
118
+ log.debug {"starting servers"}
119
+ yield
120
+
121
+ if servers_file
122
+ File.open(servers_file, "w") do |f|
123
+ YAML.dump(servers, f)
124
+ end
125
+ end
126
+ end
127
+ end
128
+
129
+ def tmpdir
130
+ @tmpdir ||= begin
131
+ require 'tmpdir'
132
+ Dir.mktmpdir "easy-serve-"
133
+ end
134
+ end
135
+
136
+ def clean_tmpdir
137
+ FileUtils.remove_entry @tmpdir if @tmpdir
138
+ end
139
+
140
+ def choose_socket_filename name, base: nil
141
+ if base
142
+ "#{base}-#{name}"
143
+ else
144
+ File.join(tmpdir, "sock-#{name}")
145
+ end
146
+ end
147
+
148
+ def inc_socket_filename name
149
+ name =~ /-\d+\z/ ? name.succ : name + "-0"
150
+ end
151
+
152
+ def server name, proto = :unix, host = nil, port = nil
153
+ server_class, *server_addr =
154
+ case proto
155
+ when /unix/i; [UNIXServer, choose_socket_filename(name, base: host)]
156
+ when /tcp/i; [TCPServer, host || '127.0.0.1', port || 0]
157
+ else raise ArgumentError, "Unknown socket protocol: #{proto.inspect}"
158
+ end
159
+
160
+ rd, wr = IO.pipe
161
+
162
+ pid = fork do
163
+ rd.close
164
+ log.progname = name
165
+ log.info "starting"
166
+
167
+ svr = server_for(server_class, *server_addr)
168
+ yield svr if block_given?
169
+ no_interrupt_if_interactive
170
+
171
+ addr =
172
+ case proto
173
+ when /unix/i; svr.addr[1]
174
+ when /tcp/i; svr.addr(false).values_at(2,1)
175
+ end
176
+ Marshal.dump addr, wr
177
+ wr.close
178
+ sleep
179
+ end
180
+
181
+ wr.close
182
+ addr = Marshal.load rd
183
+ rd.close
184
+ servers[name] = Server.new(name, pid, addr)
185
+ end
186
+
187
+ MAX_TRIES = 10
188
+
189
+ def server_for server_class, *server_addr
190
+ tries = 0
191
+ begin
192
+ server_class.new(*server_addr)
193
+ rescue Errno::EADDRINUSE => ex
194
+ if server_class == UNIXServer
195
+ if tries < MAX_TRIES
196
+ tries += 1
197
+ server_addr[0] = inc_socket_filename(server_addr[0])
198
+ log.warn {
199
+ "#{ex} -- trying again at path #{server_addr}, #{tries}/#{MAX_TRIES} times."
200
+ }
201
+ retry
202
+ end
203
+ elsif server_class == TCPServer
204
+ port = Integer(server_addr[1])
205
+ if port and tries < MAX_TRIES
206
+ tries += 1
207
+ port += 1
208
+ server_addr[1] = port
209
+ log.warn {
210
+ "#{ex} -- trying again at port #{port}, #{tries}/#{MAX_TRIES} times."
211
+ }
212
+ retry
213
+ end
214
+ else
215
+ raise ArgumentError, "unknown server_class: #{server_class.inspect}"
216
+ end
217
+ raise
218
+ end
219
+ end
220
+
221
+ def client *server_names
222
+ clients << fork do
223
+ conns = server_names.map {|sn| socket_for(*servers[sn].addr)}
224
+ yield(*conns) if block_given?
225
+ no_interrupt_if_interactive
226
+ end
227
+ end
228
+
229
+ def local *server_names
230
+ conns = server_names.map {|sn| socket_for(*servers[sn].addr)}
231
+ yield(*conns) if block_given?
232
+ ensure
233
+ conns and conns.each do |conn|
234
+ conn.close unless conn.closed?
235
+ end
236
+ log.info "stopped local client"
237
+ end
238
+
239
+ def socket_for *addr
240
+ socket_class =
241
+ case addr.size
242
+ when 1; UNIXSocket
243
+ else TCPSocket
244
+ end
245
+ socket_class.new(*addr)
246
+ end
247
+
248
+ # ^C in the irb session (parent process) should not kill the
249
+ # server (child process)
250
+ def no_interrupt_if_interactive
251
+ trap("INT") {} if interactive
252
+ end
253
+ end
metadata ADDED
@@ -0,0 +1,61 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: easy-serve
3
+ version: !ruby/object:Gem::Version
4
+ version: '0.1'
5
+ platform: ruby
6
+ authors:
7
+ - Joel VanderWerf
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2013-07-12 00:00:00.000000000 Z
12
+ dependencies: []
13
+ description: Framework for starting tcp/unix servers and connected clients under one
14
+ parent process.
15
+ email: vjoel@users.sourceforge.net
16
+ executables: []
17
+ extensions: []
18
+ extra_rdoc_files:
19
+ - README.md
20
+ - COPYING
21
+ files:
22
+ - README.md
23
+ - COPYING
24
+ - lib/easy-serve.rb
25
+ - examples/simple.rb
26
+ - examples/multi.rb
27
+ - examples/remote.rb
28
+ homepage: https://github.com/vjoel/easy-serve
29
+ licenses:
30
+ - BSD
31
+ metadata: {}
32
+ post_install_message:
33
+ rdoc_options:
34
+ - --quiet
35
+ - --line-numbers
36
+ - --inline-source
37
+ - --title
38
+ - easy-serve
39
+ - --main
40
+ - README.md
41
+ require_paths:
42
+ - lib
43
+ required_ruby_version: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - '>='
46
+ - !ruby/object:Gem::Version
47
+ version: '0'
48
+ required_rubygems_version: !ruby/object:Gem::Requirement
49
+ requirements:
50
+ - - '>='
51
+ - !ruby/object:Gem::Version
52
+ version: '0'
53
+ requirements: []
54
+ rubyforge_project:
55
+ rubygems_version: 2.0.4
56
+ signing_key:
57
+ specification_version: 4
58
+ summary: Framework for starting tcp/unix servers and connected clients under one parent
59
+ process
60
+ test_files: []
61
+ has_rdoc: