hessian 0.5.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.
- data/LICENSE +27 -0
- data/Rakefile +35 -0
- data/lib/hessian.rb +176 -0
- data/test/test_hessian_parser.rb +54 -0
- metadata +42 -0
data/LICENSE
ADDED
@@ -0,0 +1,27 @@
|
|
1
|
+
Copyright (c) 2006, Christer Sandberg (chrsan@gmail.com)
|
2
|
+
All rights reserved.
|
3
|
+
|
4
|
+
Redistribution and use in source and binary forms, with or without
|
5
|
+
modification, are permitted provided that the following conditions are met:
|
6
|
+
|
7
|
+
* Redistributions of source code must retain the above copyright notice,
|
8
|
+
this list of conditions and the following disclaimer.
|
9
|
+
|
10
|
+
* Redistributions in binary form must reproduce the above copyright
|
11
|
+
notice, this list of conditions and the following disclaimer in the
|
12
|
+
documentation and/or other materials provided with the distribution.
|
13
|
+
|
14
|
+
* The names of its contributors may be used to endorse or promote
|
15
|
+
products derived from this software without specific prior written
|
16
|
+
permission.
|
17
|
+
|
18
|
+
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
19
|
+
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
20
|
+
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
21
|
+
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE
|
22
|
+
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
23
|
+
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
24
|
+
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
25
|
+
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
26
|
+
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
27
|
+
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
data/Rakefile
ADDED
@@ -0,0 +1,35 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'rake/gempackagetask'
|
3
|
+
require 'rake/testtask'
|
4
|
+
|
5
|
+
task :default => [:test]
|
6
|
+
|
7
|
+
Rake::TestTask.new do |t|
|
8
|
+
t.verbose = true
|
9
|
+
t.warning = true
|
10
|
+
end
|
11
|
+
|
12
|
+
PKG_VERSION = "0.5.0"
|
13
|
+
PKG_FILES = FileList[
|
14
|
+
'LICENSE',
|
15
|
+
'Rakefile',
|
16
|
+
'lib/**/*.rb',
|
17
|
+
'test/**/test_*.rb'
|
18
|
+
]
|
19
|
+
|
20
|
+
spec = Gem::Specification.new do |s|
|
21
|
+
s.name = "hessian"
|
22
|
+
s.version = PKG_VERSION
|
23
|
+
s.author = "Christer Sandberg"
|
24
|
+
s.email = "chrsan@gmail.com"
|
25
|
+
s.homepage = "http://www.baanii.se/"
|
26
|
+
s.platform = Gem::Platform::RUBY
|
27
|
+
s.summary = "A Ruby Hessian client."
|
28
|
+
s.files = PKG_FILES.to_a
|
29
|
+
s.require_path = "lib"
|
30
|
+
end
|
31
|
+
|
32
|
+
package_task = Rake::GemPackageTask.new(spec) do |pkg|
|
33
|
+
pkg.need_zip = true
|
34
|
+
pkg.need_tar_gz = true
|
35
|
+
end
|
data/lib/hessian.rb
ADDED
@@ -0,0 +1,176 @@
|
|
1
|
+
require 'uri'
|
2
|
+
require 'net/http'
|
3
|
+
|
4
|
+
module Hessian
|
5
|
+
class Binary
|
6
|
+
attr :data
|
7
|
+
def initialize(data)
|
8
|
+
@data = data.to_s
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
class HessianException < RuntimeError
|
13
|
+
attr_reader :code
|
14
|
+
def initialize(code)
|
15
|
+
@code = code
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
class HessianClient
|
20
|
+
attr_reader :host, :port, :path
|
21
|
+
def initialize(url)
|
22
|
+
uri = URI.parse(url)
|
23
|
+
@host, @port, @path = uri.host, uri.port, uri.path
|
24
|
+
raise "Unsupported Hessian protocol: #{uri.scheme}" unless uri.scheme == "http"
|
25
|
+
end
|
26
|
+
|
27
|
+
def method_missing(id, *args)
|
28
|
+
return invoke(id.id2name, args)
|
29
|
+
end
|
30
|
+
|
31
|
+
private
|
32
|
+
def invoke(method, args)
|
33
|
+
req = HessianWriter.new.write_call method, args
|
34
|
+
header = { 'Content-Type' => 'application/binary' }
|
35
|
+
Net::HTTP.start(@host, @port) do |http|
|
36
|
+
res = http.send_request('POST', @path, req, header)
|
37
|
+
HessianParser.new.parse_response res.body
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
class HessianWriter
|
42
|
+
def write_call(method, args)
|
43
|
+
@refs = {}
|
44
|
+
out = [ 'c', '0', '1', 'm', method.length ].pack('ahhan') << method
|
45
|
+
args.each { |arg| out << write_object(arg) }
|
46
|
+
out << 'z'
|
47
|
+
end
|
48
|
+
|
49
|
+
private
|
50
|
+
def write_object(val)
|
51
|
+
return 'N' if val.nil?
|
52
|
+
case val
|
53
|
+
when Binary: [ 'B', val.data.length ].pack('an') << val.data
|
54
|
+
when String: [ 'S', val.length ].pack('an') << val.unpack('C*').pack('U*')
|
55
|
+
when Integer
|
56
|
+
# Max and min values for integers in Java.
|
57
|
+
if val >= -0x80000000 && val <= 0x7fffffff
|
58
|
+
[ 'I', val ].pack('aN')
|
59
|
+
else
|
60
|
+
"L%s" % to_long(val)
|
61
|
+
end
|
62
|
+
when Float: [ 'D', val ].pack('aG')
|
63
|
+
when Time: "d%s" % to_long(val.to_i * 1000)
|
64
|
+
when TrueClass: 'T'
|
65
|
+
when FalseClass: 'F'
|
66
|
+
when Array
|
67
|
+
ref = write_ref val; return ref if ref
|
68
|
+
str = [ 'V', 't', '0', '0', 'l', val.length ].pack('aahhaN')
|
69
|
+
val.each { |v| str << write_object(v) }
|
70
|
+
str << 'z'
|
71
|
+
when Hash
|
72
|
+
ref = write_ref val; return ref if ref
|
73
|
+
str = [ 'M', 't', '0', '0' ].pack('aahh')
|
74
|
+
val.each { |k, v| str << write_object(k); str << write_object(v) }
|
75
|
+
str << 'z'
|
76
|
+
else
|
77
|
+
raise "Not implemented for #{val.class}"
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
def to_long(val)
|
82
|
+
str, pos = " " * 8, 0
|
83
|
+
56.step(0, -8) { |o| str[pos] = val >> o & 0x00000000000000ff; pos += 1 }
|
84
|
+
str
|
85
|
+
end
|
86
|
+
|
87
|
+
def write_ref(val)
|
88
|
+
id = @refs[val.object_id]
|
89
|
+
if id
|
90
|
+
[ 'R', id ].pack('aN')
|
91
|
+
else
|
92
|
+
@refs[val.object_id] = @refs.length
|
93
|
+
nil
|
94
|
+
end
|
95
|
+
end
|
96
|
+
end
|
97
|
+
|
98
|
+
class HessianParser
|
99
|
+
def parse_response(res)
|
100
|
+
raise "Invalid response, expected 'r', received '#{res[0,1]}'" unless res[0,1] == 'r'
|
101
|
+
@chunks = []
|
102
|
+
@refs = []
|
103
|
+
@data = res[3..-1]
|
104
|
+
parse_object
|
105
|
+
end
|
106
|
+
|
107
|
+
private
|
108
|
+
def parse_object
|
109
|
+
t = @data.slice!(0, 1)
|
110
|
+
case t
|
111
|
+
when 'f': raise_exception
|
112
|
+
when 's', 'S', 'x', 'X'
|
113
|
+
v = from_utf8(@data.slice!(0, 2).unpack('n')[0])
|
114
|
+
@data.slice!(0, v[1])
|
115
|
+
@chunks << v[0]
|
116
|
+
if 'sx'.include? t
|
117
|
+
parse_object
|
118
|
+
else
|
119
|
+
str = @chunks.join; @chunks.clear; str
|
120
|
+
end
|
121
|
+
when 'b', 'B'
|
122
|
+
v = @data.slice!(0, @data.slice!(0, 2).unpack('n')[0])
|
123
|
+
@chunks << v
|
124
|
+
if t == 'b'
|
125
|
+
parse_object
|
126
|
+
else
|
127
|
+
bytes = @chunks.join; @chunks.clear; Binary.new bytes
|
128
|
+
end
|
129
|
+
when 'I': @data.slice!(0, 4).unpack('N')[0]
|
130
|
+
when 'L': parse_long
|
131
|
+
when 'd': l = parse_long; Time.at(l / 1000, l % 1000 * 1000)
|
132
|
+
when 'D': @data.slice!(0, 8).unpack('G')[0]
|
133
|
+
when 'T': true
|
134
|
+
when 'F': false
|
135
|
+
when 'N': nil
|
136
|
+
when 'R': @refs[@data.slice!(0, 4).unpack('N')[0]]
|
137
|
+
when 'V'
|
138
|
+
# Skip type + type length (2 bytes) if specified.
|
139
|
+
@data.slice!(0, 3 + @data.unpack('an')[1]) if @data[0,1] == 't'
|
140
|
+
# Skip the list length if specified.
|
141
|
+
@data.slice!(0, 5) if @data[0,1] == 'l'
|
142
|
+
@refs << (list = [])
|
143
|
+
list << parse_object while @data[0,1] != 'z'; list
|
144
|
+
when 'M'
|
145
|
+
# Skip type + type length (2 bytes) if specified.
|
146
|
+
@data.slice!(0, 3 + @data.unpack('an')[1]) if @data[0,1] == 't'
|
147
|
+
@refs << (map = {})
|
148
|
+
map[parse_object()] = parse_object while @data[0,1] != 'z'; map
|
149
|
+
else
|
150
|
+
raise "Invalid type: '#{t}'"
|
151
|
+
end
|
152
|
+
end
|
153
|
+
|
154
|
+
def from_utf8(len = '*')
|
155
|
+
s = @data.unpack("U#{len}").pack('C*')
|
156
|
+
[ s, s.unpack('C*').pack('U*').length ]
|
157
|
+
end
|
158
|
+
|
159
|
+
def parse_long
|
160
|
+
val, o = 0, 56
|
161
|
+
@data.slice!(0, 8).each_byte { |b| val += (b & 0xff) << o; o -= 8 }
|
162
|
+
val
|
163
|
+
end
|
164
|
+
|
165
|
+
def raise_exception
|
166
|
+
# Skip code description.
|
167
|
+
parse_object
|
168
|
+
code = parse_object
|
169
|
+
# Skip message description
|
170
|
+
parse_object
|
171
|
+
msg = parse_object
|
172
|
+
raise HessianException.new(code), msg
|
173
|
+
end
|
174
|
+
end
|
175
|
+
end
|
176
|
+
end
|
@@ -0,0 +1,54 @@
|
|
1
|
+
require 'hessian'
|
2
|
+
require 'test/unit'
|
3
|
+
|
4
|
+
class HessianParserTest < Test::Unit::TestCase
|
5
|
+
def parse res
|
6
|
+
Hessian::HessianClient::HessianParser.new.parse_response res
|
7
|
+
end
|
8
|
+
|
9
|
+
def test_integer
|
10
|
+
assert_equal 4711, parse("r\001\000I\000\000\022gz")
|
11
|
+
end
|
12
|
+
def test_long
|
13
|
+
assert_equal 2, parse("r\001\000L\000\000\000\000\000\000\000\002z")
|
14
|
+
end
|
15
|
+
def test_double
|
16
|
+
assert_equal 3.4, parse("r\001\000D@\v333333z")
|
17
|
+
end
|
18
|
+
def test_false
|
19
|
+
assert_equal false, parse("r\001\000Fz")
|
20
|
+
end
|
21
|
+
def test_true
|
22
|
+
assert_equal true, parse("r\001\000Tz")
|
23
|
+
end
|
24
|
+
def test_string
|
25
|
+
assert_equal "string", parse("r\001\000S\000\006stringz")
|
26
|
+
end
|
27
|
+
def test_null
|
28
|
+
assert_equal nil, parse("r\001\000Nz")
|
29
|
+
end
|
30
|
+
def test_date
|
31
|
+
time = parse("r\001\000d\000\000\001\010\344\036\332\360z")
|
32
|
+
assert_instance_of Time, time
|
33
|
+
assert_equal '2006-01-19 20:23:13', time.strftime("%Y-%m-%d %H:%M:%S")
|
34
|
+
end
|
35
|
+
def test_integer_array
|
36
|
+
assert_equal [ 1, 2, 3 ], parse([ "r\001\000Vt\000\004[intl\000\000\000\003",
|
37
|
+
"I\000\000\000\001I\000\000\000\002I\000\000\000\003zz" ].join)
|
38
|
+
end
|
39
|
+
def test_array
|
40
|
+
assert_equal [ 'sillen', 32 ], parse([ "r\001\000Vt\000\a[objectl\000\000\000\002",
|
41
|
+
"S\000\006sillenI\000\000\000 zz" ].join)
|
42
|
+
end
|
43
|
+
def test_array_in_array
|
44
|
+
assert_equal [ 'A list', [ 9, 3 ] ], parse([ "r\001\000Vl\000\000\000\002S\000\006",
|
45
|
+
"A listVt\000\022[java.lang.Integerl\000\000\000\002I\000\000\000\t",
|
46
|
+
"I\000\000\000\003zzz" ].join)
|
47
|
+
end
|
48
|
+
def test_map
|
49
|
+
map = { 'numbers' => [ 1.1, 1.2, 1.3 ] }
|
50
|
+
assert_equal map, parse([ "r\001\000Mt\000\000S\000\anumbersVt\000\a[double",
|
51
|
+
"l\000\000\000\003D?\361\231\231\231\231\231\232D?\363333333D?",
|
52
|
+
"\364\314\314\314\314\314\315zS\000\006sillenI\000\000\000 zz" ].join)
|
53
|
+
end
|
54
|
+
end
|
metadata
ADDED
@@ -0,0 +1,42 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
rubygems_version: 0.8.11
|
3
|
+
specification_version: 1
|
4
|
+
name: hessian
|
5
|
+
version: !ruby/object:Gem::Version
|
6
|
+
version: 0.5.0
|
7
|
+
date: 2006-01-19 00:00:00 +01:00
|
8
|
+
summary: A Ruby Hessian client.
|
9
|
+
require_paths:
|
10
|
+
- lib
|
11
|
+
email: chrsan@gmail.com
|
12
|
+
homepage: http://www.baanii.se/
|
13
|
+
rubyforge_project:
|
14
|
+
description:
|
15
|
+
autorequire:
|
16
|
+
default_executable:
|
17
|
+
bindir: bin
|
18
|
+
has_rdoc: false
|
19
|
+
required_ruby_version: !ruby/object:Gem::Version::Requirement
|
20
|
+
requirements:
|
21
|
+
-
|
22
|
+
- ">"
|
23
|
+
- !ruby/object:Gem::Version
|
24
|
+
version: 0.0.0
|
25
|
+
version:
|
26
|
+
platform: ruby
|
27
|
+
signing_key:
|
28
|
+
cert_chain:
|
29
|
+
authors:
|
30
|
+
- Christer Sandberg
|
31
|
+
files:
|
32
|
+
- LICENSE
|
33
|
+
- Rakefile
|
34
|
+
- lib/hessian.rb
|
35
|
+
- test/test_hessian_parser.rb
|
36
|
+
test_files: []
|
37
|
+
rdoc_options: []
|
38
|
+
extra_rdoc_files: []
|
39
|
+
executables: []
|
40
|
+
extensions: []
|
41
|
+
requirements: []
|
42
|
+
dependencies: []
|