spox-spockets 0.0.6
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.
- data/CHANGELOG +11 -0
- data/README.rdoc +86 -0
- data/lib/spockets.rb +1 -0
- data/lib/spockets/Exceptions.rb +37 -0
- data/lib/spockets/Spockets.rb +96 -0
- data/lib/spockets/Watcher.rb +93 -0
- metadata +71 -0
data/CHANGELOG
ADDED
@@ -0,0 +1,11 @@
|
|
1
|
+
0.0.6
|
2
|
+
* Fixed bug exposed when extremely fast input
|
3
|
+
is received.
|
4
|
+
0.0.5
|
5
|
+
|
6
|
+
* Added a delete alias
|
7
|
+
* Added an on_close method to set an action
|
8
|
+
to run when a closed socket is detected
|
9
|
+
* Added an include? method to check if a socket
|
10
|
+
is currently being watched
|
11
|
+
* Added clear method to remove all sockets
|
data/README.rdoc
ADDED
@@ -0,0 +1,86 @@
|
|
1
|
+
== Spockets ==
|
2
|
+
|
3
|
+
Spockets is a simple library for dealing with multiple sockets.
|
4
|
+
You supply a socket, and one or more blocks to
|
5
|
+
execute, and Spockets will make sure those blocks get
|
6
|
+
executed when something comes in over the wire. It's just
|
7
|
+
that simple.
|
8
|
+
|
9
|
+
There is one requirement for spockets and that is the ActionPool.
|
10
|
+
This just allows spockets to use a thread pool for executing
|
11
|
+
blocks so you don't end up having to wait on slow blocks. You
|
12
|
+
can even provide your own pool for spockets to use, so all the
|
13
|
+
action can stay in one local pool.
|
14
|
+
|
15
|
+
install (stable):
|
16
|
+
|
17
|
+
gem install spockets
|
18
|
+
|
19
|
+
install (unstable):
|
20
|
+
|
21
|
+
gem sources -a http://gems.github.com
|
22
|
+
gem install spox-spockets
|
23
|
+
|
24
|
+
or
|
25
|
+
|
26
|
+
git clone http://github.com/spox/spockets.git
|
27
|
+
cd spockets
|
28
|
+
gem build spockets.gemspec
|
29
|
+
gem install ./
|
30
|
+
|
31
|
+
It has RDocs. They are short, but will be helpful and you
|
32
|
+
should really consider giving them a look. If you want to
|
33
|
+
view them online, you can see them here:
|
34
|
+
|
35
|
+
http://dev.modspox.com/~sine/spockets
|
36
|
+
|
37
|
+
There is also a trac site. It has examples as well as
|
38
|
+
a bug tracker. It's located at:
|
39
|
+
|
40
|
+
http://dev.modspox.com/trac/spockets
|
41
|
+
|
42
|
+
Examples are usually helpful, so here we go:
|
43
|
+
|
44
|
+
Code:
|
45
|
+
|
46
|
+
require 'socket'
|
47
|
+
require 'spockets'
|
48
|
+
spockets = Spockets::Spockets.new
|
49
|
+
|
50
|
+
se = TCPServer.new(3000)
|
51
|
+
loop do
|
52
|
+
s = se.accept
|
53
|
+
puts "Socket: #{s}"
|
54
|
+
spockets.add(s){|string| puts "#{s}: #{string}" }
|
55
|
+
end
|
56
|
+
sleep
|
57
|
+
|
58
|
+
|
59
|
+
Connecting:
|
60
|
+
|
61
|
+
> telnet 192.168.0.95 3000
|
62
|
+
Trying 192.168.0.95...
|
63
|
+
Connected to 192.168.0.95.
|
64
|
+
Escape character is '^]'.
|
65
|
+
goodbyeworld
|
66
|
+
^]
|
67
|
+
telnet> quit
|
68
|
+
Connection closed.
|
69
|
+
|
70
|
+
> telnet 192.168.0.95 3000
|
71
|
+
Trying 192.168.0.95...
|
72
|
+
Connected to 192.168.0.95.
|
73
|
+
Escape character is '^]'.
|
74
|
+
foobar
|
75
|
+
complete
|
76
|
+
^]
|
77
|
+
telnet> quit
|
78
|
+
Connection closed.
|
79
|
+
|
80
|
+
Output:
|
81
|
+
|
82
|
+
Socket: #<TCPSocket:0x98ec5ac>
|
83
|
+
Socket: #<TCPSocket:0x98ec37c>
|
84
|
+
#<TCPSocket:0x98ec37c>: foobar
|
85
|
+
#<TCPSocket:0x98ec5ac>: goodbyeworld
|
86
|
+
#<TCPSocket:0x98ec37c>: complete
|
data/lib/spockets.rb
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
require 'spockets/Spockets'
|
@@ -0,0 +1,37 @@
|
|
1
|
+
module Spockets
|
2
|
+
|
3
|
+
class DuplicateSocket < Exception
|
4
|
+
attr_reader :socket
|
5
|
+
def initialize(s)
|
6
|
+
@socket = s
|
7
|
+
end
|
8
|
+
end
|
9
|
+
|
10
|
+
class UnknownSocket < Exception
|
11
|
+
attr_reader :socket
|
12
|
+
def initialize(s)
|
13
|
+
@socket = s
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
class AlreadyRunning < Exception
|
18
|
+
end
|
19
|
+
|
20
|
+
class NotRunning < Exception
|
21
|
+
end
|
22
|
+
|
23
|
+
class Resync < Exception
|
24
|
+
end
|
25
|
+
|
26
|
+
class MissingArgument < Exception
|
27
|
+
attr_reader :argument
|
28
|
+
def initialize(a)
|
29
|
+
@argument = a
|
30
|
+
end
|
31
|
+
|
32
|
+
def to_s
|
33
|
+
"Missing required argument: #{@argument}"
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
end
|
@@ -0,0 +1,96 @@
|
|
1
|
+
require 'spockets/Exceptions'
|
2
|
+
require 'spockets/Watcher'
|
3
|
+
|
4
|
+
module Spockets
|
5
|
+
|
6
|
+
class Spockets
|
7
|
+
|
8
|
+
# :pool:: ActionPool if you would like to consolidate
|
9
|
+
# :clean:: Clean string. Set to true for default or
|
10
|
+
# provide a block to clean strings
|
11
|
+
# creates a new holder
|
12
|
+
def initialize(args={})
|
13
|
+
@sockets = {}
|
14
|
+
@watcher = Watcher.new(:sockets => @sockets, :clean => args[:clean], :pool => args[:pool])
|
15
|
+
end
|
16
|
+
|
17
|
+
# socket:: socket to listen to
|
18
|
+
# block:: block to execute when activity is received
|
19
|
+
# Adds a socket to the list to listen to. When a string
|
20
|
+
# is received on the socket, it will send it to the block
|
21
|
+
# for processing
|
22
|
+
def add(socket, &block)
|
23
|
+
raise DuplicateSocket.new(socket) if @sockets.has_key?(socket)
|
24
|
+
@sockets[socket] = {}
|
25
|
+
@sockets[socket][:procs] = [block]
|
26
|
+
begin
|
27
|
+
@watcher.sync
|
28
|
+
rescue NotRunning
|
29
|
+
start
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
# socket:: socket in list
|
34
|
+
# block:: additional block to execute
|
35
|
+
# This will add additional blocks to the associated
|
36
|
+
# socket to be executed when a new string is received
|
37
|
+
def extra(socket, &block)
|
38
|
+
raise UnknownSocket.new(socket) unless @sockets.has_key?(socket)
|
39
|
+
@sockets[socket][:procs] << block
|
40
|
+
end
|
41
|
+
|
42
|
+
# socket:: socket to remove
|
43
|
+
# Removes socket from list
|
44
|
+
def remove(socket)
|
45
|
+
raise UnknownSocket.new(socket) unless @sockets.has_key?(socket)
|
46
|
+
@sockets.delete(socket)
|
47
|
+
begin
|
48
|
+
@watcher.sync
|
49
|
+
rescue NotRunning
|
50
|
+
start
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
# socket:: socket to add close action
|
55
|
+
# block:: action to perform on socket close
|
56
|
+
# Executes block when socket has been closed. Ideal
|
57
|
+
# for reconnection needs
|
58
|
+
def on_close(socket, &block)
|
59
|
+
raise UnknownSocket.new(socket) unless @sockets.has_key?(socket)
|
60
|
+
@sockets[socket][:closed] = block
|
61
|
+
end
|
62
|
+
|
63
|
+
# remove all sockets
|
64
|
+
def clear
|
65
|
+
@sockets.clear
|
66
|
+
stop
|
67
|
+
end
|
68
|
+
|
69
|
+
# start spockets
|
70
|
+
def start
|
71
|
+
raise AlreadyRunning.new if @watcher.running?
|
72
|
+
@watcher.start
|
73
|
+
end
|
74
|
+
|
75
|
+
# stop spockets
|
76
|
+
def stop
|
77
|
+
raise NotRunning.new unless @watcher.running?
|
78
|
+
@watcher.stop
|
79
|
+
end
|
80
|
+
|
81
|
+
# currently watching sockets
|
82
|
+
def running?
|
83
|
+
!@watcher.nil? && @watcher.running?
|
84
|
+
end
|
85
|
+
|
86
|
+
# socket:: a socket
|
87
|
+
# check if the given socket is being watched
|
88
|
+
def include?(socket)
|
89
|
+
@sockets.has_key?(socket)
|
90
|
+
end
|
91
|
+
|
92
|
+
alias :delete :remove
|
93
|
+
|
94
|
+
end
|
95
|
+
|
96
|
+
end
|
@@ -0,0 +1,93 @@
|
|
1
|
+
require 'actionpool'
|
2
|
+
require 'actionpool/Exceptions'
|
3
|
+
require 'iconv'
|
4
|
+
|
5
|
+
module Spockets
|
6
|
+
class Watcher
|
7
|
+
|
8
|
+
# :sockets:: socket list
|
9
|
+
# :clean:: clean UTF8 strings or provide block to run on every read string
|
10
|
+
# :pool:: ActionPool to use
|
11
|
+
# Creates a new watcher for sockets
|
12
|
+
def initialize(args)
|
13
|
+
raise MissingArgument.new(:sockets) unless args[:sockets]
|
14
|
+
@sockets = args[:sockets]
|
15
|
+
@runner = nil
|
16
|
+
@clean = args[:clean] && (args[:clean].is_a?(Proc) || args[:clean].is_a?(TrueClass)) ? args[:clean] : nil
|
17
|
+
@pool = args[:pool] && args[:pool].is_a?(ActionPool::Pool) ? args[:pool] : ActionPool::Pool.new
|
18
|
+
@ic = @clean && @clean.is_a?(TrueClass) ? Iconv.new('UTF-8//IGNORE', 'UTF-8') : nil
|
19
|
+
@stop = true
|
20
|
+
end
|
21
|
+
|
22
|
+
# start the watcher
|
23
|
+
def start
|
24
|
+
@stop = false
|
25
|
+
@runner = Thread.new{watch} if @runner.nil? && @sockets.size > 0
|
26
|
+
end
|
27
|
+
|
28
|
+
# stop the watcher
|
29
|
+
def stop
|
30
|
+
@stop = true
|
31
|
+
@runner.join(0.1)
|
32
|
+
@runner.raise Resync.new
|
33
|
+
@runner.join(0.1)
|
34
|
+
@runner.kill unless @runner.nil? || @runner.alive?
|
35
|
+
@runner = nil
|
36
|
+
end
|
37
|
+
|
38
|
+
# is the watcher running?
|
39
|
+
def running?
|
40
|
+
!@stop
|
41
|
+
end
|
42
|
+
|
43
|
+
# clean incoming strings
|
44
|
+
def clean?
|
45
|
+
@clean
|
46
|
+
end
|
47
|
+
|
48
|
+
# Ensure all sockets are being listened to
|
49
|
+
def sync
|
50
|
+
raise NotRunning.new if @runner.nil?
|
51
|
+
@runner.raise Resync.new
|
52
|
+
end
|
53
|
+
|
54
|
+
private
|
55
|
+
|
56
|
+
def watch
|
57
|
+
until(@stop)
|
58
|
+
begin
|
59
|
+
resultset = Kernel.select(@sockets.keys, nil, nil, nil)
|
60
|
+
for sock in resultset[0]
|
61
|
+
string = sock.gets
|
62
|
+
if(sock.closed? || string.nil?)
|
63
|
+
@sockets[sock][:closed].call(sock) if @sockets[sock].has_key?(:closed)
|
64
|
+
@sockets.delete(sock)
|
65
|
+
else
|
66
|
+
string = clean? ? do_clean(string) : string
|
67
|
+
process(string.dup, sock)
|
68
|
+
end
|
69
|
+
end
|
70
|
+
rescue Resync
|
71
|
+
# break select and relisten #
|
72
|
+
end
|
73
|
+
end
|
74
|
+
@runner = nil
|
75
|
+
end
|
76
|
+
|
77
|
+
def process(string, sock)
|
78
|
+
@sockets[sock][:procs].each{|b| @pool.process{ b.call(string)}}
|
79
|
+
end
|
80
|
+
|
81
|
+
def do_clean(string)
|
82
|
+
unless(@ic.nil?)
|
83
|
+
return untaint(string)
|
84
|
+
else
|
85
|
+
return @clean.call(string)
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
def untaint(s)
|
90
|
+
@ic.iconv(s + ' ')[0..-2]
|
91
|
+
end
|
92
|
+
end
|
93
|
+
end
|
metadata
ADDED
@@ -0,0 +1,71 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: spox-spockets
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.6
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- spox
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
|
12
|
+
date: 2009-05-16 00:00:00 -07:00
|
13
|
+
default_executable:
|
14
|
+
dependencies:
|
15
|
+
- !ruby/object:Gem::Dependency
|
16
|
+
name: ActionPool
|
17
|
+
type: :runtime
|
18
|
+
version_requirement:
|
19
|
+
version_requirements: !ruby/object:Gem::Requirement
|
20
|
+
requirements:
|
21
|
+
- - ">="
|
22
|
+
- !ruby/object:Gem::Version
|
23
|
+
version: "0"
|
24
|
+
version:
|
25
|
+
description: Socket helper library
|
26
|
+
email: spox@modspox.com
|
27
|
+
executables: []
|
28
|
+
|
29
|
+
extensions: []
|
30
|
+
|
31
|
+
extra_rdoc_files:
|
32
|
+
- README.rdoc
|
33
|
+
files:
|
34
|
+
- README.rdoc
|
35
|
+
- CHANGELOG
|
36
|
+
- lib/spockets.rb
|
37
|
+
- lib/spockets/Exceptions.rb
|
38
|
+
- lib/spockets/Spockets.rb
|
39
|
+
- lib/spockets/Watcher.rb
|
40
|
+
has_rdoc: true
|
41
|
+
homepage: http://dev.modspox.com/trac/spockets
|
42
|
+
post_install_message:
|
43
|
+
rdoc_options:
|
44
|
+
- --title
|
45
|
+
- Spockets
|
46
|
+
- --main
|
47
|
+
- README.rdoc
|
48
|
+
- --line-numbers
|
49
|
+
require_paths:
|
50
|
+
- lib
|
51
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
52
|
+
requirements:
|
53
|
+
- - ">="
|
54
|
+
- !ruby/object:Gem::Version
|
55
|
+
version: "0"
|
56
|
+
version:
|
57
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - ">="
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: "0"
|
62
|
+
version:
|
63
|
+
requirements: []
|
64
|
+
|
65
|
+
rubyforge_project:
|
66
|
+
rubygems_version: 1.2.0
|
67
|
+
signing_key:
|
68
|
+
specification_version: 2
|
69
|
+
summary: Socket Helper
|
70
|
+
test_files: []
|
71
|
+
|