servicy 0.0.5 → 0.0.6
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +82 -224
- data/Servicy.gemspec +7 -4
- data/VERSION +1 -1
- data/lib/api.rb +0 -1
- data/lib/service.rb +6 -6
- data/lib/servicy.rb +3 -156
- data/lib/servicy/config.rb +31 -0
- data/lib/{configurable.rb → servicy/configurable.rb} +0 -0
- data/lib/servicy/magic.rb +120 -0
- data/lib/servicy/servicy_logger.rb +16 -0
- data/lib/transport/in_memory_transport.rb +4 -2
- data/lib/transport/tcp_transport.rb +3 -1
- metadata +6 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 037bc07d1d2d5f3347fbe3502ef0b03d09286cc3
|
4
|
+
data.tar.gz: 804eb780db64765afe2902d4359e71b6c00637ed
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 5255eb13f70e5a1ec8df4341040d0c04a8ff673f4d86c1ac67dd3b542ff44eb2c9053f41876bd04a0b154832995d136bf55e276e192bd6e567c505d3e67dca02
|
7
|
+
data.tar.gz: 457ec866aafbcc3ea6be1cb41de07553ea2d8e7c4a691b42be1cb54c6b6d2005ebdfd2e487369c7fc85debc9ccc381c12342b186c69f03d5edb039a466395d70
|
data/README.md
CHANGED
@@ -8,242 +8,101 @@ SOA application configuration to register themselves by name and/or API, and
|
|
8
8
|
allow service consumers to find instances of services to use, and be relatively
|
9
9
|
sure of up-time, routing, etc.
|
10
10
|
|
11
|
-
|
12
|
-
|
11
|
+
The second goal of Servicy is to build a framework that makes it easy to write
|
12
|
+
services that are easy to write, use and test. In most cases, simply including
|
13
|
+
Servicy in a class containing your business logic. It is my opinion that a
|
14
|
+
programmer should focus on solving business problems, not architectural ones
|
15
|
+
(where possible), and that code should be, above all else, easy to read and
|
16
|
+
maintain.
|
13
17
|
|
14
|
-
|
15
|
-
==========================
|
18
|
+
An example of Servicy can be found [here](https://github.com/thomasluce/servicy_example).
|
16
19
|
|
17
|
-
Servicy
|
20
|
+
Configuring Servicy
|
21
|
+
===================
|
18
22
|
|
19
|
-
|
20
|
-
|
21
|
-
3. A persistent cache of services to allow for fast restarts
|
22
|
-
4. (possibly) A service/API router
|
23
|
-
5. Reporting service to report on service health and usage.
|
24
|
-
6. Client library for service discovery and connection
|
25
|
-
7. Service library for registration, API discovery and building
|
23
|
+
To configure Servicy for your project, there are a few options. The easiest,
|
24
|
+
and recommended way, is to use code similar to the following:
|
26
25
|
|
27
|
-
|
28
|
-
|
29
|
-
should know about the differences and send back to a service requester the
|
30
|
-
correct version based on their requirements. For this, Servicy should have a
|
31
|
-
way of discovering and/or services defining their API to Servicy.
|
32
|
-
|
33
|
-
Each service should also be able to define multiple instances, and Servicy
|
34
|
-
should have multiple strategies for choosing instances of a given service. For
|
35
|
-
example, round-robin, load-based (would require machine-level meta-data to be
|
36
|
-
reported by the services), even requests, etc. To start with, a simple
|
37
|
-
round-robin scheme will likely be the only implementation. Furthermore, Servicy
|
38
|
-
should have knowledge of if a service is up or down, and automatically remove
|
39
|
-
from its list of available providers, or add it back in as needed.
|
40
|
-
|
41
|
-
Also, Servicy should maintain statistics on usage for services and be able to
|
42
|
-
report that information back to a user. Stats should include number of
|
43
|
-
discoveries, cache hits/misses, service up-times, provider failures, and, if it
|
44
|
-
works as a router, request latency. In the event that I end up _not_ making it
|
45
|
-
a router (seems likely at this point), it should still report latency for
|
46
|
-
service discovery and query operations that require back-and-forth with the
|
47
|
-
service.
|
48
|
-
|
49
|
-
Protocol ideas
|
50
|
-
==============
|
51
|
-
|
52
|
-
Servicy is protocol agnostic, and the exact transport mechanism used to
|
53
|
-
communicate between client and server doesn't matter. The data is sent as JSON
|
54
|
-
underneath it all (for now; I may even change things to be format agnostic in
|
55
|
-
the future). You can see examples of the JSON by looking in the
|
56
|
-
Servicy::Transport::Message class.
|
57
|
-
|
58
|
-
Service registration
|
59
|
-
--------------------
|
60
|
-
|
61
|
-
Service registration allows for a service to declare that they exist, how to
|
62
|
-
reach them, and what functionality they provide. Services are named using a
|
63
|
-
Java-style, inverted domain name. For example:
|
64
|
-
|
65
|
-
```
|
66
|
-
com.mycompany.users.authentication
|
67
|
-
```
|
68
|
-
|
69
|
-
might be the name of a user authentication service provided internally. This
|
70
|
-
allows for services to be broken up by what other services they interact with,
|
71
|
-
as well as be easy to read for users browsing the registry.
|
72
|
-
|
73
|
-
Beyond the name, a service must provide some other information for its
|
74
|
-
registry. This includes:
|
75
|
-
|
76
|
-
1. Host to connect to
|
77
|
-
2. Port on the host to connect to
|
78
|
-
3. Protocol used to communicate (HTTP, HTTPS, Thrift, etc.)
|
79
|
-
4. Version number
|
80
|
-
5. Heartbeat port
|
81
|
-
|
82
|
-
The host can be either an IP address or a fully-qualified hostname. In either
|
83
|
-
case it should be route-able in the context of the Servicy host. In other
|
84
|
-
words, if Servicy cannot connect with it, it won't add it to the registry. The
|
85
|
-
port should be an integer between 1 and 65535. The protocol can be anything,
|
86
|
-
however some standards are:
|
87
|
-
|
88
|
-
* HTTP - _Must_ use un-encrypted HTTP
|
89
|
-
* HTTPS - _Must_ use TLS over HTTP
|
90
|
-
* HTTP/S - Can use either HTTP or HTTPS
|
91
|
-
* Thrift
|
92
|
-
* TCP - Direct, telnet-like communication
|
93
|
-
* UDP - Direct connection, but don't expect replies
|
94
|
-
|
95
|
-
The protocol says nothing about what information needs to be sent or received
|
96
|
-
by a client consuming the service. It is assumed that if the client is looking
|
97
|
-
for the service, it knows how to use it once it is found, and will only use the
|
98
|
-
protocol information to select between possible options provided by the
|
99
|
-
consuming library. Protocols can also be provided as a list of possible
|
100
|
-
options, ordered with most preferred first. At some point in the future, there
|
101
|
-
may be a mechanism by which a service can describe itself in a more useful way,
|
102
|
-
and client libraries can use that information to dynamically build an internal
|
103
|
-
API.
|
104
|
-
|
105
|
-
The version number must follow the format:
|
26
|
+
Servicy global settings
|
27
|
+
-----------------------
|
106
28
|
|
107
|
-
```
|
108
|
-
|
109
|
-
|
29
|
+
```ruby
|
30
|
+
Servicy.configure do |config|
|
31
|
+
config.server.host = 'localhost'
|
32
|
+
config.server.transport = Servicy::Transport.InMemory.new
|
33
|
+
config.server.load_balancer = Servicy::RoundRobinLoadBalancer.new
|
34
|
+
config.server.logger_stream = File.open(File.join('/var', 'log', 'servicy_server.log'), 'a')
|
35
|
+
config.server.config_file = File.expand_path("~/.servicy.conf")
|
110
36
|
|
111
|
-
|
37
|
+
config.client.transport = Servicy::Transport.InMemory.new
|
38
|
+
config.transport.format = Servicy::Formats.JSON
|
112
39
|
|
40
|
+
config
|
41
|
+
end
|
113
42
|
```
|
114
|
-
1.0.2-p143
|
115
|
-
```
|
116
|
-
|
117
|
-
The heartbeat port is a port that Servicy can connect to to ensure that the
|
118
|
-
service is still functioning. In many cases this will be the same port as the
|
119
|
-
primary connection port; Servicy does not attempt to use the heartbeat
|
120
|
-
connection for anything. It simply connects, and if successful, disconnects
|
121
|
-
again. For this reason, your services should be resilliant to this type of
|
122
|
-
traffic.
|
123
|
-
|
124
|
-
Along with the required information already listed, a service can provide some
|
125
|
-
optional information about the API that they provide. This information comes
|
126
|
-
with some pieces of information for each API endpoint or action provided. The
|
127
|
-
information is:
|
128
|
-
|
129
|
-
1. Endpoint identifier
|
130
|
-
2. Parameter information
|
131
|
-
3. Return information
|
132
|
-
4. (optional) Human-readable documentation.
|
133
43
|
|
134
|
-
The
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
44
|
+
The available options are:
|
45
|
+
* server.host
|
46
|
+
The host to connect to when registering or finding services.
|
47
|
+
* server.transport
|
48
|
+
The transport protocol to use when speaking with the server
|
49
|
+
* server.load_balancer
|
50
|
+
The load-balancing strategy to be used by the server when multiple providers
|
51
|
+
for the same service are available.
|
52
|
+
* server.logger_stream
|
53
|
+
An IO object to write logs to
|
54
|
+
* server.config_file
|
55
|
+
The path of the place to save and load configurations from.
|
56
|
+
* client.transport
|
57
|
+
The transport protocol for a created service to use.
|
58
|
+
* transport.format
|
59
|
+
The format to send over the wire, regardless of communication protocol.
|
60
|
+
|
61
|
+
You can also set service-specific options. For example, if you have a service
|
62
|
+
called "Hello", you could use:
|
63
|
+
|
64
|
+
```ruby
|
65
|
+
...
|
66
|
+
config.Hello.language = "english"
|
67
|
+
...
|
139
68
|
```
|
140
69
|
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
The return information is simply a description of the data that should be
|
146
|
-
expected to return.
|
147
|
-
|
148
|
-
The documentation is an arbitrary-length text string which can be used as
|
149
|
-
documentation by a developer developing against the API.
|
150
|
-
|
151
|
-
Multiple service providers can provide the same service. In that case, they are
|
152
|
-
simply added to a pool of possible providers that is selected from using some
|
153
|
-
load-balancing strategy such as round-robin or random selection.
|
154
|
-
|
155
|
-
Any service provider that has registered will periodically get ICMP pinged by
|
156
|
-
Servicy. If a response does not come back, it will be removed from the pool of
|
157
|
-
providers. If a response does come back, Servicy will then attempt to connect
|
158
|
-
to the heartbeat port to determine if the service is still functioning on the
|
159
|
-
box. If it is not, the provider will be removed from the pool, otherwise it
|
160
|
-
will be left in. This two-step process is to facilitate better logging and
|
161
|
-
debugging of issues.
|
162
|
-
|
163
|
-
Service de-registration
|
164
|
-
-----------------------
|
165
|
-
|
166
|
-
A service can remove itself from the pool voluntarily be simply sending the
|
167
|
-
name, version, host, and port of the service to be removed. However, this is
|
168
|
-
not strictly needed as the periodic heartbeat check will notice if a service
|
169
|
-
goes away. These checks happen every second.
|
170
|
-
|
171
|
-
A note on security
|
172
|
-
++++++++++++++++++
|
173
|
-
|
174
|
-
For the time-being, it is assumed that you will have Servicy and all the
|
175
|
-
services it interacts with installed on internal, secured infrastructure. As
|
176
|
-
such, Servicy takes a very trusting policy. However, that means that someone
|
177
|
-
with malintent and access to your network could de-register services, or
|
178
|
-
register a bunch of fake ones. Future versions of Servicy may attempt to
|
179
|
-
resolve this issue. We'll see...
|
180
|
-
|
181
|
-
Service discovery
|
182
|
-
-----------------
|
70
|
+
and then read the value in your service using `Servicy.config.Hello.language`.
|
71
|
+
See the [example project](https://github.com/thomasluce/servicy_example) for
|
72
|
+
more information.
|
183
73
|
|
184
|
-
|
185
|
-
|
186
|
-
lifetime of their use, only requesting a new handle should the old one be lost.
|
187
|
-
However, there is no reason why they couldn't request a new one every time.
|
188
|
-
|
189
|
-
*NOTE* In the future, it may be possible for Servicy to act as a router by
|
190
|
-
simply returning connection information for itself, and doing blind
|
191
|
-
pass-through with some strategy to the other providers. In that case, the
|
192
|
-
policy that a client uses to cache or not cache their connection information
|
193
|
-
may have to change.
|
194
|
-
|
195
|
-
A service consumer can query using one or both of two different dimensions:
|
196
|
-
|
197
|
-
1. API name
|
198
|
-
2. API functionality
|
199
|
-
|
200
|
-
When using the name dimension, you can also query by version using either an
|
201
|
-
exact match, a list of preferences, a range, a minimum, or a maximum.
|
202
|
-
|
203
|
-
When querying using functionality, you query using one or more API method
|
204
|
-
fingerprints. The fingerprints are in the following format:
|
74
|
+
Per-service settings
|
75
|
+
--------------------
|
205
76
|
|
206
|
-
|
207
|
-
|
208
|
-
|
77
|
+
If you are extracting an existing application into multiple services, you can
|
78
|
+
also define constants on your service classes that will be used when
|
79
|
+
automatically creating API's.
|
209
80
|
|
210
|
-
|
211
|
-
|
212
|
-
|
213
|
-
|
214
|
-
|
215
|
-
|
81
|
+
```ruby
|
82
|
+
class MySuperAwesomeService
|
83
|
+
PORT = 1234
|
84
|
+
HEARTBEAT_PORT = 1235
|
85
|
+
VERSION = '1.2.6-p1098'
|
86
|
+
DOMAIN = 'com.mycompany'
|
216
87
|
|
217
|
-
|
88
|
+
...
|
218
89
|
|
219
|
-
|
220
|
-
|
90
|
+
include Servicy
|
91
|
+
end
|
221
92
|
```
|
222
93
|
|
223
|
-
|
224
|
-
|
225
|
-
|
226
|
-
|
227
|
-
can handle multiple types of information:
|
228
|
-
|
229
|
-
```
|
230
|
-
user_create#username-string,password-string|signed_string#User
|
231
|
-
```
|
94
|
+
Note that in all of these cases, we `include Servicy` at the end of the
|
95
|
+
class/module so that the `.included` hook has maximum access to defined
|
96
|
+
methods. It still will not catch any methods that are added on in code that is
|
97
|
+
loaded after Servicy is included.
|
232
98
|
|
233
|
-
|
99
|
+
Other options
|
100
|
+
-------------
|
234
101
|
|
235
|
-
|
236
|
-
|
237
|
-
|
238
|
-
get full API documentation and definitions along with connection information.
|
102
|
+
Most settings are also changeable in creation of the objects themselves through
|
103
|
+
argument hashes, in the case where you just want to use one small part of
|
104
|
+
Servicy. See the full documentation and/or code for more information.
|
239
105
|
|
240
|
-
The query system will either provide a successful "error"-code, along with a
|
241
|
-
list of one or more service providers, a "not-found" error-code indicating that
|
242
|
-
the query was valid, but that there are no available services which meet the
|
243
|
-
requirements, or an "error" error-code that indicates that something was wrong
|
244
|
-
with the provided query. In the last two queries, additional error information
|
245
|
-
may be provided, but should be considered for human-use only; ie, showing an
|
246
|
-
error message to a user.
|
247
106
|
|
248
107
|
Notes on design of your objects
|
249
108
|
===============================
|
@@ -256,7 +115,7 @@ design.
|
|
256
115
|
Servicy's goal is to make it simple for you to build microservices in Ruby that
|
257
116
|
can either run locally or remotely without the developer having to worry about
|
258
117
|
or care which. That said, as the developer of a service, it is _your_
|
259
|
-
responsibility to make sure that you don't make
|
118
|
+
responsibility to make sure that you don't make Services That Suck (tm).
|
260
119
|
|
261
120
|
Don't build your objects to have iterators that require multiple-traversals
|
262
121
|
over the wire. Don't build objects that lazy-evaluate things. Don't build
|
@@ -271,11 +130,10 @@ TODO
|
|
271
130
|
|
272
131
|
Things left to do:
|
273
132
|
|
274
|
-
1.
|
275
|
-
|
276
|
-
|
277
|
-
1.
|
278
|
-
|
279
|
-
|
280
|
-
1.
|
281
|
-
saved. It gets a bit out of hand.
|
133
|
+
1. More protocols and formatters
|
134
|
+
1. Make service requests (optionally?) polling so that they can pick up on
|
135
|
+
services going up/down out of order.
|
136
|
+
1. Optional service-to-local function reverting in the event of a remote
|
137
|
+
service failure.
|
138
|
+
1. Work on code coverage.
|
139
|
+
1. Fix all the default config options. transport.port?! WTF was I thinking?!
|
data/Servicy.gemspec
CHANGED
@@ -2,16 +2,16 @@
|
|
2
2
|
# DO NOT EDIT THIS FILE DIRECTLY
|
3
3
|
# Instead, edit Jeweler::Tasks in Rakefile, and run 'rake gemspec'
|
4
4
|
# -*- encoding: utf-8 -*-
|
5
|
-
# stub: servicy 0.0.
|
5
|
+
# stub: servicy 0.0.6 ruby lib
|
6
6
|
|
7
7
|
Gem::Specification.new do |s|
|
8
8
|
s.name = "servicy"
|
9
|
-
s.version = "0.0.
|
9
|
+
s.version = "0.0.6"
|
10
10
|
|
11
11
|
s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
|
12
12
|
s.require_paths = ["lib"]
|
13
13
|
s.authors = ["Thomas Luce"]
|
14
|
-
s.date = "2014-08-
|
14
|
+
s.date = "2014-08-19"
|
15
15
|
s.description = "A service registration and discovery framework, as a server and client library."
|
16
16
|
s.email = "thomas.luce@gmail.com"
|
17
17
|
s.executables = ["servicy"]
|
@@ -25,7 +25,6 @@ Gem::Specification.new do |s|
|
|
25
25
|
"bin/servicy",
|
26
26
|
"lib/api.rb",
|
27
27
|
"lib/client.rb",
|
28
|
-
"lib/configurable.rb",
|
29
28
|
"lib/formats.rb",
|
30
29
|
"lib/formats/json.rb",
|
31
30
|
"lib/hash.rb",
|
@@ -37,6 +36,10 @@ Gem::Specification.new do |s|
|
|
37
36
|
"lib/server/service_searcher.rb",
|
38
37
|
"lib/service.rb",
|
39
38
|
"lib/servicy.rb",
|
39
|
+
"lib/servicy/config.rb",
|
40
|
+
"lib/servicy/configurable.rb",
|
41
|
+
"lib/servicy/magic.rb",
|
42
|
+
"lib/servicy/servicy_logger.rb",
|
40
43
|
"lib/transport/in_memory_transport.rb",
|
41
44
|
"lib/transport/messages.rb",
|
42
45
|
"lib/transport/null_transport.rb",
|
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
0.0.
|
1
|
+
0.0.6
|
data/lib/api.rb
CHANGED
data/lib/service.rb
CHANGED
@@ -28,7 +28,7 @@ module Servicy
|
|
28
28
|
end
|
29
29
|
|
30
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")
|
31
|
+
raise ArgumentError.new("Service port must be an integer betwee 1-65535. Got: #{args[:port].inspect}")
|
32
32
|
end
|
33
33
|
|
34
34
|
args[:heartbeat_port] ||= args[:port]
|
@@ -118,7 +118,7 @@ module Servicy
|
|
118
118
|
end
|
119
119
|
record_heartbeat(1)
|
120
120
|
return true
|
121
|
-
rescue
|
121
|
+
rescue => e
|
122
122
|
record_heartbeat(0)
|
123
123
|
return false
|
124
124
|
ensure
|
@@ -181,12 +181,12 @@ module Servicy
|
|
181
181
|
@transport.unformat(result)
|
182
182
|
end
|
183
183
|
|
184
|
-
private
|
185
|
-
|
186
184
|
def skip_heartbeat?
|
187
185
|
!!no_heartbeat
|
188
186
|
end
|
189
187
|
|
188
|
+
private
|
189
|
+
|
190
190
|
def transport_from_class_or_string(transport)
|
191
191
|
transport.is_a?(Servicy::Transport) ? transport : transport_from_string(transport)
|
192
192
|
end
|
@@ -198,12 +198,12 @@ module Servicy
|
|
198
198
|
# These are just to keep us from filling up memory.
|
199
199
|
def record_latency(t1, t2)
|
200
200
|
@latencies << t2.to_i - t1.to_i
|
201
|
-
@latencies
|
201
|
+
@latencies.slice!(2...10) if @latencies.length > 10
|
202
202
|
end
|
203
203
|
|
204
204
|
def record_heartbeat(h)
|
205
205
|
@heartbeats << h
|
206
|
-
@heartbeats
|
206
|
+
@heartbeats.slice!(2...10) if @heartbeats.length > 10
|
207
207
|
end
|
208
208
|
|
209
209
|
# The api is broken into two kinds of methods; instance and class. Each can
|
data/lib/servicy.rb
CHANGED
@@ -5,159 +5,6 @@ require 'formats'
|
|
5
5
|
require 'server'
|
6
6
|
require 'client'
|
7
7
|
require 'api'
|
8
|
-
require '
|
9
|
-
require '
|
10
|
-
|
11
|
-
module Servicy
|
12
|
-
# Get a logger instance that we can do something useful with.
|
13
|
-
def self.logger
|
14
|
-
@logger ||= begin
|
15
|
-
stream = config.server.logger_stream
|
16
|
-
stream = stream || File.open(File.join('/var', 'log', 'servicy_server.log'), 'a')
|
17
|
-
Logger.new(stream)
|
18
|
-
end
|
19
|
-
end
|
20
|
-
|
21
|
-
# After this method gets done running, you either have the state of the world
|
22
|
-
# exactly the same as it was, or your object is replaced in the global Object
|
23
|
-
# scope with a new object that will proxy methods to a remote service
|
24
|
-
# provider.
|
25
|
-
#
|
26
|
-
# To define a service provider is a bit more work, and less auto-magic
|
27
|
-
# because the exact details of the infrastructure and architecture of your
|
28
|
-
# setup will be very different from others. So Servicy doesn't make
|
29
|
-
# assumptions about how you want to do things. That said, it's relatively
|
30
|
-
# simple:
|
31
|
-
#
|
32
|
-
# # In some server class
|
33
|
-
# Servicy::Server.new(...).go!
|
34
|
-
#
|
35
|
-
# # In some service provider, possibly on a different box
|
36
|
-
# api = Servicy::Client.create_api_for(MyServiceClass)
|
37
|
-
# Servicy::Client.register_service(api)
|
38
|
-
# FIXME: @__api, and it's ilk, get overridden in the even that there is more
|
39
|
-
# than one.
|
40
|
-
def self.included(mod)
|
41
|
-
make_service_listener(mod)
|
42
|
-
|
43
|
-
# Attempt to find the service
|
44
|
-
client = Servicy::Client.new(config.server.transport)
|
45
|
-
raise 'go to bottom' unless client.connected?
|
46
|
-
|
47
|
-
# Create a new api object
|
48
|
-
@__api = Servicy::Client.create_api_for(mod)
|
49
|
-
port = Servicy.config.send(mod.to_s.to_sym).port
|
50
|
-
Servicy.logger.debug("Chose port, #{port} for service, #{mod.to_s}")
|
51
|
-
@__api.const_set("PORT", port) unless port.nil?
|
52
|
-
# TODO: for the other things as well
|
53
|
-
|
54
|
-
@__service = client.find_service(@__api.search_query)
|
55
|
-
raise 'go to bottom' unless @__service
|
56
|
-
|
57
|
-
# If all went well, then we can wrap the object to call to the remote
|
58
|
-
# service.
|
59
|
-
client = Servicy::Client.new(config.client.transport.class.new(port: port))
|
60
|
-
@__api.set_remote(client)
|
61
|
-
|
62
|
-
# ... and delegate that ish. We do it by killing off the original object,
|
63
|
-
# and replacing it with our own.
|
64
|
-
Object.send(:remove_const, mod.name.to_sym)
|
65
|
-
Object.const_set(mod.name.to_sym, @__api)
|
66
|
-
rescue => e
|
67
|
-
# Something went wrong, just give up silently, but don't do anything
|
68
|
-
# else; use this object as a regular-ass object. This is to allow you to
|
69
|
-
# use your API objects as normal objects in an environment where the
|
70
|
-
# object is bundled as part of a gem (,say, ) and there is no remote
|
71
|
-
# handler for it: use it locally
|
72
|
-
end
|
73
|
-
|
74
|
-
def self.make_service_listener(mod)
|
75
|
-
# If we didn't manage to connect to a remote client, then we are, by
|
76
|
-
# definition, the provider. We should start the transports api_server, and
|
77
|
-
# dispatch requests here.
|
78
|
-
mod.send(:define_singleton_method, :start_servicy_server) do
|
79
|
-
begin
|
80
|
-
Thread.new do
|
81
|
-
begin
|
82
|
-
port = Servicy.config.send(self.to_s.to_sym).port || Servicy.config.server.port
|
83
|
-
transport = Servicy.config.service.transport
|
84
|
-
transport = transport.nil? ? Servicy.config.server.transport : transport
|
85
|
-
client = Servicy::Client.new(transport.class.new(port: port))
|
86
|
-
|
87
|
-
client.transport.start_api do |request|
|
88
|
-
method = nil
|
89
|
-
begin
|
90
|
-
method = self.method(request.method_name.to_sym)
|
91
|
-
rescue
|
92
|
-
method = self.instance_method(request.method_name.to_sym)
|
93
|
-
method = method.bind(self.new)
|
94
|
-
end
|
95
|
-
things = transport.unformat(request.args)
|
96
|
-
args = method.parameters.map do |param|
|
97
|
-
things[param.last.to_s]
|
98
|
-
end
|
99
|
-
|
100
|
-
Servicy.logger.info "Calling #{method} with #{args.inspect}"
|
101
|
-
begin
|
102
|
-
result = method.call(*args)
|
103
|
-
Servicy::Transport::Message.api_response(result)
|
104
|
-
rescue => e
|
105
|
-
Servicy::Transport::Message.error(e.to_s + "\n" + e.backtrace.join("\n"))
|
106
|
-
end
|
107
|
-
end
|
108
|
-
|
109
|
-
client.transport.stop
|
110
|
-
rescue => e
|
111
|
-
Servicy::Transport::Message.error(e.to_s + "\n" + e.backtrace.join("\n"))
|
112
|
-
end
|
113
|
-
end
|
114
|
-
|
115
|
-
Servicy.logger.info "Creating #{self.to_s} api"
|
116
|
-
api = Servicy::Client.create_api_for(self)
|
117
|
-
port = Servicy.config.send(mod.to_s.to_sym).port
|
118
|
-
port = port.nil? ? Servicy.config.server.port : port
|
119
|
-
api.const_set 'PORT', port
|
120
|
-
# TODO: the other things, too
|
121
|
-
Servicy.logger.debug("Chose port, #{port} for service, #{mod.to_s}")
|
122
|
-
|
123
|
-
server_port = Servicy.config.server.port
|
124
|
-
transport = Servicy.config.server.transport
|
125
|
-
client = Servicy::Client.new(transport.class.new(port: server_port))
|
126
|
-
Servicy.logger.info "Registering api, #{api.to_s}"
|
127
|
-
client.register_service api
|
128
|
-
rescue => e
|
129
|
-
Servicy.logger.error "An error occurred with the service: #{e.to_s}\n#{e.backtrace.join("\n")}"
|
130
|
-
end
|
131
|
-
end
|
132
|
-
rescue => e
|
133
|
-
# Again, just let it die silently. There is no reason to make things not
|
134
|
-
# work locally.
|
135
|
-
Servicy.logger.error(e)
|
136
|
-
end
|
137
|
-
|
138
|
-
# Use this method to configure Servicy for other services
|
139
|
-
def self.configure(&block)
|
140
|
-
conf = Configurable.new
|
141
|
-
|
142
|
-
# Give some reasonable defaults
|
143
|
-
conf.server.host = 'localhost'
|
144
|
-
conf.server.transport = Servicy::Transport.InMemory.new
|
145
|
-
conf.server.load_balancer = Servicy::RoundRobinLoadBalancer.new
|
146
|
-
conf.server.logger_stream = File.open(File.join('/var', 'log', 'servicy_server.log'), 'a')
|
147
|
-
conf.server.config_file = File.expand_path("~/.servicy.conf")
|
148
|
-
# To set something for a given object, you would do something like:
|
149
|
-
# conf.transport.port = 1234
|
150
|
-
|
151
|
-
conf.client.transport = Servicy::Transport.InMemory.new
|
152
|
-
conf.transport.format = Servicy::Formats.JSON
|
153
|
-
|
154
|
-
conf = yield conf if block_given?
|
155
|
-
|
156
|
-
# Set the options
|
157
|
-
@_config = conf
|
158
|
-
end
|
159
|
-
|
160
|
-
def self.config
|
161
|
-
@_config ||= configure
|
162
|
-
end
|
163
|
-
end
|
8
|
+
require 'servicy/servicy_logger'
|
9
|
+
require 'servicy/config'
|
10
|
+
require 'servicy/magic'
|
@@ -0,0 +1,31 @@
|
|
1
|
+
require 'servicy/configurable'
|
2
|
+
|
3
|
+
module Servicy
|
4
|
+
# Use this method to configure Servicy for other services
|
5
|
+
def self.configure(&block)
|
6
|
+
conf = Configurable.new
|
7
|
+
|
8
|
+
# Give some reasonable defaults
|
9
|
+
conf.server.host = 'localhost'
|
10
|
+
conf.server.transport = Servicy::Transport.InMemory.new
|
11
|
+
conf.server.load_balancer = Servicy::RoundRobinLoadBalancer.new
|
12
|
+
conf.server.logger_stream = File.open(File.join('/var', 'log', 'servicy_server.log'), 'a')
|
13
|
+
conf.server.config_file = File.expand_path("~/.servicy.conf")
|
14
|
+
# To set something for a given object, you would do something like:
|
15
|
+
# conf.transport.port = 1234
|
16
|
+
|
17
|
+
conf.client.transport = Servicy::Transport.InMemory.new
|
18
|
+
conf.transport.format = Servicy::Formats.JSON
|
19
|
+
|
20
|
+
conf = yield conf if block_given?
|
21
|
+
raise ArgumentError("Return the config object, please") unless conf.is_a?(Configurable)
|
22
|
+
|
23
|
+
# Set the options
|
24
|
+
@_config = conf
|
25
|
+
end
|
26
|
+
|
27
|
+
def self.config
|
28
|
+
@_config ||= configure
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
File without changes
|
@@ -0,0 +1,120 @@
|
|
1
|
+
module Servicy
|
2
|
+
# After this method gets done running, you either have the state of the world
|
3
|
+
# exactly the same as it was, or your object is replaced in the global Object
|
4
|
+
# scope with a new object that will proxy methods to a remote service
|
5
|
+
# provider.
|
6
|
+
#
|
7
|
+
# To define a service provider is a bit more work, and less auto-magic
|
8
|
+
# because the exact details of the infrastructure and architecture of your
|
9
|
+
# setup will be very different from others. So Servicy doesn't make
|
10
|
+
# assumptions about how you want to do things. That said, it's relatively
|
11
|
+
# simple:
|
12
|
+
#
|
13
|
+
# # In some server class
|
14
|
+
# Servicy::Server.new(...).go!
|
15
|
+
#
|
16
|
+
# # In some service provider, possibly on a different box
|
17
|
+
# api = Servicy::Client.create_api_for(MyServiceClass)
|
18
|
+
# Servicy::Client.register_service(api)
|
19
|
+
# FIXME: @__api, and it's ilk, get overridden in the even that there is more
|
20
|
+
# than one.
|
21
|
+
def self.included(mod)
|
22
|
+
make_service_listener(mod)
|
23
|
+
|
24
|
+
# Attempt to find the service
|
25
|
+
client = Servicy::Client.new(config.server.transport)
|
26
|
+
raise 'go to bottom' unless client.connected?
|
27
|
+
|
28
|
+
# Create a new api object
|
29
|
+
@__api = Servicy::Client.create_api_for(mod)
|
30
|
+
port = Servicy.config.send(mod.to_s.to_sym).port
|
31
|
+
Servicy.logger.debug("Chose port, #{port} for service, #{mod.to_s}")
|
32
|
+
@__api.const_set("PORT", port) unless port.nil?
|
33
|
+
# TODO: for the other things as well
|
34
|
+
|
35
|
+
@__service = client.find_service(@__api.search_query)
|
36
|
+
raise 'go to bottom' unless @__service
|
37
|
+
|
38
|
+
# If all went well, then we can wrap the object to call to the remote
|
39
|
+
# service.
|
40
|
+
client = Servicy::Client.new(config.client.transport.class.new(port: port))
|
41
|
+
@__api.set_remote(client)
|
42
|
+
|
43
|
+
# ... and delegate that ish. We do it by killing off the original object,
|
44
|
+
# and replacing it with our own.
|
45
|
+
Object.send(:remove_const, mod.name.to_sym)
|
46
|
+
Object.const_set(mod.name.to_sym, @__api)
|
47
|
+
rescue => e
|
48
|
+
# Something went wrong, just give up silently, but don't do anything
|
49
|
+
# else; use this object as a regular-ass object. This is to allow you to
|
50
|
+
# use your API objects as normal objects in an environment where the
|
51
|
+
# object is bundled as part of a gem (,say, ) and there is no remote
|
52
|
+
# handler for it: use it locally
|
53
|
+
end
|
54
|
+
|
55
|
+
def self.make_service_listener(mod)
|
56
|
+
# If we didn't manage to connect to a remote client, then we are, by
|
57
|
+
# definition, the provider. We should start the transports api_server, and
|
58
|
+
# dispatch requests here.
|
59
|
+
mod.send(:define_singleton_method, :start_servicy_server) do
|
60
|
+
begin
|
61
|
+
Thread.new do
|
62
|
+
begin
|
63
|
+
port = Servicy.config.send(self.to_s.to_sym).port || Servicy.config.server.port
|
64
|
+
transport = Servicy.config.service.transport
|
65
|
+
transport = transport.nil? ? Servicy.config.server.transport : transport
|
66
|
+
client = Servicy::Client.new(transport.class.new(port: port))
|
67
|
+
|
68
|
+
client.transport.start_api do |request|
|
69
|
+
method = nil
|
70
|
+
begin
|
71
|
+
method = self.method(request.method_name.to_sym)
|
72
|
+
rescue
|
73
|
+
method = self.instance_method(request.method_name.to_sym)
|
74
|
+
method = method.bind(self.new)
|
75
|
+
end
|
76
|
+
things = transport.unformat(request.args)
|
77
|
+
args = method.parameters.map do |param|
|
78
|
+
things[param.last.to_s]
|
79
|
+
end
|
80
|
+
|
81
|
+
Servicy.logger.info "Calling #{method} with #{args.inspect}"
|
82
|
+
begin
|
83
|
+
result = method.call(*args)
|
84
|
+
Servicy::Transport::Message.api_response(result)
|
85
|
+
rescue => e
|
86
|
+
Servicy::Transport::Message.error(e.to_s + "\n" + e.backtrace.join("\n"))
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
client.transport.stop
|
91
|
+
rescue => e
|
92
|
+
Servicy::Transport::Message.error(e.to_s + "\n" + e.backtrace.join("\n"))
|
93
|
+
end
|
94
|
+
end
|
95
|
+
|
96
|
+
Servicy.logger.info "Creating #{self.to_s} api"
|
97
|
+
api = Servicy::Client.create_api_for(self)
|
98
|
+
port = Servicy.config.send(mod.to_s.to_sym).port
|
99
|
+
port = port.nil? ? Servicy.config.server.port : port
|
100
|
+
api.const_set 'PORT', port
|
101
|
+
# TODO: get port information from the class PORT stuff, too
|
102
|
+
# TODO: the other things, too
|
103
|
+
Servicy.logger.debug("Chose port, #{port} for service, #{mod.to_s}")
|
104
|
+
|
105
|
+
server_port = Servicy.config.server.port
|
106
|
+
server_port = server_port.nil? ? 1234 : server_port
|
107
|
+
transport = Servicy.config.server.transport
|
108
|
+
client = Servicy::Client.new(transport.class.new(port: server_port))
|
109
|
+
Servicy.logger.info "Registering api, #{api.to_s}"
|
110
|
+
client.register_service api
|
111
|
+
rescue => e
|
112
|
+
Servicy.logger.error "An error occurred with the service: #{e.to_s}\n#{e.backtrace.join("\n")}"
|
113
|
+
end
|
114
|
+
end
|
115
|
+
rescue => e
|
116
|
+
# Again, just let it die silently. There is no reason to make things not
|
117
|
+
# work locally.
|
118
|
+
Servicy.logger.error(e)
|
119
|
+
end
|
120
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
require 'logger'
|
2
|
+
|
3
|
+
module Servicy
|
4
|
+
# Get a logger instance that we can do something useful with.
|
5
|
+
def self.logger
|
6
|
+
@logger ||= begin
|
7
|
+
stream = config.server.logger_stream
|
8
|
+
stream = stream || File.open(File.join('/var', 'log', 'servicy_server.log'), 'a')
|
9
|
+
level = config.log_level
|
10
|
+
level = level.nil? ? Logger::WARN : level
|
11
|
+
l = Logger.new(stream)
|
12
|
+
l.level = level
|
13
|
+
l
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
@@ -28,12 +28,14 @@ module Servicy
|
|
28
28
|
end
|
29
29
|
|
30
30
|
def stop
|
31
|
-
@server && @server.close
|
31
|
+
@server && @server.close rescue nil
|
32
32
|
end
|
33
33
|
|
34
34
|
def socket_path
|
35
35
|
@socket_path ||= begin
|
36
|
-
|
36
|
+
config_path = Servicy.config.transport.path
|
37
|
+
config_path = config_path.nil ? 'servicy-in-memory-transport' : config_path
|
38
|
+
file = Tempfile.new(@config[:path] || config_path)
|
37
39
|
file.close
|
38
40
|
path = file.path
|
39
41
|
file.delete
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: servicy
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0.
|
4
|
+
version: 0.0.6
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Thomas Luce
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2014-08-
|
11
|
+
date: 2014-08-19 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: contraction
|
@@ -39,7 +39,6 @@ files:
|
|
39
39
|
- bin/servicy
|
40
40
|
- lib/api.rb
|
41
41
|
- lib/client.rb
|
42
|
-
- lib/configurable.rb
|
43
42
|
- lib/formats.rb
|
44
43
|
- lib/formats/json.rb
|
45
44
|
- lib/hash.rb
|
@@ -51,6 +50,10 @@ files:
|
|
51
50
|
- lib/server/service_searcher.rb
|
52
51
|
- lib/service.rb
|
53
52
|
- lib/servicy.rb
|
53
|
+
- lib/servicy/config.rb
|
54
|
+
- lib/servicy/configurable.rb
|
55
|
+
- lib/servicy/magic.rb
|
56
|
+
- lib/servicy/servicy_logger.rb
|
54
57
|
- lib/transport/in_memory_transport.rb
|
55
58
|
- lib/transport/messages.rb
|
56
59
|
- lib/transport/null_transport.rb
|