simplerpc 0.1.0c

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: ff810496dda2e7d4adb2c9a423bc13784986e2c3
4
+ data.tar.gz: 84a9a286a976103d6912999ebbfdbcb1fcb11a8d
5
+ SHA512:
6
+ metadata.gz: 6cc60d3b1eb680a8c04aa94b21d7da6cabe3efcfdc367a145e66d60d7bb612cd2da29e9982cd29aa484c608e649f96adb0a66067d4ab65cbade0f57d0953ba2c
7
+ data.tar.gz: 88b99defcca647ee221abb9901fc37eb391b60455f17ed1629df9400398f934323291ff6a5911d9d7b7cd8ba74a9f9752008814e4ef01bb4563c2082e4008a84
data/LICENSE ADDED
@@ -0,0 +1,4 @@
1
+ Copyright (c) Stephen Wattam 2013, <stephenwattam.com>.
2
+
3
+ As long as you retain this license you can do whatever you like with this code.
4
+ If we meet some day, and you think this stuff was worth it, you can buy me a beer.
@@ -0,0 +1,124 @@
1
+ require 'socket' # Sockets are in standard library
2
+ require 'simplerpc/serialiser'
3
+ require 'simplerpc/socket_protocol'
4
+
5
+ module SimpleRPC
6
+
7
+ # The SimpleRPC client connects to a server, either persistently on on-demand, and makes
8
+ # calls to its proxy object.
9
+ #
10
+ # Once created, you should be able to interact with the client as if it were the remote
11
+ # object.
12
+ #
13
+ class Client
14
+
15
+ # Create a new client for the network
16
+ #
17
+ # hostname:: The hostname of the server
18
+ # port:: The port to connect to
19
+ # serialiser:: An object supporting load/dump for serialising objects. Defaults to
20
+ # SimpleRPC::Serialiser
21
+ # timeout:: The socket timeout. Throws Timeout::TimeoutErrors when exceeded. Set
22
+ # to nil to disable.
23
+ def initialize(hostname, port, serialiser=Serialiser.new, timeout=nil)
24
+ @hostname = hostname
25
+ @port = port
26
+ @serialiser = serialiser
27
+ @timeout = timeout
28
+
29
+ @m = Mutex.new
30
+ end
31
+
32
+ # Connect to the server,
33
+ # or do nothing if already connected
34
+ def connect
35
+ @m.synchronize{
36
+ _connect
37
+ }
38
+ end
39
+
40
+ # Disconnect from the server
41
+ # or do nothing if already disconnected
42
+ def close
43
+ @m.synchronize{
44
+ _disconnect
45
+ }
46
+ end
47
+
48
+ # Alias for close
49
+ alias :disconnect :close
50
+
51
+ # Is the client currently connected?
52
+ def connected?
53
+ @m.synchronize{
54
+ not @s == nil
55
+ }
56
+ end
57
+
58
+ # Call a method that is otherwise clobbered
59
+ # by the client object
60
+ def call(m, *args)
61
+ method_missing(m, *args)
62
+ end
63
+
64
+ # Calls RPC on the remote object
65
+ def method_missing(m, *args, &block)
66
+ valid_call = false
67
+ result = nil
68
+
69
+ @m.synchronize{
70
+ already_connected = (not (@s == nil))
71
+ _connect if not already_connected
72
+ # send method name and arity
73
+ # #puts "c: METHOD: #{m}. ARITY: #{args.length}"
74
+ _send([m, args.length])
75
+
76
+ # receive yey/ney
77
+ valid_call = _recv
78
+
79
+ #puts "=--> #{valid_call}"
80
+
81
+ # call with args
82
+ if valid_call then
83
+ _send( args )
84
+ result = _recv
85
+ end
86
+
87
+ # Then d/c
88
+ _disconnect if not already_connected
89
+ }
90
+
91
+ # If the call wasn't valid, call super
92
+ if not valid_call then
93
+ result = super
94
+ end
95
+
96
+ return result
97
+ end
98
+
99
+ private
100
+ # Connect to the server
101
+ def _connect
102
+ @s = TCPSocket.open( @hostname, @port)
103
+ end
104
+
105
+ # Receive data from the server
106
+ def _recv
107
+ @serialiser.load( SocketProtocol::recv(@s, @timeout) )
108
+ end
109
+
110
+ # Send data to the server
111
+ def _send(obj)
112
+ SocketProtocol::send(@s, @serialiser.dump(obj), @timeout)
113
+ end
114
+
115
+ # Disconnect from the server
116
+ def _disconnect
117
+ return if not @s
118
+ @s.close
119
+ @s = nil
120
+ end
121
+ end
122
+
123
+ end
124
+
@@ -0,0 +1,67 @@
1
+
2
+ module SimpleRPC
3
+
4
+ # This class wraps three possible serialisation systems, providing a common interface to them all.
5
+ #
6
+ # It's not a necessary part of SimpleRPC---you may use any object that supports load/dump---but it is
7
+ # rather handy.
8
+ class Serialiser
9
+
10
+ SUPPORTED_METHODS = %w{marshal json msgpack yaml}.map{|s| s.to_sym}
11
+
12
+ # Create a new Serialiser with the given method. Optionally provide a binding to have
13
+ # the serialisation method execute within another context, i.e. for it to pick up
14
+ # on various libraries and classes.
15
+ #
16
+ # Supported methods are:
17
+ #
18
+ # :marshal:: Use ruby's Marshal system. A good mix of speed and generality.
19
+ # :yaml:: Use YAML. Very slow but very general
20
+ # :msgpack:: Use MessagePack gem. Very fast but not very general (limited data format support)
21
+ # :json:: Use JSON. Also slow, but better for interoperability than YAML.
22
+ #
23
+ def initialize(method = :marshal, binding=nil)
24
+ @method = method
25
+ @binding = nil
26
+ raise "Unrecognised serialisation method" if not SUPPORTED_METHODS.include?(method)
27
+
28
+ # Require prerequisites and handle msgpack not installed-iness.
29
+ case method
30
+ when :msgpack
31
+ begin
32
+ gem "msgpack", "~> 0.5"
33
+ rescue Gem::LoadError => e
34
+ $stderr.puts "The :msgpack serialisation method requires the MessagePack gem (msgpack)."
35
+ $stderr.puts "Please install it or use another serialisation method."
36
+ raise e
37
+ end
38
+ require 'msgpack'
39
+ @cls = MessagePack
40
+ when :yaml
41
+ require 'yaml'
42
+ @cls = YAML
43
+ when :json
44
+ require 'json'
45
+ @cls = JSON
46
+ else
47
+ # marshal is alaways available
48
+ @cls = Marshal
49
+ end
50
+ end
51
+
52
+ # Serialise to a string
53
+ def dump(obj)
54
+ return eval("#{@cls.to_s}.dump(obj)", @binding) if @binding
55
+ return @cls.send(:dump, obj)
56
+ end
57
+
58
+ # Deserialise from a string
59
+ def load(bits)
60
+ return eval("#{@cls.to_s}.load(bits)", @binding) if @binding
61
+ return @cls.send(:load, bits)
62
+ end
63
+
64
+ end
65
+
66
+ end
67
+
@@ -0,0 +1,138 @@
1
+
2
+
3
+ require 'socket' # Get sockets from stdlib
4
+ require 'simplerpc/serialiser'
5
+ require 'simplerpc/socket_protocol'
6
+
7
+ module SimpleRPC
8
+
9
+ # SimpleRPC's server. This wraps an object and exposes its methods to the network.
10
+ class Server
11
+
12
+ # Create a new server for a given proxy object
13
+ #
14
+ # proxy:: The object to proxy the API for---any ruby object
15
+ # port:: The port to listen on
16
+ # hostname:: The ip of the interface to listen on, or nil for all
17
+ # serialiser:: The serialiser to use
18
+ # threaded:: Should the server support multiple clients at once?
19
+ # timeout:: Socket timeout
20
+ def initialize(proxy, port, hostname=nil, serialiser=Serialiser.new, threaded=false, timeout = nil)
21
+ @proxy = proxy
22
+ @port = port
23
+ @hostname = hostname
24
+
25
+ # What format to use.
26
+ @serialiser = serialiser
27
+
28
+ # Silence errors?
29
+ @silence_errors = true
30
+
31
+ # Should we shut down?
32
+ @close = false
33
+
34
+ # Connect/receive timeouts
35
+ @timeout = timeout
36
+
37
+ # Threaded or not?
38
+ @threaded = (threaded == true)
39
+ @clients = {} if @threaded
40
+ @m = Mutex.new if @threaded
41
+ end
42
+
43
+ # Start listening forever
44
+ def listen
45
+ # Listen on one interface only if hostname given
46
+ if @hostname
47
+ @s = TCPServer.open( @hostname, @port )
48
+ else
49
+ @s = TCPServer.open(@port)
50
+ end
51
+
52
+ # Handle clients
53
+ loop{
54
+
55
+ begin
56
+ if @threaded
57
+
58
+ # Create the thread
59
+ id = rand.hash
60
+ thread = Thread.new(id, @m, @s.accept){|id, m, c|
61
+ handle_client(c)
62
+
63
+ puts "#{id} closing 1"
64
+ m.synchronize{
65
+ @clients.delete(id)
66
+ }
67
+ puts "#{id} closing 2"
68
+ }
69
+
70
+ # Add to the client list
71
+ @m.synchronize{
72
+ @clients[id] = thread
73
+ }
74
+ else
75
+ handle_client(@s.accept)
76
+ end
77
+ rescue StandardError => e
78
+ raise e if not @silence_errors
79
+ end
80
+
81
+ break if @close
82
+ }
83
+ end
84
+
85
+ # Return the number of active clients.
86
+ def active_clients
87
+ @m.synchronize{
88
+ @clients.length
89
+ }
90
+ end
91
+
92
+ # Close the server object nicely,
93
+ # waiting on threads if necessary
94
+ def close
95
+ # Ask the loop to close
96
+ @close = true
97
+
98
+ # Wait on threads
99
+ if @threaded then
100
+ @clients.each{|id, thread|
101
+ thread.join
102
+ }
103
+ end
104
+ end
105
+
106
+ private
107
+ # Handle the protocol for client c
108
+ def handle_client(c)
109
+ m, arity = recv(c)
110
+
111
+ # Check the call is valid for the proxy object
112
+ valid_call = (@proxy.respond_to?(m) and @proxy.method(m).arity == arity)
113
+
114
+ send(c, valid_call)
115
+
116
+ # Make the call if valid and send the result back
117
+ if valid_call then
118
+ args = recv(c)
119
+ send(c, @proxy.send(m, *args) )
120
+ end
121
+
122
+ c.close
123
+ end
124
+
125
+ # Receive data from a client
126
+ def recv(c)
127
+ @serialiser.load( SocketProtocol::recv(c, @timeout) )
128
+ end
129
+
130
+ # Send data to a client
131
+ def send(c, obj)
132
+ SocketProtocol::send(c, @serialiser.dump(obj), @timeout)
133
+ end
134
+ end
135
+
136
+
137
+ end
138
+
@@ -0,0 +1,42 @@
1
+ # Low-level protocol specification for SimpleRPC.
2
+ #
3
+ # This defines how data is sent at the socket level, primarily controlling what happens with partial sends/timeouts.
4
+ module SimpleRPC::SocketProtocol
5
+
6
+ # Send already-serialised payload to socket s
7
+ def self.send(s, payload, timeout=nil)
8
+ # Send length
9
+ raise Timeout::TimeoutError if not IO.select(nil, [s], nil, timeout)
10
+ s.puts(payload.length.to_s)
11
+
12
+ # Send rest incrementally
13
+ #puts "[s] send(#{payload})"
14
+ len = payload.length
15
+ while( len > 0 and x = IO.select(nil, [s], nil, timeout) )
16
+ len -= s.write( payload )
17
+ end
18
+ raise Timeout::TimeoutError if not x
19
+ #puts "[s] sent(#{payload})"
20
+ end
21
+
22
+ # Receive raw data from socket s.
23
+ def self.recv(s, timeout=nil)
24
+ # Read the length of the data
25
+ raise Timeout::TimeoutError if not IO.select([s], nil, nil, timeout)
26
+ len = s.gets.chomp.to_i
27
+
28
+ # Then read the rest incrementally
29
+ buf = ""
30
+ while( len > 0 and x = IO.select([s], nil, nil, timeout) )
31
+ #puts "[s (#{len})]"
32
+ x = s.read(len)
33
+ len -= x.length
34
+ buf += x
35
+ end
36
+ raise Timeout::TimeoutError if not x
37
+
38
+ return buf
39
+ #puts "[s] recv(#{buf})"
40
+ end
41
+
42
+ end
metadata ADDED
@@ -0,0 +1,48 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: simplerpc
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0c
5
+ platform: ruby
6
+ authors:
7
+ - Stephen Wattam
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2013-04-23 00:00:00.000000000 Z
12
+ dependencies: []
13
+ description: A very simple and fast RPC library
14
+ email: stephenwattam@gmail.com
15
+ executables: []
16
+ extensions: []
17
+ extra_rdoc_files: []
18
+ files:
19
+ - lib/simplerpc/client.rb
20
+ - lib/simplerpc/server.rb
21
+ - lib/simplerpc/socket_protocol.rb
22
+ - lib/simplerpc/serialiser.rb
23
+ - LICENSE
24
+ homepage: http://stephenwattam.com/projects/simplerpc
25
+ licenses:
26
+ - Beer
27
+ metadata: {}
28
+ post_install_message: Have fun Cing RPs :-)
29
+ rdoc_options: []
30
+ require_paths:
31
+ - lib
32
+ required_ruby_version: !ruby/object:Gem::Requirement
33
+ requirements:
34
+ - - '>='
35
+ - !ruby/object:Gem::Version
36
+ version: '1.9'
37
+ required_rubygems_version: !ruby/object:Gem::Requirement
38
+ requirements:
39
+ - - '>'
40
+ - !ruby/object:Gem::Version
41
+ version: 1.3.1
42
+ requirements: []
43
+ rubyforge_project:
44
+ rubygems_version: 2.0.0
45
+ signing_key:
46
+ specification_version: 4
47
+ summary: Simple RPC library
48
+ test_files: []