rinterface 0.0.3

Sign up to get free protection for your applications and to get access to all the features.
data/.gitignore ADDED
@@ -0,0 +1,4 @@
1
+ *.cache
2
+ *.beam
3
+ *.dump
4
+ *.gem
data/README.md ADDED
@@ -0,0 +1,111 @@
1
+ rinterface
2
+ ==========
3
+
4
+ Pure Ruby client that can send RPC calls to an Erlang node.
5
+ _It's very much a work in progress._
6
+
7
+ __License:__ MIT ?
8
+
9
+
10
+ ## Install
11
+
12
+ gem install rinterface
13
+
14
+
15
+ ## Try it out
16
+
17
+ Open a terminal and run:
18
+
19
+ rake run
20
+
21
+ This will start the erlang node named 'math'.
22
+
23
+ Open another terminal, and run:
24
+
25
+ ruby examples/client.rb
26
+
27
+
28
+ ## How to use?
29
+
30
+ In your Ruby code, make a call to the Erlang node like this:
31
+
32
+ Erlang::Node(nodename,module,function,args) => [:ok,Response] | [:badprc,Reason]
33
+
34
+
35
+ r = Erlang::Node.rpc("math","math_server","add",[10,20])
36
+
37
+ if r[0] == :badrpc
38
+ puts "Got and Error. Reason #{r[1]}"
39
+ else
40
+ puts "Success: #{r[1]}"
41
+ end
42
+
43
+ Where:
44
+
45
+ * math is the node name (the -sname of the Erlang node)
46
+ * math_server is the name of the module
47
+ * add is the funtion to call
48
+ * [10,20] is an array of arguments to pass to the function
49
+
50
+ The result will always be an Array of the form:
51
+
52
+ [:ok, Response]
53
+
54
+ Where Response is the result from the Erlang, or:
55
+
56
+ [:badrpc, Reason]
57
+
58
+ Where Reason is the 'why' it failed.
59
+
60
+ ### Experimental new way
61
+
62
+ The code above can be written like this now:
63
+
64
+ Erl::MathServer.add(10, 20)
65
+
66
+ (Ain`t Ruby a beauty? ;)
67
+
68
+
69
+ ## So you wanna...
70
+
71
+ ### Test your Erlang code from RSpec...
72
+
73
+ Here's a quick and simple example. Make sure you put the rinterface lib into RAILS_ROOT/lib and start the math_server in 'test'
74
+
75
+
76
+
77
+ ### Call Erlang from your Rails app...
78
+
79
+ Here's a quick and simple example. Make sure you put the rinterface lib into RAILS_ROOT/lib and start the math_server in 'test'
80
+ In the controller:
81
+
82
+ controllers/math_controller.rb
83
+
84
+ require "lib/rinterface"
85
+
86
+ class MathController < ApplicationController
87
+ def index
88
+ a = params[:a]
89
+ b = params[:b]
90
+ r = Erlang::Node.rpc("math","math_server","add",[a.to_i,b.to_i])
91
+
92
+ if r[0] == :badrpc
93
+ @result = "Error"
94
+ else
95
+ @result = r[1]
96
+ end
97
+ end
98
+ end
99
+
100
+
101
+ Finally, add a template for the view, and try 'http://localhost:3000/math?a=2&b=3'.
102
+ This is not ideal yet and not something I'd use yet in production,
103
+ but it's a starting point for experimenting.
104
+
105
+
106
+ ## Specs
107
+
108
+ Don`t forget to run specs before/after patching.
109
+
110
+ Use either "rake spec" or "spec spec",
111
+ it`ll start the erlang daemon automatically.
data/Rakefile ADDED
@@ -0,0 +1,107 @@
1
+ # -*- coding: utf-8 -*-
2
+ require 'rubygems'
3
+ require 'rake'
4
+ require 'rake/clean'
5
+ CLEAN << FileList['**/*.dump']
6
+ CLOBBER << FileList['**/*.beam']
7
+ SRC = FileList['**/*.erl']
8
+ OBJ = SRC.ext("beam") #)pathmap("%{examples,spec}X.beam")
9
+ ERLC_FLAGS = "-Iinclude +warn_unused_vars +warn_unused_import"
10
+ START_MODULE = "math_server"
11
+
12
+ # Gem
13
+ #
14
+ begin
15
+ require 'jeweler'
16
+ Jeweler::Tasks.new do |gem|
17
+ gem.name = "rinterface"
18
+ gem.summary = "Erlang RPC Ruby Client"
19
+ gem.description = "Pure Ruby client that can send RPC calls to an Erlang node"
20
+ gem.email = "r@interf.ace"
21
+ gem.homepage = "http://github.com/davebryson/rinterface"
22
+ gem.authors = ["Dave Bryson", "Éverton Ribeiro", "Marcos Piccinini"]
23
+ gem.add_dependency "eventmachine"
24
+ gem.add_development_dependency "rspec"
25
+ # gem is a Gem::Specification... see http://www.rubygems.org/read/chapter/20 for additional settings
26
+ end
27
+ Jeweler::GemcutterTasks.new
28
+ rescue LoadError
29
+ puts "Jeweler (or a dependency) not available. Install it with: sudo gem install jeweler"
30
+ end
31
+
32
+ # Compile
33
+ #
34
+ rule ".beam" => ".erl" do |t|
35
+ sh "erlc -o #{t.name.split("/")[0]} -W #{ERLC_FLAGS} #{t.source}"
36
+ end
37
+
38
+ desc "Compile all"
39
+ task :compile => OBJ
40
+ task :default => :compile
41
+
42
+ # Shell
43
+ #
44
+ desc "Open up a shell"
45
+ task :shell => [:compile] do
46
+ sh("erl -sname rinterface -pa examples")
47
+ end
48
+
49
+ namespace :daemon do
50
+ desc "Daemon run #{START_MODULE}:start()"
51
+ task :start => [:compile] do
52
+ pid = sh("erl -heart -W -pa examples -sname math -s #{START_MODULE} -detached")
53
+ puts "Now try 'ruby examples/client.rb' #{pid}"
54
+ end
55
+
56
+ task :status do
57
+ sh("ps aux | grep #{START_MODULE}")
58
+ end
59
+ end
60
+
61
+ namespace :spec do
62
+ desc "Open up a shell and run spec test server"
63
+ task :run => [:compile] do
64
+ sh("erl -noshell -W -pa spec -sname spec -s spec_server&")
65
+ end
66
+ desc "Open up a shell and run spec test server"
67
+ task :shell => [:compile] do
68
+ sh("erl -W -pa spec -sname spec -s spec_server")
69
+ end
70
+ end
71
+
72
+ task :test => [:compile] do
73
+ puts "Running suite.."
74
+ Rake::Task["spec"].invoke
75
+ Rake::Task["spec"].invoke
76
+ end
77
+
78
+ # Spec
79
+ #
80
+ require 'spec/rake/spectask'
81
+ Spec::Rake::SpecTask.new(:spec) do |spec|
82
+ spec.libs << 'lib' << 'spec'
83
+ spec.spec_files = FileList['spec/**/*_spec.rb']
84
+ end
85
+
86
+ Spec::Rake::SpecTask.new(:rcov) do |spec|
87
+ spec.libs << 'lib' << 'spec'
88
+ spec.pattern = 'spec/**/*_spec.rb'
89
+ spec.rcov = true
90
+ end
91
+ task :spec => :check_dependencies
92
+
93
+ # RDoc
94
+ #
95
+ require 'rake/rdoctask'
96
+ Rake::RDocTask.new do |rdoc|
97
+ if File.exist?('VERSION')
98
+ version = File.read('VERSION')
99
+ else
100
+ version = ""
101
+ end
102
+
103
+ rdoc.rdoc_dir = 'rdoc'
104
+ rdoc.title = "rinterface #{version}"
105
+ rdoc.rdoc_files.include('README*')
106
+ rdoc.rdoc_files.include('lib/**/*.rb')
107
+ end
data/VERSION ADDED
@@ -0,0 +1 @@
1
+ 0.0.3
@@ -0,0 +1,19 @@
1
+ require 'lib/rinterface'
2
+
3
+
4
+ # Try different responses...
5
+
6
+ puts "Bad rpc test. Try to call the wrong service"
7
+ r = Erlang::Node.rpc("math","matx_server","add",[10,20])
8
+ puts "Got: #{r.inspect}"
9
+
10
+ puts "--------"
11
+ puts "Bad port test, No Port for Service. Can't find a port for 'ath'"
12
+ r = Erlang::Node.rpc("ath","matx_server","add",[10,20])
13
+ puts "Got: #{r.inspect}"
14
+
15
+ puts "--------"
16
+ puts "Good call, add 10 + 20"
17
+ r = Erlang::Node.rpc("math","math_server","add",[10,20])
18
+ puts "Got: #{r.inspect}"
19
+
@@ -0,0 +1,28 @@
1
+ %%
2
+ %% Simple add server
3
+ %%
4
+ -module(math_server).
5
+ -export([start/0,add/2]).
6
+
7
+ start() ->
8
+ register(?MODULE,spawn(fun() -> loop() end)).
9
+
10
+ add(X,Y) ->
11
+ ?MODULE ! {self(),add,X,Y},
12
+ receive
13
+ {?MODULE,Response} -> Response
14
+ end.
15
+
16
+ sum(X,Y) -> X+Y.
17
+
18
+ loop() ->
19
+ receive
20
+ {From,add,X,Y} ->
21
+ error_logger:info_msg("Got the request, and doing the add...~n"),
22
+ Sum = X+Y,
23
+ From ! {?MODULE,Sum},
24
+ loop();
25
+ Any ->
26
+ error_logger:info_msg("Got a crazy msg: ~p~n",[Any]),
27
+ loop()
28
+ end.
@@ -0,0 +1,58 @@
1
+ #
2
+ # Connects to epmd to find the port number of the requested node
3
+ # this only implements port_please_request
4
+ #
5
+ module Erlang
6
+ class EpmdConnection < EM::Connection
7
+ include EM::Deferrable
8
+ attr_accessor :nodename
9
+
10
+ def self.lookup_node(nodename)
11
+ EM.connect("127.0.0.1",4369,self) do |conn|
12
+ conn.nodename = nodename
13
+ end
14
+ end
15
+
16
+ def connection_completed
17
+ send_data lookup_port
18
+ end
19
+
20
+ def receive_data(data)
21
+ parse_response(data)
22
+ end
23
+
24
+ def unbind
25
+ end
26
+
27
+ def lookup_port
28
+ out = StringIO.new('', 'w')
29
+
30
+ # Create the header with length: 2
31
+ out.write([@nodename.size + 1].pack('n'))
32
+
33
+ # Next the request
34
+ # tag. Length: 1
35
+ out.write([122].pack("C"))
36
+ # nodename
37
+ out.write(nodename)
38
+ out.string
39
+ end
40
+
41
+ # If we get a good result we only return
42
+ # the port (not reading all the information
43
+ def parse_response(input)
44
+ i = StringIO.new(input)
45
+ code = i.read(1).unpack('C').first
46
+ result = i.read(1).unpack('C').first
47
+ if result == 0
48
+ # good response read the port
49
+ port = i.read(2).unpack('n').first
50
+ set_deferred_success port
51
+ else
52
+ set_deferred_failure 0
53
+ end
54
+ end
55
+
56
+ end
57
+ end
58
+
@@ -0,0 +1,24 @@
1
+ module Rinterface
2
+
3
+ module Erl
4
+
5
+ class << self
6
+
7
+ def method_missing(fun, node, *rest)
8
+ Erlang::Node.fun(node.to_s, @@module, fun.to_s, *rest)
9
+ end
10
+
11
+ def const_missing(m)
12
+ @@module = snake(m)
13
+ self
14
+ end
15
+
16
+ def snake(txt)
17
+ txt.to_s.split(/(?=[A-Z])/).join('_').downcase
18
+ end
19
+
20
+ end
21
+
22
+ end
23
+
24
+ end
@@ -0,0 +1,223 @@
1
+ module Erlang
2
+
3
+ class Decode
4
+ include External::Types
5
+
6
+ attr_accessor :in
7
+
8
+ def self.read_bits(string)
9
+ new(StringIO.new(string))
10
+ end
11
+
12
+ def self.read_any_from(string)
13
+ new(StringIO.new(string)).read_any
14
+ end
15
+
16
+ def initialize(ins)
17
+ @in = ins
18
+ @peeked = ""
19
+ end
20
+
21
+ def read_any
22
+ raise "Bad Math on Version" unless read_1 == External::VERSION
23
+ read_any_raw
24
+ end
25
+
26
+ def read_any_raw
27
+ case peek_1
28
+ when ATOM then read_atom
29
+ when SMALL_INT then read_small_int
30
+ when INT then read_int
31
+ when SMALL_BIGNUM then read_small_bignum
32
+ when LARGE_BIGNUM then read_large_bignum
33
+ when NEW_FLOAT then read_double
34
+ when FLOAT then read_float
35
+ when NEW_REF then read_new_reference
36
+ when PID then read_pid
37
+ when SMALL_TUPLE then read_small_tuple
38
+ when LARGE_TUPLE then read_large_tuple
39
+ when NIL then read_nil
40
+ when STRING then read_erl_string
41
+ when LIST then read_list
42
+ when BIN then read_bin
43
+ else
44
+ fail("Unknown term tag: #{peek_1}")
45
+ end
46
+ end
47
+
48
+ def read(length)
49
+ # p @peeked
50
+ if length < @peeked.length
51
+ result = @peeked[0...length]
52
+ @peeked = @peeked[length..-1]
53
+ length = 0
54
+ else
55
+ result = @peeked
56
+ @peeked = ''
57
+ length -= result.length
58
+ end
59
+
60
+ if length > 0
61
+ result << @in.read(length)
62
+ end
63
+ result
64
+ end
65
+
66
+ def peek(length)
67
+ if length <= @peeked.length
68
+ @peeked[0...length]
69
+ else
70
+ read_bytes = @in.read(length - @peeked.length)
71
+ @peeked << read_bytes if read_bytes
72
+ @peeked
73
+ end
74
+ end
75
+
76
+ def peek_1
77
+ peek(1).unpack("C").first
78
+ end
79
+
80
+ def peek_2
81
+ peek(2).unpack("n").first
82
+ end
83
+
84
+ def read_1
85
+ read(1).unpack("C").first
86
+ end
87
+
88
+ def read_2
89
+ read(2).unpack("n").first
90
+ end
91
+
92
+ def read_4
93
+ read(4).unpack("N").first
94
+ end
95
+
96
+ def read_string(length)
97
+ read(length)
98
+ end
99
+
100
+ def read_atom
101
+ fail("Invalid Type, not an atom") unless read_1 == ATOM
102
+ length = read_2
103
+ if length == 0
104
+ ''
105
+ else
106
+ read_string(length).to_sym
107
+ end
108
+ end
109
+
110
+ def read_small_int
111
+ fail("Invalid Type, not a small int") unless read_1 == SMALL_INT
112
+ read_1
113
+ end
114
+
115
+ def read_int
116
+ fail("Invalid Type, not an int") unless read_1 == INT
117
+ value = read_4
118
+ negative = (value >> 31)[0] == 1
119
+ value = (value - (1 << 32)) if negative
120
+ value = Fixnum.induced_from(value)
121
+ end
122
+
123
+ def read_small_bignum
124
+ fail("Invalid Type, not a small bignum") unless read_1 == SMALL_BIGNUM
125
+ size = read_1
126
+ sign = read_1
127
+ bytes = read_string(size).unpack("C" * size)
128
+ added = bytes.zip((0..bytes.length).to_a).inject(0) do |result, byte_index|
129
+ byte, index = *byte_index
130
+ value = (byte * (256 ** index))
131
+ sign != 0 ? (result - value) : (result + value)
132
+ end
133
+ Bignum.induced_from(added)
134
+ end
135
+
136
+ def read_large_bignum
137
+ fail("Invalid Type, not a large bignum") unless read_1 == LARGE_BIGNUM
138
+ size = read_4
139
+ sign = read_1
140
+ bytes = read_string(size).unpack("C" * size)
141
+ added = bytes.zip((0..bytes.length).to_a).inject(0) do |result, byte_index|
142
+ byte, index = *byte_index
143
+ value = (byte * (256 ** index))
144
+ sign != 0 ? (result - value) : (result + value)
145
+ end
146
+ Bignum.induced_from(added)
147
+ end
148
+
149
+ def read_double
150
+ fail("Invalid Type, not a double") unless read_1 == NEW_FLOAT
151
+ read_string(64).unpack('G').first
152
+ end
153
+
154
+ def read_float
155
+ fail("Invalid Type, not a float") unless read_1 == FLOAT
156
+
157
+ read_string(32).unpack('g').first
158
+ end
159
+
160
+ def read_new_reference
161
+ fail("Invalid Type, not a new-style reference") unless read_1 == NEW_REF
162
+ size = read_2
163
+ node = read_atom
164
+ creation = read_1
165
+ id = (0...size).map{|i| read_4 }
166
+ NewReference.new(node, creation, id)
167
+ end
168
+
169
+ def read_pid
170
+ fail("Invalid Type, not a pid") unless read_1 == PID
171
+ node = read_atom
172
+ id = read_4
173
+ serial = read_4
174
+ creation = read_1
175
+ Terms::Pid.new(node, id, serial, creation)
176
+ end
177
+
178
+ def read_small_tuple
179
+ fail("Invalid Type, not a small tuple") unless read_1 == SMALL_TUPLE
180
+ arity = read_1
181
+
182
+ (0...arity).map{|i| read_any_raw }
183
+ end
184
+
185
+ def read_large_tuple
186
+ fail("Invalid Type, not a small tuple") unless read_1 == LARGE_TUPLE
187
+ arity = read_4
188
+ (0...arity).map{|i| read_any_raw}
189
+ end
190
+
191
+ def read_nil
192
+ fail("Invalid Type, not a nil list") unless read_1 == NIL
193
+ []
194
+ end
195
+
196
+ def read_erl_string
197
+ fail("Invalid Type, not an erlang string") unless read_1 == STRING
198
+ length = read_2
199
+ read_string(length).unpack('C' * length)
200
+ end
201
+
202
+ def read_list
203
+ fail("Invalid Type, not an erlang list") unless read_1 == LIST
204
+ length = read_4
205
+ list = (0...length).map{|i| read_any_raw}
206
+ read_1
207
+ list
208
+ end
209
+
210
+ def read_bin
211
+ fail("Invalid Type, not an erlang binary") unless read_1 == BIN
212
+ length = read_4
213
+ read_string(length)
214
+ end
215
+
216
+ class DecodeError < StandardError; end
217
+ def fail(str)
218
+ raise DecodeError, str
219
+ end
220
+
221
+ end
222
+
223
+ end
@@ -0,0 +1,107 @@
1
+ module Erlang
2
+ class Encoder
3
+ include External::Types
4
+ include Terms
5
+
6
+ attr_accessor :out
7
+ def initialize
8
+ @out = StringIO.new('', 'w')
9
+ end
10
+
11
+ def rewind
12
+ @out.rewind
13
+ end
14
+
15
+ def term_to_binary obj
16
+ write_1 External::VERSION
17
+ write_any_raw obj
18
+ end
19
+
20
+ def write_any_raw obj
21
+ case obj
22
+ when Symbol then write_symbol(obj)
23
+ when Fixnum, Bignum then write_integer(obj)
24
+ when Float then write_double(obj)
25
+ when Array then write_tuple(obj)
26
+ when String then write_binary(obj)
27
+ when Pid then write_pid(obj)
28
+ when List then write_list(obj)
29
+ else
30
+ raise "Failed encoding!"
31
+ end
32
+ end
33
+
34
+ def write_1(byte)
35
+ @out.write([byte].pack("C"))
36
+ end
37
+
38
+ def write_2(short)
39
+ @out.write([short].pack("n"))
40
+ end
41
+
42
+ def write_4(long)
43
+ @out.write([long].pack("N"))
44
+ end
45
+
46
+ def write_string(string)
47
+ @out.write(string)
48
+ end
49
+
50
+ def write_symbol(sym)
51
+ data = sym.to_s
52
+ write_1 ATOM
53
+ write_2 data.length
54
+ write_string data
55
+ end
56
+
57
+ # TODO: Bignum support
58
+ def write_integer(num)
59
+ if 0 <= num && num < 256
60
+ write_1 SMALL_INT
61
+ write_1 num
62
+ else
63
+ write_1 INT
64
+ write_4 num
65
+ end
66
+ end
67
+
68
+ def write_double(num)
69
+ write_1 NEW_FLOAT
70
+ @out.write([num].pack('G'))
71
+ end
72
+
73
+ def write_tuple(data)
74
+ if data.length < 256
75
+ write_1 SMALL_TUPLE
76
+ write_1 data.length
77
+ else
78
+ write_1 LARGE_TUPLE
79
+ write_4 data.length
80
+ end
81
+ data.each{|e| write_any_raw e }
82
+ end
83
+
84
+ def write_pid(pid)
85
+ write_1(103)
86
+ write_symbol(pid.node)
87
+ write_4((pid.node_id & 0x7fff))
88
+ write_4((pid.serial & 0x1fff))
89
+ write_1((pid.creation & 0x3))
90
+ end
91
+
92
+ def write_list(list)
93
+ len = list.data.size
94
+ write_1(108)
95
+ write_4(len)
96
+ list.data.each{ |i| write_any_raw i }
97
+ write_1(106)
98
+ end
99
+
100
+ def write_binary(data)
101
+ write_1 BIN
102
+ write_4 data.length
103
+ write_string data
104
+ end
105
+
106
+ end
107
+ end
@@ -0,0 +1,28 @@
1
+ #
2
+ # adopted from Erlectricity
3
+ # this version is slightly tweaked, a bit sloppy, and needs a cleanin'
4
+ #
5
+ module Erlang
6
+ module Terms
7
+
8
+ class Pid
9
+ attr_reader :node, :node_id, :serial, :creation
10
+ def initialize(node,nid,serial,created)
11
+ @node = node
12
+ @node_id = nid
13
+ @serial = serial
14
+ @creation = created
15
+ end
16
+ end
17
+
18
+ class List
19
+ attr_reader :data
20
+ def initialize(array)
21
+ @data = array
22
+ end
23
+ end
24
+
25
+ end
26
+
27
+
28
+ end