├── .gitignore ├── examples ├── client.rb ├── unixclient.rb ├── tcpclient.rb ├── tcpserver.rb ├── unixserver.rb └── server.rb ├── LICENSE.md ├── README.md ├── lib ├── hprose │ ├── unixclient.rb │ ├── tcpclient.rb │ ├── common.rb │ ├── unixserver.rb │ ├── tcpserver.rb │ ├── socketserver.rb │ ├── socketclient.rb │ ├── httpclient.rb │ ├── httpservice.rb │ ├── client.rb │ ├── service.rb │ └── io.rb ├── hprosecommon.rb ├── hproseserver.rb ├── hproseclient.rb ├── hproseio.rb └── hprose.rb ├── gemspec └── gem.spec /.gitignore: -------------------------------------------------------------------------------- 1 | *.gem 2 | *.rbc 3 | .bundle 4 | .config 5 | coverage 6 | InstalledFiles 7 | lib/bundler/man 8 | pkg 9 | rdoc 10 | spec/reports 11 | test/tmp 12 | test/version_tmp 13 | tmp 14 | 15 | # YARD artifacts 16 | .yardoc 17 | _yardoc 18 | doc/ 19 | -------------------------------------------------------------------------------- /examples/client.rb: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | require 'rubygems' 3 | require 'hprose' 4 | 5 | client = HproseClient.new('http://127.0.0.1:3000/') 6 | def error(name, e) 7 | puts name 8 | puts e 9 | end 10 | client.onerror = :error 11 | 12 | math = client.use_service(nil, :math) 13 | 14 | client.hello('World') { |result| 15 | puts result 16 | }.join 17 | 18 | client.hello('中文') { |result| 19 | puts result 20 | }.join 21 | 22 | math.add(1, 2) { |result| 23 | puts result 24 | }.join 25 | 26 | math.sub(1, 2) { |result| 27 | puts result 28 | }.join 29 | 30 | puts client.sum(1,3,4,5,6,7) 31 | user = client.getUser() 32 | puts user.name 33 | puts user.age 34 | 35 | puts client.hi('hprose') 36 | puts client.push([user, user, user]) 37 | -------------------------------------------------------------------------------- /examples/unixclient.rb: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | require 'rubygems' 3 | require 'hprose' 4 | 5 | client = HproseClient.new('unix:/tmp/my.sock') 6 | def error(name, e) 7 | puts name 8 | puts e 9 | end 10 | client.onerror = :error 11 | 12 | math = client.use_service(nil, :math) 13 | 14 | client.hello('World') { |result| 15 | puts result 16 | }.join 17 | 18 | client.hello('中文') { |result| 19 | puts result 20 | }.join 21 | 22 | math.add(1, 2) { |result| 23 | puts result 24 | }.join 25 | 26 | math.sub(1, 2) { |result| 27 | puts result 28 | }.join 29 | 30 | puts client.sum(1,3,4,5,6,7) 31 | user = client.getUser() 32 | puts user.name 33 | puts user.age 34 | 35 | puts client.hi('hprose') 36 | puts client.push([user, user, user]) 37 | -------------------------------------------------------------------------------- /examples/tcpclient.rb: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | require 'rubygems' 3 | require 'hprose' 4 | 5 | client = HproseClient.new('tcp://127.0.0.1:4321/') 6 | def error(name, e) 7 | puts name 8 | puts e 9 | end 10 | client.onerror = :error 11 | 12 | math = client.use_service(nil, :math) 13 | 14 | client.hello('World') { |result| 15 | puts result 16 | }.join 17 | 18 | client.hello('中文') { |result| 19 | puts result 20 | }.join 21 | 22 | math.add(1, 2) { |result| 23 | puts result 24 | }.join 25 | 26 | math.sub(1, 2) { |result| 27 | puts result 28 | }.join 29 | 30 | puts client.sum(1,3,4,5,6,7) 31 | user = client.getUser() 32 | puts user.name 33 | puts user.age 34 | 35 | puts client.hi('hprose') 36 | puts client.push([user, user, user]) 37 | -------------------------------------------------------------------------------- /examples/tcpserver.rb: -------------------------------------------------------------------------------- 1 | require 'rubygems' 2 | require 'hprose' 3 | 4 | def hello(name) 5 | return 'hello ' << name << '!' 6 | end 7 | 8 | class User 9 | def initialize() 10 | @name = "Tom" 11 | @age = 28 12 | end 13 | end 14 | 15 | def getUser() 16 | return User.new() 17 | end 18 | 19 | 20 | class MyService 21 | def add(a, b) 22 | return a + b 23 | end 24 | def sub(a, b) 25 | return a - b 26 | end 27 | end 28 | 29 | def mf(name, args) 30 | return name << " => " << HproseFormatter.serialize(args) 31 | end 32 | 33 | HproseClassManager.register(User, "User") 34 | 35 | server = HproseTcpServer.new 36 | server.port = 4321 37 | 38 | server.add(:hello) 39 | server.add(:sum) { |*num| 40 | result = 0 41 | num.each { |item| result += item } 42 | result 43 | } 44 | server.add(:getUser) 45 | server.add_missing_function(:mf) 46 | server.add(MyService.new, :math) 47 | server.debug = true 48 | server.start -------------------------------------------------------------------------------- /examples/unixserver.rb: -------------------------------------------------------------------------------- 1 | require 'rubygems' 2 | require 'hprose' 3 | 4 | def hello(name) 5 | return 'hello ' << name << '!' 6 | end 7 | 8 | class User 9 | def initialize() 10 | @name = "Tom" 11 | @age = 28 12 | end 13 | end 14 | 15 | def getUser() 16 | return User.new() 17 | end 18 | 19 | 20 | class MyService 21 | def add(a, b) 22 | return a + b 23 | end 24 | def sub(a, b) 25 | return a - b 26 | end 27 | end 28 | 29 | def mf(name, args) 30 | return name << " => " << HproseFormatter.serialize(args) 31 | end 32 | 33 | HproseClassManager.register(User, "User") 34 | 35 | server = HproseUnixServer.new('unix:/tmp/my.sock') 36 | 37 | server.add(:hello) 38 | server.add(:sum) { |*num| 39 | result = 0 40 | num.each { |item| result += item } 41 | result 42 | } 43 | server.add(:getUser) 44 | server.add_missing_function(:mf) 45 | server.add(MyService.new, :math) 46 | server.debug = true 47 | server.start -------------------------------------------------------------------------------- /examples/server.rb: -------------------------------------------------------------------------------- 1 | require 'rubygems' 2 | require 'rack' 3 | require 'hprose' 4 | 5 | def hello(name) 6 | return 'hello ' << name << '!' 7 | end 8 | 9 | class User 10 | def initialize() 11 | @name = "Tom" 12 | @age = 28 13 | end 14 | end 15 | 16 | def getUser() 17 | return User.new 18 | end 19 | 20 | 21 | class MyService 22 | def add(a, b) 23 | return a + b 24 | end 25 | def sub(a, b) 26 | return a - b 27 | end 28 | end 29 | 30 | def mf(name, args) 31 | return name << " => " << HproseFormatter.serialize(args) 32 | end 33 | 34 | HproseClassManager.register(User, "User") 35 | 36 | app = HproseHttpService.new 37 | 38 | app.add(:hello) 39 | app.add(:sum) { |*num| 40 | result = 0 41 | num.each { |item| result += item } 42 | result 43 | } 44 | app.add(:getUser) 45 | app.add_missing_function(:mf) 46 | app.add(MyService.new, :math) 47 | app.debug = true 48 | Rack::Handler::WEBrick.run(app, {:Port => 3000}) 49 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2008-2016 http://hprose.com 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of 6 | this software and associated documentation files (the "Software"), to deal in 7 | the Software without restriction, including without limitation the rights to 8 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 9 | the Software, and to permit persons to whom the Software is furnished to do so, 10 | subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 17 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 18 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 19 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 20 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Hprose for Ruby 2 | 3 | [![Join the chat at https://gitter.im/hprose/hprose-ruby](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/hprose/hprose-ruby?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) 4 | 5 | [![Gem Version](https://badge.fury.io/rb/hprose.png)](http://badge.fury.io/rb/hprose) 6 | 7 | *Hprose* is a High Performance Remote Object Service Engine. 8 | 9 | It is a modern, lightweight, cross-language, cross-platform, object-oriented, high performance, remote dynamic communication middleware. It is not only easy to use, but powerful. You just need a little time to learn, then you can use it to easily construct cross language cross platform distributed application system. 10 | 11 | *Hprose* supports many programming languages, for example: 12 | 13 | * AAuto Quicker 14 | * ActionScript 15 | * ASP 16 | * C++ 17 | * Dart 18 | * Delphi/Free Pascal 19 | * dotNET(C#, Visual Basic...) 20 | * Golang 21 | * Java 22 | * JavaScript 23 | * Node.js 24 | * Objective-C 25 | * Perl 26 | * PHP 27 | * Python 28 | * Ruby 29 | * ... 30 | 31 | Through *Hprose*, You can conveniently and efficiently intercommunicate between those programming languages. 32 | 33 | This project is the implementation of Hprose for Ruby. 34 | -------------------------------------------------------------------------------- /lib/hprose/unixclient.rb: -------------------------------------------------------------------------------- 1 | ############################################################ 2 | # # 3 | # hprose # 4 | # # 5 | # Official WebSite: http://www.hprose.com/ # 6 | # http://www.hprose.org/ # 7 | # # 8 | ############################################################ 9 | 10 | ############################################################ 11 | # # 12 | # hprose/unixclient.rb # 13 | # # 14 | # hprose unix client for ruby # 15 | # # 16 | # LastModified: Apr 13, 2014 # 17 | # Author: Ma Bingyao # 18 | # # 19 | ############################################################ 20 | 21 | require "hprose/socketclient" 22 | require "socket" 23 | 24 | module Hprose 25 | class UnixClient < SocketClient 26 | protected 27 | def create_socket 28 | return UNIXSocket.new(@uri.path) 29 | end 30 | end # class UnixClient 31 | end # module Hprose -------------------------------------------------------------------------------- /lib/hprose/tcpclient.rb: -------------------------------------------------------------------------------- 1 | ############################################################ 2 | # # 3 | # hprose # 4 | # # 5 | # Official WebSite: http://www.hprose.com/ # 6 | # http://www.hprose.org/ # 7 | # # 8 | ############################################################ 9 | 10 | ############################################################ 11 | # # 12 | # hprose/tcpclient.rb # 13 | # # 14 | # hprose tcp client for ruby # 15 | # # 16 | # LastModified: Apr 13, 2014 # 17 | # Author: Ma Bingyao # 18 | # # 19 | ############################################################ 20 | 21 | require "hprose/socketclient" 22 | require "socket" 23 | 24 | module Hprose 25 | class TcpClient < SocketClient 26 | protected 27 | def create_socket 28 | return TCPSocket.new(@uri.host, @uri.port) 29 | end 30 | end # class TcpClient 31 | end # module Hprose -------------------------------------------------------------------------------- /lib/hprosecommon.rb: -------------------------------------------------------------------------------- 1 | ############################################################ 2 | # # 3 | # hprose # 4 | # # 5 | # Official WebSite: http://www.hprose.com/ # 6 | # http://www.hprose.org/ # 7 | # # 8 | ############################################################ 9 | 10 | ############################################################ 11 | # # 12 | # hproseio.rb # 13 | # # 14 | # hprose io for ruby # 15 | # # 16 | # LastModified: Dec 1, 2012 # 17 | # Author: Ma Bingyao # 18 | # # 19 | ############################################################ 20 | 21 | module Hprose 22 | autoload :Exception, 'hprose/common' 23 | autoload :ResultMode, 'hprose/common' 24 | autoload :Filter, 'hprose/common' 25 | end 26 | 27 | Object.const_set(:HproseException, Hprose.const_get(:Exception)) 28 | Object.const_set(:HproseResultMode, Hprose.const_get(:ResultMode)) 29 | Object.const_set(:HproseFilter, Hprose.const_get(:Filter)) -------------------------------------------------------------------------------- /lib/hprose/common.rb: -------------------------------------------------------------------------------- 1 | ############################################################ 2 | # # 3 | # hprose # 4 | # # 5 | # Official WebSite: http://www.hprose.com/ # 6 | # http://www.hprose.org/ # 7 | # # 8 | ############################################################ 9 | 10 | ############################################################ 11 | # # 12 | # hprose/common.rb # 13 | # # 14 | # hprose common library for ruby # 15 | # # 16 | # LastModified: Mar 19, 2014 # 17 | # Author: Ma Bingyao # 18 | # # 19 | ############################################################ 20 | 21 | module Hprose 22 | class Exception < Exception; end 23 | 24 | module ResultMode 25 | Normal = 0 26 | Serialized = 1 27 | Raw = 2 28 | RawWithEndTag = 3 29 | end 30 | 31 | class Filter 32 | def input_filter(data, context) 33 | return data 34 | end 35 | def output_filter(data, context) 36 | return data 37 | end 38 | end 39 | 40 | end # module Hprose -------------------------------------------------------------------------------- /lib/hprose/unixserver.rb: -------------------------------------------------------------------------------- 1 | ############################################################ 2 | # # 3 | # hprose # 4 | # # 5 | # Official WebSite: http://www.hprose.com/ # 6 | # http://www.hprose.org/ # 7 | # # 8 | ############################################################ 9 | 10 | ############################################################ 11 | # # 12 | # hprose/unixserver.rb # 13 | # # 14 | # hprose unix server for ruby # 15 | # # 16 | # LastModified: Apr 13, 2014 # 17 | # Author: Ma Bingyao # 18 | # # 19 | ############################################################ 20 | 21 | require "hprose/socketserver" 22 | require "socket" 23 | 24 | module Hprose 25 | class UnixServer < SocketServer 26 | def initialize(uri = nil) 27 | super(uri) 28 | @path = nil 29 | unless uri.nil? then 30 | @path = @uri.path 31 | end 32 | @sockets = nil 33 | end 34 | attr_accessor :path 35 | protected 36 | def create_server_sockets 37 | @sockets = Socket.unix_server_socket(@path) 38 | end 39 | end # class UnixServer 40 | end # module Hprose -------------------------------------------------------------------------------- /lib/hproseserver.rb: -------------------------------------------------------------------------------- 1 | ############################################################ 2 | # # 3 | # hprose # 4 | # # 5 | # Official WebSite: http://www.hprose.com/ # 6 | # http://www.hprose.org/ # 7 | # # 8 | ############################################################ 9 | 10 | ############################################################ 11 | # # 12 | # hproseserver.rb # 13 | # # 14 | # hprose server for ruby # 15 | # # 16 | # LastModified: Apr 13, 2014 # 17 | # Author: Ma Bingyao # 18 | # # 19 | ############################################################ 20 | 21 | module Hprose 22 | autoload :Service, 'hprose/service' 23 | autoload :HttpService, 'hprose/httpservice' 24 | autoload :SocketServer, 'hprose/socketserver' 25 | autoload :TcpServer, 'hprose/tcpserver' 26 | autoload :UnixServer, 'hprose/unixserver' 27 | end 28 | 29 | Object.const_set(:HproseService, Hprose.const_get(:Service)) 30 | Object.const_set(:HproseHttpService, Hprose.const_get(:HttpService)) 31 | Object.const_set(:HproseTcpServer, Hprose.const_get(:TcpServer)) 32 | Object.const_set(:HproseUnixServer, Hprose.const_get(:UnixServer)) -------------------------------------------------------------------------------- /lib/hprose/tcpserver.rb: -------------------------------------------------------------------------------- 1 | ############################################################ 2 | # # 3 | # hprose # 4 | # # 5 | # Official WebSite: http://www.hprose.com/ # 6 | # http://www.hprose.org/ # 7 | # # 8 | ############################################################ 9 | 10 | ############################################################ 11 | # # 12 | # hprose/tcpserver.rb # 13 | # # 14 | # hprose tcp server for ruby # 15 | # # 16 | # LastModified: Apr 13, 2014 # 17 | # Author: Ma Bingyao # 18 | # # 19 | ############################################################ 20 | 21 | require "hprose/socketserver" 22 | require "socket" 23 | 24 | module Hprose 25 | class TcpServer < SocketServer 26 | def initialize(uri = nil) 27 | super 28 | @host = nil 29 | @port = 0 30 | unless uri.nil? then 31 | @host = @uri.host 32 | @port = @uri.port 33 | end 34 | @sockets = nil 35 | end 36 | attr_accessor :host, :port 37 | protected 38 | def create_server_sockets 39 | @sockets = Socket.tcp_server_sockets(@host, @port) 40 | end 41 | end # class TcpServer 42 | end # module Hprose -------------------------------------------------------------------------------- /lib/hproseclient.rb: -------------------------------------------------------------------------------- 1 | ############################################################ 2 | # # 3 | # hprose # 4 | # # 5 | # Official WebSite: http://www.hprose.com/ # 6 | # http://www.hprose.org/ # 7 | # # 8 | ############################################################ 9 | 10 | ############################################################ 11 | # # 12 | # hproseclient.rb # 13 | # # 14 | # hprose client for ruby # 15 | # # 16 | # LastModified: Mar 10, 2014 # 17 | # Author: Ma Bingyao # 18 | # # 19 | ############################################################ 20 | 21 | module Hprose 22 | autoload :Client, 'hprose/client' 23 | autoload :HttpClient, 'hprose/httpclient' 24 | autoload :SocketClient, 'hprose/socketclient' 25 | autoload :TcpClient, 'hprose/tcpclient' 26 | autoload :UnixClient, 'hprose/unixclient' 27 | end 28 | 29 | Object.const_set(:HproseClient, Hprose.const_get(:Client)) 30 | Object.const_set(:HproseHttpClient, Hprose.const_get(:HttpClient)) 31 | Object.const_set(:HproseSocketClient, Hprose.const_get(:SocketClient)) 32 | Object.const_set(:HproseTcpClient, Hprose.const_get(:TcpClient)) 33 | Object.const_set(:HproseUnixClient, Hprose.const_get(:UnixClient)) -------------------------------------------------------------------------------- /lib/hproseio.rb: -------------------------------------------------------------------------------- 1 | ############################################################ 2 | # # 3 | # hprose # 4 | # # 5 | # Official WebSite: http://www.hprose.com/ # 6 | # http://www.hprose.org/ # 7 | # # 8 | ############################################################ 9 | 10 | ############################################################ 11 | # # 12 | # hproseio.rb # 13 | # # 14 | # hprose io for ruby # 15 | # # 16 | # LastModified: Mar 8, 2014 # 17 | # Author: Ma Bingyao # 18 | # # 19 | ############################################################ 20 | 21 | module Hprose 22 | autoload :Tags, 'hprose/io' 23 | autoload :ClassManager, 'hprose/io' 24 | autoload :RawReader, 'hprose/io' 25 | autoload :Reader, 'hprose/io' 26 | autoload :Writer, 'hprose/io' 27 | autoload :Formatter, 'hprose/io' 28 | end 29 | 30 | Object.const_set(:HproseTags, Hprose.const_get(:Tags)) 31 | Object.const_set(:HproseClassManager, Hprose.const_get(:ClassManager)) 32 | Object.const_set(:HproseRawReader, Hprose.const_get(:RawReader)) 33 | Object.const_set(:HproseReader, Hprose.const_get(:Reader)) 34 | Object.const_set(:HproseWriter, Hprose.const_get(:Writer)) 35 | Object.const_set(:HproseFormatter, Hprose.const_get(:Formatter)) -------------------------------------------------------------------------------- /gemspec: -------------------------------------------------------------------------------- 1 | Gem::Specification.new do |s| 2 | s.name = 'hprose' 3 | s.version = '1.5.0' 4 | s.license = 'MIT' 5 | s.author = 'Ma Bingyao ( andot )' 6 | s.email = 'andot@hprose.com' 7 | s.homepage = 'http://www.hprose.com/' 8 | s.platform = Gem::Platform::RUBY 9 | s.description = <<-EOF 10 | Hprose is a High Performance Remote Object Service Engine. 11 | 12 | It is a modern, lightweight, cross-language, cross-platform, object-oriented, high performance, remote dynamic communication middleware. It is not only easy to use, but powerful. You just need a little time to learn, then you can use it to easily construct cross language cross platform distributed application system. 13 | 14 | Hprose supports many programming languages. 15 | 16 | Through Hprose, You can conveniently and efficiently intercommunicate between those programming languages. 17 | 18 | This project is the implementation of Hprose for Ruby. 19 | EOF 20 | 21 | s.summary = 'Hprose is a lightweight, secure, cross-domain, 22 | platform-independent, language-independent, 23 | envirment-independent, complex object supported, 24 | reference parameters supported, session supported, 25 | service-oriented, high performance remote object 26 | service engine. This project is the client and 27 | server implementations of the Hprose for Ruby.' 28 | candidates = Dir.glob '{examples,lib}/**/*' 29 | candidates += Dir.glob '*' 30 | s.files = candidates.delete_if { |item| 31 | item.include?('CVS') || item.include?('rdoc') || 32 | item.include?('nbproject') || 33 | File.extname(item) == '.spec' || 34 | File.extname(item) == '.gem' 35 | } 36 | s.require_path = 'lib' 37 | s.has_rdoc = false 38 | end -------------------------------------------------------------------------------- /gem.spec: -------------------------------------------------------------------------------- 1 | require 'rubygems' 2 | 3 | spec = Gem::Specification.new {|s| 4 | s.name = 'hprose' 5 | s.version = '1.5.0' 6 | s.license = 'MIT' 7 | s.author = 'Ma Bingyao ( andot )' 8 | s.email = 'andot@hprose.com' 9 | s.homepage = 'http://www.hprose.com/' 10 | s.platform = Gem::Platform::RUBY 11 | s.description = <<-EOF 12 | Hprose is a High Performance Remote Object Service Engine. 13 | 14 | It is a modern, lightweight, cross-language, cross-platform, object-oriented, high performance, remote dynamic communication middleware. It is not only easy to use, but powerful. You just need a little time to learn, then you can use it to easily construct cross language cross platform distributed application system. 15 | 16 | Hprose supports many programming languages. 17 | 18 | Through Hprose, You can conveniently and efficiently intercommunicate between those programming languages. 19 | 20 | This project is the implementation of Hprose for Ruby. 21 | EOF 22 | 23 | s.summary = 'Hprose is a lightweight, secure, cross-domain, 24 | platform-independent, language-independent, 25 | envirment-independent, complex object supported, 26 | reference parameters supported, session supported, 27 | service-oriented, high performance remote object 28 | service engine. This project is the client and 29 | server implementations of the Hprose for Ruby.' 30 | candidates = Dir.glob '{examples,lib}/**/*' 31 | candidates += Dir.glob '*' 32 | s.files = candidates.delete_if { |item| 33 | item.include?('CVS') || item.include?('rdoc') || 34 | item.include?('nbproject') || 35 | File.extname(item) == '.spec' || 36 | File.extname(item) == '.gem' 37 | } 38 | s.require_path = 'lib' 39 | s.has_rdoc = false 40 | s.add_runtime_dependency 'uuidtools' 41 | } 42 | 43 | if $0 == __FILE__ 44 | Gem::Builder.new(spec).build 45 | end 46 | -------------------------------------------------------------------------------- /lib/hprose/socketserver.rb: -------------------------------------------------------------------------------- 1 | ############################################################ 2 | # # 3 | # hprose # 4 | # # 5 | # Official WebSite: http://www.hprose.com/ # 6 | # http://www.hprose.org/ # 7 | # # 8 | ############################################################ 9 | 10 | ############################################################ 11 | # # 12 | # hprose/socketserver.rb # 13 | # # 14 | # hprose socket server for ruby # 15 | # # 16 | # LastModified: Apr 13, 2014 # 17 | # Author: Ma Bingyao # 18 | # # 19 | ############################################################ 20 | 21 | require "hprose/service" 22 | require "uri" 23 | require "socket" 24 | 25 | module Hprose 26 | class SocketServer < Service 27 | protected 28 | def create_server_sockets 29 | raise NotImplementedError.new("#{self.class.name}#create_server_sockets is an abstract method") 30 | end 31 | public 32 | def initialize(uri = nil) 33 | super() 34 | @uri = nil 35 | unless uri.nil? then 36 | @uri = URI.parse(uri) 37 | end 38 | @sockets = nil 39 | end 40 | def start 41 | begin 42 | create_server_sockets 43 | Socket.accept_loop(@sockets) do |sock, client_addrinfo| 44 | Thread.start do 45 | begin 46 | loop do 47 | buf = sock.recv(4, 0) 48 | n = buf[0].ord << 24 | buf[1].ord << 16 | buf[2].ord << 8 | buf[3].ord 49 | data = handle(sock.recv(n, 0), client_addrinfo) 50 | n = data.bytesize 51 | sock.send("" << (n >> 24 & 0xff) << (n >> 16 & 0xff) << (n >> 8 & 0xff) << (n & 0xff) << data, 0) 52 | end 53 | ensure 54 | sock.close 55 | end 56 | end 57 | end 58 | rescue ::Interrupt => e 59 | ensure 60 | stop 61 | end 62 | end 63 | def stop 64 | unless @sockets.nil? then 65 | @sockets.each {|s| s.close if !s.closed? } 66 | @sockets = nil 67 | end 68 | end 69 | end # class SocketServer 70 | end # module Hprose 71 | -------------------------------------------------------------------------------- /lib/hprose.rb: -------------------------------------------------------------------------------- 1 | ############################################################ 2 | # # 3 | # hprose # 4 | # # 5 | # Official WebSite: http://www.hprose.com/ # 6 | # http://www.hprose.org/ # 7 | # # 8 | ############################################################ 9 | 10 | ############################################################ 11 | # # 12 | # hprose.rb # 13 | # # 14 | # hprose for ruby # 15 | # # 16 | # LastModified: Mar 11, 2014 # 17 | # Author: Ma Bingyao # 18 | # # 19 | ############################################################ 20 | 21 | module Hprose 22 | autoload :Exception, 'hprose/common' 23 | autoload :ResultMode, 'hprose/common' 24 | autoload :Filter, 'hprose/common' 25 | autoload :Tags, 'hprose/io' 26 | autoload :ClassManager, 'hprose/io' 27 | autoload :RawReader, 'hprose/io' 28 | autoload :Reader, 'hprose/io' 29 | autoload :Writer, 'hprose/io' 30 | autoload :Formatter, 'hprose/io' 31 | autoload :Client, 'hprose/client' 32 | autoload :HttpClient, 'hprose/httpclient' 33 | autoload :SocketClient, 'hprose/socketclient' 34 | autoload :TcpClient, 'hprose/tcpclient' 35 | autoload :UnixClient, 'hprose/unixclient' 36 | autoload :Service, 'hprose/service' 37 | autoload :HttpService, 'hprose/httpservice' 38 | autoload :SocketServer, 'hprose/socketserver' 39 | autoload :TcpServer, 'hprose/tcpserver' 40 | autoload :UnixServer, 'hprose/unixserver' 41 | end 42 | 43 | Object.const_set(:HproseException, Hprose.const_get(:Exception)) 44 | Object.const_set(:HproseResultMode, Hprose.const_get(:ResultMode)) 45 | Object.const_set(:HproseFilter, Hprose.const_get(:Filter)) 46 | Object.const_set(:HproseTags, Hprose.const_get(:Tags)) 47 | Object.const_set(:HproseClassManager, Hprose.const_get(:ClassManager)) 48 | Object.const_set(:HproseRawReader, Hprose.const_get(:RawReader)) 49 | Object.const_set(:HproseReader, Hprose.const_get(:Reader)) 50 | Object.const_set(:HproseWriter, Hprose.const_get(:Writer)) 51 | Object.const_set(:HproseFormatter, Hprose.const_get(:Formatter)) 52 | Object.const_set(:HproseClient, Hprose.const_get(:Client)) 53 | Object.const_set(:HproseHttpClient, Hprose.const_get(:HttpClient)) 54 | Object.const_set(:HproseSocketClient, Hprose.const_get(:SocketClient)) 55 | Object.const_set(:HproseTcpClient, Hprose.const_get(:TcpClient)) 56 | Object.const_set(:HproseUnixClient, Hprose.const_get(:UnixClient)) 57 | Object.const_set(:HproseService, Hprose.const_get(:Service)) 58 | Object.const_set(:HproseHttpService, Hprose.const_get(:HttpService)) 59 | Object.const_set(:HproseSocketServer, Hprose.const_get(:SocketServer)) 60 | Object.const_set(:HproseTcpServer, Hprose.const_get(:TcpServer)) 61 | Object.const_set(:HproseUnixServer, Hprose.const_get(:UnixServer)) -------------------------------------------------------------------------------- /lib/hprose/socketclient.rb: -------------------------------------------------------------------------------- 1 | ############################################################ 2 | # # 3 | # hprose # 4 | # # 5 | # Official WebSite: http://www.hprose.com/ # 6 | # http://www.hprose.org/ # 7 | # # 8 | ############################################################ 9 | 10 | ############################################################ 11 | # # 12 | # hprose/socketclient.rb # 13 | # # 14 | # hprose socket client for ruby # 15 | # # 16 | # LastModified: Apr 13, 2014 # 17 | # Author: Ma Bingyao # 18 | # # 19 | ############################################################ 20 | 21 | require "hprose/client" 22 | require "socket" 23 | 24 | module Hprose 25 | class SocketClient < Client 26 | private 27 | module SocketConnStatus 28 | Free = 0 29 | Using = 1 30 | Closing = 2 31 | end 32 | class SocketConnEntry 33 | def initialize(uri) 34 | @uri = uri 35 | @status = SocketConnStatus::Using 36 | @socket = nil 37 | end 38 | attr_accessor :uri, :status, :socket 39 | end 40 | class SocketConnPool 41 | def initialize 42 | @pool = [] 43 | @mutex = Mutex.new 44 | end 45 | def get(uri) 46 | @mutex.synchronize do 47 | @pool.each do |entry| 48 | if entry.status == SocketConnStatus::Free then 49 | if not entry.uri.nil? and entry.uri == uri then 50 | entry.status = SocketConnStatus::Using 51 | return entry 52 | elsif entry.uri.nil? then 53 | entry.status = SocketConnStatus::Using 54 | entry.uri = uri 55 | return entry 56 | end 57 | end 58 | end 59 | entry = SocketConnEntry.new(uri) 60 | @pool << entry 61 | return entry 62 | end 63 | end 64 | def close(uri) 65 | sockets = [] 66 | @mutex.synchronize do 67 | @pool.each do |entry| 68 | if not entry.uri.nil? and entry.uri == uri then 69 | if entry.status == SocketConnStatus::Free then 70 | sockets << entry.socket 71 | entry.socket = nil 72 | entry.uri = nil 73 | else 74 | entry.status = SocketConnStatus::Closing 75 | end 76 | end 77 | end 78 | end 79 | freeSockets(sockets) 80 | end 81 | def free(entry) 82 | if entry.status == SocketConnStatus::Closing then 83 | if not entry.socket.nil? then 84 | entry.socket.close 85 | entry.socket = nil 86 | end 87 | entry.uri = nil 88 | end 89 | entry.status = SocketConnStatus::Free 90 | end 91 | private 92 | def freeSockets(sockets) 93 | Thread.start do 94 | sockets.each do |socket| 95 | socket.close 96 | end 97 | end 98 | end 99 | end 100 | public 101 | def initialize(uri = nil) 102 | super 103 | @pool = SocketConnPool.new 104 | end 105 | protected 106 | def create_socket 107 | raise NotImplementedError.new("#{self.class.name}#create_socket is an abstract method") 108 | end 109 | def send_and_receive(data) 110 | entry = @pool.get(@uri) 111 | if entry.socket.nil? then 112 | entry.socket = create_socket 113 | end 114 | begin 115 | n = data.bytesize 116 | entry.socket.send("" << (n >> 24 & 0xff) << (n >> 16 & 0xff) << (n >> 8 & 0xff) << (n & 0xff) << data, 0) 117 | buf = entry.socket.recv(4, 0) 118 | n = buf[0].ord << 24 | buf[1].ord << 16 | buf[2].ord << 8 | buf[3].ord 119 | data = entry.socket.recv(n, 0) 120 | rescue 121 | entry.status = SocketConnStatus::Closing 122 | raise 123 | ensure 124 | @pool.free(entry) 125 | end 126 | return data 127 | end 128 | end # class SocketClient 129 | end # module Hprose -------------------------------------------------------------------------------- /lib/hprose/httpclient.rb: -------------------------------------------------------------------------------- 1 | ############################################################ 2 | # # 3 | # hprose # 4 | # # 5 | # Official WebSite: http://www.hprose.com/ # 6 | # http://www.hprose.org/ # 7 | # # 8 | ############################################################ 9 | 10 | ############################################################ 11 | # # 12 | # hprose/httpclient.rb # 13 | # # 14 | # hprose http client for ruby # 15 | # # 16 | # LastModified: Apr 12, 2014 # 17 | # Author: Ma Bingyao # 18 | # # 19 | ############################################################ 20 | 21 | require "hprose/client" 22 | require "net/http" 23 | require "net/https" 24 | require "uri" 25 | 26 | module Hprose 27 | class HttpClient < Client 28 | public 29 | def initialize(uri = nil) 30 | super 31 | Net::HTTP.version_1_2 32 | @http = Net::HTTP 33 | @header = {} 34 | @timeout = 30 35 | @keepalive = true 36 | @keepalive_timeout = 300 37 | end 38 | attr_reader :header 39 | attr_accessor :timeout, :keepalive, :keepalive_timeout 40 | def proxy=(proxy) 41 | @http = case proxy 42 | when Net::HTTP then 43 | proxy 44 | when String then 45 | uri = URI.parse(proxy) 46 | Net::HTTP::Proxy(uri.host, uri.port, uri.user, uri.password) 47 | else 48 | proxy.superclass == Net::HTTP ? proxy : Net::HTTP 49 | end 50 | end 51 | protected 52 | def send_and_receive(data) 53 | httpclient = @http.new(@uri.host, @uri.port) 54 | httpclient.open_timeout = @timeout 55 | httpclient.read_timeout = @timeout 56 | httpclient.use_ssl = (@uri.scheme == 'https') 57 | httpclient.start 58 | headers = {'Content-Type' => 'application/hprose', 59 | 'Connection' => 'close'} 60 | if @keepalive then 61 | headers['Connection'] = 'keep-alive' 62 | headers['Keep-Alive'] = @keepalive_timeout.to_s 63 | end 64 | headers['Authorization'] = 'Basic ' << ["#{@uri.user}:#{@uri.password}"].pack('m').delete!("\n") unless @uri.user.nil? or @uri.password.nil? 65 | @header.each { |name, value| 66 | headers[name] = value 67 | } 68 | headers['Content-Length'] = data.bytesize.to_s 69 | headers['Cookie'] = _get_cookie(@uri.host.downcase, @uri.path, @uri.scheme == 'https') 70 | reqpath = @uri.path 71 | reqpath << '?' << @uri.query unless @uri.query.nil? 72 | response = httpclient.request_post(reqpath, data, headers) 73 | case response 74 | when Net::HTTPSuccess then 75 | cookielist = [] 76 | cookielist.concat(response['set-cookie'].split(',')) if response.key?('set-cookie') 77 | cookielist.concat(response['set-cookie2'].split(',')) if response.key?('set-cookie2') 78 | _set_cookie(cookielist, @uri.host.downcase) 79 | return response.body 80 | else 81 | raise Exception.exception(response.message) 82 | end 83 | end 84 | private 85 | @@cookie_manager = {} 86 | @@cookie_manager_mutex = Mutex.new 87 | def _set_cookie(cookielist, host) 88 | @@cookie_manager_mutex.synchronize do 89 | cookielist.each do |cookies| 90 | unless cookies == '' then 91 | cookies = cookies.strip.split(';') 92 | cookie = {} 93 | value = cookies[0].strip.split('=', 2) 94 | cookie['name'] = value[0] 95 | cookie['value'] = value.size == 2 ? value[1] : '' 96 | 1.upto(cookies.size - 1) do |i| 97 | value = cookies[i].strip.split('=', 2) 98 | cookie[value[0].upcase] = value.size == 2 ? value[1] : '' 99 | end 100 | # Tomcat can return SetCookie2 with path wrapped in " 101 | if cookie.has_key?('PATH') then 102 | cookie['PATH'][0] = '' if cookie['PATH'][0] == ?" 103 | cookie['PATH'].chop! if cookie['PATH'][-1] == ?" 104 | else 105 | cookie['PATH'] = '/' 106 | end 107 | cookie['EXPIRES'] = Time.parse(cookie['EXPIRES']) if cookie.has_key?('EXPIRES') 108 | cookie['DOMAIN'] = cookie.has_key?('DOMAIN') ? cookie['COMAIN'].downcase : host 109 | cookie['SECURE'] = cookie.has_key?('SECURE') 110 | @@cookie_manager[cookie['DOMAIN']] = {} unless @@cookie_manager.has_key?(cookie['DOMAIN']) 111 | @@cookie_manager[cookie['DOMAIN']][cookie['name']] = cookie 112 | end 113 | end 114 | end 115 | end 116 | def _get_cookie(host, path, secure) 117 | cookies = [] 118 | @@cookie_manager_mutex.synchronize do 119 | @@cookie_manager.each do |domain, cookielist| 120 | if host =~ Regexp.new(Regexp.escape(domain) + '$') then 121 | names = [] 122 | cookielist.each do |name, cookie| 123 | if cookie.has_key?('EXPIRES') and Time.now <=> cookie['EXPIRES'] > 0 then 124 | names << name 125 | elsif path =~ Regexp.new('^' + Regexp.escape(cookie['PATH'])) then 126 | if ((secure and cookie['SECURE']) or not cookie['SECURE']) and cookie['value'] != '' then 127 | cookies << (cookie['name'] + '=' + cookie['value']) 128 | end 129 | end 130 | end 131 | names.each { |name| @@cookie_manager[domain].delete(name) } 132 | end 133 | end 134 | end 135 | return cookies.join('; ') 136 | end 137 | end # class HttpClient 138 | end # module Hprose -------------------------------------------------------------------------------- /lib/hprose/httpservice.rb: -------------------------------------------------------------------------------- 1 | ############################################################ 2 | # # 3 | # hprose # 4 | # # 5 | # Official WebSite: http://www.hprose.com/ # 6 | # http://www.hprose.org/ # 7 | # # 8 | ############################################################ 9 | 10 | ############################################################ 11 | # # 12 | # hprose/httpservice.rb # 13 | # # 14 | # hprose http service for ruby # 15 | # # 16 | # LastModified: Apr 18, 2014 # 17 | # Author: Ma Bingyao # 18 | # # 19 | ############################################################ 20 | 21 | require 'hprose/io' 22 | require 'hprose/service' 23 | 24 | module Hprose 25 | class HttpService < Service 26 | def initialize 27 | super 28 | @crossdomain = false 29 | @p3p = false 30 | @get = true 31 | @origins = {} 32 | @crossdomain_xml_file = nil 33 | @crossdomain_xml_content = nil 34 | @client_access_policy_xml_file = nil 35 | @client_access_policy_xml_content = nil 36 | @on_send_header = nil 37 | @last_modified = Date.today.strftime("%a, %d %b %Y %H:%M:%S GMT") 38 | @etag = format('"%x:%x"', rand(2147483647), rand(2147483647)) 39 | end 40 | attr_accessor :crossdomain 41 | attr_accessor :p3p 42 | attr_accessor :get 43 | attr_accessor :on_send_header 44 | attr_reader :crossdomain_xml_file 45 | attr_reader :crossdomain_xml_content 46 | attr_reader :client_access_policy_xml_file 47 | attr_reader :client_access_policy_xml_content 48 | def add_access_control_allow_origin(origin) 49 | @origins[origin] = true 50 | end 51 | def remove_access_control_allow_origin(origin) 52 | @origins.delete(origin) 53 | end 54 | def crossdomain_xml_file=(filepath) 55 | @crossdomain_xml_file = filepath 56 | f = File.open(filepath) 57 | begin 58 | @crossdomain_xml_content = f.read 59 | ensure 60 | f.close 61 | end 62 | end 63 | def crossdomain_xml_content=(content) 64 | @crossdomain_xml_file = nil 65 | @crossdomain_xml_content = content 66 | end 67 | def client_access_policy_xml_file=(filepath) 68 | @client_access_policy_xml_file = filepath 69 | f = File.open(filepath) 70 | begin 71 | @client_access_policy_xml_content = f.read 72 | ensure 73 | f.close 74 | end 75 | end 76 | def client_access_policy_xml_content=(content) 77 | @client_access_policy_xml_file = nil 78 | @client_access_policy_xml_content = content 79 | end 80 | def call(context) 81 | unless @client_access_policy_xml_content.nil? then 82 | result = client_access_policy_xml_handler(context) 83 | return result if result 84 | end 85 | unless @crossdomain_xml_content.nil? then 86 | result = crossdomain_xml_handler(context) 87 | return result if result 88 | end 89 | header = default_header(context) 90 | statuscode = 200 91 | body = '' 92 | begin 93 | if (context['REQUEST_METHOD'] == 'GET') and @get then 94 | body = do_function_list(context) 95 | elsif (context['REQUEST_METHOD'] == 'POST') then 96 | body = handle(context['rack.input'].read, context) 97 | else 98 | statuscode = 403 99 | body = 'Forbidden' 100 | end 101 | rescue ::Exception => e 102 | body = do_error(e) 103 | end 104 | header['Content-Length'] = body.bytesize.to_s 105 | return [statuscode, header, [body]] 106 | end 107 | private 108 | def crossdomain_xml_handler(context) 109 | path = (context['SCRIPT_NAME'] << context['PATH_INFO']).downcase 110 | if path == '/crossdomain.xml' then 111 | if context['HTTP_IF_MODIFIED_SINCE'] == @last_modified and 112 | context['HTTP_IF_NONE_MATCH'] == @etag then 113 | return [304, {}, ['']] 114 | else 115 | header = {'Content-Type' => 'text/xml', 116 | 'Last-Modified' => @last_modified, 117 | 'Etag' => @etag, 118 | 'Content-Length' => @crossdomain_xml_content.size.to_s} 119 | return [200, header, [@crossdomain_xml_content]] 120 | end 121 | end 122 | return false 123 | end 124 | def client_access_policy_xml_handler(context) 125 | path = (context['SCRIPT_NAME'] << context['PATH_INFO']).downcase 126 | if path == '/clientaccesspolicy.xml' then 127 | if context['HTTP_IF_MODIFIED_SINCE'] == @last_modified and 128 | context['HTTP_IF_NONE_MATCH'] == @etag then 129 | return [304, {}, ['']] 130 | else 131 | header = {'Content-Type' => 'text/xml', 132 | 'Last-Modified' => @last_modified, 133 | 'Etag' => @etag, 134 | 'Content-Length' => @client_access_policy_xml_content.size.to_s} 135 | return [200, header, [@client_access_policy_xml_content]] 136 | end 137 | end 138 | return false 139 | end 140 | def default_header(context) 141 | header = {'Content-Type' => 'text/plain'} 142 | header['P3P'] = 'CP="CAO DSP COR CUR ADM DEV TAI PSA PSD ' + 143 | 'IVAi IVDi CONi TELo OTPi OUR DELi SAMi OTRi UNRi ' + 144 | 'PUBi IND PHY ONL UNI PUR FIN COM NAV INT DEM CNT ' + 145 | 'STA POL HEA PRE GOV"' if @p3p 146 | if @crossdomain then 147 | origin = context['HTTP_ORIGIN'] 148 | if (origin and origin != 'null') then 149 | if (@origins.size == 0) or @origins.has_key?(origin) then 150 | header['Access-Control-Allow-Origin'] = origin 151 | header['Access-Control-Allow-Credentials'] = 'true' 152 | end 153 | else 154 | header['Access-Control-Allow-Origin'] = '*' 155 | end 156 | end 157 | @on_send_header.call(header, context) unless @on_send_header.nil? 158 | return header 159 | end 160 | end # class HttpService 161 | end # module Hprose -------------------------------------------------------------------------------- /lib/hprose/client.rb: -------------------------------------------------------------------------------- 1 | ############################################################ 2 | # # 3 | # hprose # 4 | # # 5 | # Official WebSite: http://www.hprose.com/ # 6 | # http://www.hprose.org/ # 7 | # # 8 | ############################################################ 9 | 10 | ############################################################ 11 | # # 12 | # hprose/client.rb # 13 | # # 14 | # hprose client for ruby # 15 | # # 16 | # LastModified: Mar 22, 2014 # 17 | # Author: Ma Bingyao # 18 | # # 19 | ############################################################ 20 | 21 | require "hprose/common" 22 | require "hprose/io" 23 | require "uri" 24 | 25 | module Hprose 26 | class Client 27 | class << self 28 | alias :__new__ :new 29 | def inherited(subclass) 30 | class << subclass 31 | alias :new :__new__ 32 | end 33 | end 34 | end 35 | public 36 | def self.new(uri) 37 | u = URI.parse(uri) 38 | case u.scheme 39 | when 'http', 'https' then 40 | return HttpClient.new(uri) 41 | when 'tcp', 'tcp4', 'tcp6' then 42 | return TcpClient.new(uri) 43 | when 'unix' then 44 | return UnixClient.new(uri) 45 | else 46 | raise Exception.exception("The " << u.scheme << " client isn't implemented.") 47 | end 48 | end 49 | def initialize(uri = nil) 50 | @onerror = nil 51 | @filters = [] 52 | @simple = false 53 | self.uri = uri 54 | end 55 | def uri=(uri) 56 | @uri = URI.parse(uri) unless uri.nil? 57 | end 58 | attr_reader :uri 59 | attr_accessor :simple 60 | def filter 61 | return nil if @filters.empty? 62 | return @filters[0] 63 | end 64 | def filter=(filter) 65 | @filters.clear 66 | @filters << filter unless (filter.nil?) 67 | end 68 | def add_filter(filter) 69 | @filters << filter 70 | end 71 | def remove_filter(filter) 72 | @filters.delete(filter) 73 | end 74 | def onerror(&block) 75 | @onerror = block if block_given? 76 | @onerror 77 | end 78 | def onerror=(error_handler) 79 | error_handler = error_handler.to_sym if error_handler.is_a?(String) 80 | if error_handler.is_a?(Symbol) then 81 | error_handler = Object.method(error_handler) 82 | end 83 | @onerror = error_handler 84 | end 85 | def use_service(uri = nil, namespace = nil) 86 | self.uri = uri 87 | Proxy.new(self, namespace) 88 | end 89 | def [](namespace) 90 | Proxy.new(self, namespace) 91 | end 92 | def invoke(methodname, args = [], byref = false, resultMode = Normal, simple = nil, &block) 93 | simple = @simple if simple.nil? 94 | if block_given? then 95 | Thread.start do 96 | begin 97 | result = do_invoke(methodname, args, byref, resultMode, simple) 98 | case block.arity 99 | when 0 then yield 100 | when 1 then yield result 101 | when 2 then yield result, args 102 | end 103 | rescue ::Exception => e 104 | @onerror.call(methodname, e) if (@onerror.is_a?(Proc) or 105 | @onerror.is_a?(Method) or 106 | @onerror.respond_to?(:call)) 107 | end 108 | end 109 | else 110 | return do_invoke(methodname, args, byref, resultMode, simple) 111 | end 112 | end 113 | protected 114 | def send_and_receive(data) 115 | raise NotImplementedError.new("#{self.class.name}#send_and_receive is an abstract method") 116 | end 117 | private 118 | include Tags 119 | include ResultMode 120 | def do_output(methodname, args, byref, simple) 121 | stream = StringIO.new 122 | writer = Writer.new(stream, simple) 123 | stream.putc(TagCall) 124 | writer.write_string(methodname.to_s) 125 | if (args.size > 0 or byref) then 126 | writer.reset 127 | writer.write_list(args) 128 | writer.write_boolean(true) if byref 129 | end 130 | stream.putc(TagEnd) 131 | data = stream.string 132 | stream.close 133 | @filters.each do | filter | 134 | data = filter.output_filter(data, self) 135 | end 136 | return data 137 | end 138 | def do_input(data, args, resultMode) 139 | @filters.reverse_each do | filter | 140 | data = filter.input_filter(data, self) 141 | end 142 | raise Exception.exception("Wrong Response: \r\n#{data}") if data.nil? or data.empty? or data[data.size - 1].ord != TagEnd 143 | return data if resultMode == RawWithEndTag 144 | return data.chop! if resultMode == Raw 145 | stream = StringIO.new(data, 'rb') 146 | reader = Reader.new(stream) 147 | result = nil 148 | while (tag = stream.getbyte) != TagEnd do 149 | case tag 150 | when TagResult then 151 | if resultMode == Normal then 152 | reader.reset 153 | result = reader.unserialize 154 | else 155 | s = reader.read_raw 156 | result = s.string 157 | s.close 158 | end 159 | when TagArgument then 160 | reader.reset 161 | a = reader.read_list 162 | args.each_index { |i| args[i] = a[i] } 163 | when TagError then 164 | reader.reset 165 | result = Exception.exception(reader.read_string()) 166 | else 167 | raise Exception.exception("Wrong Response: \r\n#{data}") 168 | end 169 | end 170 | return result 171 | end 172 | def do_invoke(methodname, args, byref, resultMode, simple) 173 | data = do_output(methodname, args, byref, simple) 174 | result = do_input(send_and_receive(data), args, resultMode) 175 | raise result if result.is_a?(Exception) 176 | return result 177 | end 178 | def method_missing(methodname, *args, &block) 179 | self.invoke(methodname, args, &block) 180 | end 181 | 182 | class Proxy 183 | def initialize(client, namespace = nil) 184 | @client = client 185 | @namespace = namespace 186 | end 187 | def [](namespace) 188 | Proxy.new(@client, @namespace.to_s + '_' + namespace.to_s) 189 | end 190 | def method_missing(methodname, *args, &block) 191 | methodname = @namespace.to_s + '_' + methodname.to_s unless @namespace.nil? 192 | @client.invoke(methodname, args, &block) 193 | end 194 | end # class Proxy 195 | end # class Client 196 | end # module Hprose -------------------------------------------------------------------------------- /lib/hprose/service.rb: -------------------------------------------------------------------------------- 1 | ############################################################ 2 | # # 3 | # hprose # 4 | # # 5 | # Official WebSite: http://www.hprose.com/ # 6 | # http://www.hprose.org/ # 7 | # # 8 | ############################################################ 9 | 10 | ############################################################ 11 | # # 12 | # hprose/service.rb # 13 | # # 14 | # hprose service for ruby # 15 | # # 16 | # LastModified: Mar 22, 2014 # 17 | # Author: Ma Bingyao # 18 | # # 19 | ############################################################ 20 | 21 | require "hprose/common" 22 | require "hprose/io" 23 | 24 | module Hprose 25 | class Service 26 | private 27 | include Tags 28 | include ResultMode 29 | public 30 | attr_accessor :debug, :simple 31 | attr_accessor :on_before_invoke, :on_after_invoke 32 | attr_accessor :on_send_error 33 | def initialize 34 | @functions = {} 35 | @funcNames = {} 36 | @resultMode = {} 37 | @simpleMode = {} 38 | @debug = $DEBUG 39 | @filters = [] 40 | @simple = false 41 | @on_before_invoke = nil 42 | @on_after_invoke = nil 43 | @on_send_error = nil 44 | end 45 | def filter 46 | return nil if @filters.empty? 47 | return @filters[0] 48 | end 49 | def filter=(filter) 50 | @filters.clear 51 | @filters << filter unless (filter.nil?) 52 | end 53 | def add_filter(filter) 54 | @filters << filter 55 | end 56 | def remove_filter(filter) 57 | @filters.delete(filter) 58 | end 59 | def add(*args, &block) 60 | case args.size 61 | when 1 then 62 | case args[0] 63 | when Array then add_functions(args[0]) 64 | when Class then add_class_methods(args[0]) 65 | when String, Symbol then block_given? ? add_block(args[0], &block) : add_function(args[0]) 66 | when Proc, Method then add_function(args[0]) 67 | else add_instance_methods(args[0]) 68 | end 69 | when 2 then 70 | case args[0] 71 | when Array then 72 | case args[1] 73 | when Array then add_functions(args[0], args[1]) 74 | else add_methods(args[0], args[1]) 75 | end 76 | when Class then 77 | case args[1] 78 | when Class then add_class_methods(args[0], args[1]) 79 | when String, Symbol then add_class_methods(args[0], args[0], args[1]) 80 | else raise Exception.exception('wrong arguments') 81 | end 82 | when String, Symbol then 83 | case args[1] 84 | when String, Symbol then add_function(args[0], args[1]) 85 | else add_method(args[0], args[1]) 86 | end 87 | when Proc, Method then 88 | case args[1] 89 | when String, Symbol then add_function(args[0], args[1]) 90 | else raise Exception.exception('wrong arguments') 91 | end 92 | else 93 | case args[1] 94 | when Class then add_instance_methods(args[0], args[1]) 95 | when String, Symbol then add_instance_methods(args[0], nil, args[1]) 96 | else raise Exception.exception('wrong arguments') 97 | end 98 | end 99 | when 3 then 100 | case args[0] 101 | when Array then 102 | if args[1].nil? then 103 | case args[2] 104 | when Array then add_functions(args[0], args[2]) 105 | else raise Exception.exception('wrong arguments') 106 | end 107 | else 108 | case args[2] 109 | when Array, String, Symbol then add_methods(args[0], args[1], args[2]) 110 | else raise Exception.exception('wrong arguments') 111 | end 112 | end 113 | when Class then 114 | case args[2] 115 | when String, Symbol then 116 | if args[1].is_a?(Class) then 117 | add_class_methods(args[0], args[1], args[2]) 118 | else 119 | add_instance_methods(args[1], args[0], args[2]) 120 | end 121 | else raise Exception.exception('wrong arguments') 122 | end 123 | when String, Symbol then 124 | case args[2] 125 | when String, Symbol then 126 | if args[1].nil? then 127 | add_function(args[0], args[2]) 128 | else 129 | add_method(args[0], args[1], args[2]) 130 | end 131 | else raise Exception.exception('wrong arguments') 132 | end 133 | when Proc, Method then raise Exception.exception('wrong arguments') 134 | else 135 | if args[1].is_a?(Class) and (args[2].is_a?(String) or args[2].is_a?(Symbol)) then 136 | add_instance_methods(args[0], args[1], args[2]) 137 | else 138 | raise Exception.exception('wrong arguments') 139 | end 140 | end 141 | else raise Exception.exception('wrong arguments') 142 | end 143 | end 144 | def add_missing_function(function, resultMode = Normal, simple = nil) 145 | add_function(function, '*', resultMode, simple) 146 | end 147 | def add_block(methodname, resultMode = Normal, simple = nil, &block) 148 | if block_given? then 149 | methodname = methodname.to_s if methodname.is_a?(Symbol) 150 | aliasname = methodname.downcase 151 | @functions[aliasname] = block 152 | @funcNames[aliasname] = methodname 153 | @resultMode[aliasname] = resultMode 154 | @simpleMode[aliasname] = simple 155 | else 156 | raise Exception.exception('block must be given') 157 | end 158 | end 159 | def add_function(function, aliasname = nil, resultMode = Normal, simple = nil) 160 | function = function.to_s if function.is_a?(Symbol) 161 | aliasname = aliasname.to_s if aliasname.is_a?(Symbol) 162 | if function.is_a?(String) then 163 | aliasname = function if aliasname.nil? 164 | function = Object.method(function) 165 | end 166 | unless function.is_a?(Proc) or function.is_a?(Method) or function.respond_to?(:call) then 167 | raise Exception.exception('function must be callable') 168 | end 169 | if aliasname.nil? then 170 | if function.is_a?(Method) then 171 | aliasname = function.inspect 172 | aliasname[/#(.*?)#/] = '' 173 | aliasname[/>$/] = '' 174 | aliasname[/<(.*?)>\./] = '' if !aliasname[/<(.*?)>\./].nil? 175 | else 176 | raise Exception.exception('need a alias name for function') 177 | end 178 | end 179 | name = aliasname.downcase 180 | @functions[name] = function 181 | @funcNames[name] = aliasname 182 | @resultMode[name] = resultMode 183 | @simpleMode[name] = simple 184 | end 185 | def add_functions(functions, aliases = nil, resultMode = Normal, simple = nil) 186 | unless functions.is_a?(Array) then 187 | raise Exception.exception('argument functions is not an array') 188 | end 189 | count = functions.size 190 | unless aliases.nil? or aliases.is_a?(Array) and count == aliases.size then 191 | raise Exception.exception('the count of functions is not matched with aliases') 192 | end 193 | count.times do |i| 194 | function = functions[i] 195 | if aliases.nil? then 196 | add_function(function, nil, resultMode, simple) 197 | else 198 | add_function(function, aliases[i], resultMode, simple) 199 | end 200 | end 201 | end 202 | def add_method(methodname, belongto, aliasname = nil, resultMode = Normal, simple = nil) 203 | function = belongto.method(methodname) 204 | add_function(function, (aliasname.nil? ? methodname : aliasname), resultMode, simple) 205 | end 206 | def add_methods(methods, belongto, aliases = nil, resultMode = Normal, simple = nil) 207 | unless methods.is_a?(Array) then 208 | raise Exception.exception('argument methods is not an array') 209 | end 210 | aliases = aliases.to_s if aliases.is_a?(Symbol) 211 | count = methods.size 212 | if aliases.is_a?(String) then 213 | alias_prefix = aliases 214 | aliases = Array.new(count) { |i| alias_prefix + '_' + methods[i].to_s } 215 | end 216 | if not aliases.nil? and count != aliases.size then 217 | raise Exception.exception('The count of methods is not matched with aliases') 218 | end 219 | count.times do |i| 220 | method = methods[i] 221 | function = belongto.method(method) 222 | add_function(function, (aliases.nil? ? method : aliases[i]), resultMode, simple) 223 | end 224 | end 225 | def add_instance_methods(obj, cls = nil, alias_prefix = nil, resultMode = Normal, simple = nil) 226 | alias_prefix = alias_prefix.to_s if alias_prefix.is_a?(Symbol) 227 | cls = obj.class if cls.nil? 228 | methods = cls.public_instance_methods(false) 229 | aliases = Array.new(methods.size) do |i| 230 | if alias_prefix.nil? then 231 | methods[i].to_s 232 | else 233 | alias_prefix + '_' + methods[i].to_s 234 | end 235 | end 236 | methods.map! { |method| cls.instance_method(method).bind(obj) } 237 | add_functions(methods, aliases, resultMode, simple) 238 | end 239 | def add_class_methods(cls, execcls = nil, alias_prefix = nil, resultMode = Normal, simple = nil) 240 | alias_prefix = alias_prefix.to_s if alias_prefix.is_a?(Symbol) 241 | execcls = cls if execcls.nil? 242 | methods = cls.singleton_methods(false) 243 | aliases = Array.new(methods.size) do |i| 244 | if alias_prefix.nil? then 245 | methods[i].to_s 246 | else 247 | alias_prefix + '_' + methods[i].to_s 248 | end 249 | end 250 | methods.map! { |method| execcls.method(method) } 251 | add_functions(methods, aliases, resultMode, simple) 252 | end 253 | protected 254 | def output_filter(data, context) 255 | @filters.each do | filter | 256 | data = filter.output_filter(data, context) 257 | end 258 | return data 259 | end 260 | def response_end(ostream, context) 261 | data = ostream.string 262 | ostream.close 263 | return output_filter(data, context) 264 | end 265 | def fix_args(args, function, context) 266 | (args.length + 1 == function.arity) ? args + [context] : args 267 | end 268 | def fire_before_invoke_event(name, args, byref, context) 269 | unless @on_before_invoke.nil? then 270 | case @on_before_invoke.arity 271 | when 0 then @on_before_invoke.call() 272 | when 1 then @on_before_invoke.call(name) 273 | when 2 then @on_before_invoke.call(name, args) 274 | when 3 then @on_before_invoke.call(name, args, byref) 275 | else @on_before_invoke.call(name, args, byref, context) 276 | end 277 | end 278 | end 279 | def fire_after_invoke_event(name, args, byref, result, context) 280 | unless @on_after_invoke.nil? then 281 | case @on_after_invoke.arity 282 | when 0 then @on_after_invoke.call() 283 | when 1 then @on_after_invoke.call(name) 284 | when 2 then @on_after_invoke.call(name, args) 285 | when 3 then @on_after_invoke.call(name, args, byref) 286 | when 4 then @on_after_invoke.call(name, args, byref, result) 287 | else @on_after_invoke.call(name, args, byref, result, context) 288 | end 289 | end 290 | end 291 | def fire_error_event(e, context) 292 | unless @on_send_error.nil? then 293 | case @on_send_error.arity 294 | when 0 then @on_send_error.call() 295 | when 1 then @on_send_error.call(e) 296 | else @on_send_error.call(e, context) 297 | end 298 | end 299 | end 300 | def do_error(e, context) 301 | fire_error_event(e, context) 302 | error = @debug ? e.backtrace.unshift(e.message).join("\r\n") : e.message 303 | ostream = StringIO.new 304 | writer = Writer.new(ostream, true) 305 | ostream.putc(TagError) 306 | writer.write_string(error) 307 | ostream.putc(TagEnd) 308 | return response_end(ostream, context) 309 | end 310 | def do_invoke(istream, context) 311 | simpleReader = Reader.new(istream, true) 312 | begin 313 | name = simpleReader.read_string 314 | aliasname = name.downcase 315 | args = [] 316 | byref = false 317 | tag = simpleReader.check_tags([TagList, TagCall, TagEnd]) 318 | if tag == TagList then 319 | reader = Reader.new(istream) 320 | args = reader.read_list_without_tag 321 | tag = reader.check_tags([TagTrue, TagCall, TagEnd]) 322 | if tag == TagTrue then 323 | byref = true 324 | tag = reader.check_tags([TagCall, TagEnd]) 325 | end 326 | end 327 | fire_before_invoke_event(name, args, byref, context) 328 | result = nil 329 | if @functions.has_key?(aliasname) then 330 | function = @functions[aliasname] 331 | resultMode = @resultMode[aliasname] 332 | simple = @simpleMode[aliasname] 333 | result = function.call(*fix_args(args, function, context)) 334 | elsif @functions.has_key?('*') then 335 | function = @functions['*'] 336 | resultMode = @resultMode['*'] 337 | simple = @simpleMode[aliasname] 338 | result = function.call(name, args) 339 | else 340 | raise Exception.exception("Can't find this function " << name) 341 | end 342 | fire_after_invoke_event(name, args, byref, result, context) 343 | if resultMode == RawWithEndTag then 344 | return output_filter(result, context) 345 | end 346 | ostream = StringIO.new 347 | if resultMode == Raw then 348 | ostream.write(result) 349 | else 350 | ostream.putc(TagResult) 351 | if resultMode == Serialized then 352 | ostream.write(result) 353 | else 354 | simple = @simple if simple.nil? 355 | writer = Writer.new(ostream, simple) 356 | writer.serialize(result) 357 | if byref then 358 | ostream.putc(TagArgument) 359 | writer.reset 360 | writer.write_list(args) 361 | end 362 | end 363 | end 364 | end while tag == TagCall 365 | ostream.putc(TagEnd) 366 | return response_end(ostream, context) 367 | end 368 | def do_function_list(context) 369 | ostream = StringIO.new 370 | writer = Writer.new(ostream, true) 371 | ostream.putc(TagFunctions) 372 | writer.write_list(@funcNames.values) 373 | ostream.putc(TagEnd) 374 | return response_end(ostream, context) 375 | end 376 | def handle(data, context) 377 | istream = nil 378 | begin 379 | @filters.reverse_each do | filter | 380 | data = filter.input_filter(data, context) 381 | end 382 | raise Exception.exception("Wrong Request: \r\n#{data}") if data.nil? or data.empty? or data[data.size - 1].ord != TagEnd 383 | istream = StringIO.new(data, 'rb') 384 | tag = istream.getbyte 385 | case tag 386 | when TagCall then return do_invoke(istream, context) 387 | when TagEnd then return do_function_list(context) 388 | else raise Exception.exception("Wrong Request: \r\n#{data}") 389 | end 390 | rescue ::Interrupt => e 391 | rescue ::Exception => e 392 | return do_error(e, context) 393 | ensure 394 | istream.close unless istream.nil? 395 | end 396 | end 397 | end # class Service 398 | end # module Hprose 399 | -------------------------------------------------------------------------------- /lib/hprose/io.rb: -------------------------------------------------------------------------------- 1 | ############################################################ 2 | # # 3 | # hprose # 4 | # # 5 | # Official WebSite: http://www.hprose.com/ # 6 | # http://www.hprose.org/ # 7 | # # 8 | ############################################################ 9 | 10 | ############################################################ 11 | # # 12 | # hprose/io.rb # 13 | # # 14 | # hprose io stream library for ruby # 15 | # # 16 | # LastModified: Mar 8, 2014 # 17 | # Author: Ma Bingyao # 18 | # # 19 | ############################################################ 20 | 21 | require 'stringio' 22 | require 'thread' 23 | require 'uuidtools' 24 | require 'hprose/common' 25 | 26 | class String 27 | def utf8? 28 | return false if unpack('U*').find { |e| e > 0x10ffff } rescue return false 29 | true 30 | end 31 | def ulength 32 | (a = unpack('U*')) rescue return -1 33 | return -1 if a.find { |e| e > 0x10ffff } 34 | a.size + a.find_all { |e| e > 0xffff }.size 35 | end 36 | alias usize ulength 37 | end 38 | 39 | include UUIDTools 40 | 41 | module Hprose 42 | module Tags 43 | # Serialize Tags 44 | TagInteger = ?i.ord 45 | TagLong = ?l.ord 46 | TagDouble = ?d.ord 47 | TagNull = ?n.ord 48 | TagEmpty = ?e.ord 49 | TagTrue = ?t.ord 50 | TagFalse = ?f.ord 51 | TagNaN = ?N.ord 52 | TagInfinity = ?I.ord 53 | TagDate = ?D.ord 54 | TagTime = ?T.ord 55 | TagUTC = ?Z.ord 56 | TagBytes = ?b.ord 57 | TagUTF8Char = ?u.ord 58 | TagString = ?s.ord 59 | TagGuid = ?g.ord 60 | TagList = ?a.ord 61 | TagMap = ?m.ord 62 | TagClass = ?c.ord 63 | TagObject = ?o.ord 64 | TagRef = ?r.ord 65 | # Serialize Marks 66 | TagPos = ?+.ord 67 | TagNeg = ?-.ord 68 | TagSemicolon = ?;.ord 69 | TagOpenbrace = ?{.ord 70 | TagClosebrace = ?}.ord 71 | TagQuote = ?".ord 72 | TagPoint = ?..ord 73 | # Protocol Tags 74 | TagFunctions = ?F.ord 75 | TagCall = ?C.ord 76 | TagResult = ?R.ord 77 | TagArgument = ?A.ord 78 | TagError = ?E.ord 79 | TagEnd = ?z.ord 80 | # Number Tags 81 | TagZero = ?0.ord 82 | TagNine = ?9.ord 83 | end # module Tags 84 | 85 | module Stream 86 | def readuntil(stream, char) 87 | s = StringIO.new 88 | while true do 89 | c = stream.getbyte 90 | break if c.nil? or (c == char) 91 | s.putc(c) 92 | end 93 | result = s.string 94 | s.close 95 | return result 96 | end 97 | def readint(stream, char) 98 | s = readuntil(stream, char) 99 | return 0 if s == '' 100 | return s.to_i 101 | end 102 | end # module Stream 103 | 104 | class ClassManager 105 | class << self 106 | private 107 | @@class_cache1 = {} 108 | @@class_cache2 = {} 109 | @@class_cache_lock = Mutex.new 110 | def get_class(name) 111 | name.split('.').inject(Object) {|x, y| x.const_get(y) } rescue return nil 112 | end 113 | def get_class2(name, ps, i, c) 114 | if i < ps.size then 115 | p = ps[i] 116 | name[p] = c 117 | cls = get_class2(name, ps, i + 1, '.') 118 | if (i + 1 < ps.size) and (cls.nil?) then 119 | cls = get_class2(name, ps, i + 1, '_') 120 | end 121 | return cls 122 | else 123 | return get_class(name) 124 | end 125 | end 126 | def get_class_by_alias(name) 127 | cls = nil 128 | if cls.nil? then 129 | ps = [] 130 | p = name.index('_') 131 | while not p.nil? 132 | ps.push(p) 133 | p = name.index('_', p + 1) 134 | end 135 | cls = get_class2(name, ps, 0, '.') 136 | if cls.nil? then 137 | cls = get_class2(name, ps, 0, '_') 138 | end 139 | end 140 | if cls.nil? then 141 | return Object.const_set(name.to_sym, Class.new) 142 | else 143 | return cls 144 | end 145 | end 146 | public 147 | def register(cls, aliasname) 148 | @@class_cache_lock.synchronize do 149 | @@class_cache1[cls] = aliasname 150 | @@class_cache2[aliasname] = cls 151 | end 152 | end 153 | 154 | def getClass(aliasname) 155 | return @@class_cache2[aliasname] if @@class_cache2.key?(aliasname) 156 | cls = get_class_by_alias(aliasname) 157 | register(cls, aliasname) 158 | return cls 159 | end 160 | 161 | def getClassAlias(cls) 162 | return @@class_cache1[cls] if @@class_cache1.key?(cls) 163 | if cls == Struct then 164 | aliasname = cls.to_s 165 | aliasname['Struct::'] = '' unless aliasname['Struct::'].nil? 166 | else 167 | aliasname = cls.to_s.split('::').join('_') 168 | end 169 | register(cls, aliasname) 170 | return aliasname 171 | end 172 | end 173 | end 174 | 175 | class RawReader 176 | private 177 | include Tags 178 | include Stream 179 | def read_number_raw(ostream) 180 | ostream.write(readuntil(@stream, TagSemicolon)) 181 | ostream.putc(TagSemicolon) 182 | end 183 | def read_datetime_raw(ostream) 184 | while true 185 | c = @stream.getbyte 186 | ostream.putc(c) 187 | break if (c == TagSemicolon) or (c == TagUTC) 188 | end 189 | end 190 | def read_utf8char_raw(ostream) 191 | c = @stream.getbyte 192 | ostream.putc(c) 193 | if (c & 0xE0) == 0xC0 then 194 | ostream.putc(@stream.getbyte()) 195 | elsif (c & 0xF0) == 0xE0 then 196 | ostream.write(@stream.read(2)) 197 | elsif c > 0x7F then 198 | raise Exception.exception('Bad utf-8 encoding') 199 | end 200 | end 201 | def read_bytes_raw(ostream) 202 | count = readuntil(@stream, TagQuote) 203 | ostream.write(count) 204 | ostream.putc(TagQuote) 205 | count = ((count == '') ? 0 : count.to_i) 206 | ostream.write(@stream.read(count + 1)) 207 | end 208 | def read_string_raw(ostream) 209 | count = readuntil(@stream, TagQuote) 210 | ostream.write(count) 211 | ostream.putc(TagQuote) 212 | count = ((count == '') ? 0 : count.to_i) 213 | i = 0 214 | while i < count 215 | c = @stream.getbyte 216 | ostream.putc(c) 217 | if (c & 0xE0) == 0xC0 then 218 | ostream.putc(@stream.getbyte()) 219 | elsif (c & 0xF0) == 0xE0 then 220 | ostream.write(@stream.read(2)) 221 | elsif (c & 0xF8) == 0xF0 then 222 | ostream.write(@stream.read(3)) 223 | i += 1 224 | end 225 | i += 1 226 | end 227 | ostream.putc(@stream.getbyte()) 228 | end 229 | def read_guid_raw(ostream) 230 | ostream.write(@stream.read(38)) 231 | end 232 | def read_complex_raw(ostream) 233 | ostream.write(readuntil(@stream, TagOpenbrace)) 234 | ostream.write(TagOpenbrace) 235 | tag = @stream.getbyte 236 | while tag != TagClosebrace 237 | read_raw(ostream, tag) 238 | tag = @stream.getbyte 239 | end 240 | ostream.putc(tag) 241 | end 242 | public 243 | def initialize(stream) 244 | @stream = stream 245 | end 246 | attr_accessor :stream 247 | def unexpected_tag(tag, expect_tag = nil) 248 | if tag.nil? then 249 | raise Exception.exception("No byte found in stream") 250 | elsif expect_tag.nil? then 251 | raise Exception.exception("Unexpected serialize tag '#{tag.chr}' in stream") 252 | else 253 | raise Exception.exception("Tag '#{expect_tag}' expected, but '#{tag.chr}' found in stream") 254 | end 255 | end 256 | def read_raw(ostream = nil, tag = nil) 257 | ostream = StringIO.new if ostream.nil? 258 | tag = @stream.getbyte if tag.nil? 259 | ostream.putc(tag) 260 | case tag 261 | when TagZero..TagNine, TagNull, TagEmpty, TagTrue, TagFalse, TagNaN then {} 262 | when TagInfinity then ostream.putc(@stream.getbyte()) 263 | when TagInteger, TagLong, TagDouble, TagRef then read_number_raw(ostream) 264 | when TagDate, TagTime then read_datetime_raw(ostream) 265 | when TagUTF8Char then read_utf8char_raw(ostream) 266 | when TagBytes then read_bytes_raw(ostream) 267 | when TagString then read_string_raw(ostream) 268 | when TagGuid then read_guid_raw(ostream) 269 | when TagList, TagMap, TagObject then read_complex_raw(ostream) 270 | when TagClass then read_complex_raw(ostream); read_raw(ostream) 271 | when TagError then read_raw(ostream) 272 | else unexpected_tag(tag) 273 | end 274 | return ostream 275 | end 276 | end # class RawReader 277 | 278 | class Reader < RawReader 279 | private 280 | class FakeReaderRefer 281 | def set(val) 282 | end 283 | def read(index) 284 | raise Exception.exception("Unexpected serialize tag 'r' in stream") 285 | end 286 | def reset 287 | end 288 | end 289 | class RealReaderRefer 290 | def initialize() 291 | @ref = [] 292 | end 293 | def set(val) 294 | @ref << val 295 | end 296 | def read(index) 297 | @ref[index] 298 | end 299 | def reset 300 | @ref.clear 301 | end 302 | end 303 | def read_integer_without_tag 304 | return readuntil(@stream, TagSemicolon).to_i 305 | end 306 | def read_long_without_tag 307 | return readuntil(@stream, TagSemicolon).to_i 308 | end 309 | def read_double_without_tag 310 | return readuntil(@stream, TagSemicolon).to_f 311 | end 312 | def read_infinity_without_tag 313 | return (@stream.getbyte == TagPos) ? 1.0/0.0 : -1.0/0.0 314 | end 315 | def read_utf8char_without_tag 316 | c = @stream.getbyte 317 | sio = StringIO.new 318 | sio.putc(c) 319 | if ((c & 0xE0) == 0xC0) then 320 | sio.putc(@stream.getbyte()) 321 | elsif ((c & 0xF0) == 0xE0) then 322 | sio.write(@stream.read(2)) 323 | elsif c > 0x7F then 324 | raise Exception.exception("Bad utf-8 encoding") 325 | end 326 | s = sio.string 327 | sio.close 328 | return s 329 | end 330 | def read_string_without_ref 331 | sio = StringIO.new 332 | count = readint(@stream, TagQuote) 333 | i = 0 334 | while i < count do 335 | c = @stream.getbyte 336 | sio.putc(c) 337 | if ((c & 0xE0) == 0xC0) then 338 | sio.putc(@stream.getbyte()) 339 | elsif ((c & 0xF0) == 0xE0) then 340 | sio.write(@stream.read(2)) 341 | elsif ((c & 0xF8) == 0xF0) then 342 | sio.write(@stream.read(3)) 343 | i += 1 344 | end 345 | i += 1 346 | end 347 | @stream.getbyte 348 | s = sio.string 349 | sio.close 350 | return s 351 | end 352 | def read_class 353 | cls = ClassManager.getClass(read_string_without_ref) 354 | count = readint(@stream, TagOpenbrace) 355 | fields = Array.new(count) { read_string } 356 | @stream.getbyte 357 | @classref << [cls, count, fields] 358 | end 359 | def read_usec 360 | usec = 0 361 | tag = @stream.getbyte 362 | if tag == TagPoint then 363 | usec = @stream.read(3).to_i * 1000 364 | tag = @stream.getbyte 365 | if (TagZero..TagNine) === tag then 366 | usec = usec + (tag << @stream.read(2)).to_i 367 | tag = @stream.getbyte 368 | if (TagZero..TagNine) === tag then 369 | @stream.read(2) 370 | tag = @stream.getbyte 371 | end 372 | end 373 | end 374 | return tag, usec 375 | end 376 | def read_ref 377 | return @refer.read(readint(@stream, TagSemicolon)) 378 | end 379 | public 380 | def initialize(stream, simple = false) 381 | super(stream) 382 | @classref = [] 383 | @refer = (simple ? FakeReaderRefer.new : RealReaderRefer.new) 384 | end 385 | def unserialize 386 | tag = @stream.getbyte 387 | return case tag 388 | when TagZero..TagNine then tag - TagZero 389 | when TagInteger then read_integer_without_tag 390 | when TagLong then read_long_without_tag 391 | when TagDouble then read_double_without_tag 392 | when TagNull then nil 393 | when TagEmpty then "" 394 | when TagTrue then true 395 | when TagFalse then false 396 | when TagNaN then 0.0/0.0 397 | when TagInfinity then read_infinity_without_tag 398 | when TagDate then read_date_without_tag 399 | when TagTime then read_time_without_tag 400 | when TagBytes then read_bytes_without_tag 401 | when TagUTF8Char then read_utf8char_without_tag 402 | when TagString then read_string_without_tag 403 | when TagGuid then read_guid_without_tag 404 | when TagList then read_list_without_tag 405 | when TagMap then read_map_without_tag 406 | when TagClass then read_class; read_object_without_tag 407 | when TagObject then read_object_without_tag 408 | when TagRef then read_ref 409 | when TagError then raise Exception.exception(read_string) 410 | else unexpected_tag(tag) 411 | end 412 | end 413 | def check_tag(expect_tag) 414 | tag = @stream.getbyte 415 | unexpected_tag(tag, expect_tag.chr) if tag != expect_tag 416 | end 417 | def check_tags(expect_tags) 418 | tag = @stream.getbyte 419 | unexpected_tag(tag, expect_tags.pack('c*')) unless expect_tags.include?(tag) 420 | return tag 421 | end 422 | def read_integer 423 | tag = @stream.getbyte 424 | return case tag 425 | when TagZero..TagNine then tag - TagZero 426 | when TagInteger then read_integer_without_tag 427 | else unexpected_tag(tag) 428 | end 429 | end 430 | def read_long 431 | tag = @stream.getbyte 432 | return case tag 433 | when TagZero..TagNine then tag - TagZero 434 | when TagInteger, TagLong then read_long_without_tag 435 | else unexpected_tag(tag) 436 | end 437 | end 438 | def read_double 439 | tag = @stream.getbyte 440 | return case tag 441 | when TagZero..TagNine then tag - TagZero 442 | when TagInteger, TagLong, TagDouble then read_double_without_tag 443 | when TagNaN then 0.0/0.0 444 | when TagInfinity then read_infinity_without_tag 445 | else unexpected_tag(tag) 446 | end 447 | end 448 | def read_boolean 449 | tag = check_tags([TagTrue, TagFalse]) 450 | return tag == TagTrue 451 | end 452 | def read_date_without_tag 453 | year = @stream.read(4).to_i 454 | month = @stream.read(2).to_i 455 | day = @stream.read(2).to_i 456 | tag = @stream.getbyte 457 | if tag == TagTime then 458 | hour = @stream.read(2).to_i 459 | min = @stream.read(2).to_i 460 | sec = @stream.read(2).to_i 461 | tag, usec = read_usec 462 | if tag == TagUTC then 463 | date = Time.utc(year, month, day, hour, min, sec, usec) 464 | else 465 | date = Time.local(year, month, day, hour, min, sec, usec) 466 | end 467 | elsif tag == TagUTC then 468 | date = Time.utc(year, month, day) 469 | else 470 | date = Time.local(year, month, day) 471 | end 472 | @refer.set(date) 473 | return date 474 | end 475 | def read_date 476 | tag = @stream.getbyte 477 | return case tag 478 | when TagNull then nil 479 | when TagRef then read_ref 480 | when TagDate then read_date_without_tag 481 | else unexpected_tag(tag) 482 | end 483 | end 484 | def read_time_without_tag 485 | hour = @stream.read(2).to_i 486 | min = @stream.read(2).to_i 487 | sec = @stream.read(2).to_i 488 | tag, usec = read_usec 489 | if tag == TagUTC then 490 | time = Time.utc(1970, 1, 1, hour, min, sec, usec) 491 | else 492 | time = Time.local(1970, 1, 1, hour, min, sec, usec) 493 | end 494 | @refer.set(time) 495 | return time 496 | end 497 | def read_time 498 | tag = @stream.getbyte 499 | return case tag 500 | when TagNull then nil 501 | when TagRef then read_ref 502 | when TagTime then read_time_without_tag 503 | else unexpected_tag(tag) 504 | end 505 | end 506 | def read_bytes_without_tag 507 | bytes = @stream.read(readint(@stream, TagQuote)) 508 | @stream.getbyte 509 | @refer.set(bytes) 510 | return bytes 511 | end 512 | def read_bytes 513 | tag = @stream.getbyte 514 | return case tag 515 | when TagNull then nil 516 | when TagEmpty then "" 517 | when TagRef then read_ref 518 | when TagBytes then read_bytes_without_tag 519 | else unexpected_tag(tag) 520 | end 521 | end 522 | def read_string_without_tag 523 | s = read_string_without_ref 524 | @refer.set(s) 525 | return s 526 | end 527 | def read_string 528 | tag = @stream.getbyte 529 | return case tag 530 | when TagNull then nil 531 | when TagEmpty then "" 532 | when TagUTF8Char then read_utf8char_without_tag 533 | when TagRef then read_ref 534 | when TagString then read_string_without_tag 535 | else unexpected_tag(tag) 536 | end 537 | end 538 | def read_guid_without_tag 539 | @stream.getbyte 540 | guid = UUID.parse(@stream.read(36)) 541 | @stream.getbyte 542 | @refer.set(guid) 543 | return guid 544 | end 545 | def read_guid 546 | tag = @stream.getbyte 547 | return case tag 548 | when TagNull then nil 549 | when TagRef then read_ref 550 | when TagGuid then read_guid_without_tag 551 | else unexpected_tag(tag) 552 | end 553 | end 554 | def read_list_without_tag 555 | count = readint(@stream, TagOpenbrace) 556 | list = Array.new(count) 557 | @refer.set(list) 558 | list.size.times do |i| 559 | list[i] = unserialize 560 | end 561 | @stream.getbyte 562 | return list 563 | end 564 | def read_list 565 | tag = @stream.getbyte 566 | return case tag 567 | when TagNull then nil 568 | when TagRef then read_ref 569 | when TagList then read_list_without_tag 570 | else unexpected_tag(tag) 571 | end 572 | end 573 | def read_map_without_tag 574 | map = {} 575 | @refer.set(map) 576 | readint(@stream, TagOpenbrace).times do 577 | k = unserialize 578 | v = unserialize 579 | map[k] = v 580 | end 581 | @stream.getbyte 582 | return map 583 | end 584 | def read_map 585 | tag = @stream.getbyte 586 | return case tag 587 | when TagNull then nil 588 | when TagRef then read_ref 589 | when TagMap then read_map_without_tag 590 | else unexpected_tag(tag) 591 | end 592 | end 593 | def read_object_without_tag 594 | cls, count, fields = @classref[readint(@stream, TagOpenbrace)] 595 | obj = cls.new 596 | @refer.set(obj) 597 | vars = obj.instance_variables 598 | count.times do |i| 599 | key = fields[i] 600 | var = '@' << key 601 | value = unserialize 602 | begin 603 | obj[key] = value 604 | rescue 605 | unless vars.include?(var) then 606 | cls.send(:attr_accessor, key) 607 | cls.send(:public, key, key + '=') 608 | end 609 | obj.instance_variable_set(var.to_sym, value) 610 | end 611 | end 612 | @stream.getbyte 613 | return obj 614 | end 615 | def read_object 616 | tag = @stream.getbyte 617 | return case tag 618 | when TagNull then nil 619 | when TagRef then read_ref 620 | when TagClass then read_class; read_object 621 | when TagObject then read_object_without_tag 622 | else unexpected_tag(tag) 623 | end 624 | end 625 | def reset 626 | @classref.clear 627 | @refer.reset 628 | end 629 | end # class Reader 630 | 631 | class Writer 632 | private 633 | include Tags 634 | include Stream 635 | class FakeWriterRefer 636 | def set(val) 637 | end 638 | def write(stream, val) 639 | false 640 | end 641 | def reset 642 | end 643 | end 644 | class RealWriterRefer 645 | include Tags 646 | def initialize() 647 | @ref = {} 648 | @refcount = 0 649 | end 650 | def set(val) 651 | @ref[val.object_id] = @refcount 652 | @refcount += 1 653 | end 654 | def write(stream, val) 655 | id = val.object_id 656 | if @ref.key?(id) then 657 | stream.putc(TagRef) 658 | stream.write(@ref[id].to_s) 659 | stream.putc(TagSemicolon) 660 | return true 661 | end 662 | return false 663 | end 664 | def reset 665 | @ref.clear 666 | @refcount = 0 667 | end 668 | end 669 | def write_class(classname, fields, vars) 670 | count = fields.size 671 | @stream.putc(TagClass) 672 | @stream.write(classname.ulength.to_s) 673 | @stream.putc(TagQuote) 674 | @stream.write(classname) 675 | @stream.putc(TagQuote) 676 | @stream.write(count.to_s) if count > 0 677 | @stream.putc(TagOpenbrace) 678 | fields.each { |field| write_string(field) } 679 | @stream.putc(TagClosebrace) 680 | index = @fieldsref.size 681 | @classref[classname] = index 682 | @fieldsref << [fields, vars] 683 | return index 684 | end 685 | def write_usec(usec) 686 | if usec > 0 then 687 | @stream.putc(TagPoint) 688 | @stream.write(usec.div(1000).to_s.rjust(3, '0')) 689 | @stream.write(usec.modulo(1000).to_s.rjust(3, '0')) if usec % 1000 > 0 690 | end 691 | end 692 | def write_ref(obj) 693 | return @refer.write(@stream, obj) 694 | end 695 | public 696 | def initialize(stream, simple = false) 697 | @stream = stream 698 | @classref = {} 699 | @fieldsref = [] 700 | @refer = (simple ? FakeWriterRefer.new : RealWriterRefer.new) 701 | end 702 | attr_accessor :stream 703 | def serialize(obj) 704 | case obj 705 | when NilClass then @stream.putc(TagNull) 706 | when FalseClass then @stream.putc(TagFalse) 707 | when TrueClass then @stream.putc(TagTrue) 708 | when Fixnum then write_integer(obj) 709 | when Bignum then write_long(obj) 710 | when Float then write_double(obj) 711 | when String then 712 | len = obj.length 713 | if len == 0 then 714 | @stream.putc(TagEmpty) 715 | elsif (len < 4) and (obj.ulength == 1) then 716 | write_utf8char(obj) 717 | elsif not write_ref(obj) then 718 | if obj.utf8? then 719 | write_string(obj) 720 | else 721 | write_bytes(obj) 722 | end 723 | end 724 | when Symbol then write_string_with_ref(obj) 725 | when UUID then write_guid_with_ref(obj) 726 | when Time then write_date_with_ref(obj) 727 | when Array, Range, MatchData then write_list_with_ref(obj) 728 | when Hash then write_map_with_ref(obj) 729 | when Binding, Class, Dir, Exception, IO, Numeric, 730 | Method, Module, Proc, Regexp, Thread, ThreadGroup then 731 | raise Exception.exception('This type is not supported to serialize') 732 | else write_object_with_ref(obj) 733 | end 734 | end 735 | def write_integer(integer) 736 | if (0..9) === integer then 737 | @stream.putc(integer.to_s) 738 | else 739 | @stream.putc((-2147483648..2147483647) === integer ? TagInteger : TagLong) 740 | @stream.write(integer.to_s) 741 | @stream.putc(TagSemicolon) 742 | end 743 | end 744 | def write_long(long) 745 | if (0..9) === long then 746 | @stream.putc(long.to_s) 747 | else 748 | @stream.putc(TagLong) 749 | @stream.write(long.to_s) 750 | @stream.putc(TagSemicolon) 751 | end 752 | end 753 | def write_double(double) 754 | if double.nan? then 755 | write_nan 756 | elsif double.finite? then 757 | @stream.putc(TagDouble) 758 | @stream.write(double.to_s) 759 | @stream.putc(TagSemicolon) 760 | else 761 | write_infinity(double > 0) 762 | end 763 | end 764 | def write_nan 765 | @stream.putc(TagNaN) 766 | end 767 | def write_infinity(positive = true) 768 | @stream.putc(TagInfinity) 769 | @stream.putc(positive ? TagPos : TagNeg) 770 | end 771 | def write_null 772 | @stream.putc(TagNull) 773 | end 774 | def write_empty 775 | @stream.putc(TagEmpty) 776 | end 777 | def write_boolean(bool) 778 | @stream.putc(bool ? TagTrue : TagFalse) 779 | end 780 | def write_date(time) 781 | @refer.set(time) 782 | if time.hour == 0 and time.min == 0 and time.sec == 0 and time.usec == 0 then 783 | @stream.putc(TagDate) 784 | @stream.write(time.strftime('%Y%m%d')) 785 | @stream.putc(time.utc? ? TagUTC : TagSemicolon) 786 | elsif time.year == 1970 and time.mon == 1 and time.day == 1 then 787 | @stream.putc(TagTime) 788 | @stream.write(time.strftime('%H%M%S')) 789 | write_usec(time.usec) 790 | @stream.putc(time.utc? ? TagUTC : TagSemicolon) 791 | else 792 | @stream.putc(TagDate) 793 | @stream.write(time.strftime('%Y%m%d' << TagTime << '%H%M%S')) 794 | write_usec(time.usec) 795 | @stream.putc(time.utc? ? TagUTC : TagSemicolon) 796 | end 797 | end 798 | def write_date_with_ref(time) 799 | write_date(time) unless write_ref(time) 800 | end 801 | alias write_time write_date 802 | alias write_time_with_ref write_date_with_ref 803 | def write_bytes(bytes) 804 | @refer.set(bytes) 805 | length = bytes.length 806 | @stream.putc(TagBytes) 807 | @stream.write(length.to_s) if length > 0 808 | @stream.putc(TagQuote) 809 | @stream.write(bytes) 810 | @stream.putc(TagQuote) 811 | end 812 | def write_bytes_with_ref(bytes) 813 | write_bytes(bytes) unless write_ref(bytes) 814 | end 815 | def write_utf8char(utf8char) 816 | @stream.putc(TagUTF8Char) 817 | @stream.write(utf8char) 818 | end 819 | def write_string(string) 820 | @refer.set(string) 821 | string = string.to_s 822 | length = string.ulength 823 | @stream.putc(TagString) 824 | @stream.write(length.to_s) if length > 0 825 | @stream.putc(TagQuote) 826 | @stream.write(string) 827 | @stream.putc(TagQuote) 828 | end 829 | def write_string_with_ref(string) 830 | write_string(string) unless write_ref(string) 831 | end 832 | def write_guid(guid) 833 | @refer.set(guid) 834 | @stream.putc(TagGuid) 835 | @stream.putc(TagOpenbrace) 836 | @stream.write(guid.to_s) 837 | @stream.putc(TagClosebrace) 838 | end 839 | def write_guid_with_ref(guid) 840 | write_guid(guid) unless write_ref(guid) 841 | end 842 | def write_list(list) 843 | @refer.set(list) 844 | list = list.to_a 845 | count = list.size 846 | @stream.putc(TagList) 847 | @stream.write(count.to_s) if count > 0 848 | @stream.putc(TagOpenbrace) 849 | count.times do |i| 850 | serialize(list[i]) 851 | end 852 | @stream.putc(TagClosebrace) 853 | end 854 | def write_list_with_ref(list) 855 | write_list(list) unless write_ref(list) 856 | end 857 | def write_map(map) 858 | @refer.set(map) 859 | size = map.size 860 | @stream.putc(TagMap) 861 | @stream.write(size.to_s) if size > 0 862 | @stream.putc(TagOpenbrace) 863 | map.each do |key, value| 864 | serialize(key) 865 | serialize(value) 866 | end 867 | @stream.putc(TagClosebrace) 868 | end 869 | def write_map_with_ref(map) 870 | write_map(map) unless write_ref(map) 871 | end 872 | def write_object(object) 873 | classname = ClassManager.getClassAlias(object.class) 874 | if @classref.key?(classname) then 875 | index = @classref[classname] 876 | fields, vars = @fieldsref[index] 877 | else 878 | if object.is_a?(Struct) then 879 | vars = nil 880 | fields = object.members 881 | else 882 | vars = object.instance_variables 883 | fields = vars.map { |var| var.to_s.delete('@') } 884 | end 885 | index = write_class(classname, fields, vars) 886 | end 887 | @stream.putc(TagObject) 888 | @stream.write(index.to_s) 889 | @stream.putc(TagOpenbrace) 890 | @refer.set(object) 891 | if vars.nil? then 892 | fields.each { |field| serialize(object[field]) } 893 | else 894 | vars.each { |var| serialize(object.instance_variable_get(var)) } 895 | end 896 | @stream.putc(TagClosebrace) 897 | end 898 | def write_object_with_ref(object) 899 | write_object(object) unless write_ref(object) 900 | end 901 | def reset 902 | @classref.clear 903 | @fieldsref.clear 904 | @refer.reset 905 | end 906 | end # class Writer 907 | 908 | class Formatter 909 | class << self 910 | def serialize(variable, simple = false) 911 | stream = StringIO.new 912 | writer = Writer.new(stream, simple) 913 | writer.serialize(variable) 914 | s = stream.string 915 | stream.close 916 | return s 917 | end 918 | def unserialize(variable_representation, simple = false) 919 | stream = StringIO.new(variable_representation, 'rb') 920 | reader = Reader.new(stream, simple) 921 | obj = reader.unserialize 922 | stream.close 923 | return obj 924 | end 925 | end # class 926 | end # class Formatter 927 | end # module Hprose 928 | --------------------------------------------------------------------------------