stargate 0.1.1
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/.editorconfig +12 -0
- data/.gitignore +9 -0
- data/CHANGELOG.md +13 -0
- data/Dockerfile +11 -0
- data/Gemfile +4 -0
- data/README.md +240 -0
- data/Rakefile +17 -0
- data/bin/console +14 -0
- data/bin/setup +7 -0
- data/docker-compose.yml +4 -0
- data/examples/client/client.rb +27 -0
- data/examples/server/config.ru +45 -0
- data/examples/server/start +7 -0
- data/lib/stargate.rb +28 -0
- data/lib/stargate/client.rb +12 -0
- data/lib/stargate/client/core_ext/kernel.rb +8 -0
- data/lib/stargate/client/errors.rb +12 -0
- data/lib/stargate/client/injector.rb +37 -0
- data/lib/stargate/client/protocol.rb +72 -0
- data/lib/stargate/client/protocol/http.rb +47 -0
- data/lib/stargate/client/protocol/inproc.rb +28 -0
- data/lib/stargate/client/proxy.rb +52 -0
- data/lib/stargate/client/remote_execution_error.rb +22 -0
- data/lib/stargate/codec.rb +34 -0
- data/lib/stargate/codec/bencode.rb +28 -0
- data/lib/stargate/codec/json.rb +28 -0
- data/lib/stargate/codec/message_pack.rb +28 -0
- data/lib/stargate/errors.rb +4 -0
- data/lib/stargate/marshal.rb +7 -0
- data/lib/stargate/marshal/marshaller.rb +58 -0
- data/lib/stargate/marshal/payload.rb +31 -0
- data/lib/stargate/marshal/unmarshaller.rb +46 -0
- data/lib/stargate/metadata.rb +68 -0
- data/lib/stargate/serialization.rb +21 -0
- data/lib/stargate/server.rb +10 -0
- data/lib/stargate/server/caller.rb +21 -0
- data/lib/stargate/server/engine/inproc.rb +15 -0
- data/lib/stargate/server/engine/sinatra.rb +97 -0
- data/lib/stargate/server/errors.rb +25 -0
- data/lib/stargate/server/registry.rb +26 -0
- data/lib/stargate/server/registry_version.rb +59 -0
- data/lib/stargate/version.rb +3 -0
- data/stargate.gemspec +41 -0
- metadata +367 -0
@@ -0,0 +1,31 @@
|
|
1
|
+
require 'stargate/serialization'
|
2
|
+
|
3
|
+
module Stargate
|
4
|
+
module Marshal
|
5
|
+
# Internal: We need some packaging to move stuff from one machine to another. We have to know what types
|
6
|
+
# are coming in. We distinguish four base types:
|
7
|
+
#
|
8
|
+
# * Simple type (string, number, booleans),
|
9
|
+
# * Symbol,
|
10
|
+
# * List (Array),
|
11
|
+
# * Hash
|
12
|
+
#
|
13
|
+
# Later, we can pack any other type that has been registered for exchange.
|
14
|
+
class Payload < Struct.new(:type, :data)
|
15
|
+
include Serialization
|
16
|
+
|
17
|
+
LIST = :a
|
18
|
+
SIMPLE = :b
|
19
|
+
SYMBOL = :s
|
20
|
+
HASH = :h
|
21
|
+
|
22
|
+
def serialize
|
23
|
+
[ type, data ]
|
24
|
+
end
|
25
|
+
|
26
|
+
def inspect
|
27
|
+
serialize.inspect
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
@@ -0,0 +1,46 @@
|
|
1
|
+
module Stargate
|
2
|
+
module Marshal
|
3
|
+
# Internal: On the client side there's unmarshalling needed then. Use this unmarshaller to unpack data.
|
4
|
+
# Unmarshaller is assigned to a module (namespace). It shall look up within that module in case of unpacking
|
5
|
+
# complex objects of specific type.
|
6
|
+
class Unmarshaller
|
7
|
+
# Public: Unpacks given object from payload wrap.
|
8
|
+
#
|
9
|
+
# There are two scenarios for unpacking:
|
10
|
+
#
|
11
|
+
# * Basic value - Just return value or adapt contents (like symbol, arrays or hashes).
|
12
|
+
# * An object of specific type - Check if our namespace defines such class. No? Erorr. Yes. Unmarshal into.
|
13
|
+
#
|
14
|
+
# Returns object unmarshalled.
|
15
|
+
def unmarshal(data)
|
16
|
+
payload = Payload.new(*data)
|
17
|
+
|
18
|
+
case payload.type.to_sym
|
19
|
+
when Payload::SIMPLE
|
20
|
+
payload.data
|
21
|
+
when Payload::SYMBOL
|
22
|
+
payload.data.to_sym
|
23
|
+
when Payload::LIST
|
24
|
+
unmarshal_list(payload.data)
|
25
|
+
when Payload::HASH
|
26
|
+
unmarshal_hash(payload.data)
|
27
|
+
else
|
28
|
+
klass = payload.type.constantize
|
29
|
+
klass.new(unmarshal_hash(payload.data))
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
private
|
34
|
+
|
35
|
+
# Internal: Unmarshals standalone hash.
|
36
|
+
def unmarshal_hash(hash)
|
37
|
+
hash.inject({}) { |result,(k,v)| result[k] = unmarshal(v); result }
|
38
|
+
end
|
39
|
+
|
40
|
+
# Internal: Unmarshals list.
|
41
|
+
def unmarshal_list(list)
|
42
|
+
list.map { |obj| unmarshal(obj) }
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
@@ -0,0 +1,68 @@
|
|
1
|
+
module Stargate
|
2
|
+
class Metadata
|
3
|
+
include Serialization
|
4
|
+
|
5
|
+
def self.from_hash(hash)
|
6
|
+
hash.symbolize_keys!
|
7
|
+
|
8
|
+
new(hash[:klass], hash[:name]).tap do |metadata|
|
9
|
+
metadata.class_methods(*hash.fetch(:class_methods, []))
|
10
|
+
metadata.attributes(*hash.fetch(:attributes, []))
|
11
|
+
metadata.readers(*hash.fetch(:readers, []))
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
attr_reader :klass
|
16
|
+
|
17
|
+
attr_reader :name
|
18
|
+
|
19
|
+
def initialize(klass, name, &block)
|
20
|
+
@klass, @name = klass, name
|
21
|
+
@class_methods = []
|
22
|
+
@attributes = []
|
23
|
+
@readers = []
|
24
|
+
|
25
|
+
instance_eval(&block) if block_given?
|
26
|
+
end
|
27
|
+
|
28
|
+
def active_record?
|
29
|
+
@active_record
|
30
|
+
end
|
31
|
+
|
32
|
+
def class_methods(*names)
|
33
|
+
load_or_add(:class_methods, *names)
|
34
|
+
end
|
35
|
+
|
36
|
+
def attributes(*names)
|
37
|
+
load_or_add(:attributes, *names)
|
38
|
+
end
|
39
|
+
|
40
|
+
def readers(*names)
|
41
|
+
load_or_add(:readers, *names)
|
42
|
+
end
|
43
|
+
|
44
|
+
def serialize
|
45
|
+
{
|
46
|
+
name: name,
|
47
|
+
class_methods: class_methods,
|
48
|
+
attributes: attributes,
|
49
|
+
readers: readers
|
50
|
+
}
|
51
|
+
end
|
52
|
+
|
53
|
+
def inspect
|
54
|
+
"#<#{self.class.name} name=#{name.inspect} class_methods=#{class_methods.inspect} attributes=#{attributes.inspect} readers=#{readers.inspect}"
|
55
|
+
end
|
56
|
+
|
57
|
+
private
|
58
|
+
|
59
|
+
def klass_is_an_active_record?
|
60
|
+
defined?(::ActiveRecord) && klass && klass < ::ActiveRecord::Base
|
61
|
+
end
|
62
|
+
|
63
|
+
def load_or_add(method, *args)
|
64
|
+
value = instance_variable_get("@#{method}")
|
65
|
+
args.empty? ? value : value.concat(args.flatten.map(&:to_sym))
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
module Stargate
|
2
|
+
# Internal: This tiny mixin provides serialization directives for includee class. The target class must
|
3
|
+
# implement #serialize method.
|
4
|
+
module Serialization
|
5
|
+
def serialize
|
6
|
+
raise NotImplementedError, "Not implemented: #{self.class.name}#serialize"
|
7
|
+
end
|
8
|
+
|
9
|
+
def to_json(*args)
|
10
|
+
serialize.to_json(*args)
|
11
|
+
end
|
12
|
+
|
13
|
+
def to_msgpack(*args)
|
14
|
+
serialize.to_msgpack(*args)
|
15
|
+
end
|
16
|
+
|
17
|
+
def bencode
|
18
|
+
serialize.bencode
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
module Stargate
|
2
|
+
module Server
|
3
|
+
class Caller
|
4
|
+
include Logging
|
5
|
+
include Stargate::Marshal
|
6
|
+
|
7
|
+
def initialize(registry_version)
|
8
|
+
@marshaller = Marshaller.new(registry_version)
|
9
|
+
@registry_version = registry_version
|
10
|
+
end
|
11
|
+
|
12
|
+
def call(klass_name, method, *args)
|
13
|
+
method, metadata = method.to_sym, @registry_version[klass_name]
|
14
|
+
metadata.class_methods.include?(method) or raise MethodNotExposedError, "Method not exposed: #{klass_name}.#{method}"
|
15
|
+
result = metadata.klass.send(method, *args)
|
16
|
+
log.debug("Method call performed", class: klass_name, method: method, result:result.inspect)
|
17
|
+
@marshaller.marshal(result)
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
require 'stargate/server'
|
2
|
+
|
3
|
+
module Stargate
|
4
|
+
module Server
|
5
|
+
module Engine
|
6
|
+
class Inproc
|
7
|
+
def self.register(name, registry)
|
8
|
+
registry.each do |version_number, actual_registry|
|
9
|
+
::Stargate::INPROC["inproc://#{name}/v#{version_number}"] = actual_registry
|
10
|
+
end
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
@@ -0,0 +1,97 @@
|
|
1
|
+
require 'stargate/server'
|
2
|
+
|
3
|
+
module Stargate
|
4
|
+
module Server
|
5
|
+
module Engine
|
6
|
+
class Sinatra
|
7
|
+
include Logging
|
8
|
+
|
9
|
+
def initialize(registry)
|
10
|
+
require 'sinatra/base'
|
11
|
+
|
12
|
+
logger = log
|
13
|
+
|
14
|
+
@app = ::Sinatra.new do
|
15
|
+
helpers Helpers
|
16
|
+
|
17
|
+
registry.each do |version_number, actual_registry|
|
18
|
+
caller = Caller.new(actual_registry)
|
19
|
+
|
20
|
+
get "/v#{version_number}/definitions" do
|
21
|
+
log.debug("Serving registry definitions")
|
22
|
+
content_type(response_codec.content_type)
|
23
|
+
response_codec.encode(actual_registry)
|
24
|
+
end
|
25
|
+
|
26
|
+
post "/v#{version_number}/:klass_name.:method" do |klass_name, method|
|
27
|
+
args = parse_args
|
28
|
+
log.debug("Executing call", class: klass_name, method: method, args: args)
|
29
|
+
result = caller.call(klass_name, method, *args)
|
30
|
+
content_type(response_codec.content_type)
|
31
|
+
response_codec.encode(result)
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
error Stargate::Server::NotFoundError do
|
36
|
+
error_response(404)
|
37
|
+
end
|
38
|
+
|
39
|
+
error Stargate::Codec::Error do
|
40
|
+
error_response(400)
|
41
|
+
end
|
42
|
+
|
43
|
+
error NotImplementedError do
|
44
|
+
error_response(501)
|
45
|
+
end
|
46
|
+
|
47
|
+
error 500 do
|
48
|
+
error_response(500)
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
@app.set(:default_mime_type, ::Stargate::Codec::JSON.content_type)
|
53
|
+
@app.set(:show_exceptions, false)
|
54
|
+
@app.set(:logger, log)
|
55
|
+
end
|
56
|
+
|
57
|
+
def call(env)
|
58
|
+
@app.call(env)
|
59
|
+
end
|
60
|
+
|
61
|
+
module Helpers
|
62
|
+
def log
|
63
|
+
settings.logger
|
64
|
+
end
|
65
|
+
|
66
|
+
def parse_args
|
67
|
+
request.body.rewind
|
68
|
+
|
69
|
+
args = body_codec.decode(request.body.read)
|
70
|
+
args = [] if args.nil?
|
71
|
+
args = [ args ] if args.kind_of?(Hash)
|
72
|
+
args
|
73
|
+
end
|
74
|
+
|
75
|
+
def error_response(code)
|
76
|
+
status(code)
|
77
|
+
err = env['sinatra.error']
|
78
|
+
log.error(err)
|
79
|
+
response_codec.encode({error: err.class.name, message: err.message})
|
80
|
+
end
|
81
|
+
|
82
|
+
def body_codec
|
83
|
+
Codec[request.media_type || settings.default_mime_type]
|
84
|
+
end
|
85
|
+
|
86
|
+
def response_codec
|
87
|
+
Codec[accept_type || settings.default_mime_type]
|
88
|
+
end
|
89
|
+
|
90
|
+
def accept_type
|
91
|
+
accept = env['HTTP_ACCEPT'] and accept.split(/\s*[;,]\s*/, 2).first.downcase
|
92
|
+
end
|
93
|
+
end
|
94
|
+
end
|
95
|
+
end
|
96
|
+
end
|
97
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
module Stargate
|
2
|
+
module Server
|
3
|
+
# Public: Base server error.
|
4
|
+
Error = Class.new(::Stargate::Error)
|
5
|
+
|
6
|
+
# Public: Base class for not-found type errors.
|
7
|
+
NotFoundError = Class.new(Error)
|
8
|
+
|
9
|
+
# Public: Raised when trying to get non-existent registry version.
|
10
|
+
RegistryVersionUndefinedError = Class.new(Error)
|
11
|
+
|
12
|
+
# Public: Raised when requested class is not exposed/registered.
|
13
|
+
ClassNotExposedError = Class.new(NotFoundError)
|
14
|
+
|
15
|
+
# Public: Raised when called method is not exposed.
|
16
|
+
MethodNotExposedError = Class.new(NotFoundError)
|
17
|
+
|
18
|
+
# Public: Raised when trying to register the same class twice within single registry.
|
19
|
+
ClassAlreadyRegisteredError = Class.new(Error)
|
20
|
+
|
21
|
+
# Public: Raised when trying to return (and pack into payload) data of unregistered or unsupported type.
|
22
|
+
# For example when exposed class method returns value of type different than itself, hash or basic.
|
23
|
+
UnregisteredClassExposureError = Class.new(Error)
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
module Stargate
|
2
|
+
module Server
|
3
|
+
class Registry
|
4
|
+
include Enumerable
|
5
|
+
|
6
|
+
attr_reader :versions
|
7
|
+
|
8
|
+
def initialize(&block)
|
9
|
+
@versions = {}
|
10
|
+
instance_eval(&block) if block_given?
|
11
|
+
end
|
12
|
+
|
13
|
+
def version(number, &block)
|
14
|
+
@versions[number] ||= RegistryVersion.new(number, &block)
|
15
|
+
end
|
16
|
+
|
17
|
+
def [](version)
|
18
|
+
versions[version] or raise RegistryVersionUndefinedError, "No such registry version: #{version}"
|
19
|
+
end
|
20
|
+
|
21
|
+
def each(&block)
|
22
|
+
versions.each(&block)
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
@@ -0,0 +1,59 @@
|
|
1
|
+
require 'forwardable'
|
2
|
+
|
3
|
+
module Stargate
|
4
|
+
module Server
|
5
|
+
class RegistryVersion
|
6
|
+
include Logging
|
7
|
+
include Enumerable
|
8
|
+
include Serialization
|
9
|
+
|
10
|
+
attr_reader :version
|
11
|
+
|
12
|
+
attr_reader :definitions
|
13
|
+
|
14
|
+
def initialize(version = 1, &block)
|
15
|
+
@version = version
|
16
|
+
@definitions = []
|
17
|
+
|
18
|
+
instance_eval(&block) if block_given?
|
19
|
+
end
|
20
|
+
|
21
|
+
def serve(klass, params = {}, &block)
|
22
|
+
raise ClassAlreadyRegisteredError, "Class already registered: #{klass.name}" if registered?(klass)
|
23
|
+
|
24
|
+
params.symbolize_keys!
|
25
|
+
klass_name = params[:as] || klass.name
|
26
|
+
log.debug("Registering class", local_class_name: klass.name, serve_as: klass_name)
|
27
|
+
@definitions << Metadata.new(klass, klass_name, &block)
|
28
|
+
|
29
|
+
self
|
30
|
+
end
|
31
|
+
|
32
|
+
def registered?(klass)
|
33
|
+
@definitions.find { |metadata| metadata.klass == klass }
|
34
|
+
end
|
35
|
+
|
36
|
+
def metadata_for(obj)
|
37
|
+
metadata = @definitions.find { |metadata| metadata.klass == obj.class }
|
38
|
+
metadata or raise ClassNotExposedError, "Class not registered: #{obj.class.name}"
|
39
|
+
end
|
40
|
+
|
41
|
+
def [](klass_name)
|
42
|
+
metadata = @definitions.find { |metadata| metadata.name == klass_name }
|
43
|
+
metadata or raise ClassNotExposedError, "Class not registered under name: #{klass_name}"
|
44
|
+
end
|
45
|
+
|
46
|
+
def each(&block)
|
47
|
+
@definitions.each(&block)
|
48
|
+
end
|
49
|
+
|
50
|
+
def size
|
51
|
+
@definitions.size
|
52
|
+
end
|
53
|
+
|
54
|
+
def serialize
|
55
|
+
{ version: @version, definitions: @definitions.map(&:serialize) }
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
data/stargate.gemspec
ADDED
@@ -0,0 +1,41 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'stargate/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |spec|
|
7
|
+
spec.name = "stargate"
|
8
|
+
spec.version = Stargate::VERSION
|
9
|
+
spec.authors = ["jobandtalent", "Kris Kovalik"]
|
10
|
+
spec.email = ["kris.kovalik@jobandtalent.com", "hi@kkvlk.me"]
|
11
|
+
|
12
|
+
spec.summary = %q{A portal to remote services.}
|
13
|
+
spec.description = %q{Stargate opens a portal to call remote methods via simple and reliable RPC protocols.}
|
14
|
+
spec.homepage = "https://github.com/jobandtalent/stargate"
|
15
|
+
|
16
|
+
spec.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
|
17
|
+
spec.bindir = "exe"
|
18
|
+
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
|
19
|
+
spec.require_paths = ["lib"]
|
20
|
+
|
21
|
+
spec.add_development_dependency "bundler", "~> 1.10"
|
22
|
+
spec.add_development_dependency "rake", "~> 10.0"
|
23
|
+
spec.add_development_dependency "minitest", "~> 5.8"
|
24
|
+
spec.add_development_dependency "minitest-reporters", "~> 1.1"
|
25
|
+
spec.add_development_dependency "rack-test", "~> 0.6"
|
26
|
+
spec.add_development_dependency "mocha", "~> 1.1"
|
27
|
+
spec.add_development_dependency "webmock", "~> 1.22"
|
28
|
+
spec.add_development_dependency "puma", "~> 2.11"
|
29
|
+
spec.add_development_dependency "vcr", "~> 3.0"
|
30
|
+
spec.add_development_dependency "rake-bump", "~> 0.4", ">= 0.4.6"
|
31
|
+
|
32
|
+
spec.add_dependency "json", "~> 1.8"
|
33
|
+
spec.add_dependency "msgpack", "~> 0.7"
|
34
|
+
spec.add_dependency "bencode", "~> 0.8"
|
35
|
+
spec.add_dependency "activemodel", ">= 3.0"
|
36
|
+
spec.add_dependency "activesupport", ">= 3.0"
|
37
|
+
spec.add_dependency "sinatra", "~> 1.4"
|
38
|
+
spec.add_dependency "rest-client", "~> 1.8"
|
39
|
+
spec.add_dependency "log4r", "~> 1.1"
|
40
|
+
spec.add_dependency "perfume", "~> 0.3", ">= 0.3.1"
|
41
|
+
end
|