em-rserve 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +4 -0
- data/Gemfile +4 -0
- data/README +26 -0
- data/Rakefile +1 -0
- data/TODO +14 -0
- data/em-rserve.gemspec +20 -0
- data/lib/em-rserve/connection.rb +84 -0
- data/lib/em-rserve/fibered_connection.rb +66 -0
- data/lib/em-rserve/pooler.rb +76 -0
- data/lib/em-rserve/protocol/connector.rb +89 -0
- data/lib/em-rserve/protocol/id.rb +14 -0
- data/lib/em-rserve/protocol/parser.rb +93 -0
- data/lib/em-rserve/protocol/request.rb +22 -0
- data/lib/em-rserve/qap1/constants.rb +111 -0
- data/lib/em-rserve/qap1/header.rb +45 -0
- data/lib/em-rserve/qap1/message.rb +26 -0
- data/lib/em-rserve/qap1/rpack.rb +127 -0
- data/lib/em-rserve/r/r_to_ruby/translator.rb +152 -0
- data/lib/em-rserve/r/ruby_to_r/translator.rb +131 -0
- data/lib/em-rserve/r/sexp.rb +421 -0
- data/lib/em-rserve/version.rb +5 -0
- data/lib/em-rserve.rb +11 -0
- data/samples/assign.rb +211 -0
- data/samples/detach_attach.rb +70 -0
- data/samples/fibers.rb +32 -0
- data/samples/run.rb +66 -0
- data/samples/sql.rb +167 -0
- data/samples/views/plot.erb +6 -0
- data/samples/webplot.rb +37 -0
- data/specs/id_parser_spec.rb +45 -0
- data/specs/message_parser_spec.rb +70 -0
- data/specs/parser_spec.rb +80 -0
- data/specs/ruby_to_r_translator_spec.rb +88 -0
- data/specs/spec_helper.rb +3 -0
- metadata +101 -0
data/.gitignore
ADDED
data/Gemfile
ADDED
data/README
ADDED
@@ -0,0 +1,26 @@
|
|
1
|
+
|
2
|
+
EM::Rserve is an attempt to bring RServe over EventMachine.
|
3
|
+
Consider it a somewhat stable version.
|
4
|
+
|
5
|
+
EM::Rserve is pure-ruby and should work wherever EventMachine is supported.
|
6
|
+
|
7
|
+
So far it can:
|
8
|
+
- connect to a server
|
9
|
+
- detach and reattach a session
|
10
|
+
- parse most low-level messages
|
11
|
+
- parse most R' S-expressions to a ruby tree
|
12
|
+
- translate several common R' S-expressions to "base" ruby objects such as arrays, strings, hashes ...
|
13
|
+
- translate several Ruby objects (arrays of integers, strings etc.) to R' S-expression
|
14
|
+
- evaluate strings of R code
|
15
|
+
- handle pools of connections with ruby fibers
|
16
|
+
|
17
|
+
Limitations:
|
18
|
+
- large QAP1 messages (>8MB) are not supported, this limitation means that you cannot transfer large object directly over RServe protocol
|
19
|
+
- no support for the connections with a password. somehow, password without encryption defeats the purpose. hence, if you need a password, you should setup an SSH proxy instead
|
20
|
+
|
21
|
+
Links:
|
22
|
+
- http://www.r-project.org/
|
23
|
+
- http://www.rforge.net/Rserve/
|
24
|
+
- http://rubyeventmachine.com/
|
25
|
+
|
26
|
+
Contributions welcome.
|
data/Rakefile
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
require 'bundler/gem_tasks'
|
data/TODO
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
* missing types
|
2
|
+
* special handling in ruby2r translator
|
3
|
+
- Factor
|
4
|
+
- Table
|
5
|
+
* RESP_ERR as a flag and not an OR-ed value
|
6
|
+
-> will need to modify Header#error? and Header#error
|
7
|
+
* support for long (>8M) messages
|
8
|
+
* login command
|
9
|
+
support crypt and store salt from connection setup
|
10
|
+
* better attach method
|
11
|
+
* write doc
|
12
|
+
* more spec once the RServe spec is better understood/have enough examples
|
13
|
+
* subclass Request for each commmand instead of current quick'n dirty mechanism
|
14
|
+
* better test requests stacking/unstacking
|
data/em-rserve.gemspec
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
$:.push File.expand_path("../lib", __FILE__)
|
3
|
+
require "em-rserve/version"
|
4
|
+
|
5
|
+
Gem::Specification.new do |s|
|
6
|
+
s.name = "em-rserve"
|
7
|
+
s.version = EM::Rserve::VERSION
|
8
|
+
s.authors = ["crapooze"]
|
9
|
+
s.email = ["crapooze@gmail.com"]
|
10
|
+
s.homepage = ""
|
11
|
+
s.summary = %q{An EventMachine client for RServe}
|
12
|
+
s.description = %q{Do evented stats with EventMachine and RServe}
|
13
|
+
|
14
|
+
s.rubyforge_project = "em-rserve"
|
15
|
+
|
16
|
+
s.files = `git ls-files`.split("\n")
|
17
|
+
s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
|
18
|
+
s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
|
19
|
+
s.require_paths = ["lib"]
|
20
|
+
end
|
@@ -0,0 +1,84 @@
|
|
1
|
+
|
2
|
+
require "em-rserve/protocol/connector"
|
3
|
+
require "em-rserve/protocol/parser"
|
4
|
+
require "em-rserve/protocol/request"
|
5
|
+
require "em-rserve/qap1/constants"
|
6
|
+
require "em-rserve/qap1/header"
|
7
|
+
require "em-rserve/qap1/message"
|
8
|
+
|
9
|
+
module EM::Rserve
|
10
|
+
class Connection < EM::Connection
|
11
|
+
include Protocol::Connector
|
12
|
+
include QAP1
|
13
|
+
|
14
|
+
def shutdown!(&blk)
|
15
|
+
header = Header.new(Constants::CMD_shutdown,0,0,0)
|
16
|
+
send_data header.to_bin
|
17
|
+
|
18
|
+
request(&blk)
|
19
|
+
end
|
20
|
+
|
21
|
+
def r_eval(string, void=false,&blk)
|
22
|
+
data = Message.encode_string(string)
|
23
|
+
if void
|
24
|
+
header = Header.new(0x0002, data.length, 0, 0)
|
25
|
+
else
|
26
|
+
header = Header.new(0x0003, data.length, 0, 0)
|
27
|
+
end
|
28
|
+
send_data header.to_bin
|
29
|
+
send_data data
|
30
|
+
|
31
|
+
request(&blk)
|
32
|
+
end
|
33
|
+
|
34
|
+
def login(user,pwd, crypted=true, &blk)
|
35
|
+
raise NotImplementedError, "will come later"
|
36
|
+
#XXX need to read the salt during connection setup
|
37
|
+
cifer = crypted ? pwd : crypt(pwd, salt)
|
38
|
+
data = Message.encode_string([user, cifer].join("\n"))
|
39
|
+
header = Header.new(Constants::CMD_login, data.length, 0, 0)
|
40
|
+
send_data header.to_bin
|
41
|
+
send_data data
|
42
|
+
|
43
|
+
request(&blk)
|
44
|
+
end
|
45
|
+
|
46
|
+
def detach(&blk)
|
47
|
+
header = Header.new(Constants::CMD_detachSession, 0, 0, 0)
|
48
|
+
send_data header.to_bin #port, key of 20 bytes
|
49
|
+
|
50
|
+
request(&blk)
|
51
|
+
end
|
52
|
+
|
53
|
+
def attach(key, &blk)
|
54
|
+
#XXX it seems that there is no need to send a Header + Message, and
|
55
|
+
#raw_writing because the server does a read of 32 bytes on newly accepted
|
56
|
+
#connections
|
57
|
+
raise ArgumentError, "wrong key length, Rserve wants 32bytes" unless key.size == 32
|
58
|
+
send_data key
|
59
|
+
|
60
|
+
request(&blk)
|
61
|
+
end
|
62
|
+
|
63
|
+
def assign(symbol, sexp_node, parse_symbol_name=true, &blk)
|
64
|
+
data = Message.new([symbol.to_s, sexp_node]).to_bin
|
65
|
+
data << "\xFF" * data.length % 4
|
66
|
+
header = if parse_symbol_name
|
67
|
+
Header.new(Constants::CMD_setSEXP, data.length, 0, 0)
|
68
|
+
else
|
69
|
+
Header.new(Constants::CMD_assignSEXP, data.length, 0, 0)
|
70
|
+
end
|
71
|
+
send_data header.to_bin
|
72
|
+
send_data data
|
73
|
+
|
74
|
+
request(&blk)
|
75
|
+
end
|
76
|
+
|
77
|
+
# MISSING:
|
78
|
+
# - open/close/delete/read/write files
|
79
|
+
# - set encoding
|
80
|
+
# - set buffer size
|
81
|
+
# - control commands
|
82
|
+
# - serial commands
|
83
|
+
end
|
84
|
+
end
|
@@ -0,0 +1,66 @@
|
|
1
|
+
|
2
|
+
require "fiber"
|
3
|
+
require "em-rserve/connection"
|
4
|
+
|
5
|
+
module EM::Rserve
|
6
|
+
class FiberedConnection < EM::Rserve::Connection
|
7
|
+
attr_accessor :fiber
|
8
|
+
|
9
|
+
def self.start(fiber=Fiber.current, *args)
|
10
|
+
conn = super(*args)
|
11
|
+
conn.fiber = fiber
|
12
|
+
Fiber.yield
|
13
|
+
end
|
14
|
+
|
15
|
+
def ready
|
16
|
+
super
|
17
|
+
fiber.resume self if fiber
|
18
|
+
end
|
19
|
+
|
20
|
+
def call(script, *args)
|
21
|
+
r_eval(script.to_s, *args) do |req|
|
22
|
+
req.errback do |err|
|
23
|
+
fiber.resume nil
|
24
|
+
end
|
25
|
+
|
26
|
+
req.callback do |msg|
|
27
|
+
unless msg
|
28
|
+
fiber.resume nil
|
29
|
+
next
|
30
|
+
end
|
31
|
+
root = msg.parameters.first
|
32
|
+
if root
|
33
|
+
fiber.resume EM::Rserve::R::RtoRuby::Translator.r_to_ruby(root)
|
34
|
+
else
|
35
|
+
fiber.resume nil
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
Fiber.yield
|
41
|
+
end
|
42
|
+
|
43
|
+
def set(sym, val)
|
44
|
+
root = EM::Rserve::R::RubytoR::Translator.ruby_to_r val
|
45
|
+
|
46
|
+
assign(sym, root) do |req|
|
47
|
+
req.errback do |err|
|
48
|
+
fiber.resume nil
|
49
|
+
end
|
50
|
+
req.callback do |msg|
|
51
|
+
fiber.resume val
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
Fiber.yield
|
56
|
+
end
|
57
|
+
|
58
|
+
# *WARNING* this method is unsafe always check your input parameter
|
59
|
+
def get(sym)
|
60
|
+
call(sym)
|
61
|
+
end
|
62
|
+
|
63
|
+
alias :[]= :set
|
64
|
+
alias :[] :get
|
65
|
+
end
|
66
|
+
end
|
@@ -0,0 +1,76 @@
|
|
1
|
+
|
2
|
+
require "em-rserve/fibered_connection"
|
3
|
+
|
4
|
+
module EM::Rserve
|
5
|
+
class Pooler
|
6
|
+
class << self
|
7
|
+
def r(klass=FiberedConnection)
|
8
|
+
Fiber.new do
|
9
|
+
begin
|
10
|
+
conn = klass.start
|
11
|
+
yield conn
|
12
|
+
ensure
|
13
|
+
conn.close_connection
|
14
|
+
end
|
15
|
+
end.resume
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
attr_reader :connections, :size, :connection_class
|
20
|
+
def initialize(size=10, klass=FiberedConnection)
|
21
|
+
@connections = []
|
22
|
+
@size = size
|
23
|
+
@connection_class = klass
|
24
|
+
end
|
25
|
+
|
26
|
+
def empty?
|
27
|
+
connections.empty?
|
28
|
+
end
|
29
|
+
|
30
|
+
def full?
|
31
|
+
connections.size >= size
|
32
|
+
end
|
33
|
+
|
34
|
+
def connection
|
35
|
+
#XXX duplicated code from Pooler.r to avoid proc-ing the blk
|
36
|
+
Fiber.new do
|
37
|
+
yield connection_class.start
|
38
|
+
end.resume
|
39
|
+
end
|
40
|
+
|
41
|
+
def r
|
42
|
+
conn = connections.shift
|
43
|
+
if conn
|
44
|
+
Fiber.new do
|
45
|
+
begin
|
46
|
+
conn.fiber = Fiber.current
|
47
|
+
yield conn
|
48
|
+
ensure
|
49
|
+
conn.close_connection
|
50
|
+
end
|
51
|
+
fill 1 unless full?
|
52
|
+
end.resume
|
53
|
+
else
|
54
|
+
fill size
|
55
|
+
connection do |conn|
|
56
|
+
begin
|
57
|
+
yield conn
|
58
|
+
ensure
|
59
|
+
conn.close_connection
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
def preconnect!
|
66
|
+
connection do |conn|
|
67
|
+
conn.fiber = nil
|
68
|
+
connections << conn
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
def fill(n=size)
|
73
|
+
n.times{preconnect!}
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
@@ -0,0 +1,89 @@
|
|
1
|
+
|
2
|
+
require "em-rserve/protocol/parser"
|
3
|
+
require "em-rserve/protocol/request"
|
4
|
+
|
5
|
+
module EM::Rserve
|
6
|
+
module Protocol
|
7
|
+
module Connector
|
8
|
+
def self.included(obj)
|
9
|
+
obj.extend ClassMethods
|
10
|
+
end
|
11
|
+
|
12
|
+
module ClassMethods
|
13
|
+
def start(server='127.0.0.1', port=6311)
|
14
|
+
EM.connect(server, port, self)
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
extend ClassMethods
|
19
|
+
|
20
|
+
attr_accessor :request_queue
|
21
|
+
|
22
|
+
def post_init
|
23
|
+
super
|
24
|
+
#type of parser carries the state, no need to carry it internally and do
|
25
|
+
#zillions of state check
|
26
|
+
@parser = nil
|
27
|
+
replace_parser! IDParser
|
28
|
+
@request_queue = []
|
29
|
+
end
|
30
|
+
|
31
|
+
def request
|
32
|
+
r = Request.new
|
33
|
+
yield r if block_given?
|
34
|
+
request_queue << r
|
35
|
+
r
|
36
|
+
end
|
37
|
+
|
38
|
+
def replace_parser!(klass)
|
39
|
+
new_parser = klass.new(self)
|
40
|
+
if @parser
|
41
|
+
new_parser.replace(@parser)
|
42
|
+
end
|
43
|
+
@parser = new_parser
|
44
|
+
end
|
45
|
+
|
46
|
+
def receive_data(dat)
|
47
|
+
@parser << dat
|
48
|
+
end
|
49
|
+
|
50
|
+
def receive_id(id)
|
51
|
+
# on last line, the messaging can start
|
52
|
+
if id.last_one?
|
53
|
+
replace_parser! MessageParser
|
54
|
+
EM.next_tick do
|
55
|
+
ready
|
56
|
+
end
|
57
|
+
throw :stop
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
# HOOKS TO OVERRIDE, PLEASE CALL SUPER
|
62
|
+
|
63
|
+
def ready
|
64
|
+
end
|
65
|
+
|
66
|
+
def receive_message_header(head)
|
67
|
+
if head.error?
|
68
|
+
receive_error_message_header(head)
|
69
|
+
elsif head.ok?
|
70
|
+
receive_success_message_header(head)
|
71
|
+
else
|
72
|
+
raise RuntimeError, "nor OK, nor error message #{head}"
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
def receive_error_message_header(head)
|
77
|
+
request_queue.shift.error(head)
|
78
|
+
end
|
79
|
+
|
80
|
+
def receive_success_message_header(head)
|
81
|
+
request_queue.shift.success(nil) unless head.body?
|
82
|
+
end
|
83
|
+
|
84
|
+
def receive_message(msg)
|
85
|
+
request_queue.shift.success(msg)
|
86
|
+
end
|
87
|
+
end
|
88
|
+
end
|
89
|
+
end
|
@@ -0,0 +1,93 @@
|
|
1
|
+
|
2
|
+
require 'em-rserve/protocol/id'
|
3
|
+
require 'em-rserve/qap1/header'
|
4
|
+
require 'em-rserve/qap1/message'
|
5
|
+
require 'em-rserve/r/sexp'
|
6
|
+
|
7
|
+
module EM::Rserve
|
8
|
+
module Protocol
|
9
|
+
class Parser
|
10
|
+
attr_reader :handler, :buffer
|
11
|
+
|
12
|
+
def initialize(handler)
|
13
|
+
@handler = handler
|
14
|
+
@buffer = ''
|
15
|
+
end
|
16
|
+
|
17
|
+
def replace(other)
|
18
|
+
@buffer.replace(other.buffer)
|
19
|
+
self
|
20
|
+
end
|
21
|
+
|
22
|
+
def << data
|
23
|
+
buffer << data if data
|
24
|
+
parse_loop!
|
25
|
+
end
|
26
|
+
|
27
|
+
def parse_loop!
|
28
|
+
catch :stop do
|
29
|
+
loop do
|
30
|
+
parse!
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
# should overload this method and throw :stop when more data is needed or any other
|
36
|
+
# reason to stop parsing
|
37
|
+
def parse!
|
38
|
+
raise NotImplementedError, "this class is intended to be a top class, not a useful parser"
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
# This parser is useful only at the beginning.
|
43
|
+
# Instead of carrying its dynamic all the time (e.g., keeping a state).
|
44
|
+
# We pop-it out as another parser.
|
45
|
+
class IDParser < Parser
|
46
|
+
def parse!
|
47
|
+
if buffer.size >= 4
|
48
|
+
dat = buffer.slice(0, 4)
|
49
|
+
@buffer = buffer.slice(4 .. -1)
|
50
|
+
handler.receive_id(Protocol::ID.new(dat))
|
51
|
+
else
|
52
|
+
throw :stop
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
# This message parser will parse qap1 headers and associated qap1 data.
|
58
|
+
class MessageParser < Parser
|
59
|
+
def initialize(handler)
|
60
|
+
super(handler)
|
61
|
+
@header = nil
|
62
|
+
end
|
63
|
+
|
64
|
+
def parse!
|
65
|
+
if @header
|
66
|
+
#XXX here we have a header with the size of data
|
67
|
+
#to expect, out approach is to delay the message
|
68
|
+
#until we have all the data, not well suited for
|
69
|
+
#streams but for all other messages this is the
|
70
|
+
#good way of doing it
|
71
|
+
expected_length = @header.message_length
|
72
|
+
if expected_length > 0 and buffer.size >= expected_length
|
73
|
+
dat = buffer.slice(0, expected_length)
|
74
|
+
@buffer = buffer.slice(expected_length .. -1)
|
75
|
+
message = QAP1::Message.from_bin dat
|
76
|
+
handler.receive_message message
|
77
|
+
@header = nil
|
78
|
+
else
|
79
|
+
throw :stop
|
80
|
+
end
|
81
|
+
elsif buffer.size >= 16
|
82
|
+
dat = buffer.slice(0, 16)
|
83
|
+
@buffer = buffer.slice(16 .. -1)
|
84
|
+
@header = QAP1::Header.from_bin dat
|
85
|
+
handler.receive_message_header @header
|
86
|
+
@header = nil unless @header.body?
|
87
|
+
else
|
88
|
+
throw :stop
|
89
|
+
end
|
90
|
+
end
|
91
|
+
end
|
92
|
+
end
|
93
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
|
2
|
+
module EM::Rserve
|
3
|
+
module Protocol
|
4
|
+
Request = Struct.new(:callback_blk, :errback_blk) do
|
5
|
+
def callback(&blk)
|
6
|
+
self.callback_blk = blk
|
7
|
+
end
|
8
|
+
|
9
|
+
def errback(&blk)
|
10
|
+
self.errback_blk = blk
|
11
|
+
end
|
12
|
+
|
13
|
+
def error(val)
|
14
|
+
errback_blk.call(val) if errback_blk
|
15
|
+
end
|
16
|
+
|
17
|
+
def success(val)
|
18
|
+
callback_blk.call(val) if callback_blk
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
@@ -0,0 +1,111 @@
|
|
1
|
+
|
2
|
+
module EM::Rserve
|
3
|
+
module QAP1
|
4
|
+
module Constants
|
5
|
+
CMD_RESP=0x010000 # all responses have this flag set
|
6
|
+
RESP_OK=(CMD_RESP|0x0001) # command succeeded; returned parameters depend on the command issued
|
7
|
+
RESP_ERR=(CMD_RESP|0x0002) # command failed, check stats code attached string may describe the error
|
8
|
+
|
9
|
+
ERR_auth_failed=0x41 # auth.failed or auth.requested but no login came. in case of authentification failure due to name/pwd mismatch, server may send CMD_accessDenied instead
|
10
|
+
ERR_conn_broken=0x42 # connection closed or broken packet killed it */
|
11
|
+
|
12
|
+
ERR_inv_cmd=0x43 # unsupported/invalid command */
|
13
|
+
ERR_inv_par=0x44 # some parameters are invalid */
|
14
|
+
ERR_Rerror=0x45 # R-error occured, usually followed by connection shutdown */
|
15
|
+
ERR_IOerror=0x46 # I/O error */
|
16
|
+
|
17
|
+
|
18
|
+
ERR_notOpen=0x47 # attempt to perform fileRead/Write on closed file */
|
19
|
+
ERR_accessDenied=0x48 # this answer is also valid on CMD_login; otherwise it's sent if the server deosn;t allow the user to issue the specified command. (e.g. some server admins may block file I/O operations for some users)
|
20
|
+
ERR_unsupportedCmd=0x49 # unsupported command */
|
21
|
+
ERR_unknownCmd=0x4a # unknown command - the difference between unsupported and unknown is that unsupported commands are known to the server but for some reasons (e.g. platform dependent) it's not supported. unknown commands are simply not recognized by the server at all. */
|
22
|
+
ERR_data_overflow=0x4b # incoming packet is too big. currently there is a limit as of the size of an incoming packet. */
|
23
|
+
ERR_object_too_big=0x4c # the requested object is too big to be transported in that way. If received after CMD_eval then the evaluation itself was successful. optional parameter is the size of the object
|
24
|
+
ERR_out_of_mem=0x4d # out of memory. the connection is usually closed after this error was sent
|
25
|
+
ERR_ctrl_closed=0x4e # control pipe to the master process is closed or broken
|
26
|
+
ERR_session_busy=0x50 # session is still busy */
|
27
|
+
ERR_detach_failed=0x51 # unable to detach session (cannot determine peer IP or problems creating a listening socket for resume) */
|
28
|
+
|
29
|
+
|
30
|
+
CMD_login=0x001 # "name\npwd" : - */
|
31
|
+
CMD_voidEval=0x002 # string : - */
|
32
|
+
CMD_eval=0x003 # string : encoded SEXP */
|
33
|
+
CMD_shutdown=0x004 # [admin-pwd] : - */
|
34
|
+
|
35
|
+
#/* file I/O routines. server may answe */
|
36
|
+
CMD_openFile=0x010 # fn : - */
|
37
|
+
CMD_createFile=0x011 # fn : - */
|
38
|
+
CMD_closeFile=0x012 # - : - */
|
39
|
+
CMD_readFile=0x013 # [int size] : data... ; if size not present,
|
40
|
+
#server is free to choose any value - usually
|
41
|
+
#it uses the size of its static buffer */
|
42
|
+
CMD_writeFile=0x014 # data : - */
|
43
|
+
CMD_removeFile=0x015 # fn : - */
|
44
|
+
|
45
|
+
# /* object manipulation */
|
46
|
+
CMD_setSEXP=0x020 # string(name), REXP : - */
|
47
|
+
CMD_assignSEXP=0x021 # string(name), REXP : - ; same as setSEXP except that the name is parsed */
|
48
|
+
|
49
|
+
# /* session management (since 0.4-0) */
|
50
|
+
CMD_detachSession=0x030 # : session key */
|
51
|
+
CMD_detachedVoidEval=0x031 # string : session key; doesn't */
|
52
|
+
CMD_attachSession=0x032 # session key : - */
|
53
|
+
|
54
|
+
# control commands (since 0.6-0) - passed on to the master process */
|
55
|
+
# Note: currently all control commands are asychronous, i.e. RESP_OK
|
56
|
+
# indicates that the command was enqueued in the master pipe, but there
|
57
|
+
# is no guarantee that it will be processed. Moreover non-forked
|
58
|
+
# connections (e.g. the default debug setup) don't process any
|
59
|
+
# control commands until the current client connection is closed so
|
60
|
+
# the connection issuing the control command will never see its
|
61
|
+
# result.
|
62
|
+
CMD_ctrl=0x40 # -- not a command - just a constant -- */
|
63
|
+
CMD_ctrlEval=0x42 # string : - */
|
64
|
+
CMD_ctrlSource=0x45 # string : - */
|
65
|
+
CMD_ctrlShutdown=0x44 # - : - */
|
66
|
+
|
67
|
+
# /* 'internal' commands (since 0.1-9) */
|
68
|
+
CMD_setBufferSize=0x081 # [int sendBufSize] this commad allow clients to request bigger buffer sizes if large data is to be transported from Rserve to the client. (incoming buffer is resized automatically) */
|
69
|
+
CMD_setEncoding=0x082 # string (one of "native","latin1","utf8") : -; since 0.5-3 */
|
70
|
+
|
71
|
+
# /* special commands - the payload of packages with this mask does not contain defined parameters */
|
72
|
+
|
73
|
+
CMD_SPECIAL_MASK=0xf0
|
74
|
+
|
75
|
+
CMD_serEval=0xf5 # serialized eval - the packets are raw serialized data without data header */
|
76
|
+
CMD_serAssign=0xf6 # serialized assign - serialized list with [[1]]=name, [[2]]=value */
|
77
|
+
CMD_serEEval=0xf7 # serialized expression eval - like serEval with one additional evaluation round */
|
78
|
+
|
79
|
+
# data types for the transport protocol (QAP1)do NOT confuse with XT_.. values.
|
80
|
+
|
81
|
+
DT_INT=1 # int */
|
82
|
+
DT_CHAR=2 # char */
|
83
|
+
DT_DOUBLE=3 # double */
|
84
|
+
DT_STRING=4 # 0 terminted string */
|
85
|
+
DT_BYTESTREAM=5 # stream of bytes (unlike DT_STRING may contain 0) */
|
86
|
+
DT_SEXP=10 # encoded SEXP */
|
87
|
+
DT_ARRAY=11 # array of objects (i.e. first 4 bytes specify how many subsequent objects are part of the array; 0 is legitimate) */
|
88
|
+
DT_LARGE=64 # new in 0102: if this flag is set then the length of the object is coded as 56-bit integer enlarging the header by 4 bytes */
|
89
|
+
|
90
|
+
ERROR_DESCRIPTIONS={
|
91
|
+
ERR_auth_failed=>'auth.failed or auth.requested but no login came',
|
92
|
+
ERR_conn_broken=>'connection closed or broken packet killed it',
|
93
|
+
ERR_inv_cmd=>"unsupported/invalid command",
|
94
|
+
ERR_inv_par=>"some parameters are invalid",
|
95
|
+
ERR_Rerror=>"R-error",
|
96
|
+
ERR_IOerror=>"I/O error",
|
97
|
+
ERR_notOpen=>"attempt to perform fileRead/Write on closed file",
|
98
|
+
ERR_accessDenied=>"Access denied",
|
99
|
+
ERR_unsupportedCmd=>"unsupported command",
|
100
|
+
ERR_unknownCmd=>"unknown command",
|
101
|
+
ERR_data_overflow=>"data overflow",
|
102
|
+
ERR_object_too_big=>"requested object is too big",
|
103
|
+
ERR_out_of_mem=>"out of memory",
|
104
|
+
ERR_ctrl_closed=>"control pipe to the master process is closed or broken",
|
105
|
+
ERR_session_busy=>"session still busy",
|
106
|
+
ERR_detach_failed=>"unable to detach seesion"
|
107
|
+
} # error descriptions
|
108
|
+
|
109
|
+
end
|
110
|
+
end
|
111
|
+
end
|
@@ -0,0 +1,45 @@
|
|
1
|
+
|
2
|
+
require 'em-rserve/qap1/constants'
|
3
|
+
|
4
|
+
module EM::Rserve
|
5
|
+
module QAP1
|
6
|
+
Header = Struct.new(:command, :length, :offset, :length2) do
|
7
|
+
|
8
|
+
def self.from_bin(dat)
|
9
|
+
raise unless dat.size == 16
|
10
|
+
self.new(* dat.unpack('VVVV'))
|
11
|
+
end
|
12
|
+
|
13
|
+
def message_length
|
14
|
+
length #TODO: use length2
|
15
|
+
end
|
16
|
+
|
17
|
+
def body?
|
18
|
+
message_length > 0
|
19
|
+
end
|
20
|
+
|
21
|
+
def to_bin
|
22
|
+
to_a.pack('VVVV')
|
23
|
+
end
|
24
|
+
|
25
|
+
def response?
|
26
|
+
command & Constants::CMD_RESP > 0
|
27
|
+
end
|
28
|
+
|
29
|
+
def ok?
|
30
|
+
command & Constants::RESP_OK > 0
|
31
|
+
end
|
32
|
+
|
33
|
+
def error?
|
34
|
+
((command & Constants::RESP_ERR) & ~Constants::RESP_OK) > 0
|
35
|
+
end
|
36
|
+
|
37
|
+
def error
|
38
|
+
((command & ~Constants::RESP_ERR) >> 24) & 0xff
|
39
|
+
end
|
40
|
+
|
41
|
+
# -> self.for_message
|
42
|
+
# -> prepare_message
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|