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,12 @@
|
|
1
|
+
require 'stargate'
|
2
|
+
|
3
|
+
module Stargate
|
4
|
+
module Client
|
5
|
+
require 'stargate/client/errors'
|
6
|
+
require 'stargate/client/remote_execution_error'
|
7
|
+
require 'stargate/client/protocol'
|
8
|
+
require 'stargate/client/proxy'
|
9
|
+
require 'stargate/client/injector'
|
10
|
+
require 'stargate/client/core_ext/kernel'
|
11
|
+
end
|
12
|
+
end
|
@@ -0,0 +1,12 @@
|
|
1
|
+
module Stargate
|
2
|
+
module Client
|
3
|
+
# Public: Base client error.
|
4
|
+
Error = Class.new(::Stargate::Error)
|
5
|
+
|
6
|
+
# Public: Raised when unable to read data from the remote.
|
7
|
+
FetchError = Class.new(Error)
|
8
|
+
|
9
|
+
# Public: Raised when unable to fetch directives from the remote.
|
10
|
+
DirectivesFetchError = Class.new(FetchError)
|
11
|
+
end
|
12
|
+
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
module Stargate
|
2
|
+
module Client
|
3
|
+
class Injector
|
4
|
+
include Logging
|
5
|
+
include Stargate::Client
|
6
|
+
|
7
|
+
def initialize(url)
|
8
|
+
@uri = URI.parse(url)
|
9
|
+
@uri.scheme, codec_id = @uri.scheme.split('+')
|
10
|
+
@codec = Codec.find_by_id(codec_id)
|
11
|
+
@protocol = Protocol[@uri.scheme].new(@codec, @uri)
|
12
|
+
@definitions = @protocol.fetch_definitions
|
13
|
+
|
14
|
+
inject!
|
15
|
+
end
|
16
|
+
|
17
|
+
private
|
18
|
+
|
19
|
+
def inject!
|
20
|
+
log.debug("Injecting remote defintions", url: @protocol.safe_uri.to_s)
|
21
|
+
protocol = @protocol
|
22
|
+
|
23
|
+
@definitions.each do |metadata|
|
24
|
+
klass = Class.new(Proxy) { configure_stargate_portal(protocol, metadata) }
|
25
|
+
*namespaces, klass_name = metadata.name.split('::')
|
26
|
+
|
27
|
+
namespace = namespaces.inject(Object) do |namespace, name|
|
28
|
+
namespace.const_defined?(name) ? namespace.const_get(name) : Module.new.tap { |m| m = namespace.const_set(name, m) }
|
29
|
+
end
|
30
|
+
|
31
|
+
namespace.const_set(klass_name, klass)
|
32
|
+
log.debug("Loaded remote class", metadata.serialize)
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
@@ -0,0 +1,72 @@
|
|
1
|
+
module Stargate
|
2
|
+
module Client
|
3
|
+
class Protocol
|
4
|
+
UnsupportedProtocolError = Class.new(::Stargate::Client::Error)
|
5
|
+
|
6
|
+
include Logging
|
7
|
+
include Stargate::Marshal
|
8
|
+
|
9
|
+
DEFAULT_TIMEOUT = 15
|
10
|
+
|
11
|
+
@@transports = {}
|
12
|
+
|
13
|
+
def self.transports
|
14
|
+
@@transports
|
15
|
+
end
|
16
|
+
|
17
|
+
def self.register(protocols, transport)
|
18
|
+
protocols.each { |protocol| transports[protocol] = transport }
|
19
|
+
end
|
20
|
+
|
21
|
+
def self.[](protocol)
|
22
|
+
transports[protocol.downcase] or raise UnsupportedProtocolError, "Unsupported protocol: #{protocol}"
|
23
|
+
end
|
24
|
+
|
25
|
+
attr_reader :codec
|
26
|
+
attr_reader :uri
|
27
|
+
attr_reader :safe_uri
|
28
|
+
attr_reader :options
|
29
|
+
|
30
|
+
def initialize(codec, uri)
|
31
|
+
@codec = codec
|
32
|
+
@uri = uri
|
33
|
+
@safe_uri = uri.dup.tap { |safe_uri| safe_uri.user, safe_uri.password = nil, nil }
|
34
|
+
@options = default_options.merge(parse_options_from_uri_fragment)
|
35
|
+
@unmarshaller = Unmarshaller.new
|
36
|
+
end
|
37
|
+
|
38
|
+
def fetch_definitions
|
39
|
+
raise NotImplementedError
|
40
|
+
end
|
41
|
+
|
42
|
+
def call(klass, method, *args)
|
43
|
+
raise NotImplementedError
|
44
|
+
end
|
45
|
+
|
46
|
+
protected
|
47
|
+
|
48
|
+
def default_options
|
49
|
+
{ timeout: DEFAULT_TIMEOUT }
|
50
|
+
end
|
51
|
+
|
52
|
+
def unpack_payload(data)
|
53
|
+
@unmarshaller.unmarshal(data)
|
54
|
+
end
|
55
|
+
|
56
|
+
def unpack_definitions(definitions)
|
57
|
+
definitions.map { |metadata| ::Stargate::Metadata.from_hash(metadata) }
|
58
|
+
end
|
59
|
+
|
60
|
+
def parse_options_from_uri_fragment
|
61
|
+
uri.fragment.to_s.split(';').inject({}) do |options, entry|
|
62
|
+
k, v = entry.split('=')
|
63
|
+
options[k.to_sym] = v
|
64
|
+
options
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
require 'stargate/client/protocol/http'
|
70
|
+
require 'stargate/client/protocol/inproc'
|
71
|
+
end
|
72
|
+
end
|
@@ -0,0 +1,47 @@
|
|
1
|
+
require 'rest-client'
|
2
|
+
|
3
|
+
module Stargate
|
4
|
+
module Client
|
5
|
+
class Protocol::HTTP < Protocol
|
6
|
+
def fetch_definitions
|
7
|
+
log.debug("Loading remote definitions", url: safe_uri.to_s)
|
8
|
+
|
9
|
+
request('definitions', 'GET') do |response|
|
10
|
+
raise DirectivesFetchError, "Unable to fetch directives from #{safe_uri.to_s}" unless response.code == 200
|
11
|
+
data = codec.decode(response.body.to_s)
|
12
|
+
definitions = data['definitions'] or return []
|
13
|
+
unpack_definitions(definitions)
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
def call(klass, method, *args)
|
18
|
+
log.debug("Executing remote method", class: klass.remote_name, method: method)
|
19
|
+
|
20
|
+
request_options = {}
|
21
|
+
request_options[:payload] = codec.encode(args)
|
22
|
+
request_options[:timeout] = options[:timeout] if options[:timeout]
|
23
|
+
request_headers = { 'Content-Type': codec.content_type }
|
24
|
+
|
25
|
+
request("#{klass.remote_name}.#{method}", 'POST', request_options, request_headers) do |response|
|
26
|
+
data = codec.decode(response.body.to_s)
|
27
|
+
raise RemoteExecutionError.from_hash(data) unless response.code == 200
|
28
|
+
unpack_payload(data)
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
private
|
33
|
+
|
34
|
+
def request(path, method, options = {}, headers = {}, &block)
|
35
|
+
options = {
|
36
|
+
url: File.join(uri.to_s, path),
|
37
|
+
method: method,
|
38
|
+
headers: { 'Accept': codec.content_type, 'User-Agent': 'Stargate' }.merge(headers)
|
39
|
+
}.merge(options)
|
40
|
+
|
41
|
+
RestClient::Request.execute(options, &block)
|
42
|
+
end
|
43
|
+
|
44
|
+
register %w[http https], self
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
require 'rest-client'
|
2
|
+
|
3
|
+
module Stargate
|
4
|
+
module Client
|
5
|
+
class Protocol::Inproc < Protocol
|
6
|
+
RegistryNotFoundError = Class.new(::Stargate::Client::Error)
|
7
|
+
|
8
|
+
def initialize(*)
|
9
|
+
super
|
10
|
+
@registry_version = ::Stargate::INPROC[uri.to_s]
|
11
|
+
@caller = ::Stargate::Server::Caller.new(@registry_version)
|
12
|
+
raise RegistryNotFoundError, "Local registry not found: #{uri.to_s}" unless @registry_version
|
13
|
+
end
|
14
|
+
|
15
|
+
def fetch_definitions
|
16
|
+
unpack_definitions(@registry_version.definitions.map(&:serialize))
|
17
|
+
end
|
18
|
+
|
19
|
+
def call(klass, method, *args)
|
20
|
+
unpack_payload(@caller.call(klass.remote_name, method, *args))
|
21
|
+
rescue => err
|
22
|
+
raise RemoteExecutionError.new(err.class.to_s, err.message)
|
23
|
+
end
|
24
|
+
|
25
|
+
register %w[inproc], self
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
@@ -0,0 +1,52 @@
|
|
1
|
+
module Stargate
|
2
|
+
module Client
|
3
|
+
class Proxy
|
4
|
+
def self.metaclass
|
5
|
+
class << self; self; end
|
6
|
+
end
|
7
|
+
|
8
|
+
def self.configure_stargate_portal(protocol, metadata)
|
9
|
+
metaclass.instance_eval do
|
10
|
+
metadata.class_methods.each do |method|
|
11
|
+
define_method(method) do |*args|
|
12
|
+
protocol.call(self, method, *args)
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
define_method(:remote_name) do
|
17
|
+
metadata.name
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
define_attributes(*metadata.attributes)
|
22
|
+
define_readers(*metadata.readers)
|
23
|
+
end
|
24
|
+
|
25
|
+
def self.define_attributes(*names)
|
26
|
+
names.each { |name| attr_accessor(name) }
|
27
|
+
end
|
28
|
+
|
29
|
+
def self.define_readers(*names)
|
30
|
+
names.each do |name|
|
31
|
+
name = name.to_s
|
32
|
+
simple_name = name.split('?').first
|
33
|
+
attr_reader(simple_name)
|
34
|
+
alias name simple_name if simple_name != name
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
def initialize(attributes = {})
|
39
|
+
attributes.each do |name,value|
|
40
|
+
instance_variable_set("@#{name}", value)
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
def to_h
|
45
|
+
self.class.stargate_directives.attributes.inject({}) do |hash,name|
|
46
|
+
hash[name] = instance_variable_get("@#{name}")
|
47
|
+
hash
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
module Stargate
|
2
|
+
module Client
|
3
|
+
# Public: Raised when data has been fetched but execution failed.
|
4
|
+
class RemoteExecutionError < Error
|
5
|
+
def self.from_hash(hash)
|
6
|
+
hash.symbolize_keys!
|
7
|
+
new(hash[:error], hash[:message])
|
8
|
+
end
|
9
|
+
|
10
|
+
attr_reader :cause_type
|
11
|
+
|
12
|
+
def initialize(cause_type, message)
|
13
|
+
@cause_type = cause_type
|
14
|
+
super(message)
|
15
|
+
end
|
16
|
+
|
17
|
+
def inspect
|
18
|
+
"#{self.class.name} caused by #{cause_type}: #{message}"
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
module Stargate
|
2
|
+
module Codec
|
3
|
+
# Public: Base codec error.
|
4
|
+
Error = Class.new(::Stargate::Error)
|
5
|
+
|
6
|
+
# Public: Raised when given codec is not registered.
|
7
|
+
UndefinedCodecError = Class.new(Error)
|
8
|
+
|
9
|
+
# Public: Raised when codec cannot decode given data string.
|
10
|
+
DecodeError = Class.new(Error)
|
11
|
+
|
12
|
+
@@codecs = {}
|
13
|
+
|
14
|
+
def self.codecs
|
15
|
+
@@codecs
|
16
|
+
end
|
17
|
+
|
18
|
+
def self.register(codec)
|
19
|
+
codecs[codec.content_type.to_s] = codec
|
20
|
+
end
|
21
|
+
|
22
|
+
def self.find_by_id(id)
|
23
|
+
codecs.values.find { |codec| codec.id == id.to_sym } or raise UndefinedCodecError, "No such codec: #{id}"
|
24
|
+
end
|
25
|
+
|
26
|
+
def self.[](content_type)
|
27
|
+
codecs[content_type.to_s] or raise UndefinedCodecError, "No codec for type: #{content_type}"
|
28
|
+
end
|
29
|
+
|
30
|
+
require 'stargate/codec/json'
|
31
|
+
require 'stargate/codec/bencode'
|
32
|
+
require 'stargate/codec/message_pack'
|
33
|
+
end
|
34
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
require 'bencode'
|
2
|
+
|
3
|
+
module Stargate
|
4
|
+
module Codec
|
5
|
+
# Internal: BEncode (BitTorrent encoding) powered codec.
|
6
|
+
class BEncode
|
7
|
+
def self.id
|
8
|
+
:bencode
|
9
|
+
end
|
10
|
+
|
11
|
+
def self.content_type
|
12
|
+
'application/bencode'
|
13
|
+
end
|
14
|
+
|
15
|
+
def self.encode(obj)
|
16
|
+
obj.bencode
|
17
|
+
end
|
18
|
+
|
19
|
+
def self.decode(str)
|
20
|
+
::BEncode::Parser.new(str).parse!
|
21
|
+
rescue ::BEncode::DecodeError => err
|
22
|
+
raise DecodeError, "Arguments cannot be decoded: bencode parser error"
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
register BEncode
|
27
|
+
end
|
28
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
require 'json'
|
2
|
+
|
3
|
+
module Stargate
|
4
|
+
module Codec
|
5
|
+
# Internal: Default codec based on JSON parser.
|
6
|
+
class JSON
|
7
|
+
def self.id
|
8
|
+
:json
|
9
|
+
end
|
10
|
+
|
11
|
+
def self.content_type
|
12
|
+
'application/json'
|
13
|
+
end
|
14
|
+
|
15
|
+
def self.encode(obj)
|
16
|
+
obj.to_json
|
17
|
+
end
|
18
|
+
|
19
|
+
def self.decode(str)
|
20
|
+
::JSON.parse(str)
|
21
|
+
rescue ::JSON::ParserError => err
|
22
|
+
raise DecodeError, "Arguments cannot be decoded: JSON parser error"
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
register JSON
|
27
|
+
end
|
28
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
require 'msgpack'
|
2
|
+
|
3
|
+
module Stargate
|
4
|
+
module Codec
|
5
|
+
# Internal: Serialization done with MessagePack format.
|
6
|
+
class MessagePack
|
7
|
+
def self.id
|
8
|
+
:msgpack
|
9
|
+
end
|
10
|
+
|
11
|
+
def self.content_type
|
12
|
+
'application/msgpack'
|
13
|
+
end
|
14
|
+
|
15
|
+
def self.encode(obj)
|
16
|
+
::MessagePack.pack(obj)
|
17
|
+
end
|
18
|
+
|
19
|
+
def self.decode(str)
|
20
|
+
::MessagePack.unpack(str)
|
21
|
+
rescue ::MessagePack::MalformedFormatError => err
|
22
|
+
raise DecodeError, "Arguments cannot be decoded: MessagePack parser error"
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
register MessagePack
|
27
|
+
end
|
28
|
+
end
|
@@ -0,0 +1,58 @@
|
|
1
|
+
module Stargate
|
2
|
+
module Marshal
|
3
|
+
# Internal: Use it in order to pack data for exchange. Marshaller is created per instance of registry
|
4
|
+
# version. It packs basic values and objects of registered classes into Stargate::Marshal::Payload wraps.
|
5
|
+
class Marshaller
|
6
|
+
# Public: Constructor. Initializes marshaller for given registry version object.
|
7
|
+
def initialize(registry_version)
|
8
|
+
@registry_version = registry_version
|
9
|
+
end
|
10
|
+
|
11
|
+
# Public: Returns given object after marshalling.
|
12
|
+
def marshal(obj)
|
13
|
+
case obj
|
14
|
+
when String, Numeric, TrueClass, FalseClass, Float, BigDecimal, NilClass
|
15
|
+
Payload.new(Payload::SIMPLE, obj)
|
16
|
+
when Symbol
|
17
|
+
Payload.new(Payload::SYMBOL, obj.to_s)
|
18
|
+
when Hash
|
19
|
+
Payload.new(Payload::HASH, marshal_hash(obj))
|
20
|
+
when Array
|
21
|
+
Payload.new(Payload::LIST, marshal_list(obj))
|
22
|
+
else
|
23
|
+
Payload.new(*marshal_complex_object(obj))
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
private
|
28
|
+
|
29
|
+
# Internal: Returns array marshalled.
|
30
|
+
def marshal_list(obj)
|
31
|
+
obj.map { |x| marshal(x) }
|
32
|
+
end
|
33
|
+
|
34
|
+
# Internal: Returns hash marshalled.
|
35
|
+
def marshal_hash(obj)
|
36
|
+
obj.inject({}) do |res,(k,v)|
|
37
|
+
res[k.to_s] = marshal(v)
|
38
|
+
res
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
# Internal: Checks if type of given object is registered for exchange. No? Raises an error. Yes? Marshals
|
43
|
+
# all public attributes and computed readers.
|
44
|
+
#
|
45
|
+
# Returns marshalled object.
|
46
|
+
def marshal_complex_object(obj)
|
47
|
+
metadata = @registry_version.metadata_for(obj)
|
48
|
+
|
49
|
+
attributes = (metadata.attributes + metadata.readers).inject({}) do |result,key|
|
50
|
+
result[key.to_s] = marshal(obj.send(key.to_sym))
|
51
|
+
result
|
52
|
+
end
|
53
|
+
|
54
|
+
[ metadata.name, attributes ]
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|