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.
Files changed (5) hide show
  1. data/LICENSE +27 -0
  2. data/Rakefile +35 -0
  3. data/lib/hessian.rb +176 -0
  4. data/test/test_hessian_parser.rb +54 -0
  5. 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: []