gmalamid-spinal 0.0.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|
+
|