gmalamid-spinal 0.0.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/COPYING +18 -0
- data/README +12 -0
- data/Rakefile +16 -0
- data/etc/demo/daemon.rb +8 -0
- data/etc/demo/web.rb +22 -0
- data/etc/protocol.txt +26 -0
- data/lib/spinal/client/blocking.rb +28 -0
- data/lib/spinal/client/evented.rb +51 -0
- data/lib/spinal/rack_remote_query.rb +35 -0
- data/lib/spinal/service.rb +29 -0
- data/lib/spinal/service/connection.rb +24 -0
- data/lib/spinal/service/registration.rb +34 -0
- data/lib/spinal/service/request.rb +30 -0
- data/lib/spinal/service/runner.rb +45 -0
- data/lib/spinal/shared.rb +6 -0
- data/lib/spinal/shared/error.rb +12 -0
- data/lib/spinal/shared/packet.rb +67 -0
- data/lib/spinal/shared/packet_parser.rb +37 -0
- data/lib/spinal/shared/status_codes.rb +8 -0
- data/lib/spinal/spinald.rb +118 -0
- data/lib/spinal/thin_deferrable_connection.rb +40 -0
- data/spinal.gemspec +38 -0
- metadata +80 -0
data/COPYING
ADDED
@@ -0,0 +1,18 @@
|
|
1
|
+
Copyright (c) 2008 nutrun.com, Andy Kent, George Malamidis
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
4
|
+
of this software and associated documentation files (the "Software"), to
|
5
|
+
deal in the Software without restriction, including without limitation the
|
6
|
+
rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
|
7
|
+
sell copies of the Software, and to permit persons to whom the Software is
|
8
|
+
furnished to do so, subject to the following conditions:
|
9
|
+
|
10
|
+
The above copyright notice and this permission notice shall be included in
|
11
|
+
all copies or substantial portions of the Software.
|
12
|
+
|
13
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
14
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
15
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
|
16
|
+
THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
17
|
+
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
18
|
+
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README
ADDED
@@ -0,0 +1,12 @@
|
|
1
|
+
--- It's still very early days and this is experimental code, read: "Here be dragons" ---
|
2
|
+
|
3
|
+
Spinal is lightweight middleware for bidirectional process distribution over TCP.
|
4
|
+
It comes in three parts all currently implemented in Ruby:
|
5
|
+
|
6
|
+
spinald - This is the server daemon, it manages connections and distributes requests. Servers are cluster-able in a share nothing style architecture.
|
7
|
+
|
8
|
+
Spinal Client Library - The client Library makes it super simple to delegate work to a Spinal Service, requests can be blocking or asynchronous.
|
9
|
+
|
10
|
+
Spinal Service Library - Use the service library to implement workers. As with servers, services are cluster-able and distributed.
|
11
|
+
|
12
|
+
See etc/demo for a simplified example using Rack as a client to a directory service.
|
data/Rakefile
ADDED
@@ -0,0 +1,16 @@
|
|
1
|
+
require "rubygems"
|
2
|
+
require "rake/testtask"
|
3
|
+
|
4
|
+
task :default => :test
|
5
|
+
|
6
|
+
Rake::TestTask.new do |t|
|
7
|
+
t.pattern = "test/*_test.rb"
|
8
|
+
end
|
9
|
+
|
10
|
+
task(:check_gemspec) do
|
11
|
+
require 'rubygems/specification'
|
12
|
+
data = File.read('spinal.gemspec')
|
13
|
+
spec = nil
|
14
|
+
Thread.new { spec = eval("$SAFE = 3\n#{data}") }.join
|
15
|
+
puts spec
|
16
|
+
end
|
data/etc/demo/daemon.rb
ADDED
data/etc/demo/web.rb
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
require "rubygems"
|
2
|
+
require "thin"
|
3
|
+
|
4
|
+
$: << File.join(File.dirname(__FILE__), '..', '..', 'lib')
|
5
|
+
|
6
|
+
require "spinal/thin_deferrable_connection"
|
7
|
+
require "spinal/rack_remote_query"
|
8
|
+
|
9
|
+
Thin::Connection.send(:include, Spinal::ThinDeferrableConnection)
|
10
|
+
|
11
|
+
class Webapp
|
12
|
+
include Spinal::Rack::RemoteQuery
|
13
|
+
|
14
|
+
def call(env)
|
15
|
+
request = Rack::Request.new(env)
|
16
|
+
remote_query('wiretap', request.params.to_yaml) do |response|
|
17
|
+
[response.status, {'Content-Type' => 'text/plain'}, response.body]
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
Rack::Handler::Thin.run(Webapp.new, :Host => 'localhost', :Port => 4567)
|
data/etc/protocol.txt
ADDED
@@ -0,0 +1,26 @@
|
|
1
|
+
Requests
|
2
|
+
|
3
|
+
Query a resource identified by URI
|
4
|
+
========================
|
5
|
+
QUERY
|
6
|
+
address
|
7
|
+
ASCII payload
|
8
|
+
___END___
|
9
|
+
========================
|
10
|
+
|
11
|
+
Register Service with Spinal on resource identified by URI
|
12
|
+
========================
|
13
|
+
REGISTER
|
14
|
+
address
|
15
|
+
host:port
|
16
|
+
___END___
|
17
|
+
========================
|
18
|
+
|
19
|
+
|
20
|
+
Responses
|
21
|
+
========================
|
22
|
+
RESPONSE
|
23
|
+
[200|400|...]
|
24
|
+
ASCII body
|
25
|
+
___END___
|
26
|
+
========================
|
@@ -0,0 +1,28 @@
|
|
1
|
+
require "socket"
|
2
|
+
require "fcntl"
|
3
|
+
|
4
|
+
$: << File.join(File.dirname(__FILE__), '..')
|
5
|
+
|
6
|
+
require "shared"
|
7
|
+
|
8
|
+
module Spinal
|
9
|
+
module Client
|
10
|
+
class Blocking
|
11
|
+
def initialize(host, port)
|
12
|
+
@socket = TCPSocket.new(host, port.to_i)
|
13
|
+
@socket.fcntl(Fcntl::F_SETFD, Fcntl::FD_CLOEXEC)
|
14
|
+
end
|
15
|
+
|
16
|
+
def query(address, payload)
|
17
|
+
packet = Packet::Query.new(address, payload)
|
18
|
+
@socket.write(packet.raw)
|
19
|
+
packet_parser, resp = PacketParser.new, []
|
20
|
+
loop do
|
21
|
+
str = @socket.gets("\r\n").strip
|
22
|
+
packet_parser.parse(str)
|
23
|
+
return packet_parser.packet if packet_parser.has_packet?
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
@@ -0,0 +1,51 @@
|
|
1
|
+
require "rubygems"
|
2
|
+
require "eventmachine"
|
3
|
+
|
4
|
+
$: << File.join(File.dirname(__FILE__), '..')
|
5
|
+
|
6
|
+
require "shared"
|
7
|
+
|
8
|
+
module Spinal
|
9
|
+
module Client
|
10
|
+
module Evented
|
11
|
+
def query(uri, payload = '', &response)
|
12
|
+
send_data(Packet::Query.new(uri, payload).raw)
|
13
|
+
@response = response
|
14
|
+
end
|
15
|
+
|
16
|
+
def receive_data(data)
|
17
|
+
packet_parser.parse(data)
|
18
|
+
packet_parser.each_packet { |packet| @response.call(packet) }
|
19
|
+
end
|
20
|
+
|
21
|
+
def packet_parser
|
22
|
+
@packet_parser ||= PacketParser.new
|
23
|
+
end
|
24
|
+
|
25
|
+
def self.connect(opts={})
|
26
|
+
client = nil
|
27
|
+
EM::safe_run(opts[:background]) do
|
28
|
+
client = EM.connect(opts[:host] || '0.0.0.0', opts[:port] || 8888, self)
|
29
|
+
end
|
30
|
+
client
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
module EventMachine
|
37
|
+
def EventMachine::safe_run(background = nil, &block)
|
38
|
+
if EM::reactor_running?
|
39
|
+
EM::next_tick(&block)
|
40
|
+
sleep unless background
|
41
|
+
else
|
42
|
+
if background
|
43
|
+
$em_reactor_thread = Thread.new do
|
44
|
+
EM::run(&block)
|
45
|
+
end
|
46
|
+
else
|
47
|
+
EM::run(&block)
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
module Spinal
|
2
|
+
module Rack
|
3
|
+
module RemoteQuery
|
4
|
+
def remote_query(address, payload='', &response_callback)
|
5
|
+
@response = Spinal::ThinDeferrableConnection::DeferredResponse.new(&response_callback)
|
6
|
+
connection.send_data(Packet::Query.new(address, payload).raw)
|
7
|
+
@response
|
8
|
+
end
|
9
|
+
|
10
|
+
def receive_packet(packet)
|
11
|
+
@response.deliver(packet)
|
12
|
+
end
|
13
|
+
|
14
|
+
def connection
|
15
|
+
@connection ||= EM.connect('0.0.0.0', 8888, SpinalConnection, self)
|
16
|
+
end
|
17
|
+
|
18
|
+
module SpinalConnection
|
19
|
+
def initialize(client)
|
20
|
+
super
|
21
|
+
@client = client
|
22
|
+
end
|
23
|
+
|
24
|
+
def receive_data(data)
|
25
|
+
packet_parser.parse(data)
|
26
|
+
packet_parser.each_packet { |packet| @client.receive_packet(packet) }
|
27
|
+
end
|
28
|
+
|
29
|
+
def packet_parser
|
30
|
+
@packet_parser ||= PacketParser.new
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
require "rubygems"
|
2
|
+
require "eventmachine"
|
3
|
+
|
4
|
+
$: << File.dirname(__FILE__)
|
5
|
+
|
6
|
+
require "shared"
|
7
|
+
require "service/registration"
|
8
|
+
require "service/connection"
|
9
|
+
require "service/runner"
|
10
|
+
require "service/request"
|
11
|
+
|
12
|
+
module Spinal
|
13
|
+
module Service
|
14
|
+
def call(request)
|
15
|
+
raise "A Spinal::Service must implement the call(request) method"
|
16
|
+
end
|
17
|
+
|
18
|
+
module ClassMethods
|
19
|
+
def run_as(address, opts={})
|
20
|
+
opts[:address] = address
|
21
|
+
Runner.new(self.new, opts).run
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
def self.included(target)
|
26
|
+
target.extend(ClassMethods)
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
module Spinal
|
2
|
+
module Service
|
3
|
+
module Connection
|
4
|
+
attr_writer :service, :servers, :address, :host, :port
|
5
|
+
|
6
|
+
def receive_data(data)
|
7
|
+
packet_parser.parse(data)
|
8
|
+
packet_parser.each_packet do |packet|
|
9
|
+
request = Request.new(@service)
|
10
|
+
request.callback {|response| send_data(response)}
|
11
|
+
request.handle(packet)
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
def service
|
16
|
+
@service || proc {|req| "No service specified."}
|
17
|
+
end
|
18
|
+
|
19
|
+
def packet_parser
|
20
|
+
@packet_parser ||= PacketParser.new
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
module Spinal
|
2
|
+
module Registration
|
3
|
+
def receive_data(data)
|
4
|
+
packet_parser.parse(data)
|
5
|
+
packet_parser.each_packet do |packet|
|
6
|
+
@status = packet.status
|
7
|
+
end
|
8
|
+
end
|
9
|
+
|
10
|
+
def bind(servers, service_address, service_host, service_port)
|
11
|
+
servers.each do |server|
|
12
|
+
server_host, server_port = server.split(':')
|
13
|
+
conn = EM.connect(server_host, server_port.to_i, self)
|
14
|
+
packet = Packet::Register.new(service_address, service_host, service_port).raw
|
15
|
+
conn.send_data(packet)
|
16
|
+
end
|
17
|
+
end
|
18
|
+
module_function :bind
|
19
|
+
|
20
|
+
def unbind
|
21
|
+
if @status == StatusCodes::OK
|
22
|
+
puts "Registration complete!"
|
23
|
+
else
|
24
|
+
puts "[WARN] failed to register with spinald"
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
private
|
29
|
+
|
30
|
+
def packet_parser
|
31
|
+
@packet_parser ||= PacketParser.new
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
module Spinal
|
2
|
+
module Service
|
3
|
+
class Request
|
4
|
+
include EM::Deferrable
|
5
|
+
attr_reader :service
|
6
|
+
|
7
|
+
def initialize(service)
|
8
|
+
@service = service
|
9
|
+
end
|
10
|
+
|
11
|
+
def handle(packet)
|
12
|
+
@packet = packet
|
13
|
+
EM.spawn do |request|
|
14
|
+
response = begin
|
15
|
+
[StatusCodes::OK, request.service.call(request.payload)]
|
16
|
+
rescue Spinal::Error => e
|
17
|
+
[e.code, e.message]
|
18
|
+
rescue Exception => e
|
19
|
+
[StatusCodes::SERVER_ERROR, e.message]
|
20
|
+
end
|
21
|
+
request.set_deferred_success(Packet::Response.new(*response).raw)
|
22
|
+
end.notify(self)
|
23
|
+
end
|
24
|
+
|
25
|
+
def payload
|
26
|
+
@packet.payload
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
@@ -0,0 +1,45 @@
|
|
1
|
+
module Spinal
|
2
|
+
module Service
|
3
|
+
class Runner
|
4
|
+
DEFAULT_OPTIONS = {
|
5
|
+
:host => "127.0.0.1",
|
6
|
+
:port => 5000,
|
7
|
+
:servers => ["127.0.0.1:8888"]
|
8
|
+
}
|
9
|
+
|
10
|
+
attr_reader :service
|
11
|
+
|
12
|
+
def initialize(service, options)
|
13
|
+
@service = service
|
14
|
+
@options = DEFAULT_OPTIONS.merge(options)
|
15
|
+
end
|
16
|
+
|
17
|
+
def run
|
18
|
+
trap(:INT) {EM.stop}
|
19
|
+
EM.run do
|
20
|
+
EM.start_server(host, port, Spinal::Service::Connection) do |t|
|
21
|
+
t.service,t.host, t.port = service, host, port
|
22
|
+
end
|
23
|
+
puts "#{service} (#{address}) listening on #{host}:#{port}"
|
24
|
+
Registration.bind(servers, address, host, port)
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
def host
|
29
|
+
@options[:host]
|
30
|
+
end
|
31
|
+
|
32
|
+
def servers
|
33
|
+
@options[:servers]
|
34
|
+
end
|
35
|
+
|
36
|
+
def address
|
37
|
+
@options[:address]
|
38
|
+
end
|
39
|
+
|
40
|
+
def port
|
41
|
+
@options[:port]
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
@@ -0,0 +1,67 @@
|
|
1
|
+
module Spinal
|
2
|
+
module Packet
|
3
|
+
BOUNDARY = "___END___" unless defined?(BOUNDARY)
|
4
|
+
CR = "\r\n" unless defined?(CR)
|
5
|
+
|
6
|
+
def self.from_a(ary)
|
7
|
+
type = ary.shift
|
8
|
+
case type
|
9
|
+
when "QUERY" : Query.from_a(ary)
|
10
|
+
when "RESPONSE" : Response.from_a(ary)
|
11
|
+
when "REGISTER" : Register.from_a(ary)
|
12
|
+
else raise("Unrecognized packet type '#{type}'")
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
class Query
|
17
|
+
attr_reader :address, :payload
|
18
|
+
|
19
|
+
def initialize(address, payload)
|
20
|
+
@address, @payload = address, payload
|
21
|
+
end
|
22
|
+
|
23
|
+
def self.from_a(ary)
|
24
|
+
self.new(ary.shift, ary.join(CR))
|
25
|
+
end
|
26
|
+
|
27
|
+
def raw
|
28
|
+
["QUERY", @address, @payload, "#{BOUNDARY}#{CR}"] * CR
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
|
33
|
+
class Response
|
34
|
+
attr_reader :status, :body
|
35
|
+
|
36
|
+
def initialize(status, body = '')
|
37
|
+
@status, @body = status.to_i, body
|
38
|
+
end
|
39
|
+
|
40
|
+
def self.from_a(ary)
|
41
|
+
self.new(ary.shift, ary.join(CR))
|
42
|
+
end
|
43
|
+
|
44
|
+
def raw
|
45
|
+
["RESPONSE", @status, @body, "#{BOUNDARY}#{CR}"] * CR
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
class Register
|
50
|
+
attr_reader :address, :host, :port
|
51
|
+
|
52
|
+
def initialize(address, host, port)
|
53
|
+
@address, @host, @port = address, host, port.to_i
|
54
|
+
end
|
55
|
+
|
56
|
+
def self.from_a(ary)
|
57
|
+
address = ary.shift
|
58
|
+
host, port = ary.shift.split(":")
|
59
|
+
self.new(address, host, port)
|
60
|
+
end
|
61
|
+
|
62
|
+
def raw
|
63
|
+
["REGISTER", @address, "#{@host}:#{@port}", "#{BOUNDARY}#{CR}"] * CR
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
module Spinal
|
2
|
+
class PacketParser
|
3
|
+
def initialize
|
4
|
+
@packets, @data = [], ''
|
5
|
+
end
|
6
|
+
|
7
|
+
def parse(packet)
|
8
|
+
@data << packet
|
9
|
+
tokens, remainder = [], ''
|
10
|
+
#FIXME: OUCH!! This will iterate through the entire payload/body...
|
11
|
+
@data.split(Packet::CR).each do |token|
|
12
|
+
unless token == Packet::BOUNDARY
|
13
|
+
tokens << token
|
14
|
+
remainder << "#{token}#{Packet::CR}"
|
15
|
+
else
|
16
|
+
@packets << Packet.from_a(tokens)
|
17
|
+
tokens, remainder = [], ''
|
18
|
+
end
|
19
|
+
end
|
20
|
+
@data = remainder
|
21
|
+
end
|
22
|
+
|
23
|
+
def each_packet
|
24
|
+
while packet = @packets.pop
|
25
|
+
yield packet
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
def packet
|
30
|
+
@packets.pop
|
31
|
+
end
|
32
|
+
|
33
|
+
def has_packet?
|
34
|
+
@packets.size == 1
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
@@ -0,0 +1,118 @@
|
|
1
|
+
require "rubygems"
|
2
|
+
require "eventmachine"
|
3
|
+
|
4
|
+
$: << File.dirname(__FILE__)
|
5
|
+
|
6
|
+
require "shared"
|
7
|
+
|
8
|
+
module Spinal
|
9
|
+
#FIXME: Tmp hacks
|
10
|
+
def debug(str) puts str if @debug end
|
11
|
+
module_function :debug
|
12
|
+
def debug=(bool) @debug = bool end
|
13
|
+
module_function :debug=
|
14
|
+
|
15
|
+
# Keep tmp hacks above this line
|
16
|
+
# ==================================================
|
17
|
+
|
18
|
+
module Spinald
|
19
|
+
|
20
|
+
def receive_data(data)
|
21
|
+
Spinal.debug "Spinal received: #{data}"
|
22
|
+
packet_parser.parse(data)
|
23
|
+
packet_parser.each_packet { |packet| handle_request(packet) }
|
24
|
+
end
|
25
|
+
|
26
|
+
def handle_request(packet)
|
27
|
+
case packet.class.name
|
28
|
+
when 'Spinal::Packet::Query' : handle_query(packet)
|
29
|
+
when 'Spinal::Packet::Register' : handle_registration(packet)
|
30
|
+
else raise("Unknown packet type: #{packet.class.inspect}")
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
def handle_query(packet)
|
35
|
+
if resource = resources[packet.address]
|
36
|
+
resource.request(packet.raw) { |resp| send_data(resp) }
|
37
|
+
else
|
38
|
+
send_data(Packet::Response.new(StatusCodes::UNKNOWN_RESOURCE).raw)
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
def handle_registration(packet)
|
43
|
+
resources[packet.address] ||= Resource.new(packet.address)
|
44
|
+
resources[packet.address].add_service(packet.host, packet.port)
|
45
|
+
send_data(Packet::Response.new(StatusCodes::OK).raw)
|
46
|
+
close_connection_after_writing
|
47
|
+
end
|
48
|
+
|
49
|
+
def resources
|
50
|
+
@@resources ||= {}
|
51
|
+
end
|
52
|
+
|
53
|
+
def packet_parser
|
54
|
+
@packet_parser ||= PacketParser.new
|
55
|
+
end
|
56
|
+
|
57
|
+
class Resource
|
58
|
+
def initialize(address)
|
59
|
+
@address = address
|
60
|
+
@queue = []
|
61
|
+
@pool = ServiceConnectionPool.new
|
62
|
+
end
|
63
|
+
|
64
|
+
def add_service(host, port)
|
65
|
+
@pool.connect_service(host, port)
|
66
|
+
end
|
67
|
+
|
68
|
+
def request(data, &reply_callback)
|
69
|
+
if conn = @pool.reserve
|
70
|
+
conn.reply &reply_callback
|
71
|
+
conn.release do
|
72
|
+
@pool.release(conn)
|
73
|
+
if queued_request = @queue.pop
|
74
|
+
request(queued_request[0], &queued_request[1])
|
75
|
+
end
|
76
|
+
end
|
77
|
+
conn.send_data(data)
|
78
|
+
else
|
79
|
+
@queue.unshift([data, reply_callback])
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
class ServiceConnectionPool
|
85
|
+
def initialize
|
86
|
+
@connections = []
|
87
|
+
end
|
88
|
+
|
89
|
+
def connect_service(host, port)
|
90
|
+
@connections.unshift(EM.connect(host, port, ServiceConnection))
|
91
|
+
end
|
92
|
+
|
93
|
+
def reserve
|
94
|
+
@connections.pop
|
95
|
+
end
|
96
|
+
|
97
|
+
def release(conn)
|
98
|
+
@connections.unshift(conn)
|
99
|
+
end
|
100
|
+
end
|
101
|
+
|
102
|
+
module ServiceConnection
|
103
|
+
def receive_data(data)
|
104
|
+
Spinal.debug "ServiceConnection received: #{data}"
|
105
|
+
@reply.call(data)
|
106
|
+
@release.call
|
107
|
+
end
|
108
|
+
|
109
|
+
def reply(&cb)
|
110
|
+
@reply = cb
|
111
|
+
end
|
112
|
+
|
113
|
+
def release(&cb)
|
114
|
+
@release = cb
|
115
|
+
end
|
116
|
+
end
|
117
|
+
end
|
118
|
+
end
|
@@ -0,0 +1,40 @@
|
|
1
|
+
$: << File.dirname(__FILE__)
|
2
|
+
require "shared"
|
3
|
+
|
4
|
+
module Spinal
|
5
|
+
module ThinDeferrableConnection
|
6
|
+
def process
|
7
|
+
if threaded?
|
8
|
+
@request.threaded = true
|
9
|
+
EventMachine.defer(method(:pre_process), method(:post_process))
|
10
|
+
else
|
11
|
+
@request.threaded = false
|
12
|
+
pre_process_result = pre_process
|
13
|
+
if pre_process_result.is_a?(DeferredResponse)
|
14
|
+
pre_process_result.post_process_callback { |res| post_process(res) }
|
15
|
+
else
|
16
|
+
post_process(pre_process_result)
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
def self.included(target)
|
22
|
+
target.send(:remove_method, :process)
|
23
|
+
end
|
24
|
+
|
25
|
+
class DeferredResponse
|
26
|
+
def initialize(&response_callback)
|
27
|
+
@response_callback = response_callback
|
28
|
+
end
|
29
|
+
|
30
|
+
def post_process_callback(&post_process_callback)
|
31
|
+
@post_process_callback = post_process_callback
|
32
|
+
end
|
33
|
+
|
34
|
+
def deliver(packet)
|
35
|
+
result = @response_callback.call(packet)
|
36
|
+
@post_process_callback.call(result)
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
data/spinal.gemspec
ADDED
@@ -0,0 +1,38 @@
|
|
1
|
+
GEMSPEC =Gem::Specification.new do |s|
|
2
|
+
s.name = 'spinal'
|
3
|
+
s.version = '0.0.0'
|
4
|
+
s.platform = Gem::Platform::RUBY
|
5
|
+
s.rubyforge_project = "spinal"
|
6
|
+
s.summary, s.description = 'Lightweight middleware for bidirectional process distribution over TCP'
|
7
|
+
s.authors = 'Andy Kent, George Malamidis'
|
8
|
+
s.email = 'george@nutrun.com'
|
9
|
+
# s.homepage = 'http://spinal.rubyforge.org'
|
10
|
+
s.has_rdoc = false
|
11
|
+
s.rdoc_options += ['--quiet', '--title', 'Spinal', '--main', 'README']
|
12
|
+
s.extra_rdoc_files = ['README', 'COPYING']
|
13
|
+
s.files = [
|
14
|
+
"COPYING",
|
15
|
+
"Rakefile",
|
16
|
+
"README",
|
17
|
+
"spinal.gemspec",
|
18
|
+
"etc/demo/daemon.rb",
|
19
|
+
"etc/demo/service.rb",
|
20
|
+
"etc/demo/web.rb",
|
21
|
+
"etc/protocol.txt",
|
22
|
+
"lib/spinal/client/blocking.rb",
|
23
|
+
"lib/spinal/client/evented.rb",
|
24
|
+
"lib/spinal/rack_remote_query.rb",
|
25
|
+
"lib/spinal/service/connection.rb",
|
26
|
+
"lib/spinal/service/registration.rb",
|
27
|
+
"lib/spinal/service/request.rb",
|
28
|
+
"lib/spinal/service/runner.rb",
|
29
|
+
"lib/spinal/service.rb",
|
30
|
+
"lib/spinal/shared/error.rb",
|
31
|
+
"lib/spinal/shared/packet.rb",
|
32
|
+
"lib/spinal/shared/packet_parser.rb",
|
33
|
+
"lib/spinal/shared/status_codes.rb",
|
34
|
+
"lib/spinal/shared.rb",
|
35
|
+
"lib/spinal/spinald.rb",
|
36
|
+
"lib/spinal/thin_deferrable_connection.rb"
|
37
|
+
]
|
38
|
+
end
|
metadata
ADDED
@@ -0,0 +1,80 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: gmalamid-spinal
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Andy Kent, George Malamidis
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
|
12
|
+
date: 2008-10-27 00:00:00 -07:00
|
13
|
+
default_executable:
|
14
|
+
dependencies: []
|
15
|
+
|
16
|
+
description:
|
17
|
+
email: george@nutrun.com
|
18
|
+
executables: []
|
19
|
+
|
20
|
+
extensions: []
|
21
|
+
|
22
|
+
extra_rdoc_files:
|
23
|
+
- README
|
24
|
+
- COPYING
|
25
|
+
files:
|
26
|
+
- COPYING
|
27
|
+
- Rakefile
|
28
|
+
- README
|
29
|
+
- spinal.gemspec
|
30
|
+
- etc/demo/daemon.rb
|
31
|
+
- etc/demo/service.rb
|
32
|
+
- etc/demo/web.rb
|
33
|
+
- etc/protocol.txt
|
34
|
+
- lib/spinal/client/blocking.rb
|
35
|
+
- lib/spinal/client/evented.rb
|
36
|
+
- lib/spinal/rack_remote_query.rb
|
37
|
+
- lib/spinal/service/connection.rb
|
38
|
+
- lib/spinal/service/registration.rb
|
39
|
+
- lib/spinal/service/request.rb
|
40
|
+
- lib/spinal/service/runner.rb
|
41
|
+
- lib/spinal/service.rb
|
42
|
+
- lib/spinal/shared/error.rb
|
43
|
+
- lib/spinal/shared/packet.rb
|
44
|
+
- lib/spinal/shared/packet_parser.rb
|
45
|
+
- lib/spinal/shared/status_codes.rb
|
46
|
+
- lib/spinal/shared.rb
|
47
|
+
- lib/spinal/spinald.rb
|
48
|
+
- lib/spinal/thin_deferrable_connection.rb
|
49
|
+
has_rdoc: false
|
50
|
+
homepage:
|
51
|
+
post_install_message:
|
52
|
+
rdoc_options:
|
53
|
+
- --quiet
|
54
|
+
- --title
|
55
|
+
- Spinal
|
56
|
+
- --main
|
57
|
+
- README
|
58
|
+
require_paths:
|
59
|
+
- lib
|
60
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
61
|
+
requirements:
|
62
|
+
- - ">="
|
63
|
+
- !ruby/object:Gem::Version
|
64
|
+
version: "0"
|
65
|
+
version:
|
66
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
67
|
+
requirements:
|
68
|
+
- - ">="
|
69
|
+
- !ruby/object:Gem::Version
|
70
|
+
version: "0"
|
71
|
+
version:
|
72
|
+
requirements: []
|
73
|
+
|
74
|
+
rubyforge_project: spinal
|
75
|
+
rubygems_version: 1.2.0
|
76
|
+
signing_key:
|
77
|
+
specification_version: 2
|
78
|
+
summary: Lightweight middleware for bidirectional process distribution over TCP
|
79
|
+
test_files: []
|
80
|
+
|