servicy 0.0.3
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.
- 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: []
|