protoplasm-em-server 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +4 -0
- data/Gemfile +7 -0
- data/README.md +101 -0
- data/Rakefile +73 -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/lib/protoplasm.rb +9 -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 +110 -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
|
@@ -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
|
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,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,110 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: protoplasm-em-server
|
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: eventmachine
|
16
|
+
requirement: &70267574080920 !ruby/object:Gem::Requirement
|
17
|
+
none: false
|
18
|
+
requirements:
|
19
|
+
- - ! '>='
|
20
|
+
- !ruby/object:Gem::Version
|
21
|
+
version: '0'
|
22
|
+
type: :runtime
|
23
|
+
prerelease: false
|
24
|
+
version_requirements: *70267574080920
|
25
|
+
- !ruby/object:Gem::Dependency
|
26
|
+
name: beefcake
|
27
|
+
requirement: &70267574077460 !ruby/object:Gem::Requirement
|
28
|
+
none: false
|
29
|
+
requirements:
|
30
|
+
- - ~>
|
31
|
+
- !ruby/object:Gem::Version
|
32
|
+
version: 0.3.7
|
33
|
+
type: :runtime
|
34
|
+
prerelease: false
|
35
|
+
version_requirements: *70267574077460
|
36
|
+
- !ruby/object:Gem::Dependency
|
37
|
+
name: rake
|
38
|
+
requirement: &70267574063560 !ruby/object:Gem::Requirement
|
39
|
+
none: false
|
40
|
+
requirements:
|
41
|
+
- - ! '>='
|
42
|
+
- !ruby/object:Gem::Version
|
43
|
+
version: '0'
|
44
|
+
type: :development
|
45
|
+
prerelease: false
|
46
|
+
version_requirements: *70267574063560
|
47
|
+
- !ruby/object:Gem::Dependency
|
48
|
+
name: minitest
|
49
|
+
requirement: &70267574051180 !ruby/object:Gem::Requirement
|
50
|
+
none: false
|
51
|
+
requirements:
|
52
|
+
- - ~>
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: 2.6.1
|
55
|
+
type: :development
|
56
|
+
prerelease: false
|
57
|
+
version_requirements: *70267574051180
|
58
|
+
description: A protoplasm server backed by EventMachine.
|
59
|
+
email:
|
60
|
+
- joshbuddy@gmail.com
|
61
|
+
executables: []
|
62
|
+
extensions: []
|
63
|
+
extra_rdoc_files: []
|
64
|
+
files:
|
65
|
+
- .gitignore
|
66
|
+
- Gemfile
|
67
|
+
- README.md
|
68
|
+
- Rakefile
|
69
|
+
- lib/protoplasm.rb
|
70
|
+
- lib/protoplasm/client/blocking_client.rb
|
71
|
+
- lib/protoplasm/server/em_server.rb
|
72
|
+
- lib/protoplasm/types/types.rb
|
73
|
+
- lib/protoplasm/version.rb
|
74
|
+
- protoplasm-blocking-client.gemspec
|
75
|
+
- protoplasm-em-server.gemspec
|
76
|
+
- test/protoplasm_test.rb
|
77
|
+
- test/test_helper.rb
|
78
|
+
homepage: https://github.com/bazaarlabs/protoplasm
|
79
|
+
licenses: []
|
80
|
+
post_install_message:
|
81
|
+
rdoc_options: []
|
82
|
+
require_paths:
|
83
|
+
- lib
|
84
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
85
|
+
none: false
|
86
|
+
requirements:
|
87
|
+
- - ! '>='
|
88
|
+
- !ruby/object:Gem::Version
|
89
|
+
version: '0'
|
90
|
+
segments:
|
91
|
+
- 0
|
92
|
+
hash: -1390660265886085013
|
93
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
94
|
+
none: false
|
95
|
+
requirements:
|
96
|
+
- - ! '>='
|
97
|
+
- !ruby/object:Gem::Version
|
98
|
+
version: '0'
|
99
|
+
segments:
|
100
|
+
- 0
|
101
|
+
hash: -1390660265886085013
|
102
|
+
requirements: []
|
103
|
+
rubyforge_project: protoplasm-em-server
|
104
|
+
rubygems_version: 1.8.10
|
105
|
+
signing_key:
|
106
|
+
specification_version: 3
|
107
|
+
summary: A protoplasm server backed by EventMachine
|
108
|
+
test_files:
|
109
|
+
- test/protoplasm_test.rb
|
110
|
+
- test/test_helper.rb
|