hessian 0.5.0

Sign up to get free protection for your applications and to get access to all the features.
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: []