servicy 0.0.3
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/README.md +257 -0
- data/Servicy.gemspec +47 -0
- data/VERSION +1 -0
- data/bin/servicy +305 -0
- data/lib/api.rb +126 -0
- data/lib/client.rb +114 -0
- data/lib/hash.rb +14 -0
- data/lib/load_balancer/random.rb +12 -0
- data/lib/load_balancer/round_robin.rb +18 -0
- data/lib/load_balancer.rb +25 -0
- data/lib/server/server.rb +222 -0
- data/lib/server/service_searcher.rb +115 -0
- data/lib/server.rb +8 -0
- data/lib/service.rb +200 -0
- data/lib/servicy.rb +9 -0
- data/lib/transport/in_memory_transport.rb +44 -0
- data/lib/transport/messages.rb +141 -0
- data/lib/transport/tcp_transport.rb +50 -0
- data/lib/transports.rb +54 -0
- data/test.rb +74 -0
- metadata +65 -0
data/lib/service.rb
ADDED
@@ -0,0 +1,200 @@
|
|
1
|
+
require 'timeout'
|
2
|
+
|
3
|
+
module Servicy
|
4
|
+
class Service
|
5
|
+
include Comparable
|
6
|
+
attr_reader :name, :version, :host, :port, :heartbeat_port, :protocol,
|
7
|
+
:api, :heartbeat_check_rate, :latencies, :heartbeats
|
8
|
+
attr_accessor :heartbeat_last_check
|
9
|
+
|
10
|
+
NAME_REGEX = /[^\.]+\.([^\.]+\.?)+/
|
11
|
+
VERSION_REGEX = /\d+\.\d+\.\d+(-p?\d+)?/
|
12
|
+
|
13
|
+
# Create a new service.
|
14
|
+
# (see Servicy::Server#register)
|
15
|
+
def initialize(args={})
|
16
|
+
args = args.inject({}) { |h,(k,v)| h[k.to_sym] = v; h }
|
17
|
+
raise ArgumentError.new("Must provide a service name") unless args[:name]
|
18
|
+
raise ArgumentError.new("Must provide a service host") unless args[:host]
|
19
|
+
raise ArgumentError.new("Must provide a service port") unless args[:port]
|
20
|
+
|
21
|
+
unless args[:name] =~ NAME_REGEX
|
22
|
+
raise ArgumentError.new("Service name must be in inverse-domain format")
|
23
|
+
end
|
24
|
+
|
25
|
+
args[:version] ||= '1.0.0'
|
26
|
+
unless args[:version] =~ VERSION_REGEX
|
27
|
+
raise ArgumentError.new("Service version must be in formation M.m.r(-p)?")
|
28
|
+
end
|
29
|
+
|
30
|
+
if !args[:port].is_a?(Fixnum) || args[:port] < 1 || args[:port] > 65535
|
31
|
+
raise ArgumentError.new("Service port must be an integer betwee 1-65535")
|
32
|
+
end
|
33
|
+
|
34
|
+
args[:heartbeat_port] ||= args[:port]
|
35
|
+
if !args[:heartbeat_port].is_a?(Fixnum) || args[:heartbeat_port] < 1 || args[:heartbeat_port] > 65535
|
36
|
+
raise ArgumentError.new("Service heartbeat port must be an integer betwee 1-65535")
|
37
|
+
end
|
38
|
+
|
39
|
+
if args[:api]
|
40
|
+
raise ArgumentError.new("Service api not correctly defiend") unless check_api(args[:api])
|
41
|
+
end
|
42
|
+
|
43
|
+
args[:protocol] ||= 'HTTP/S'
|
44
|
+
|
45
|
+
@name = args[:name]
|
46
|
+
@version = args[:version]
|
47
|
+
@host = args[:host]
|
48
|
+
@protocol = args[:protocol]
|
49
|
+
@port = args[:port]
|
50
|
+
@heartbeat_port = args[:heartbeat_port]
|
51
|
+
@api = args[:api] || {}
|
52
|
+
@heartbeat_check_rate = args[:heartbeat_check_rate] || 1
|
53
|
+
@heartbeat_last_check = 0
|
54
|
+
@latencies = []
|
55
|
+
@heartbeats = []
|
56
|
+
end
|
57
|
+
|
58
|
+
# Get a configuration value
|
59
|
+
# This method exists mostly so that I wouldn't have to change a bunch of
|
60
|
+
# early tests, and because I know some people like to access things like a
|
61
|
+
# hash.
|
62
|
+
# @param[Symbol] thing The config value to get.
|
63
|
+
# @return [Object,nil] The value if found, nil otherwise.
|
64
|
+
def [](thing)
|
65
|
+
return self.send(thing) if self.respond_to?(thing)
|
66
|
+
nil
|
67
|
+
end
|
68
|
+
|
69
|
+
# Build a hash of the configuration values for this object.
|
70
|
+
# Used to dump json configurations.
|
71
|
+
# @return [Hash] Configuration data for the Service
|
72
|
+
def as_json
|
73
|
+
{
|
74
|
+
name: name,
|
75
|
+
version: version,
|
76
|
+
host: host,
|
77
|
+
protocol: protocol,
|
78
|
+
port: port,
|
79
|
+
heartbeat_port: heartbeat_port,
|
80
|
+
api: api
|
81
|
+
}
|
82
|
+
end
|
83
|
+
|
84
|
+
# Used by the Comparable mixin for comparing two services.
|
85
|
+
# (see Comparable)
|
86
|
+
def <=>(other)
|
87
|
+
self.as_json <=> other.as_json
|
88
|
+
end
|
89
|
+
|
90
|
+
# Returns the version of the current service as a number that we can use
|
91
|
+
# for comparisons to other version numbers.
|
92
|
+
# @return [Integer] A number representing the version
|
93
|
+
def version_as_number
|
94
|
+
self.class.version_as_number(version)
|
95
|
+
end
|
96
|
+
|
97
|
+
# (see #version_as_number)
|
98
|
+
# @param [String] A version string as per {Servicy::Server#register}
|
99
|
+
def self.version_as_number(version)
|
100
|
+
parts = version.split('.')
|
101
|
+
parts.last.gsub(/\D/, '')
|
102
|
+
parts[0].to_i * 1000 + parts[1].to_i * 100 + parts[2].to_i * 10 + (parts[3] || 0)
|
103
|
+
end
|
104
|
+
|
105
|
+
# Check weather or not a service is up, based on connecting to the hearbeat
|
106
|
+
# port, and reading a single byte.
|
107
|
+
def up?
|
108
|
+
t1 = Time.now
|
109
|
+
s = TCPSocket.new(host, heartbeat_port)
|
110
|
+
Timeout.timeout(5) do
|
111
|
+
s.recvfrom(1)
|
112
|
+
end
|
113
|
+
record_heartbeat(1)
|
114
|
+
return true
|
115
|
+
rescue
|
116
|
+
record_heartbeat(0)
|
117
|
+
return false
|
118
|
+
ensure
|
119
|
+
s.close rescue nil
|
120
|
+
t2 = Time.now
|
121
|
+
record_latency(t1, t2)
|
122
|
+
end
|
123
|
+
|
124
|
+
# Returns what the avg latency for a service is, based on the timings of
|
125
|
+
# their heartbeat connections.
|
126
|
+
# @return [Float] avg latency is ms
|
127
|
+
def avg_latency
|
128
|
+
latencies.reduce(0.0, &:+) / latencies.length.to_f
|
129
|
+
end
|
130
|
+
|
131
|
+
# Returns the uptime for a service based on heartbeat checks as a float
|
132
|
+
# between 0 and 1 -- a percentage.
|
133
|
+
# @return [Float] avg uptime as a percentage (0..1)
|
134
|
+
def uptime
|
135
|
+
heartbeats.reduce(0, &:+) / heartbeats.length.to_f
|
136
|
+
end
|
137
|
+
|
138
|
+
# Get a nice, printable name
|
139
|
+
# return [String]
|
140
|
+
def to_s
|
141
|
+
name + "#" + host
|
142
|
+
end
|
143
|
+
|
144
|
+
# Returns a hash with the configuration options needed for registration and
|
145
|
+
# remote service operation.
|
146
|
+
def to_h
|
147
|
+
{
|
148
|
+
name: name,
|
149
|
+
version: version,
|
150
|
+
host: host,
|
151
|
+
port: port,
|
152
|
+
heartbeat_port: heartbeat_port,
|
153
|
+
protocol: protocol,
|
154
|
+
api: api
|
155
|
+
}
|
156
|
+
end
|
157
|
+
|
158
|
+
# Return the api for a given method (instance, or class) or nil if not
|
159
|
+
# found.
|
160
|
+
def api_for(method_type, method)
|
161
|
+
api[method_type] && api[method_type].select { |a| a[:name] == method }.first
|
162
|
+
end
|
163
|
+
|
164
|
+
private
|
165
|
+
|
166
|
+
# These are just to keep us from filling up memory.
|
167
|
+
def record_latency(t1, t2)
|
168
|
+
@latencies << t2 - t1
|
169
|
+
@latencies = @latencies[0...10] if @latencies.length > 10
|
170
|
+
end
|
171
|
+
|
172
|
+
def record_heartbeat(h)
|
173
|
+
@heartbeats << h
|
174
|
+
@heartbeats = @heartbeats[0...10] if @heartbeats.length > 10
|
175
|
+
end
|
176
|
+
|
177
|
+
# The api is broken into two kinds of methods; instance and class. Each can
|
178
|
+
# define method name, argument number and types, and return types.
|
179
|
+
def check_api(api_def)
|
180
|
+
raise ArgumentError.new("API can only define instance and class methods") unless api_def.contains_only?(:instance, :class)
|
181
|
+
|
182
|
+
api_def.each do |(_, methods)|
|
183
|
+
# Each method is itself a hash of name, args, return type, and docs.
|
184
|
+
methods.each do |method|
|
185
|
+
raise ArgumentError.new("Methods can only contain name, args, return, and docs") unless method.contains_only?(:name, :args, :return, :docs)
|
186
|
+
raise ArgumentError.new("Methods must define a name") unless method.include? :name
|
187
|
+
|
188
|
+
method[:args].each do |arg|
|
189
|
+
# Each argument is an optional name, and optional type, and an
|
190
|
+
# option "required" flag.
|
191
|
+
raise ArgumentError.new("Arguments can only define name, type, and required") unless arg.contains_only?(:name, :type, :required)
|
192
|
+
end
|
193
|
+
|
194
|
+
return false if method[:return] && !method[:return].contains_only?(:type)
|
195
|
+
end
|
196
|
+
end
|
197
|
+
end
|
198
|
+
end
|
199
|
+
end
|
200
|
+
|
data/lib/servicy.rb
ADDED
@@ -0,0 +1,9 @@
|
|
1
|
+
$: << File.expand_path(File.join(File.dirname(__FILE__)))
|
2
|
+
|
3
|
+
# NOTE: You will almost never need both of these things. In all likeliness, you
|
4
|
+
# will want to do something like `require 'servicy/client'` yourself. This is
|
5
|
+
# here mostly as a convinience to rspec.
|
6
|
+
require 'hash'
|
7
|
+
require 'server'
|
8
|
+
require 'client'
|
9
|
+
require 'api'
|
@@ -0,0 +1,44 @@
|
|
1
|
+
require 'transports'
|
2
|
+
require 'socket'
|
3
|
+
require 'tempfile'
|
4
|
+
|
5
|
+
module Servicy
|
6
|
+
# This is a simple transport implementation that is to be used primarily for
|
7
|
+
# testing, so that I don't have to have a bunch of servers running just to
|
8
|
+
# run rspec...
|
9
|
+
class InMemoryTransport < Servicy::Transport
|
10
|
+
def send(message)
|
11
|
+
socket = UNIXSocket.new socket_path
|
12
|
+
socket.puts message.body
|
13
|
+
message = Message.new(socket.gets)
|
14
|
+
socket.close
|
15
|
+
message
|
16
|
+
end
|
17
|
+
|
18
|
+
def start
|
19
|
+
@server = UNIXServer.open(socket_path)
|
20
|
+
while s = @server.accept
|
21
|
+
Thread.new do
|
22
|
+
message = Message.new s.gets
|
23
|
+
result = yield message
|
24
|
+
s.puts result.body
|
25
|
+
s.close
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
def stop
|
31
|
+
@server && @server.close
|
32
|
+
end
|
33
|
+
|
34
|
+
def socket_path
|
35
|
+
@socket_path ||= begin
|
36
|
+
file = Tempfile.new(@config[:path] || 'servicy-in-memory-transport')
|
37
|
+
file.close
|
38
|
+
path = file.path
|
39
|
+
file.delete
|
40
|
+
path
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
@@ -0,0 +1,141 @@
|
|
1
|
+
module Servicy
|
2
|
+
class Transport
|
3
|
+
# The Message class contains messages passed between client and server, as
|
4
|
+
# well as utility methods for making messages to be sent.
|
5
|
+
class Message
|
6
|
+
include Comparable
|
7
|
+
|
8
|
+
attr_reader :struct
|
9
|
+
|
10
|
+
# Create a new message that will be sent over some transport.
|
11
|
+
# @param [Hash] stuff A hash that will constitute the message. You should
|
12
|
+
# likely never call this directy (i.e. Message.new), and instead use the
|
13
|
+
# class methods {#registration}, {#query}, etc.
|
14
|
+
def initialize(stuff)
|
15
|
+
if stuff.is_a?(Hash)
|
16
|
+
@struct = stuff
|
17
|
+
else
|
18
|
+
@struct = JSON.parse(stuff)
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
def method_missing(name, *args, &block)
|
23
|
+
return struct[name] if struct.keys.include?(name)
|
24
|
+
return struct[name.to_s] if struct.keys.include?(name.to_s)
|
25
|
+
super
|
26
|
+
end
|
27
|
+
|
28
|
+
# The body of the message for cases where you send the message over the
|
29
|
+
# wire, or through a database.
|
30
|
+
# @return [String] A JSON representation of the message
|
31
|
+
def body
|
32
|
+
struct.to_json
|
33
|
+
end
|
34
|
+
|
35
|
+
# The response code.
|
36
|
+
# @return [Integer] 200 if successful, 404 if not-found, 500 if error.
|
37
|
+
def response_code
|
38
|
+
!!struct[:success] ? 200 : struct[:response_code]
|
39
|
+
end
|
40
|
+
|
41
|
+
# Create a service registration message
|
42
|
+
# @param [{Servicy::Service}] service The service that is being registered.
|
43
|
+
# @return [{Message}] A {Message} that can be sent through a
|
44
|
+
# {Servicy::Transport}
|
45
|
+
def self.registration(service)
|
46
|
+
new({
|
47
|
+
message: "registration",
|
48
|
+
service: service.as_json
|
49
|
+
})
|
50
|
+
end
|
51
|
+
|
52
|
+
# Create a service discovery message.
|
53
|
+
# @param [Hash] args A hash that defines the query. (see Servicy::Server#find)
|
54
|
+
# @return [{Message}] A {Message} that can be sent through a
|
55
|
+
# {Servicy::Transport}
|
56
|
+
def self.query(args, only_one=false)
|
57
|
+
raise ArgumentError.new("Invalid search query") unless query_valid?(args)
|
58
|
+
new({
|
59
|
+
message: "query",
|
60
|
+
query: args,
|
61
|
+
only_one: only_one
|
62
|
+
})
|
63
|
+
end
|
64
|
+
|
65
|
+
# Create a service discover message where I only want one provider.
|
66
|
+
# (see .query)
|
67
|
+
def self.query_one(args)
|
68
|
+
query(args, true)
|
69
|
+
end
|
70
|
+
|
71
|
+
# Create a services search response message
|
72
|
+
# @param [Array<{Servicy::Service}>] services The services that were
|
73
|
+
# found based on a {Servicy::Server} query
|
74
|
+
# @return [{Message}] A {Message} that can be sent through a
|
75
|
+
# {Servicy::Transport}
|
76
|
+
def self.services(services)
|
77
|
+
new({
|
78
|
+
success: true,
|
79
|
+
message: "services",
|
80
|
+
services: services.map { |s| s.as_json }
|
81
|
+
})
|
82
|
+
end
|
83
|
+
|
84
|
+
# Create an error response message
|
85
|
+
# @param [String] error_message The error message to report
|
86
|
+
# @param [Integer] response_code The response code for the error.
|
87
|
+
# Defaults to 500
|
88
|
+
# @return [{Message}] A {Message} that can be sent through a
|
89
|
+
# {Servicy::Transport}
|
90
|
+
def self.error(error_message, response_code=500)
|
91
|
+
new({
|
92
|
+
success: false,
|
93
|
+
error: error_message
|
94
|
+
})
|
95
|
+
end
|
96
|
+
|
97
|
+
# A utility method for 404 Not-Found error message
|
98
|
+
# (see .error)
|
99
|
+
def self.not_found
|
100
|
+
error "No results found", 404
|
101
|
+
end
|
102
|
+
|
103
|
+
# A generic success message
|
104
|
+
def self.success
|
105
|
+
new({
|
106
|
+
success: true
|
107
|
+
})
|
108
|
+
end
|
109
|
+
|
110
|
+
# A message sent to a server to gather statistics about the server, also
|
111
|
+
# used by the server to send the response back to the client.
|
112
|
+
# @param [Hash,nil] stats The stats to report, if any.
|
113
|
+
def self.statistics(stats=nil)
|
114
|
+
new({
|
115
|
+
message: "stats",
|
116
|
+
stats: stats
|
117
|
+
})
|
118
|
+
end
|
119
|
+
|
120
|
+
# Allow comparing of messages. This doesn't make for useful sorting, at
|
121
|
+
# the moment, but does make it easy to know if two things are saying the
|
122
|
+
# same thing.
|
123
|
+
def <=>(b)
|
124
|
+
self.struct == b.struct ? 0 : -1
|
125
|
+
end
|
126
|
+
|
127
|
+
private
|
128
|
+
|
129
|
+
def self.query_valid?(args)
|
130
|
+
return false if !args[:name] && !args[:api]
|
131
|
+
if args[:min_version] && args[:max_version]
|
132
|
+
min = Servicy::Service.version_as_number(args[:min_version])
|
133
|
+
max = Servicy::Service.version_as_number(args[:max_version])
|
134
|
+
return false if min > max
|
135
|
+
end
|
136
|
+
return false if args[:version] && (args[:min_version] || args[:max_version])
|
137
|
+
true
|
138
|
+
end
|
139
|
+
end
|
140
|
+
end
|
141
|
+
end
|
@@ -0,0 +1,50 @@
|
|
1
|
+
require 'transports'
|
2
|
+
require 'socket'
|
3
|
+
|
4
|
+
module Servicy
|
5
|
+
# This is a transport that is almost an exact duplicate of the UNIXSocket
|
6
|
+
# transport, but can actually go over the wire. That whole, "Everything is a
|
7
|
+
# file" business works out well...
|
8
|
+
class TCPTransport < Servicy::Transport
|
9
|
+
HELLO = "Servicy says HI!"
|
10
|
+
def send(message)
|
11
|
+
socket = TCPSocket.new host, port
|
12
|
+
hello = socket.gets
|
13
|
+
raise "Not a servicy server" unless hello.strip == HELLO
|
14
|
+
socket.send message.body + "\n", 0
|
15
|
+
# FIXME: For some reason, this stalls when using the command-line client.
|
16
|
+
# Wireshark isn't helpful while I'm on the vpn, so I will have to fix
|
17
|
+
# this later.
|
18
|
+
message = Message.new(socket.gets)
|
19
|
+
socket.close
|
20
|
+
message
|
21
|
+
end
|
22
|
+
|
23
|
+
def start(&block)
|
24
|
+
@server = TCPServer.open(port)
|
25
|
+
while s = @server.accept
|
26
|
+
Thread.new do
|
27
|
+
s.puts HELLO
|
28
|
+
message = Message.new s.gets
|
29
|
+
result = yield message
|
30
|
+
s.puts result.body
|
31
|
+
s.close
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
def stop
|
37
|
+
@server && @server.close
|
38
|
+
end
|
39
|
+
|
40
|
+
private
|
41
|
+
|
42
|
+
def host
|
43
|
+
@config[:host]
|
44
|
+
end
|
45
|
+
|
46
|
+
def port
|
47
|
+
@config[:port]
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
data/lib/transports.rb
ADDED
@@ -0,0 +1,54 @@
|
|
1
|
+
require 'service'
|
2
|
+
require 'transport/messages'
|
3
|
+
|
4
|
+
module Servicy
|
5
|
+
# The Transport class is an abstract class whos implementations handle the
|
6
|
+
# actual communication over the wire or in memory between server and client.
|
7
|
+
# However, in the abstract, it doesn't matter how things are transported,
|
8
|
+
# only that they are and conform to the format described here.
|
9
|
+
class Transport
|
10
|
+
attr_reader :config
|
11
|
+
|
12
|
+
# @param [Hash] config Configuration options for the transport
|
13
|
+
def initialize(config={})
|
14
|
+
@config = config
|
15
|
+
end
|
16
|
+
|
17
|
+
# Override this method to send a message from {Client} to {Server}
|
18
|
+
def send(messge)
|
19
|
+
raise 'Not implemented'
|
20
|
+
end
|
21
|
+
|
22
|
+
# Override this method to yield a message when received on the {Server}. It
|
23
|
+
# should yield to the provided block the message received as a
|
24
|
+
# {Transport::Message} object, and send back the return value of the block
|
25
|
+
# to the client.
|
26
|
+
def start(&block)
|
27
|
+
raise 'Not implemented'
|
28
|
+
end
|
29
|
+
|
30
|
+
# Called when a transport is stopped
|
31
|
+
def stop
|
32
|
+
end
|
33
|
+
|
34
|
+
# This attempts to load a service based on the name.
|
35
|
+
def self.method_missing(name, *args, &block)
|
36
|
+
class_name = "#{name.to_s}Transport"
|
37
|
+
begin
|
38
|
+
c = Module.const_get(class_name)
|
39
|
+
return c if c && c.ancestors.include?(Servicy::Transport)
|
40
|
+
rescue NameError
|
41
|
+
c = Servicy.const_get(class_name)
|
42
|
+
return c if c && c.ancestors.include?(Servicy::Transport)
|
43
|
+
end
|
44
|
+
rescue
|
45
|
+
super
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
# Load all the transports
|
51
|
+
Dir[File.expand_path(File.join(File.dirname(__FILE__), 'transport', '*.rb'))].each do |file|
|
52
|
+
next if file.start_with?('.') || File.directory?(file)
|
53
|
+
require File.expand_path(file)
|
54
|
+
end
|
data/test.rb
ADDED
@@ -0,0 +1,74 @@
|
|
1
|
+
require './lib/servicy'
|
2
|
+
|
3
|
+
class Foo
|
4
|
+
def initialize(thing)
|
5
|
+
@thing = thing
|
6
|
+
end
|
7
|
+
|
8
|
+
# Do some foobar work
|
9
|
+
# @param [String] a
|
10
|
+
# @param [Integer] b
|
11
|
+
def foobar(a,b)
|
12
|
+
p a
|
13
|
+
p b
|
14
|
+
end
|
15
|
+
|
16
|
+
# Do something on the class
|
17
|
+
# @return [Hash{Integer => Array<String>}]
|
18
|
+
def self.barbaz
|
19
|
+
puts "OH MY GOD!"
|
20
|
+
{ 1 => ['foo', 'bar'] }
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
# Create a new class that wraps foo. It will be called FooAPI, and will behave
|
25
|
+
# just like foo, but with contract checking. It will be the place that in the
|
26
|
+
# future we extend to behave more like an API
|
27
|
+
api = Servicy::Client.create_api_for Foo
|
28
|
+
# p api
|
29
|
+
|
30
|
+
# Instance-y-stuff
|
31
|
+
foo = api.new(:something)
|
32
|
+
foo.foobar('string',1)
|
33
|
+
|
34
|
+
# Class-y stuff
|
35
|
+
api.barbaz
|
36
|
+
|
37
|
+
# Now we should get some explosions for failing to comply with the contracts
|
38
|
+
begin
|
39
|
+
foo.foobar(:not_a_string, 'or an integer')
|
40
|
+
rescue => e
|
41
|
+
puts e
|
42
|
+
end
|
43
|
+
|
44
|
+
###############################################################################
|
45
|
+
# Making a working api with an api object. This is just some ideas to outline
|
46
|
+
# how I would like the interface to look, not really any working code just yet.
|
47
|
+
#
|
48
|
+
|
49
|
+
# Finding a registered service and using a remote service instead of a local
|
50
|
+
# object. This would take the API object, gather information about it, make the
|
51
|
+
# query to the service discovery portal, and return a new api model (that is
|
52
|
+
# the same specifications as the original) that talks with the remote service
|
53
|
+
# instead of the local object, raising an error if something goes wrong.
|
54
|
+
transport = Servicy::Transport.TCP.new(port: 1234)
|
55
|
+
api = Servicy::Client.find_service_provider_for(api, transport)
|
56
|
+
|
57
|
+
# This should also be able to work with non-API objects given to it. In the
|
58
|
+
# case of a library that you don't have installed locally.
|
59
|
+
service = Servicy::Client.find_service(name: 'foobar') # This alredy exists
|
60
|
+
api = service.api
|
61
|
+
|
62
|
+
# Starting a server with a given client/api object. Ideally, you can start
|
63
|
+
# multipl servers.
|
64
|
+
api.start_server('HTTP', 80)
|
65
|
+
api.start_server('HTTPS', 443)
|
66
|
+
api.start_server('TCP', 1235)
|
67
|
+
|
68
|
+
# Maybe it would be better to do it with Transport objects...
|
69
|
+
t = Transport.tcp.new(1235)
|
70
|
+
api.start_server t
|
71
|
+
|
72
|
+
# Combining registration and server starting
|
73
|
+
api.register_with('127.0.0.1:1234', Transport.TCP.new(1235))
|
74
|
+
|
metadata
ADDED
@@ -0,0 +1,65 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: servicy
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.3
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Thomas Luce
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2014-08-11 00:00:00.000000000 Z
|
12
|
+
dependencies: []
|
13
|
+
description: A service registration and discovery framework, as a server and client
|
14
|
+
library.
|
15
|
+
email: thomas.luce@gmail.com
|
16
|
+
executables:
|
17
|
+
- servicy
|
18
|
+
extensions: []
|
19
|
+
extra_rdoc_files:
|
20
|
+
- README.md
|
21
|
+
files:
|
22
|
+
- README.md
|
23
|
+
- Servicy.gemspec
|
24
|
+
- VERSION
|
25
|
+
- bin/servicy
|
26
|
+
- lib/api.rb
|
27
|
+
- lib/client.rb
|
28
|
+
- lib/hash.rb
|
29
|
+
- lib/load_balancer.rb
|
30
|
+
- lib/load_balancer/random.rb
|
31
|
+
- lib/load_balancer/round_robin.rb
|
32
|
+
- lib/server.rb
|
33
|
+
- lib/server/server.rb
|
34
|
+
- lib/server/service_searcher.rb
|
35
|
+
- lib/service.rb
|
36
|
+
- lib/servicy.rb
|
37
|
+
- lib/transport/in_memory_transport.rb
|
38
|
+
- lib/transport/messages.rb
|
39
|
+
- lib/transport/tcp_transport.rb
|
40
|
+
- lib/transports.rb
|
41
|
+
- test.rb
|
42
|
+
homepage: https://github.com/thomasluce/servicy
|
43
|
+
licenses: []
|
44
|
+
metadata: {}
|
45
|
+
post_install_message:
|
46
|
+
rdoc_options: []
|
47
|
+
require_paths:
|
48
|
+
- lib
|
49
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
50
|
+
requirements:
|
51
|
+
- - '>='
|
52
|
+
- !ruby/object:Gem::Version
|
53
|
+
version: '0'
|
54
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
55
|
+
requirements:
|
56
|
+
- - '>='
|
57
|
+
- !ruby/object:Gem::Version
|
58
|
+
version: '0'
|
59
|
+
requirements: []
|
60
|
+
rubyforge_project:
|
61
|
+
rubygems_version: 2.2.2
|
62
|
+
signing_key:
|
63
|
+
specification_version: 4
|
64
|
+
summary: A service registration and discovery framework
|
65
|
+
test_files: []
|