hessian2 1.0.2
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/.gitignore +6 -0
- data/Gemfile +4 -0
- data/README.rdoc +22 -0
- data/Rakefile +1 -0
- data/hessian2.gemspec +24 -0
- data/lib/hessian2/version.rb +3 -0
- data/lib/hessian2.rb +231 -0
- data/test/servlet_invoker.rb +17 -0
- data/test/test_hessian_parser.rb +57 -0
- metadata +57 -0
data/.gitignore
ADDED
data/Gemfile
ADDED
data/README.rdoc
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
= hessian2
|
2
|
+
|
3
|
+
hessian客户端。在Christer Sandberg的hessian基础上,修正了pack UTF-8(中文等字符),指定Long型,更好地被ruby1.9支持。
|
4
|
+
|
5
|
+
hessian client. based on Christer Sandberg's hessian, fixed UTF-8 pack, allow declare long type, ruby1.9 tested.
|
6
|
+
|
7
|
+
== Using
|
8
|
+
|
9
|
+
gem install hessian2
|
10
|
+
|
11
|
+
require 'hessian2'
|
12
|
+
|
13
|
+
url = 'http://localhost:8080/person_service'
|
14
|
+
|
15
|
+
client = Hessian2::HessianClient.new(url)
|
16
|
+
|
17
|
+
#call remote function: Map<Long, Person> queryPassportUidMap(List<Long> uidList)
|
18
|
+
puts client.queryPassportUidMap([Hessian2::TypeWrapper.new('Long', 47), Hessian2::TypeWrapper.new('Long', 48)])
|
19
|
+
|
20
|
+
== Authors
|
21
|
+
|
22
|
+
* {Takafan}[http://hululuu.com]
|
data/Rakefile
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
require "bundler/gem_tasks"
|
data/hessian2.gemspec
ADDED
@@ -0,0 +1,24 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
$:.push File.expand_path("../lib", __FILE__)
|
3
|
+
require "hessian2/version"
|
4
|
+
|
5
|
+
Gem::Specification.new do |s|
|
6
|
+
s.name = "hessian2"
|
7
|
+
s.version = Hessian2::VERSION
|
8
|
+
s.authors = ["takafan"]
|
9
|
+
s.email = ["takafan@163.com"]
|
10
|
+
s.homepage = "http://github.com/takafan/hessian2"
|
11
|
+
s.summary = %q{Hessian2}
|
12
|
+
s.description = %q{hessian client. based on Christer Sandberg's hessian, fixed UTF-8 pack, allow declare long type, ruby1.9 tested.}
|
13
|
+
|
14
|
+
s.rubyforge_project = "hessian2"
|
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
|
+
|
21
|
+
# specify any dependencies here; for example:
|
22
|
+
# s.add_development_dependency "rspec"
|
23
|
+
# s.add_runtime_dependency "rest-client"
|
24
|
+
end
|
data/lib/hessian2.rb
ADDED
@@ -0,0 +1,231 @@
|
|
1
|
+
require "hessian2/version"
|
2
|
+
|
3
|
+
require 'uri'
|
4
|
+
require 'net/http'
|
5
|
+
require 'net/https'
|
6
|
+
|
7
|
+
module Hessian2
|
8
|
+
class TypeWrapper
|
9
|
+
attr_accessor :hessian_type, :object
|
10
|
+
def initialize(hessian_type, object)
|
11
|
+
@hessian_type, @object = hessian_type, object
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
class Binary
|
16
|
+
attr :data
|
17
|
+
def initialize(data)
|
18
|
+
@data = data.to_s
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
class HessianException < RuntimeError
|
23
|
+
attr_reader :code
|
24
|
+
def initialize(code)
|
25
|
+
@code = code
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
class HessianClient
|
30
|
+
attr_accessor :user, :password
|
31
|
+
attr_reader :scheme, :host, :port, :path, :proxy
|
32
|
+
def initialize(url, proxy = {})
|
33
|
+
uri = URI.parse(url)
|
34
|
+
@scheme, @host, @port, @path = uri.scheme, uri.host, uri.port, uri.path
|
35
|
+
raise "Unsupported Hessian protocol: #@scheme" unless @scheme == 'http' || @scheme == 'https'
|
36
|
+
@proxy = proxy
|
37
|
+
end
|
38
|
+
|
39
|
+
def method_missing(id, *args)
|
40
|
+
return invoke(id.id2name, args)
|
41
|
+
end
|
42
|
+
|
43
|
+
private
|
44
|
+
def invoke(method, args)
|
45
|
+
call = HessianWriter.new.write_call method, args
|
46
|
+
header = { 'Content-Type' => 'application/binary' }
|
47
|
+
req = Net::HTTP::Post.new(@path, header)
|
48
|
+
req.basic_auth @user, @password if @user
|
49
|
+
conn = Net::HTTP.new(@host, @port, *@proxy.values_at(:host, :port, :user, :password))
|
50
|
+
conn.use_ssl = true and conn.verify_mode = OpenSSL::SSL::VERIFY_NONE if @scheme == 'https'
|
51
|
+
conn.start do |http|
|
52
|
+
res = http.request(req, call)
|
53
|
+
HessianParser.new.parse_response res.body
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
class HessianWriter
|
58
|
+
def write_call(method, args)
|
59
|
+
@refs = {}
|
60
|
+
out = [ 'c', '0', '1', 'm', method.length ].pack('ahhan') << method
|
61
|
+
args.each { |arg| out << write_object(arg) }
|
62
|
+
out << 'z'
|
63
|
+
end
|
64
|
+
|
65
|
+
private
|
66
|
+
def write_object(val, hessian_type = nil)
|
67
|
+
return 'N' if val.nil?
|
68
|
+
case val
|
69
|
+
when TypeWrapper
|
70
|
+
val.hessian_type == 'Long' ? "L%s" % to_long(val.object) : write_object(val.object, val.hessian_type)
|
71
|
+
when Struct
|
72
|
+
write_object(val.members.inject({}) { |map, m| map[m] = val[m]; map })
|
73
|
+
when Binary
|
74
|
+
[ 'B', val.data.length ].pack('an') << val.data
|
75
|
+
when String
|
76
|
+
[ 'S', val.length ].pack('an') << val.unpack('C*').pack('U*')
|
77
|
+
when
|
78
|
+
Integer
|
79
|
+
# Max and min values for integers in Java.
|
80
|
+
if val >= -0x80000000 && val <= 0x7fffffff
|
81
|
+
[ 'I', val ].pack('aN')
|
82
|
+
else
|
83
|
+
"L%s" % to_long(val)
|
84
|
+
end
|
85
|
+
when Float
|
86
|
+
[ 'D', val ].pack('aG')
|
87
|
+
when Time
|
88
|
+
"d%s" % to_long((val.to_f * 1000).to_i)
|
89
|
+
when TrueClass
|
90
|
+
'T'
|
91
|
+
when FalseClass
|
92
|
+
'F'
|
93
|
+
when Array
|
94
|
+
ref = write_ref val; return ref if ref
|
95
|
+
t = hessian_type_string(hessian_type, val)
|
96
|
+
str = 'Vt' << t << 'l' << [ val.length ].pack('N')
|
97
|
+
val.each { |v| str << write_object(v) }
|
98
|
+
str << 'z'
|
99
|
+
when Hash
|
100
|
+
ref = write_ref val; return ref if ref
|
101
|
+
str = 'Mt' << hessian_type_string(hessian_type, val)
|
102
|
+
val.each { |k, v| str << write_object(k); str << write_object(v) }
|
103
|
+
str << 'z'
|
104
|
+
else
|
105
|
+
raise "Not implemented for #{val.class}"
|
106
|
+
end
|
107
|
+
end
|
108
|
+
|
109
|
+
def hessian_type_string(hessian_type, object)
|
110
|
+
if hessian_type.nil? && object.respond_to?(:hessian_type)
|
111
|
+
hessian_type = object.hessian_type
|
112
|
+
end
|
113
|
+
hessian_type ? [ hessian_type.length, hessian_type ].pack('na*') : "\000\000"
|
114
|
+
end
|
115
|
+
|
116
|
+
def to_long(val)
|
117
|
+
val2 = val.to_s(2)
|
118
|
+
['0' * (64 - val2.size) << val2].pack("B*")
|
119
|
+
end
|
120
|
+
|
121
|
+
def write_ref(val)
|
122
|
+
id = @refs[val.object_id]
|
123
|
+
if id
|
124
|
+
[ 'R', id ].pack('aN')
|
125
|
+
else
|
126
|
+
@refs[val.object_id] = @refs.length
|
127
|
+
nil
|
128
|
+
end
|
129
|
+
end
|
130
|
+
end
|
131
|
+
|
132
|
+
class HessianParser
|
133
|
+
def parse_response(res)
|
134
|
+
raise "Invalid response, expected 'r', received '#{res[0,1]}'" unless res[0,1] == 'r'
|
135
|
+
@chunks = []
|
136
|
+
@refs = []
|
137
|
+
@data = res[3..-1]
|
138
|
+
parse_object
|
139
|
+
end
|
140
|
+
|
141
|
+
private
|
142
|
+
def parse_object
|
143
|
+
t = @data.slice!(0, 1)
|
144
|
+
|
145
|
+
case t
|
146
|
+
when 'f'
|
147
|
+
raise_exception
|
148
|
+
when 's', 'S', 'x', 'X'
|
149
|
+
|
150
|
+
v = from_utf8(@data.slice!(0, 2).unpack('n')[0])
|
151
|
+
@data.slice!(0, v[1])
|
152
|
+
@chunks << v[0]
|
153
|
+
|
154
|
+
if 'sx'.include? t
|
155
|
+
|
156
|
+
parse_object
|
157
|
+
else
|
158
|
+
str = @chunks.join; @chunks.clear; str
|
159
|
+
end
|
160
|
+
when 'b', 'B'
|
161
|
+
v = @data.slice!(0, @data.slice!(0, 2).unpack('n')[0])
|
162
|
+
@chunks << v
|
163
|
+
if t == 'b'
|
164
|
+
parse_object
|
165
|
+
else
|
166
|
+
bytes = @chunks.join; @chunks.clear; Binary.new bytes
|
167
|
+
end
|
168
|
+
when 'I'
|
169
|
+
@data.slice!(0, 4).unpack('N')[0]
|
170
|
+
when 'L'
|
171
|
+
parse_long
|
172
|
+
when 'd'
|
173
|
+
l = parse_long; Time.at(l / 1000, l % 1000 * 1000)
|
174
|
+
when 'D'
|
175
|
+
@data.slice!(0, 8).unpack('G')[0]
|
176
|
+
when 'T'
|
177
|
+
true
|
178
|
+
when 'F'
|
179
|
+
false
|
180
|
+
when 'N'
|
181
|
+
nil
|
182
|
+
when 'R'
|
183
|
+
@refs[@data.slice!(0, 4).unpack('N')[0]]
|
184
|
+
when 'V'
|
185
|
+
# Skip type + type length (2 bytes) if specified.
|
186
|
+
@data.slice!(0, 3 + @data.unpack('an')[1]) if @data[0,1] == 't'
|
187
|
+
# Skip the list length if specified.
|
188
|
+
@data.slice!(0, 5) if @data[0,1] == 'l'
|
189
|
+
@refs << (list = [])
|
190
|
+
list << parse_object while @data[0,1] != 'z'
|
191
|
+
# Get rid of the 'z'.
|
192
|
+
@data.slice!(0, 1)
|
193
|
+
list
|
194
|
+
when 'M'
|
195
|
+
# Skip type + type length (2 bytes) if specified.
|
196
|
+
@data.slice!(0, 3 + @data.unpack('an')[1]) if @data[0,1] == 't'
|
197
|
+
@refs << (map = {})
|
198
|
+
map[parse_object()] = parse_object while @data[0,1] != 'z'
|
199
|
+
# Get rid of the 'z'.
|
200
|
+
@data.slice!(0, 1)
|
201
|
+
map
|
202
|
+
else
|
203
|
+
puts @data
|
204
|
+
raise "Invalid type: '#{t}'"
|
205
|
+
end
|
206
|
+
end
|
207
|
+
|
208
|
+
def from_utf8(len = '*')
|
209
|
+
s = @data.unpack("U#{len}").pack('U*')
|
210
|
+
[ s, s.unpack('C*').pack('U*').length ]
|
211
|
+
end
|
212
|
+
|
213
|
+
def parse_long
|
214
|
+
val, o = 0, 56
|
215
|
+
@data.slice!(0, 8).each_byte { |b| val += (b & 0xff) << o; o -= 8 }
|
216
|
+
val
|
217
|
+
end
|
218
|
+
|
219
|
+
def raise_exception
|
220
|
+
# Skip code description.
|
221
|
+
parse_object
|
222
|
+
code = parse_object
|
223
|
+
# Skip message description
|
224
|
+
parse_object
|
225
|
+
msg = parse_object
|
226
|
+
raise HessianException.new(code), msg
|
227
|
+
end
|
228
|
+
end
|
229
|
+
end
|
230
|
+
end
|
231
|
+
|
@@ -0,0 +1,17 @@
|
|
1
|
+
require 'net/http'
|
2
|
+
|
3
|
+
HEADER = { 'Content-Type' => 'application/binary' }
|
4
|
+
|
5
|
+
call = %w(c 0 1 m).pack('ahha')
|
6
|
+
methods = %w(
|
7
|
+
getInt getLong getDouble getFalse getTrue getString getNull
|
8
|
+
getDate getIntArray getObjectArray getArrayInList getMap
|
9
|
+
)
|
10
|
+
|
11
|
+
methods.each do |m|
|
12
|
+
Net::HTTP.start('localhost', 8080) do |http|
|
13
|
+
res = http.send_request('POST', '/test',
|
14
|
+
call + [ m.length, m ].pack('na*') + 'z', HEADER)
|
15
|
+
p res.body
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,57 @@
|
|
1
|
+
lib_path = File.expand_path('../../lib', __FILE__)
|
2
|
+
$:.unshift(lib_path)
|
3
|
+
require 'hessian2'
|
4
|
+
require 'test/unit'
|
5
|
+
|
6
|
+
class HessianParserTest < Test::Unit::TestCase
|
7
|
+
def parse res
|
8
|
+
Hessian2::HessianClient::HessianParser.new.parse_response res
|
9
|
+
end
|
10
|
+
|
11
|
+
def test_integer
|
12
|
+
assert_equal 4711, parse("r\001\000I\000\000\022gz")
|
13
|
+
end
|
14
|
+
def test_long
|
15
|
+
assert_equal 2, parse("r\001\000L\000\000\000\000\000\000\000\002z")
|
16
|
+
end
|
17
|
+
def test_double
|
18
|
+
assert_equal 3.4, parse("r\001\000D@\v333333z")
|
19
|
+
end
|
20
|
+
def test_false
|
21
|
+
assert_equal false, parse("r\001\000Fz")
|
22
|
+
end
|
23
|
+
def test_true
|
24
|
+
assert_equal true, parse("r\001\000Tz")
|
25
|
+
end
|
26
|
+
def test_string
|
27
|
+
assert_equal "string", parse("r\001\000S\000\006stringz")
|
28
|
+
end
|
29
|
+
def test_null
|
30
|
+
assert_equal nil, parse("r\001\000Nz")
|
31
|
+
end
|
32
|
+
def test_date
|
33
|
+
time = parse("r\001\000d\000\000\001\010\344\036\332\360z")
|
34
|
+
assert_instance_of Time, time
|
35
|
+
assert_equal '2006-01-19 19:23:13', time.getutc.strftime("%Y-%m-%d %H:%M:%S")
|
36
|
+
assert_equal 520000, time.usec
|
37
|
+
end
|
38
|
+
def test_integer_array
|
39
|
+
assert_equal [ 1, 2, 3 ], parse([ "r\001\000Vt\000\004[intl\000\000\000\003",
|
40
|
+
"I\000\000\000\001I\000\000\000\002I\000\000\000\003zz" ].join)
|
41
|
+
end
|
42
|
+
def test_array
|
43
|
+
assert_equal [ 'sillen', 32 ], parse([ "r\001\000Vt\000\a[objectl\000\000\000\002",
|
44
|
+
"S\000\006sillenI\000\000\000 zz" ].join)
|
45
|
+
end
|
46
|
+
def test_array_in_array
|
47
|
+
assert_equal [ 'A list', [ 9, 3 ] ], parse([ "r\001\000Vl\000\000\000\002S\000\006",
|
48
|
+
"A listVt\000\022[java.lang.Integerl\000\000\000\002I\000\000\000\t",
|
49
|
+
"I\000\000\000\003zzz" ].join)
|
50
|
+
end
|
51
|
+
def test_map
|
52
|
+
map = { 'sillen' => 32, 'numbers' => [ 1.1, 1.2, 1.3 ] }
|
53
|
+
assert_equal map, parse([ "r\001\000Mt\000\000S\000\anumbersVt\000\a[double",
|
54
|
+
"l\000\000\000\003D?\361\231\231\231\231\231\232D?\363333333D?",
|
55
|
+
"\364\314\314\314\314\314\315zS\000\006sillenI\000\000\000 zz" ].join)
|
56
|
+
end
|
57
|
+
end
|
metadata
ADDED
@@ -0,0 +1,57 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: hessian2
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 1.0.2
|
5
|
+
prerelease:
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- takafan
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2011-11-24 00:00:00.000000000 Z
|
13
|
+
dependencies: []
|
14
|
+
description: hessian client. based on Christer Sandberg's hessian, fixed UTF-8 pack,
|
15
|
+
allow declare long type, ruby1.9 tested.
|
16
|
+
email:
|
17
|
+
- takafan@163.com
|
18
|
+
executables: []
|
19
|
+
extensions: []
|
20
|
+
extra_rdoc_files: []
|
21
|
+
files:
|
22
|
+
- .gitignore
|
23
|
+
- Gemfile
|
24
|
+
- README.rdoc
|
25
|
+
- Rakefile
|
26
|
+
- hessian2.gemspec
|
27
|
+
- lib/hessian2.rb
|
28
|
+
- lib/hessian2/version.rb
|
29
|
+
- test/servlet_invoker.rb
|
30
|
+
- test/test_hessian_parser.rb
|
31
|
+
homepage: http://github.com/takafan/hessian2
|
32
|
+
licenses: []
|
33
|
+
post_install_message:
|
34
|
+
rdoc_options: []
|
35
|
+
require_paths:
|
36
|
+
- lib
|
37
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
38
|
+
none: false
|
39
|
+
requirements:
|
40
|
+
- - ! '>='
|
41
|
+
- !ruby/object:Gem::Version
|
42
|
+
version: '0'
|
43
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
44
|
+
none: false
|
45
|
+
requirements:
|
46
|
+
- - ! '>='
|
47
|
+
- !ruby/object:Gem::Version
|
48
|
+
version: '0'
|
49
|
+
requirements: []
|
50
|
+
rubyforge_project: hessian2
|
51
|
+
rubygems_version: 1.8.11
|
52
|
+
signing_key:
|
53
|
+
specification_version: 3
|
54
|
+
summary: Hessian2
|
55
|
+
test_files:
|
56
|
+
- test/servlet_invoker.rb
|
57
|
+
- test/test_hessian_parser.rb
|