servicy 0.0.5 → 0.0.6
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 +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
|