backport 0.1.0 → 0.2.0

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
  SHA256:
3
- metadata.gz: 631a99833ea290a7aa20e2e18f188eb19b81a141e32288feeb96ac514c39a86c
4
- data.tar.gz: 5dcd3ea31192dbb810d18f7e5ca6c3757f3613a8e0a625f0d14e7801714a0e62
3
+ metadata.gz: 2aedfc84d845aedeb6d7146cb21a74c4d1d74d90844407982ce454e8aa2d16b3
4
+ data.tar.gz: e222830378478e7990601880b42e60dd4e34ba1791e63345a2a5fa0a2898bbf8
5
5
  SHA512:
6
- metadata.gz: 1ab759e6cd1fb063dbc9f56648d0f98771b3b2390593937c73c6d6b8e655110644b94028f2739dab3bb2f7b5ca704d2bba0838d604866b9939f607eccd85fae5
7
- data.tar.gz: 8e6531f1ad46e37f01086991bca2c6ac1ddd262c381fb514a3a4b0674b5d7001412c1b469ae56a03d50a2edf69040bd761940413b3b2d741d4fb7ec3d1b3bfec
6
+ metadata.gz: 5cd8632b158f0e5dda4e5377b5fc74a9e7ae416a738941dcf276cd48d8040c671eb9e337a089215e092ec499fecbadfaf6c015f49f09b72bc4cee556780afa4f
7
+ data.tar.gz: e52ea4e36b3bfbd397dabc9410d07af6106d1c7bc1e9dbac98d18a2def432300e64696ee3e277ad1e19639d61027ccb46d3a3f665603031ffab26c81d4f7ba83
data/.rspec CHANGED
@@ -1,3 +1,2 @@
1
- --format documentation
2
1
  --color
3
2
  --require spec_helper
data/.travis.yml CHANGED
@@ -3,5 +3,16 @@ sudo: false
3
3
  language: ruby
4
4
  cache: bundler
5
5
  rvm:
6
+ - 2.1
7
+ - 2.2
8
+ - 2.3
9
+ - 2.4
10
+ - 2.5.1
6
11
  - 2.5.3
12
+ - jruby-9.0.5.0
13
+ - jruby-9.1.16.0
14
+ matrix:
15
+ include:
16
+ - rvm: 2.4
17
+ os: osx
7
18
  before_install: gem install bundler -v 1.17.2
data/CHANGELOG.md ADDED
@@ -0,0 +1,12 @@
1
+ ## 0.2.0 - December 21, 2018
2
+ - Minor bug fixes in STDIO server
3
+ - More efficient client reads
4
+ - Rename server methods `prepare` for clarity
5
+ - Improved socket state handling
6
+ - More accurate interval time
7
+ - Adapter#remote attribute
8
+ - Adapter#close method
9
+ - Socket exception handling
10
+
11
+ ## 0.1.0 - December 20, 2018
12
+ First release
data/README.md CHANGED
@@ -20,7 +20,7 @@ gem 'backport'
20
20
 
21
21
  ## Usage
22
22
 
23
- This example demonstrates a simple echo server.
23
+ A simple echo server:
24
24
 
25
25
  ```ruby
26
26
  require 'backport'
@@ -40,8 +40,17 @@ module MyAdapter
40
40
  end
41
41
 
42
42
  Backport.run do
43
- Backport.start_tcp_server(adapter: MyAdapter)
44
- Backport.start_interval 1 do
43
+ Backport.prepare_tcp_server(host: 'localhost', port: 8000, adapter: MyAdapter)
44
+ end
45
+ ```
46
+
47
+ An interval server that runs once per second:
48
+
49
+ ```ruby
50
+ require 'backport'
51
+
52
+ Backport.run do
53
+ Backport.prepare_interval 1 do
45
54
  puts "tick"
46
55
  end
47
56
  end
data/backport.gemspec CHANGED
@@ -23,4 +23,5 @@ Gem::Specification.new do |spec|
23
23
  spec.add_development_dependency "bundler", "~> 1.17"
24
24
  spec.add_development_dependency "rake", "~> 10.0"
25
25
  spec.add_development_dependency "rspec", "~> 3.0"
26
+ spec.add_development_dependency "simplecov", "~> 0.14"
26
27
  end
data/lib/backport.rb CHANGED
@@ -8,28 +8,57 @@ module Backport
8
8
  autoload :Client, 'backport/client'
9
9
 
10
10
  class << self
11
- def start_stdio_server adapter: Adapter
12
- machine.start_server Backport::Server::Stdio.new(adapter: adapter)
11
+ # Prepare a STDIO server to run in Backport.
12
+ #
13
+ # @param adapter [Adapter]
14
+ # @return [void]
15
+ def prepare_stdio_server adapter: Adapter
16
+ machine.prepare Backport::Server::Stdio.new(adapter: adapter)
13
17
  end
14
18
 
15
- def start_tcp_server host: 'localhost', port: 1117, adapter: Adapter
16
- machine.start_server Backport::Server::Tcpip.new(host: host, port: port, adapter: adapter)
19
+ # Prepare a TCP server to run in Backport.
20
+ #
21
+ # @param host [String]
22
+ # @param port [Integer]
23
+ # @param adapter [Adapter]
24
+ # @return [void]
25
+ def prepare_tcp_server host: 'localhost', port: 1117, adapter: Adapter
26
+ machine.prepare Backport::Server::Tcpip.new(host: host, port: port, adapter: adapter)
17
27
  end
18
28
 
19
- def start_interval period, &block
20
- machine.start_server Backport::Server::Interval.new(period, &block)
29
+ # Prepare an interval server to run in Backport.
30
+ #
31
+ # @param period [Float] Seconds between intervals
32
+ # @return [void]
33
+ def prepare_interval period, &block
34
+ machine.prepare Backport::Server::Interval.new(period, &block)
21
35
  end
22
36
 
37
+ # Run the Backport machine. The provided block will be executed before the
38
+ # machine starts. Program execution is halted until the machine stops.
39
+ #
40
+ # @example Print "tick" once per second
41
+ # Backport.run do
42
+ # Backport.prepare_interval 1 do
43
+ # puts "tick"
44
+ # end
45
+ # end
46
+ #
47
+ # @return [void]
23
48
  def run &block
24
49
  machine.run &block
25
50
  end
26
51
 
52
+ # Stop the Backport machine.
53
+ #
54
+ # @return [void]
27
55
  def stop
28
56
  machine.stop
29
57
  end
30
58
 
31
59
  private
32
60
 
61
+ # @return [Machine]
33
62
  def machine
34
63
  @machine ||= Machine.new
35
64
  end
@@ -1,27 +1,67 @@
1
1
  module Backport
2
2
  class Adapter
3
- def initialize output
3
+ # A hash of information about the client connection. The data can vary
4
+ # based on the transport, e.g., :hostname and :address for TCP connections
5
+ # or :filename for file streams.
6
+ #
7
+ # @return [Hash{Symbol => String, Integer}]
8
+ attr_reader :remote
9
+
10
+ # @param output [IO]
11
+ # @param remote [Hash{Symbol => String, Integer}]
12
+ def initialize output, remote = {}
4
13
  @out = output
14
+ @remote = remote
5
15
  end
6
16
 
7
- def opening
8
- STDERR.puts "Opening"
9
- end
17
+ # A callback triggered when a client connection is opening. Subclasses
18
+ # and/or modules should override this method to provide their own
19
+ # functionality.
20
+ #
21
+ # @return [void]
22
+ def opening; end
10
23
 
11
- def closing
12
- STDERR.puts "Closing"
13
- end
24
+ # A callback triggered when a client connection is closing. Subclasses
25
+ # and/or modules should override this method to provide their own
26
+ # functionality.
27
+ #
28
+ # @return [void]
29
+ def closing; end
14
30
 
15
- def sending data
16
- STDERR.puts "Client sent #{data}"
17
- end
31
+ # A callback triggered when the client is sending data to the server.
32
+ # Subclasses and/or modules should override this method to provide their
33
+ # own functionality.
34
+ #
35
+ # @param data [String]
36
+ # @return [void]
37
+ def sending(data); end
18
38
 
39
+ # Send data to the client.
40
+ #
41
+ # @param data [String]
42
+ # @return [void]
19
43
  def write data
20
- @out.print data
44
+ @out.write data
45
+ @out.flush
21
46
  end
22
47
 
48
+ # Send a line of data to the client.
49
+ #
50
+ # @param data [String]
51
+ # @return [void]
23
52
  def write_line data
24
53
  @out.puts data
54
+ @out.flush
55
+ end
56
+
57
+ def closed?
58
+ @closed ||= false
59
+ end
60
+
61
+ def close
62
+ return if closed?
63
+ @closed = true
64
+ closing
25
65
  end
26
66
  end
27
67
  end
@@ -1,10 +1,12 @@
1
1
  module Backport
2
2
  class Client
3
- def initialize input, output, adapter
3
+ # @return [Adapter]
4
+ attr_reader :adapter
5
+
6
+ def initialize input, output, adapter, remote = {}
4
7
  @in = input
5
8
  @out = output
6
- # @todo Adapter can either be an Adapter class or a module
7
- @adapter = make_adapter(adapter)
9
+ @adapter = make_adapter(adapter, remote)
8
10
  @stopped = true
9
11
  @buffer = ''
10
12
  end
@@ -41,11 +43,11 @@ module Backport
41
43
 
42
44
  private
43
45
 
44
- def make_adapter cls_mod
45
- if cls_mod.is_a?(Class)
46
- @adapter = cls_mod.new(@out)
47
- elsif cls_mod.is_a?(Module)
48
- @adapter = Adapter.new(@out)
46
+ def make_adapter cls_mod, remote
47
+ if cls_mod.is_a?(Class) && cls_mod <= Backport::Adapter
48
+ @adapter = cls_mod.new(@out, remote)
49
+ elsif cls_mod.class == Module
50
+ @adapter = Adapter.new(@out, remote)
49
51
  @adapter.extend cls_mod
50
52
  else
51
53
  raise TypeError, "#{cls_mod} is not a valid Backport adapter"
@@ -59,13 +61,17 @@ module Backport
59
61
  def run_input_thread
60
62
  Thread.new do
61
63
  until stopped?
62
- char = @in.getc
63
- if char.nil?
64
- STDERR.puts "Client received nil. Stopping"
64
+ @in.flush
65
+ begin
66
+ chars = @in.sysread(255)
67
+ rescue EOFError, Errno::ECONNRESET
68
+ chars = nil
69
+ end
70
+ if chars.nil?
65
71
  stop
66
72
  break
67
73
  end
68
- mutex.synchronize { @buffer.concat char }
74
+ mutex.synchronize { @buffer.concat chars }
69
75
  end
70
76
  end
71
77
  end
@@ -4,30 +4,48 @@ module Backport
4
4
  @stopped = true
5
5
  end
6
6
 
7
+ # Run the machine. If a block is provided, it gets executed before the
8
+ # maching starts its main loop. The main loop halts program execution
9
+ # until the machine is stopped.
10
+ #
11
+ # @return [void]
7
12
  def run
8
13
  return unless stopped?
14
+ servers.clear
9
15
  @stopped = false
10
16
  yield if block_given?
11
17
  run_server_thread
12
18
  end
13
19
 
20
+ # Stop the machine.
21
+ #
22
+ # @return [void]
14
23
  def stop
15
24
  servers.map(&:stop)
16
25
  servers.clear
17
26
  @stopped = true
18
27
  end
19
28
 
29
+ # True if the machine is stopped.
30
+ #
20
31
  def stopped?
21
32
  @stopped ||= false
22
33
  end
23
34
 
24
- def start_server server
35
+ # Add a server to the machine. The server will be started when the machine
36
+ # starts. If the machine is already running, the server will be started
37
+ # immediately.
38
+ #
39
+ # @param server [Server::Base]
40
+ # @return [void]
41
+ def prepare server
25
42
  servers.push server
43
+ server.start unless stopped?
26
44
  end
27
45
 
28
46
  private
29
47
 
30
- # @return [Array<Backport::Server>]
48
+ # @return [Array<Server::Base>]
31
49
  def servers
32
50
  @servers ||= []
33
51
  end
@@ -35,10 +53,7 @@ module Backport
35
53
  def run_server_thread
36
54
  servers.map(&:start)
37
55
  until stopped?
38
- servers.each do |server|
39
- server.tick
40
- sleep 0.001
41
- end
56
+ servers.each(&:tick)
42
57
  sleep 0.001
43
58
  end
44
59
  end
@@ -1,21 +1,40 @@
1
1
  module Backport
2
2
  module Server
3
3
  class Base
4
- def stopped?
5
- @stopped = false if @stopped.nil?
6
- @stopped
4
+ def started?
5
+ @started ||= false
6
+ @started
7
7
  end
8
8
 
9
9
  def stop
10
10
  stopping
11
- @stopped = true
11
+ @started = false
12
12
  end
13
13
 
14
+ # A callback triggered when a Machine starts running or the server is
15
+ # added to a running machine. Subclasses should override this method to
16
+ # provide their own functionality.
17
+ #
18
+ # @return [void]
19
+ def starting; end
20
+
21
+ # A callback triggered when the server is stopping. Subclasses should
22
+ # override this method to provide their own functionality.
23
+ #
24
+ # @return [void]
14
25
  def stopping; end
15
26
 
27
+ # A callback triggered from the main loop of a running Machine.
28
+ # Subclasses should override this method to provide their own
29
+ # functionality.
30
+ #
31
+ # @return [void]
16
32
  def tick; end
17
33
 
18
- def start; end
34
+ def start
35
+ starting
36
+ @started = true
37
+ end
19
38
  end
20
39
  end
21
40
  end
@@ -8,7 +8,7 @@ module Backport
8
8
  end
9
9
  end
10
10
 
11
- def start
11
+ def starting
12
12
  clients.map(&:run)
13
13
  end
14
14
 
@@ -16,8 +16,6 @@ module Backport
16
16
  clients.map(&:stop)
17
17
  end
18
18
 
19
- protected
20
-
21
19
  def clients
22
20
  @clients ||= []
23
21
  end
@@ -8,13 +8,10 @@ module Backport
8
8
  end
9
9
 
10
10
  def tick
11
- return unless Time.now - @last_time > @period
11
+ now = Time.now
12
+ return unless now - @last_time > @period
12
13
  @block.call
13
- @last_time = Time.now
14
- end
15
-
16
- def stop
17
- @stopped = true
14
+ @last_time = now
18
15
  end
19
16
  end
20
17
  end
@@ -6,7 +6,7 @@ module Backport
6
6
  def initialize input: STDIN, output: STDOUT, adapter: Adapter
7
7
  @in = input
8
8
  @out = output
9
- @in.binmode
9
+ @out.binmode
10
10
  @adapter = adapter
11
11
  clients.push Client.new(input, output, adapter)
12
12
  end
@@ -8,28 +8,44 @@ module Backport
8
8
  def initialize host: 'localhost', port: 1117, adapter: Adapter
9
9
  @socket = TCPServer.new(host, port)
10
10
  @adapter = adapter
11
- end
12
-
13
- def stopped?
14
- @stopped ||= false
11
+ @stopped = false
15
12
  end
16
13
 
17
14
  def tick
18
15
  mutex.synchronize do
19
16
  clients.each do |client|
17
+ if client.adapter.closed?
18
+ client.stop
19
+ next
20
+ end
20
21
  input = client.read
21
22
  client.sending input unless input.nil?
22
23
  end
24
+ clients.delete_if(&:stopped?)
23
25
  end
24
26
  end
25
27
 
26
- def start
27
- super
28
+ def starting
28
29
  start_accept_thread
29
30
  end
30
31
 
32
+ def stopping
33
+ super
34
+ begin
35
+ socket.shutdown
36
+ rescue Errno::ENOTCONN, IOError
37
+ # ignore
38
+ end
39
+ socket.close
40
+ end
41
+
42
+ def stopped?
43
+ @stopped
44
+ end
45
+
31
46
  private
32
47
 
48
+ # @return [TCPSocket]
33
49
  attr_reader :socket
34
50
 
35
51
  def mutex
@@ -39,10 +55,23 @@ module Backport
39
55
  def start_accept_thread
40
56
  Thread.new do
41
57
  until stopped?
42
- conn = socket.accept
43
- mutex.synchronize do
44
- clients.push Client.new(conn, conn, @adapter)
45
- clients.last.run
58
+ begin
59
+ conn = socket.accept
60
+ mutex.synchronize do
61
+ addr = conn.addr(true)
62
+ data = {
63
+ family: addr[0],
64
+ port: addr[1],
65
+ hostname: addr[2],
66
+ address: addr[3]
67
+ }
68
+ clients.push Client.new(conn, conn, @adapter, data)
69
+ clients.last.run
70
+ end
71
+ rescue Exception => e
72
+ STDERR.puts "Server stopped with exception [#{e.class}] #{e.message}"
73
+ stop
74
+ break
46
75
  end
47
76
  end
48
77
  end
@@ -1,3 +1,3 @@
1
1
  module Backport
2
- VERSION = "0.1.0"
2
+ VERSION = "0.2.0"
3
3
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: backport
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0
4
+ version: 0.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Fred Snyder
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2018-12-20 00:00:00.000000000 Z
11
+ date: 2018-12-21 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler
@@ -52,6 +52,20 @@ dependencies:
52
52
  - - "~>"
53
53
  - !ruby/object:Gem::Version
54
54
  version: '3.0'
55
+ - !ruby/object:Gem::Dependency
56
+ name: simplecov
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - "~>"
60
+ - !ruby/object:Gem::Version
61
+ version: '0.14'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - "~>"
67
+ - !ruby/object:Gem::Version
68
+ version: '0.14'
55
69
  description:
56
70
  email:
57
71
  - fsnyder@castwide.com
@@ -62,6 +76,7 @@ files:
62
76
  - ".gitignore"
63
77
  - ".rspec"
64
78
  - ".travis.yml"
79
+ - CHANGELOG.md
65
80
  - Gemfile
66
81
  - LICENSE.txt
67
82
  - README.md