easy-serve 0.1

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 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: