Permalink
Browse files

initial commit

  • Loading branch information...
0 parents commit b2d3c7637714f8adebb3074c864127b2eee566c6 @authorNari committed Nov 3, 2012
@@ -0,0 +1,17 @@
+*.gem
+*.rbc
+.bundle
+.config
+.yardoc
+Gemfile.lock
+InstalledFiles
+_yardoc
+coverage
+doc/
+lib/bundler/man
+pkg
+rdoc
+spec/reports
+test/tmp
+test/version_tmp
+tmp
@@ -0,0 +1,9 @@
+source 'https://rubygems.org'
+
+# Specify your gem's dependencies in msgpack-rpc-over-http.gemspec
+gemspec
+
+group :test do
+ gem 'test-unit', '~> 2.5.2'
+ gem 'thin'
+end
22 LICENSE
@@ -0,0 +1,22 @@
+Copyright (c) 2012 Narihiro Nakamura
+
+MIT License
+
+Permission is hereby granted, free of charge, to any person obtaining
+a copy of this software and associated documentation files (the
+"Software"), to deal in the Software without restriction, including
+without limitation the rights to use, copy, modify, merge, publish,
+distribute, sublicense, and/or sell copies of the Software, and to
+permit persons to whom the Software is furnished to do so, subject to
+the following conditions:
+
+The above copyright notice and this permission notice shall be
+included in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,97 @@
+# MessagePack-RPC over HTTP (Ruby)
+
+This library provides [MessagePack-RPC](https://github.com/msgpack/msgpack-rpc) via HTTP as XML-RPC.
+The original MessagePack-RPC Server in Ruby is not good in some cases.
+It doesn't scale. It's incompatible with Thread. There is no decent termination...
+
+We alreadly have various high perfomance HTTP servers.
+We can use these in MessagePack-RPC over HTTP.
+
+**CAUTION**
+
+There is no compatibility with other implementation of normal MessagePack-RPC (not over HTTP).
+So you can not connect a normal RPC client to a RPC server over HTTP.
+
+## Usage
+
+**Server**
+
+confir.ru:
+```ruby
+require 'msgpack-rpc-over-http'
+class MyHandler
+ def add(x,y) return x+y end
+end
+
+run MessagePack::RPCOverHTTP::Server.app(MyHandler.new)
+```
+
+rackup:
+```ruby
+% rackup config.ru -s thin
+>> Thin web server (v1.5.0 codename Knife)
+>> Maximum connections set to 1024
+>> Listening on 0.0.0.0:9292, CTRL+C to stop
+```
+
+**Client**
+
+client.rb:
+```ruby
+require 'msgpack-rpc-over-http'
+c = MessagePack::RPCOverHTTP::Client.new("http://0.0.0.0:9292/")
+result = c.call(:add, 1, 2) #=> 3
+```
+
+## Extended futures
+
+Support streaming response via Chunked Transfer-Encoding.
+
+```ruby
+# server side
+class Handler
+ include MessagePack::RPCOverHTTP::Server::Streamer
+ def log
+ return stream do
+ File.open('/var/log/syslog') do |f|
+ while line = f.gets.chomp
+ # write a chunked data
+ chunk(line)
+ end
+ end
+ end
+ end
+end
+
+# client
+client = MessagePack::RPCOverHTTP::Client.new("http://0.0.0.0:80/")
+client.stream do |line|
+ p line # => "Nov 3 ..."
+end
+```
+
+## Installation
+
+Add this line to your application's Gemfile:
+
+ gem 'msgpack-rpc-over-http'
+
+And then execute:
+
+ $ bundle
+
+Or install it yourself as:
+
+ $ gem install msgpack-rpc-over-http
+
+## Usage
+
+TODO: Write usage instructions here
+
+## Contributing
+
+1. Fork it
+2. Create your feature branch (`git checkout -b my-new-feature`)
+3. Commit your changes (`git commit -am 'Added some feature'`)
+4. Push to the branch (`git push origin my-new-feature`)
+5. Create new Pull Request
@@ -0,0 +1,2 @@
+#!/usr/bin/env rake
+require "bundler/gem_tasks"
@@ -0,0 +1,16 @@
+require 'msgpack'
+require_relative "msgpack/rpc_over_http/version"
+require_relative "msgpack/rpc_over_http/error"
+require_relative "msgpack/rpc_over_http/server"
+require_relative "msgpack/rpc_over_http/client"
+
+module MessagePack
+ module RPCOverHTTP
+ REQUEST = 0 # [0, msgid, method, param]
+ RESPONSE = 1 # [1, msgid, error, result]
+ NOTIFY = 2 # [2, method, param]
+
+ NO_METHOD_ERROR = 0x01;
+ ARGUMENT_ERROR = 0x02;
+ end
+end
@@ -0,0 +1,111 @@
+require 'celluloid'
+require 'httpclient'
+require 'forwardable'
+
+module MessagePack
+ module RPCOverHTTP
+
+ # Cliet for MessagePack-RPC over HTTP.
+ class Client
+ extend Forwardable
+
+ HEADER = {"Content-Type" => 'text/plain'}
+
+ def initialize(url, options={})
+ @url = url
+ @client = HTTPClient.new
+ @reqtable = {}
+ @seqid = 0
+ end
+
+ def_delegators(:@client,
+ :connect_timeout, :send_timeout, :receive_timeout)
+
+ # call-seq:
+ # call(symbol, *args) -> result of remote method
+ #
+ # Calls remote method.
+ # This method is same as call_async(method, *args).value
+ def call(method, *args)
+ return send_request(method, args)
+ end
+
+ # call-seq:
+ # call_async(symbol, *args) -> Celluloid::Future
+ #
+ # Calls remote method asynchronously.
+ # This method is non-blocking and returns Future.
+ def call_async(method, *args)
+ return Celluloid::Future.new{ send_request(method, args) }
+ end
+
+ # call-seq:
+ # callback(symbol, *args) {|res, err| } -> Celluloid::Future
+ #
+ # Calls remote method asynchronously.
+ # The callback method is called with Future when the result is reached.
+ # `err' is assigned a instance of RemoteError or child if res is nil.
+ def callback(method, *args, &block)
+ return Celluloid::Future.new do
+ begin
+ block.call(send_request(method, args))
+ rescue RemoteError => ex
+ block.call(nil, ex)
+ end
+ end
+ end
+
+ # call-seq:
+ # stream(symbol, *args) {|chunk| }
+ #
+ # Calls remote method with streaming.
+ # Remote method have to return a chunked response.
+ def stream(method, *args, &block)
+ data = create_request_body(method, args)
+ @client.post_content(@url, :body => data, :header => HEADER) do |chunk|
+ begin
+ block.call(get_result(chunk))
+ rescue RemoteError => ex
+ block.call(nil, ex)
+ end
+ end
+ end
+
+ # call-seq:
+ # stream_async(symbol, *args) {|chunk| } -> Celluloid::Future
+ #
+ # Calls remote method asynchronously with streaming.
+ def stream_async(method, *args, &block)
+ return Celluloid::Future.new do
+ stream(method, *args, &block)
+ end
+ end
+
+ private
+ def send_request(method, param)
+ data = create_request_body(method, param)
+ body = @client.post_content(@url, :body => data, :header => HEADER)
+ return get_result(body)
+ end
+
+ def create_request_body(method, param)
+ method = method.to_s
+ msgid = @seqid
+ @seqid += 1
+ @seqid = 0 if @seqid >= (1 << 31)
+ data = [REQUEST, msgid, method, param].to_msgpack
+ end
+
+ def get_result(body)
+ type, msgid, err, res = MessagePack.unpack(body)
+ raise "Unknown message type #{type}" if type != RESPONSE
+
+ if err.nil?
+ return res
+ else
+ raise RemoteError.create(err, res)
+ end
+ end
+ end
+ end
+end
@@ -0,0 +1,44 @@
+module MessagePack
+
+ ##
+ ## MessagePack-RPCOverHTTP Exception
+ ##
+ #
+ # RemoteError
+ # |
+ # +-- RuntimeError
+ # |
+ # +-- (user-defined errors)
+ #
+ module RPCOverHTTP
+ class RemoteError < StandardError
+ def initialize(code, *data)
+ @code = code.to_s
+ @data = data
+ super(@data.shift || @code)
+ end
+
+ attr_reader :code
+ attr_reader :data
+
+ def self.create(code, data)
+ error_class = constantize(code)
+ if error_class < RemoteError
+ error_class.new(code, *data)
+ else
+ self.new(code, *data)
+ end
+ end
+
+ private
+ def self.constantize(name)
+ return name.split("::").inject(Object) do |memo, i|
+ memo.const_get(i)
+ end
+ end
+ end
+
+ class RuntimeError < RemoteError
+ end
+ end
+end
@@ -0,0 +1,25 @@
+require 'rack'
+require 'rack/builder'
+require_relative 'server/dispatcher'
+require_relative 'server/request_unpacker'
+require_relative 'server/response_packer'
+require_relative 'server/streamer'
+
+module MessagePack
+ module RPCOverHTTP
+ class Server
+
+ # Retruns the application for MessagePack-RPC.
+ # It's create with Rack::Builder
+ def self.app(handler)
+ return Rack::Builder.app do
+ use Rack::Chunked
+ use RequestUnpacker
+ use ResponsePacker
+ use Dispatcher
+ run handler
+ end
+ end
+ end
+ end
+end
@@ -0,0 +1,24 @@
+module MessagePack
+ module RPCOverHTTP
+ class Server
+
+ # Dispatcher of user-defined handler.
+ class Dispatcher
+ def initialize(handler, accept=handler.public_methods)
+ @handler = handler
+ @accept = accept
+ end
+
+ def call(env)
+ method = env['msgpack-rpc.method']
+ params = env['msgpack-rpc.params']
+ unless @accept.include?(method)
+ raise NoMethodError, "method `#{method}' is not accepted"
+ end
+
+ return @handler.send(method, *params)
+ end
+ end
+ end
+ end
+end
Oops, something went wrong.

0 comments on commit b2d3c76

Please sign in to comment.