protoplasm-blocking-client 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/.gitignore +4 -0
- data/Gemfile +7 -0
- data/README.md +101 -0
- data/Rakefile +73 -0
- data/lib/protoplasm.rb +9 -0
- data/lib/protoplasm/client/blocking_client.rb +57 -0
- data/lib/protoplasm/server/em_server.rb +81 -0
- data/lib/protoplasm/types/types.rb +62 -0
- data/lib/protoplasm/version.rb +3 -0
- data/protoplasm-blocking-client.gemspec +25 -0
- data/protoplasm-em-server.gemspec +26 -0
- data/test/protoplasm_test.rb +46 -0
- data/test/test_helper.rb +122 -0
- metadata +99 -0
data/.gitignore
ADDED
data/Gemfile
ADDED
data/README.md
ADDED
@@ -0,0 +1,101 @@
|
|
1
|
+
# Protoplasm
|
2
|
+
|
3
|
+
Protoplasm makes is easy to define an RPC server/client which is backed by protobuf through Beefcake.
|
4
|
+
|
5
|
+
## Defining your service endpoints
|
6
|
+
|
7
|
+
The current service model is very simple. You can send only one type of protobuf object (the request object). This object must have an enum and a series of optional command fields to allow it to send commands. Here is an example of how to create a request object type:
|
8
|
+
|
9
|
+
```ruby
|
10
|
+
class Command
|
11
|
+
include Beefcake::Message
|
12
|
+
module Type
|
13
|
+
PING = 1
|
14
|
+
UPCASE = 2
|
15
|
+
end
|
16
|
+
required :type, Type, 1
|
17
|
+
optional :ping_command, PingCommand, 2
|
18
|
+
optional :upcase_command, UpcaseCommand, 3
|
19
|
+
end
|
20
|
+
```
|
21
|
+
|
22
|
+
In this case, your request object would be able to accept one, and only one subcommand object. Those types are `PingCommand`, `UpcaseCommand` and `EvenCommand`.
|
23
|
+
|
24
|
+
So, in order to mark this `Command` class as your request class, you'd do the following:
|
25
|
+
|
26
|
+
```ruby
|
27
|
+
module Types
|
28
|
+
include Protoplasm::Types
|
29
|
+
|
30
|
+
# .. your actual classes would go here
|
31
|
+
|
32
|
+
request_class Command
|
33
|
+
request_type_field :type
|
34
|
+
end
|
35
|
+
```
|
36
|
+
|
37
|
+
## Defining your response objects
|
38
|
+
|
39
|
+
Every subcommand can choose to relay back no objects, one object, or stream any number of objects. Those objects must all be of the same type.
|
40
|
+
|
41
|
+
To define which objects you expect back, you must add the following.
|
42
|
+
|
43
|
+
```ruby
|
44
|
+
module Types
|
45
|
+
rpc_map Command::Type::PING, :ping_command, nil
|
46
|
+
rpc_map Command::Type::UPCASE, :upcase_command, UpcaseResponse
|
47
|
+
rpc_map Command::Type::EVEN, :even_command, EvenResponse, :streaming => true
|
48
|
+
```
|
49
|
+
|
50
|
+
In this case, this would define the ping command as returning no object, the upcase command returns a single object, of type `UpcaseResponse`, and the even command return any number of `EvenResponse` objects.
|
51
|
+
|
52
|
+
## Server implementation
|
53
|
+
|
54
|
+
Currently there is a single server implementation `EMServer`, which defines a non-blocking EventMachine based server. To create an `EMServer`, you subclass `Protoplasm::EMServer` and setup handlers for each of your command types. For example, a worker server could look like this:
|
55
|
+
|
56
|
+
```ruby
|
57
|
+
class Server < Protoplasm::EMServer
|
58
|
+
def process_ping_command(ping_command)
|
59
|
+
# do nothing
|
60
|
+
end
|
61
|
+
|
62
|
+
def process_upcase_command(upcase_command)
|
63
|
+
send_response(:response => cmd.word.upcase)
|
64
|
+
end
|
65
|
+
|
66
|
+
def process_even_command(even_command)
|
67
|
+
(1..even_command.top).each do |num|
|
68
|
+
send_response(:num => num) if num % 2 == 0
|
69
|
+
end
|
70
|
+
finish_streaming
|
71
|
+
end
|
72
|
+
end
|
73
|
+
```
|
74
|
+
|
75
|
+
This server then could be started with `Server.start(3000)` which would start on port 3000 and process requests.
|
76
|
+
|
77
|
+
## Client
|
78
|
+
|
79
|
+
Currently there is a single client implementation: `BlockingClient`. It defines a blocking `TCPSocket` based client. To create a client for this example, you would do the following.
|
80
|
+
|
81
|
+
```ruby
|
82
|
+
class Client < Protoplasm::BlockingClient
|
83
|
+
def initialize(host, port)
|
84
|
+
super(Types, host, port)
|
85
|
+
end
|
86
|
+
|
87
|
+
def ping
|
88
|
+
send_request(:ping_command)
|
89
|
+
end
|
90
|
+
|
91
|
+
def upcase(word)
|
92
|
+
send_request(:upcase_command, :word => word).response
|
93
|
+
end
|
94
|
+
|
95
|
+
def evens(top)
|
96
|
+
send_request(:even_command, :top => top) { |resp| yield resp.num }
|
97
|
+
end
|
98
|
+
end
|
99
|
+
```
|
100
|
+
|
101
|
+
Look at the full example under `test/test_helper.rb`.
|
data/Rakefile
ADDED
@@ -0,0 +1,73 @@
|
|
1
|
+
require 'bundler'
|
2
|
+
require 'rake/testtask'
|
3
|
+
require './lib/protoplasm/version'
|
4
|
+
|
5
|
+
task :test do
|
6
|
+
Rake::TestTask.new do |t|
|
7
|
+
Dir['test/*_test.rb'].each{|f| require File.expand_path(f)}
|
8
|
+
end
|
9
|
+
end
|
10
|
+
|
11
|
+
def version
|
12
|
+
Protoplasm::VERSION
|
13
|
+
end
|
14
|
+
|
15
|
+
def version_tag
|
16
|
+
"v#{version}"
|
17
|
+
end
|
18
|
+
|
19
|
+
def tag_version
|
20
|
+
system("git tag -a -m \"Version #{version}\" #{version_tag}") or raise("Cannot tag version")
|
21
|
+
Bundler.ui.confirm "Tagged #{version_tag}"
|
22
|
+
yield
|
23
|
+
rescue
|
24
|
+
Bundler.ui.error "Untagged #{version_tag} due to error"
|
25
|
+
system("git tag -d #{version_tag}") or raise("Cannot untag version")
|
26
|
+
raise
|
27
|
+
end
|
28
|
+
|
29
|
+
desc "Release client & server (#{version})"
|
30
|
+
task :release do
|
31
|
+
tag_version do
|
32
|
+
Rake::Task["em_server:release_without_tagging"].invoke
|
33
|
+
Rake::Task["blocking_client:release_without_tagging"].invoke
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
desc "Install client & server (#{version})"
|
38
|
+
task :install do
|
39
|
+
Rake::Task["em_server:install"].invoke
|
40
|
+
Rake::Task["blocking_client:install"].invoke
|
41
|
+
end
|
42
|
+
|
43
|
+
desc "Build client & server (#{version})"
|
44
|
+
task :build do
|
45
|
+
Rake::Task["em_server:build"].invoke
|
46
|
+
Rake::Task["blocking_client:build"].invoke
|
47
|
+
end
|
48
|
+
|
49
|
+
namespace :em_server do
|
50
|
+
helper = Bundler::GemHelper.new(File.dirname(__FILE__), "protoplasm-em-server")
|
51
|
+
helper.install
|
52
|
+
helper.instance_eval do
|
53
|
+
task :release_without_tagging do
|
54
|
+
guard_clean
|
55
|
+
built_gem_path = build_gem
|
56
|
+
git_push
|
57
|
+
rubygem_push(built_gem_path)
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
namespace :blocking_client do
|
63
|
+
helper = Bundler::GemHelper.new(File.dirname(__FILE__), "protoplasm-blocking-client")
|
64
|
+
helper.install
|
65
|
+
helper.instance_eval do
|
66
|
+
task :release_without_tagging do
|
67
|
+
guard_clean
|
68
|
+
built_gem_path = build_gem
|
69
|
+
git_push
|
70
|
+
rubygem_push(built_gem_path)
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
data/lib/protoplasm.rb
ADDED
@@ -0,0 +1,9 @@
|
|
1
|
+
require "protoplasm/version"
|
2
|
+
|
3
|
+
# Protoplasm
|
4
|
+
# This defines BlockingClient and EMServer.
|
5
|
+
module Protoplasm
|
6
|
+
autoload :BlockingClient, "protoplasm/client/blocking_client"
|
7
|
+
autoload :EMServer, "protoplasm/server/em_server"
|
8
|
+
autoload :Types, "protoplasm/types/types"
|
9
|
+
end
|
@@ -0,0 +1,57 @@
|
|
1
|
+
require 'socket'
|
2
|
+
|
3
|
+
module Protoplasm
|
4
|
+
class BlockingClient
|
5
|
+
def self.for_types(types)
|
6
|
+
cls = Class.new(self)
|
7
|
+
cls.class_eval do
|
8
|
+
(class << self; self; end).send(:define_method, :_types) { types }
|
9
|
+
end
|
10
|
+
cls
|
11
|
+
end
|
12
|
+
|
13
|
+
private
|
14
|
+
def host_port
|
15
|
+
raise "Must be implemented by the client class"
|
16
|
+
end
|
17
|
+
|
18
|
+
def _socket
|
19
|
+
host, port = host_port
|
20
|
+
@_socket ||= TCPSocket.open(host, port)
|
21
|
+
end
|
22
|
+
|
23
|
+
def send_request(field, *args, &blk)
|
24
|
+
s = ''
|
25
|
+
type = self.class._types.request_type_for_field(field)
|
26
|
+
cmd_class = type.command_class.fields.values.find{|f| f.name == field}
|
27
|
+
cmd = self.class._types.request_class.new(self.class._types.request_type_field => type.type, type.field => cmd_class.type.new(*args))
|
28
|
+
cmd.encode(s)
|
29
|
+
socket = _socket
|
30
|
+
socket.write([0, s.size].pack("CQ"))
|
31
|
+
socket.write s
|
32
|
+
socket.flush
|
33
|
+
fetch_objects = true
|
34
|
+
obj = nil
|
35
|
+
while fetch_objects
|
36
|
+
response_code = socket.readpartial(1).unpack("C").first
|
37
|
+
case response_code
|
38
|
+
when Types::Response::NORMAL
|
39
|
+
fetch_objects = !type.void?
|
40
|
+
if fetch_objects
|
41
|
+
len_buf = ''
|
42
|
+
socket.readpartial(8 - len_buf.size, len_buf) while len_buf.size != 8
|
43
|
+
len = len_buf.unpack("Q").first
|
44
|
+
buf = ''
|
45
|
+
socket.readpartial(len - buf.size, buf) until buf.size == len
|
46
|
+
obj = type.response_class.decode(buf)
|
47
|
+
yield obj if block_given?
|
48
|
+
end
|
49
|
+
fetch_objects = false unless type.streaming?
|
50
|
+
when Types::Response::STOP_STREAMING
|
51
|
+
fetch_objects = false
|
52
|
+
end
|
53
|
+
end
|
54
|
+
obj
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
@@ -0,0 +1,81 @@
|
|
1
|
+
require 'eventmachine'
|
2
|
+
|
3
|
+
module Protoplasm
|
4
|
+
class EMServer < EventMachine::Connection
|
5
|
+
def self.for_types(types)
|
6
|
+
cls = Class.new(self)
|
7
|
+
cls.class_eval do
|
8
|
+
(class << self; self; end).send(:define_method, :_types) { types }
|
9
|
+
end
|
10
|
+
cls
|
11
|
+
end
|
12
|
+
|
13
|
+
|
14
|
+
def self.start(port)
|
15
|
+
if EM.reactor_running?
|
16
|
+
EM::start_server("0.0.0.0", port, self) do |srv|
|
17
|
+
yield srv if block_given?
|
18
|
+
end
|
19
|
+
else
|
20
|
+
begin
|
21
|
+
EM.run do
|
22
|
+
start(port)
|
23
|
+
end
|
24
|
+
rescue Interrupt
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
def post_init
|
30
|
+
@_response_types = []
|
31
|
+
@data = ''
|
32
|
+
end
|
33
|
+
|
34
|
+
def receive_data(data)
|
35
|
+
@data << data
|
36
|
+
data_ready
|
37
|
+
end
|
38
|
+
|
39
|
+
def finish_streaming
|
40
|
+
@_response_types.shift
|
41
|
+
send_data [Types::Response::STOP_STREAMING].pack("C")
|
42
|
+
end
|
43
|
+
|
44
|
+
def send_void
|
45
|
+
@_response_types.shift
|
46
|
+
send_data [Types::Response::NORMAL].pack("C")
|
47
|
+
end
|
48
|
+
|
49
|
+
def data_ready
|
50
|
+
@control = @data.slice!(0, 1).unpack("C").first unless @control
|
51
|
+
case @control
|
52
|
+
when Types::Request::NORMAL
|
53
|
+
@size = @data.slice!(0, 8).unpack("Q").first unless @size
|
54
|
+
if @data.size >= @size
|
55
|
+
buf = @data.slice!(0, @size)
|
56
|
+
@size, @control = nil, nil
|
57
|
+
obj = self.class._types.request_class.decode(buf)
|
58
|
+
type = self.class._types.request_type_for_request(obj)
|
59
|
+
@_response_types << type
|
60
|
+
EM.next_tick do
|
61
|
+
send(:"process_#{type.field}", obj.send(type.field))
|
62
|
+
end
|
63
|
+
data_ready unless @data.empty?
|
64
|
+
end
|
65
|
+
else
|
66
|
+
# illegal char
|
67
|
+
close_connection
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
def send_response(*args)
|
72
|
+
type = @_response_types.first
|
73
|
+
@_response_types.shift unless type.streaming?
|
74
|
+
obj = type.response_class.new(*args)
|
75
|
+
s = ''
|
76
|
+
obj.encode(s)
|
77
|
+
send_data [Types::Response::NORMAL, s.size].pack("CQ")
|
78
|
+
send_data s
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
@@ -0,0 +1,62 @@
|
|
1
|
+
require 'beefcake'
|
2
|
+
|
3
|
+
module Protoplasm
|
4
|
+
module Types
|
5
|
+
module Request
|
6
|
+
NORMAL = 0
|
7
|
+
end
|
8
|
+
|
9
|
+
module Response
|
10
|
+
NORMAL = 0
|
11
|
+
STOP_STREAMING = 10
|
12
|
+
end
|
13
|
+
|
14
|
+
def self.included(cls)
|
15
|
+
cls.extend(ClassMethods)
|
16
|
+
end
|
17
|
+
|
18
|
+
class RequestResponseType < Struct.new(:request_class, :response_class, :type, :field, :streaming)
|
19
|
+
|
20
|
+
alias_method :streaming?, :streaming
|
21
|
+
|
22
|
+
def command_class
|
23
|
+
request_class
|
24
|
+
end
|
25
|
+
|
26
|
+
def void?
|
27
|
+
response_class.nil?
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
module ClassMethods
|
32
|
+
def request_class(request_class = nil)
|
33
|
+
request_class ? @request_class = request_class : @request_class
|
34
|
+
end
|
35
|
+
|
36
|
+
def request_type(request_obj)
|
37
|
+
request_obj.send(@request_type_field)
|
38
|
+
end
|
39
|
+
|
40
|
+
def request_type_field(field = nil)
|
41
|
+
field ? @request_type_field = field : @request_type_field
|
42
|
+
end
|
43
|
+
|
44
|
+
def rpc_map(type, field, response_class, opts = nil)
|
45
|
+
@response_map_by_field ||= {}
|
46
|
+
@response_map_by_type ||= {}
|
47
|
+
streaming = opts && opts.key?(:streaming) ? opts[:streaming] : false
|
48
|
+
rrt = RequestResponseType.new(@request_class, response_class, type, field, streaming)
|
49
|
+
@response_map_by_field[field] = rrt
|
50
|
+
@response_map_by_type[type] = rrt
|
51
|
+
end
|
52
|
+
|
53
|
+
def request_type_for_field(field)
|
54
|
+
@response_map_by_field[field]
|
55
|
+
end
|
56
|
+
|
57
|
+
def request_type_for_request(req)
|
58
|
+
@response_map_by_type[req.send(@request_type_field)]
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
$:.push File.expand_path("../lib", __FILE__)
|
3
|
+
require "protoplasm/version"
|
4
|
+
|
5
|
+
Gem::Specification.new do |s|
|
6
|
+
s.name = "protoplasm-blocking-client"
|
7
|
+
s.version = Protoplasm::VERSION
|
8
|
+
s.authors = ["Josh Hull"]
|
9
|
+
s.email = ["joshbuddy@gmail.com"]
|
10
|
+
s.homepage = "https://github.com/bazaarlabs/protoplasm"
|
11
|
+
s.summary = %q{A blocking client for a Protoplasm server}
|
12
|
+
s.description = %q{A blocking client for a Protoplasm server.}
|
13
|
+
|
14
|
+
s.rubyforge_project = "protoplasm-blocking-client"
|
15
|
+
|
16
|
+
s.files = `git ls-files`.split("\n")
|
17
|
+
s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
|
18
|
+
s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
|
19
|
+
s.require_paths = ["lib"]
|
20
|
+
|
21
|
+
s.add_dependency "beefcake", "~> 0.3.7"
|
22
|
+
|
23
|
+
s.add_development_dependency 'rake'
|
24
|
+
s.add_development_dependency 'minitest', "~> 2.6.1"
|
25
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
$:.push File.expand_path("../lib", __FILE__)
|
3
|
+
require "protoplasm/version"
|
4
|
+
|
5
|
+
Gem::Specification.new do |s|
|
6
|
+
s.name = "protoplasm-em-server"
|
7
|
+
s.version = Protoplasm::VERSION
|
8
|
+
s.authors = ["Josh Hull"]
|
9
|
+
s.email = ["joshbuddy@gmail.com"]
|
10
|
+
s.homepage = "https://github.com/bazaarlabs/protoplasm"
|
11
|
+
s.summary = %q{A protoplasm server backed by EventMachine}
|
12
|
+
s.description = %q{A protoplasm server backed by EventMachine.}
|
13
|
+
|
14
|
+
s.rubyforge_project = "protoplasm-em-server"
|
15
|
+
|
16
|
+
s.files = `git ls-files`.split("\n")
|
17
|
+
s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
|
18
|
+
s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
|
19
|
+
s.require_paths = ["lib"]
|
20
|
+
|
21
|
+
s.add_dependency "eventmachine"
|
22
|
+
s.add_dependency "beefcake", "~> 0.3.7"
|
23
|
+
|
24
|
+
s.add_development_dependency 'rake'
|
25
|
+
s.add_development_dependency 'minitest', "~> 2.6.1"
|
26
|
+
end
|
@@ -0,0 +1,46 @@
|
|
1
|
+
require File.expand_path('../test_helper', __FILE__)
|
2
|
+
|
3
|
+
describe "Protoplasm test server" do
|
4
|
+
it "should ping" do
|
5
|
+
with_proto_server(ProtoplasmTest::EMServer) do |port|
|
6
|
+
client = ProtoplasmTest::Client.new('127.0.0.1', port)
|
7
|
+
client.ping
|
8
|
+
pass
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
it "should upcase" do
|
13
|
+
with_proto_server(ProtoplasmTest::EMServer) do |port|
|
14
|
+
client = ProtoplasmTest::Client.new('127.0.0.1', port)
|
15
|
+
assert_equal "LOWER", client.upcase('lower')
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
it "should give you even numbers" do
|
20
|
+
with_proto_server(ProtoplasmTest::EMServer) do |port|
|
21
|
+
client = ProtoplasmTest::Client.new('127.0.0.1', port)
|
22
|
+
nums = []
|
23
|
+
client.evens(10) do |resp|
|
24
|
+
nums << resp
|
25
|
+
end
|
26
|
+
assert_equal [2, 4, 6, 8], nums
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
it "should allow multiple calls" do
|
31
|
+
with_proto_server(ProtoplasmTest::EMServer) do |port|
|
32
|
+
client = ProtoplasmTest::Client.new('127.0.0.1', port)
|
33
|
+
client.ping
|
34
|
+
assert_equal "LOWER", client.upcase('lower')
|
35
|
+
assert_equal "UPPER", client.upcase('upper')
|
36
|
+
nums = []
|
37
|
+
client.evens(10) do |resp|
|
38
|
+
nums << resp
|
39
|
+
end
|
40
|
+
assert_equal [2, 4, 6, 8], nums
|
41
|
+
client.ping
|
42
|
+
assert_equal "LOWER", client.upcase('lower')
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
end
|
data/test/test_helper.rb
ADDED
@@ -0,0 +1,122 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'minitest/autorun'
|
3
|
+
require 'protoplasm'
|
4
|
+
require 'timeout'
|
5
|
+
|
6
|
+
class ProtoplasmTest
|
7
|
+
module Types
|
8
|
+
include Protoplasm::Types
|
9
|
+
|
10
|
+
class PingCommand
|
11
|
+
include Beefcake::Message
|
12
|
+
end
|
13
|
+
|
14
|
+
class UpcaseCommand
|
15
|
+
include Beefcake::Message
|
16
|
+
required :word, :string, 1
|
17
|
+
end
|
18
|
+
|
19
|
+
class EvenCommand
|
20
|
+
include Beefcake::Message
|
21
|
+
required :top, :uint32, 1
|
22
|
+
end
|
23
|
+
|
24
|
+
class UpcaseResponse
|
25
|
+
include Beefcake::Message
|
26
|
+
required :response, :string, 1
|
27
|
+
end
|
28
|
+
|
29
|
+
class EvenResponse
|
30
|
+
include Beefcake::Message
|
31
|
+
required :num, :uint32, 1
|
32
|
+
end
|
33
|
+
|
34
|
+
class Command
|
35
|
+
include Beefcake::Message
|
36
|
+
module Type
|
37
|
+
PING = 1
|
38
|
+
UPCASE = 2
|
39
|
+
EVEN = 3
|
40
|
+
end
|
41
|
+
required :type, Type, 1
|
42
|
+
optional :ping_command, PingCommand, 2
|
43
|
+
optional :upcase_command, UpcaseCommand, 3
|
44
|
+
optional :even_command, EvenCommand, 4
|
45
|
+
end
|
46
|
+
|
47
|
+
request_class Command
|
48
|
+
request_type_field :type
|
49
|
+
rpc_map Command::Type::PING, :ping_command, nil
|
50
|
+
rpc_map Command::Type::UPCASE, :upcase_command, UpcaseResponse
|
51
|
+
rpc_map Command::Type::EVEN, :even_command, EvenResponse, :streaming => true
|
52
|
+
end
|
53
|
+
|
54
|
+
class EMServer < Protoplasm::EMServer.for_types(Types)
|
55
|
+
def process_upcase_command(cmd)
|
56
|
+
send_response(:response => cmd.word.upcase)
|
57
|
+
end
|
58
|
+
|
59
|
+
def process_even_command(cmd)
|
60
|
+
spit_out_even(1, cmd.top)
|
61
|
+
end
|
62
|
+
|
63
|
+
def process_ping_command(cmd)
|
64
|
+
send_void
|
65
|
+
end
|
66
|
+
|
67
|
+
def spit_out_even(cur, top)
|
68
|
+
EM.next_tick do
|
69
|
+
if cur == top
|
70
|
+
finish_streaming
|
71
|
+
else
|
72
|
+
if cur % 2 == 0
|
73
|
+
send_response(:num => cur)
|
74
|
+
end
|
75
|
+
spit_out_even(cur + 1, top)
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
class Client < Protoplasm::BlockingClient.for_types(Types)
|
82
|
+
def initialize(host, port)
|
83
|
+
@host, @port = host, port
|
84
|
+
end
|
85
|
+
|
86
|
+
def ping
|
87
|
+
send_request(:ping_command)
|
88
|
+
end
|
89
|
+
|
90
|
+
def upcase(word)
|
91
|
+
send_request(:upcase_command, :word => word).response
|
92
|
+
end
|
93
|
+
|
94
|
+
def evens(top)
|
95
|
+
send_request(:even_command, :top => top) { |resp| yield resp.num }
|
96
|
+
end
|
97
|
+
|
98
|
+
def host_port
|
99
|
+
[@host, @port]
|
100
|
+
end
|
101
|
+
end
|
102
|
+
end
|
103
|
+
|
104
|
+
class MiniTest::Spec
|
105
|
+
def with_proto_server(cls)
|
106
|
+
port = 19866
|
107
|
+
pid = fork { cls.start(port) }
|
108
|
+
begin
|
109
|
+
Timeout.timeout(10.0) {
|
110
|
+
begin
|
111
|
+
TCPSocket.open("127.0.0.1", port).close
|
112
|
+
rescue
|
113
|
+
sleep(0.1)
|
114
|
+
retry
|
115
|
+
end
|
116
|
+
}
|
117
|
+
yield port
|
118
|
+
ensure
|
119
|
+
Process.kill("INT", pid) if pid
|
120
|
+
end
|
121
|
+
end
|
122
|
+
end
|
metadata
ADDED
@@ -0,0 +1,99 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: protoplasm-blocking-client
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
prerelease:
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Josh Hull
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2011-11-29 00:00:00.000000000Z
|
13
|
+
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
15
|
+
name: beefcake
|
16
|
+
requirement: &70238713002280 !ruby/object:Gem::Requirement
|
17
|
+
none: false
|
18
|
+
requirements:
|
19
|
+
- - ~>
|
20
|
+
- !ruby/object:Gem::Version
|
21
|
+
version: 0.3.7
|
22
|
+
type: :runtime
|
23
|
+
prerelease: false
|
24
|
+
version_requirements: *70238713002280
|
25
|
+
- !ruby/object:Gem::Dependency
|
26
|
+
name: rake
|
27
|
+
requirement: &70238713001380 !ruby/object:Gem::Requirement
|
28
|
+
none: false
|
29
|
+
requirements:
|
30
|
+
- - ! '>='
|
31
|
+
- !ruby/object:Gem::Version
|
32
|
+
version: '0'
|
33
|
+
type: :development
|
34
|
+
prerelease: false
|
35
|
+
version_requirements: *70238713001380
|
36
|
+
- !ruby/object:Gem::Dependency
|
37
|
+
name: minitest
|
38
|
+
requirement: &70238713000320 !ruby/object:Gem::Requirement
|
39
|
+
none: false
|
40
|
+
requirements:
|
41
|
+
- - ~>
|
42
|
+
- !ruby/object:Gem::Version
|
43
|
+
version: 2.6.1
|
44
|
+
type: :development
|
45
|
+
prerelease: false
|
46
|
+
version_requirements: *70238713000320
|
47
|
+
description: A blocking client for a Protoplasm server.
|
48
|
+
email:
|
49
|
+
- joshbuddy@gmail.com
|
50
|
+
executables: []
|
51
|
+
extensions: []
|
52
|
+
extra_rdoc_files: []
|
53
|
+
files:
|
54
|
+
- .gitignore
|
55
|
+
- Gemfile
|
56
|
+
- README.md
|
57
|
+
- Rakefile
|
58
|
+
- lib/protoplasm.rb
|
59
|
+
- lib/protoplasm/client/blocking_client.rb
|
60
|
+
- lib/protoplasm/server/em_server.rb
|
61
|
+
- lib/protoplasm/types/types.rb
|
62
|
+
- lib/protoplasm/version.rb
|
63
|
+
- protoplasm-blocking-client.gemspec
|
64
|
+
- protoplasm-em-server.gemspec
|
65
|
+
- test/protoplasm_test.rb
|
66
|
+
- test/test_helper.rb
|
67
|
+
homepage: https://github.com/bazaarlabs/protoplasm
|
68
|
+
licenses: []
|
69
|
+
post_install_message:
|
70
|
+
rdoc_options: []
|
71
|
+
require_paths:
|
72
|
+
- lib
|
73
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
74
|
+
none: false
|
75
|
+
requirements:
|
76
|
+
- - ! '>='
|
77
|
+
- !ruby/object:Gem::Version
|
78
|
+
version: '0'
|
79
|
+
segments:
|
80
|
+
- 0
|
81
|
+
hash: 380352482441046279
|
82
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
83
|
+
none: false
|
84
|
+
requirements:
|
85
|
+
- - ! '>='
|
86
|
+
- !ruby/object:Gem::Version
|
87
|
+
version: '0'
|
88
|
+
segments:
|
89
|
+
- 0
|
90
|
+
hash: 380352482441046279
|
91
|
+
requirements: []
|
92
|
+
rubyforge_project: protoplasm-blocking-client
|
93
|
+
rubygems_version: 1.8.10
|
94
|
+
signing_key:
|
95
|
+
specification_version: 3
|
96
|
+
summary: A blocking client for a Protoplasm server
|
97
|
+
test_files:
|
98
|
+
- test/protoplasm_test.rb
|
99
|
+
- test/test_helper.rb
|