protoplasm-client 0.0.1
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 +5 -0
- data/README.md +103 -0
- data/Rakefile +74 -0
- data/lib/protoplasm.rb +7 -0
- data/lib/protoplasm/client/blocking_client.rb +63 -0
- data/lib/protoplasm/server/em_server.rb +71 -0
- data/lib/protoplasm/types/types.rb +69 -0
- data/lib/protoplasm/version.rb +3 -0
- data/protoplasm-client.gemspec +25 -0
- data/protoplasm-server.gemspec +27 -0
- data/test/protoplasm_test.rb +46 -0
- data/test/test_helper.rb +118 -0
- metadata +99 -0
data/.gitignore
ADDED
data/Gemfile
ADDED
data/README.md
ADDED
@@ -0,0 +1,103 @@
|
|
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 one protobuf object (the request object). This object must have an enum and a series of optional command fields to allow it to send commands. From the tests, this is a valid request object definition:
|
8
|
+
|
9
|
+
```ruby
|
10
|
+
class Command
|
11
|
+
include Beefcake::Message
|
12
|
+
module Type
|
13
|
+
PING = 1
|
14
|
+
UPCASE = 2
|
15
|
+
EVEN = 3
|
16
|
+
end
|
17
|
+
required :type, Type, 1
|
18
|
+
optional :ping_command, PingCommand, 2
|
19
|
+
optional :upcase_command, UpcaseCommand, 3
|
20
|
+
optional :even_command, EvenCommand, 4
|
21
|
+
end
|
22
|
+
```
|
23
|
+
|
24
|
+
In this case, your request object would be able to accept one, and only one subcommand object. Those types are `PingCommand`, `UpcaseCommand` and `EvenCommand`.
|
25
|
+
|
26
|
+
So, in order to mark this `Command` class as your request class, you'd do the following:
|
27
|
+
|
28
|
+
```ruby
|
29
|
+
module Types
|
30
|
+
include Protoplasm::Types
|
31
|
+
|
32
|
+
# .. your actual classes would go here
|
33
|
+
|
34
|
+
request_class Command
|
35
|
+
request_type_field :type
|
36
|
+
end
|
37
|
+
```
|
38
|
+
|
39
|
+
## Defining your response objects
|
40
|
+
|
41
|
+
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.
|
42
|
+
|
43
|
+
To define which objects you expect back, you must add the following.
|
44
|
+
|
45
|
+
```ruby
|
46
|
+
module Types
|
47
|
+
rpc_map Command::Type::PING, :ping_command, nil
|
48
|
+
rpc_map Command::Type::UPCASE, :upcase_command, UpcaseResponse
|
49
|
+
rpc_map Command::Type::EVEN, :even_command, EvenResponse, :streaming => true
|
50
|
+
```
|
51
|
+
|
52
|
+
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.
|
53
|
+
|
54
|
+
## Server implementation
|
55
|
+
|
56
|
+
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:
|
57
|
+
|
58
|
+
```ruby
|
59
|
+
class Server < Protoplasm::EMServer
|
60
|
+
def process_ping_command(ping_command)
|
61
|
+
# do nothing
|
62
|
+
end
|
63
|
+
|
64
|
+
def process_upcase_command(upcase_command)
|
65
|
+
send_response(:response => cmd.word.upcase)
|
66
|
+
end
|
67
|
+
|
68
|
+
def process_even_command(even_command)
|
69
|
+
(1..even_command.top).each do |num|
|
70
|
+
send_response(:num => num) if num % 2 == 0
|
71
|
+
end
|
72
|
+
finish_streaming
|
73
|
+
end
|
74
|
+
end
|
75
|
+
```
|
76
|
+
|
77
|
+
This server then could be started with `Server.start(3000)` which would start on port 3000 and process requests.
|
78
|
+
|
79
|
+
## Client
|
80
|
+
|
81
|
+
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.
|
82
|
+
|
83
|
+
```ruby
|
84
|
+
class Client < Protoplasm::BlockingClient
|
85
|
+
def initialize(host, port)
|
86
|
+
super(Types, host, port)
|
87
|
+
end
|
88
|
+
|
89
|
+
def ping
|
90
|
+
send_request(:ping_command)
|
91
|
+
end
|
92
|
+
|
93
|
+
def upcase(word)
|
94
|
+
send_request(:upcase_command, :word => word).response
|
95
|
+
end
|
96
|
+
|
97
|
+
def evens(top)
|
98
|
+
send_request(:even_command, :top => top) { |resp| yield resp.num }
|
99
|
+
end
|
100
|
+
end
|
101
|
+
```
|
102
|
+
|
103
|
+
Look at the full example under `test/test_helper.rb`.
|
data/Rakefile
ADDED
@@ -0,0 +1,74 @@
|
|
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
|
+
|
30
|
+
desc "Release client & server (#{version})"
|
31
|
+
task :release do
|
32
|
+
tag_version do
|
33
|
+
Rake::Task["server:release_without_tagging"].invoke
|
34
|
+
Rake::Task["client:release_without_tagging"].invoke
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
desc "Install client & server (#{version})"
|
39
|
+
task :install do
|
40
|
+
Rake::Task["server:install"].invoke
|
41
|
+
Rake::Task["client:install"].invoke
|
42
|
+
end
|
43
|
+
|
44
|
+
desc "Build client & server (#{version})"
|
45
|
+
task :build do
|
46
|
+
Rake::Task["server:build"].invoke
|
47
|
+
Rake::Task["client:build"].invoke
|
48
|
+
end
|
49
|
+
|
50
|
+
namespace :server do
|
51
|
+
helper = Bundler::GemHelper.new(File.dirname(__FILE__), "protoplasm-server")
|
52
|
+
helper.install
|
53
|
+
helper.instance_eval do
|
54
|
+
task :release_without_tagging do
|
55
|
+
guard_clean
|
56
|
+
built_gem_path = build_gem
|
57
|
+
git_push
|
58
|
+
rubygem_push(built_gem_path)
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
namespace :client do
|
64
|
+
helper = Bundler::GemHelper.new(File.dirname(__FILE__), "protoplasm-client")
|
65
|
+
helper.install
|
66
|
+
helper.instance_eval do
|
67
|
+
task :release_without_tagging do
|
68
|
+
guard_clean
|
69
|
+
built_gem_path = build_gem
|
70
|
+
git_push
|
71
|
+
rubygem_push(built_gem_path)
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
data/lib/protoplasm.rb
ADDED
@@ -0,0 +1,63 @@
|
|
1
|
+
require 'socket'
|
2
|
+
|
3
|
+
module Protoplasm
|
4
|
+
class BlockingClient
|
5
|
+
attr_reader :_types
|
6
|
+
|
7
|
+
def initialize(_types, host, port)
|
8
|
+
@_types, @host, @port = _types, host, port
|
9
|
+
end
|
10
|
+
|
11
|
+
private
|
12
|
+
def _socket(force_new = false)
|
13
|
+
if force_new
|
14
|
+
TCPSocket.open(@host, @port)
|
15
|
+
else
|
16
|
+
@_socket ||= TCPSocket.open(@host, @port)
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
def _master_socket
|
21
|
+
@_master_socket ||= _socket
|
22
|
+
end
|
23
|
+
|
24
|
+
def send_request(field, *args, &blk)
|
25
|
+
s = ''
|
26
|
+
type = _types.request_type_for_field(field)
|
27
|
+
cmd_class = type.command_class.fields.values.find{|f| f.name == field}
|
28
|
+
cmd = _types.request_class.new(_types.request_type_field => type.type, type.field => cmd_class.type.new(*args))
|
29
|
+
cmd.encode(s)
|
30
|
+
socket = _socket(type.streaming?)
|
31
|
+
socket.write([0, s.size].pack("CQ"))
|
32
|
+
socket.write s
|
33
|
+
socket.flush
|
34
|
+
unless type.void?
|
35
|
+
if type.streaming?
|
36
|
+
begin
|
37
|
+
until socket.eof?
|
38
|
+
len = socket.read(8).unpack("Q").first
|
39
|
+
buf = ''
|
40
|
+
while buf.size < len
|
41
|
+
socket.read(len - buf.size, buf)
|
42
|
+
end
|
43
|
+
yield type.response_class.decode(buf)
|
44
|
+
end
|
45
|
+
ensure
|
46
|
+
socket.close
|
47
|
+
end
|
48
|
+
else
|
49
|
+
len_buf = socket.readpartial(8)
|
50
|
+
while len_buf.size < 8
|
51
|
+
socket.readpartial(8 - len_buf.size, len_buf)
|
52
|
+
end
|
53
|
+
len = len_buf.unpack("Q").first
|
54
|
+
buf = ''
|
55
|
+
until buf.size == len or socket.eof?
|
56
|
+
socket.readpartial(len - buf.size, buf)
|
57
|
+
end
|
58
|
+
type.response_class.decode(buf)
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
@@ -0,0 +1,71 @@
|
|
1
|
+
require 'eventmachine'
|
2
|
+
|
3
|
+
module Protoplasm
|
4
|
+
class EMServer < EventMachine::Connection
|
5
|
+
CONTROL_REQUEST = 0
|
6
|
+
|
7
|
+
def self.start(types, port)
|
8
|
+
if EM.reactor_running?
|
9
|
+
EM::start_server("0.0.0.0", port, self) do |srv|
|
10
|
+
srv._types = types
|
11
|
+
yield srv if block_given?
|
12
|
+
end
|
13
|
+
else
|
14
|
+
begin
|
15
|
+
EM.run do
|
16
|
+
start(types, port)
|
17
|
+
end
|
18
|
+
rescue Interrupt
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
attr_accessor :_types
|
24
|
+
|
25
|
+
def post_init
|
26
|
+
@_response_types = []
|
27
|
+
@data = ''
|
28
|
+
end
|
29
|
+
|
30
|
+
def receive_data(data)
|
31
|
+
@data << data
|
32
|
+
data_ready
|
33
|
+
end
|
34
|
+
|
35
|
+
def finish_streaming
|
36
|
+
close_connection_after_writing
|
37
|
+
end
|
38
|
+
|
39
|
+
def data_ready
|
40
|
+
@control = @data.slice!(0, 1).unpack("C").first unless @control
|
41
|
+
case @control
|
42
|
+
when CONTROL_REQUEST
|
43
|
+
@size = @data.slice!(0, 8).unpack("Q").first unless @size
|
44
|
+
|
45
|
+
if @data.size >= @size
|
46
|
+
buf = @data.slice!(0, @size)
|
47
|
+
@size, @control = nil, nil
|
48
|
+
obj = _types.request_class.decode(buf)
|
49
|
+
type = _types.request_type_for_request(obj)
|
50
|
+
@_response_types << type unless type.void?
|
51
|
+
EM.next_tick { send(:"process_#{type.field}", obj.send(type.field)) }
|
52
|
+
data_ready unless @data.empty?
|
53
|
+
#EM.next_tick { data_ready } #todo left over data needs to be processed still
|
54
|
+
end
|
55
|
+
else
|
56
|
+
# illegal char
|
57
|
+
close_connection
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
def send_response(*args)
|
62
|
+
type = @_response_types.first
|
63
|
+
@_response_types.shift unless type.streaming?
|
64
|
+
obj = type.response_class.new(*args)
|
65
|
+
s = ''
|
66
|
+
obj.encode(s)
|
67
|
+
send_data [s.size].pack("Q")
|
68
|
+
send_data s
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
@@ -0,0 +1,69 @@
|
|
1
|
+
require 'beefcake'
|
2
|
+
|
3
|
+
module Protoplasm
|
4
|
+
module Types
|
5
|
+
# the truest thing i know ... the enum pattern matches to a response class
|
6
|
+
# therefore, you need to know the following
|
7
|
+
# where is the request class
|
8
|
+
# where is the field class
|
9
|
+
# how does the field map to response classes
|
10
|
+
|
11
|
+
def self.included(cls)
|
12
|
+
cls.extend(ClassMethods)
|
13
|
+
end
|
14
|
+
|
15
|
+
class RequestResponseType < Struct.new(:request_class, :response_class, :type, :field, :streaming)
|
16
|
+
|
17
|
+
alias_method :streaming?, :streaming
|
18
|
+
|
19
|
+
def command_class
|
20
|
+
request_class
|
21
|
+
end
|
22
|
+
|
23
|
+
def void?
|
24
|
+
response_class.nil?
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
module ClassMethods
|
29
|
+
def request_class(request_class = nil)
|
30
|
+
request_class ? @request_class = request_class : @request_class
|
31
|
+
end
|
32
|
+
|
33
|
+
def request_type(request_obj)
|
34
|
+
request_obj.send(@request_type_field)
|
35
|
+
end
|
36
|
+
|
37
|
+
def request_type_field(field = nil)
|
38
|
+
field ? @request_type_field = field : @request_type_field
|
39
|
+
end
|
40
|
+
|
41
|
+
def rpc_map(type, field, response_class, opts = nil)
|
42
|
+
@response_map_by_field ||= {}
|
43
|
+
@response_map_by_type ||= {}
|
44
|
+
streaming = opts && opts.key?(:streaming) ? opts[:streaming] : false
|
45
|
+
rrt = RequestResponseType.new(@request_class, response_class, type, field, streaming)
|
46
|
+
@response_map_by_field[field] = rrt
|
47
|
+
@response_map_by_type[type] = rrt
|
48
|
+
end
|
49
|
+
|
50
|
+
def request_type_for_field(field)
|
51
|
+
@response_map_by_field[field]
|
52
|
+
end
|
53
|
+
|
54
|
+
def request_type_for_request(req)
|
55
|
+
@response_map_by_type[req.send(@request_type_field)]
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
# module Unilogger
|
62
|
+
# module Types
|
63
|
+
# include Protoplasm::Types
|
64
|
+
#
|
65
|
+
# request_class RequestCommand
|
66
|
+
# request_type_field :command_type
|
67
|
+
# response_map LogCommand::CommandType::INSERT, nil
|
68
|
+
# end
|
69
|
+
# 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-client"
|
7
|
+
s.version = Protoplasm::VERSION
|
8
|
+
s.authors = ["Josh Hull"]
|
9
|
+
s.email = ["joshbuddy@gmail.com"]
|
10
|
+
s.homepage = ""
|
11
|
+
s.summary = %q{The protoplasm client}
|
12
|
+
s.description = %q{The protoplasm client.}
|
13
|
+
|
14
|
+
s.rubyforge_project = "protoplasm"
|
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,27 @@
|
|
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-server"
|
7
|
+
s.version = Protoplasm::VERSION
|
8
|
+
s.authors = ["Josh Hull"]
|
9
|
+
s.email = ["joshbuddy@gmail.com"]
|
10
|
+
s.homepage = ""
|
11
|
+
s.summary = %q{The protoplasm server}
|
12
|
+
s.description = %q{The protoplasm server.}
|
13
|
+
|
14
|
+
s.rubyforge_project = "protoplasm"
|
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
|
+
|
27
|
+
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,118 @@
|
|
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
|
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
|
+
# no op
|
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
|
82
|
+
def initialize(host, port)
|
83
|
+
super(Types, 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
|
+
end
|
98
|
+
end
|
99
|
+
|
100
|
+
class MiniTest::Spec
|
101
|
+
def with_proto_server(cls)
|
102
|
+
port = 19866
|
103
|
+
pid = fork { cls.start(ProtoplasmTest::Types, port) }
|
104
|
+
begin
|
105
|
+
Timeout.timeout(10.0) {
|
106
|
+
begin
|
107
|
+
TCPSocket.open("127.0.0.1", port).close
|
108
|
+
rescue
|
109
|
+
sleep(0.1)
|
110
|
+
retry
|
111
|
+
end
|
112
|
+
}
|
113
|
+
yield port
|
114
|
+
ensure
|
115
|
+
Process.kill("INT", pid) if pid
|
116
|
+
end
|
117
|
+
end
|
118
|
+
end
|
metadata
ADDED
@@ -0,0 +1,99 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: protoplasm-client
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.1
|
5
|
+
prerelease:
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Josh Hull
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2011-11-21 00:00:00.000000000Z
|
13
|
+
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
15
|
+
name: beefcake
|
16
|
+
requirement: &70270602546480 !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: *70270602546480
|
25
|
+
- !ruby/object:Gem::Dependency
|
26
|
+
name: rake
|
27
|
+
requirement: &70270602545980 !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: *70270602545980
|
36
|
+
- !ruby/object:Gem::Dependency
|
37
|
+
name: minitest
|
38
|
+
requirement: &70270602545400 !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: *70270602545400
|
47
|
+
description: The protoplasm client.
|
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-client.gemspec
|
64
|
+
- protoplasm-server.gemspec
|
65
|
+
- test/protoplasm_test.rb
|
66
|
+
- test/test_helper.rb
|
67
|
+
homepage: ''
|
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: -3078516638908246795
|
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: -3078516638908246795
|
91
|
+
requirements: []
|
92
|
+
rubyforge_project: protoplasm
|
93
|
+
rubygems_version: 1.8.10
|
94
|
+
signing_key:
|
95
|
+
specification_version: 3
|
96
|
+
summary: The protoplasm client
|
97
|
+
test_files:
|
98
|
+
- test/protoplasm_test.rb
|
99
|
+
- test/test_helper.rb
|