hoth 0.2.2 → 0.3.0

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.
@@ -40,13 +40,13 @@ After defining all you services, you need to specify in which modules they live.
40
40
  endpoint :default do
41
41
  host 'localhost'
42
42
  port 3000
43
- transport_type :http
43
+ transport :http
44
44
  end
45
45
 
46
46
  endpoint :bert do
47
47
  host 'localhost'
48
48
  port 9999
49
- transport_type :bert
49
+ transport :bert
50
50
  end
51
51
  end
52
52
 
@@ -54,13 +54,13 @@ After defining all you services, you need to specify in which modules they live.
54
54
  endpoint :default do
55
55
  host '192.168.1.12'
56
56
  port 3000
57
- transport_type :http
57
+ transport :http
58
58
  end
59
59
 
60
60
  endpoint :bert do
61
61
  host '192.168.1.15'
62
62
  port 9999
63
- transport_type :bert
63
+ transport :bert
64
64
  end
65
65
  end
66
66
 
data/TODO ADDED
@@ -0,0 +1,2 @@
1
+ * Make the rack provider independent from one specific transport.
2
+ * Make the bodies of the rack_provider return an object which responds to each in order not break on Ruby 1.9.
@@ -2,10 +2,8 @@ require 'singleton'
2
2
 
3
3
  require 'active_support/inflector'
4
4
 
5
- require 'hoth/transport/hoth_transport'
6
- require 'hoth/transport/http_transport'
7
- require 'hoth/transport/bert_transport'
8
- require 'hoth/transport/workling_transport'
5
+ # must be loaded after alls transports and all encodings
6
+ require 'hoth/transport'
9
7
 
10
8
  require 'hoth/service_definition'
11
9
  require 'hoth/service_module'
@@ -0,0 +1,28 @@
1
+ require 'json'
2
+
3
+ module Hoth
4
+ module Encoding
5
+ class Json
6
+
7
+ class <<self
8
+ def encode(string)
9
+ string.to_json
10
+ end
11
+
12
+ def decode(string)
13
+ begin
14
+ Hoth::Logger.debug "Original params before decode: #{string.inspect}"
15
+ JSON.parse(string)
16
+ rescue JSON::ParserError => jpe
17
+ raise EncodingError.wrap(jpe)
18
+ end
19
+ end
20
+
21
+ def content_type
22
+ "application/json"
23
+ end
24
+ end
25
+
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,19 @@
1
+ module Hoth
2
+ module Encoding
3
+ class NoOp
4
+
5
+ class <<self
6
+ def encode(string)
7
+ string
8
+ end
9
+
10
+ def decode(string)
11
+ string
12
+ end
13
+
14
+ def content_type; end
15
+ end
16
+
17
+ end
18
+ end
19
+ end
@@ -1,6 +1,6 @@
1
1
  module Hoth
2
2
  class Endpoint
3
- attr_accessor :host, :port, :module_name, :transport_type
3
+ attr_accessor :host, :port, :module_name, :transport
4
4
 
5
5
  class ConfigEvaluator
6
6
  attr_reader :endpoint
@@ -9,7 +9,7 @@ module Hoth
9
9
  instance_eval(&block)
10
10
  end
11
11
 
12
- [:host, :port, :module_name, :transport_type].each do |endpoint_attribute|
12
+ [:host, :port, :module_name, :transport].each do |endpoint_attribute|
13
13
  define_method endpoint_attribute do |value|
14
14
  endpoint.send("#{endpoint_attribute}=", value)
15
15
  end
@@ -23,5 +23,6 @@ module Hoth
23
23
  def to_url
24
24
  "http://#{@host}:#{@port}/execute"
25
25
  end
26
+
26
27
  end
27
28
  end
@@ -10,4 +10,5 @@ module Hoth
10
10
  end
11
11
 
12
12
  class TransportError < HothException; end
13
+ class TransportException < HothException; end
13
14
  end
@@ -1,10 +1,10 @@
1
1
  class Exception
2
- def to_json(*a)
2
+ def to_json(*params)
3
3
  {
4
4
  'json_class' => self.class.name,
5
5
  'message' => self.message,
6
6
  'backtrace' => self.backtrace
7
- }.to_json(*a)
7
+ }.to_json(*params)
8
8
  end
9
9
 
10
10
  def self.json_create(hash)
@@ -14,7 +14,7 @@ module Hoth
14
14
  mod(service_name) do
15
15
  fun(:execute) do |*args|
16
16
  return_value = begin
17
- Hoth::Transport::BertTransport::TuplePreparer.prepare(Hoth::Services.send(service_name, *args))
17
+ Hoth::Transport::Bert::TuplePreparer.prepare(Hoth::Services.send(service_name, *args))
18
18
  rescue Exception => e
19
19
  Ernie.log %Q{An Exception occured: #{e.message} -- #{e.backtrace.join("\n\t")}}
20
20
  false
@@ -1,5 +1,4 @@
1
1
  require 'rack/request'
2
- require 'json'
3
2
 
4
3
  module Hoth
5
4
  module Providers
@@ -10,24 +9,37 @@ module Hoth
10
9
  end
11
10
 
12
11
  def call(env)
12
+ Hoth::Logger.debug "env: #{env.inspect}"
13
13
  if env["PATH_INFO"] =~ /^\/execute/
14
14
  begin
15
15
  req = Rack::Request.new(env)
16
16
 
17
17
  service_name = req.params["name"]
18
18
  service_params = req.params["params"]
19
- json_payload = JSON({"result" => Hoth::Services.send(service_name, service_params)})
19
+
20
+ responsible_service = ServiceRegistry.locate_service(service_name)
21
+
22
+ decoded_params = responsible_service.transport.encoder.decode(service_params)
23
+ result = Hoth::Services.send(service_name, *decoded_params)
24
+
25
+ encoded_result = responsible_service.transport.encoder.encode({"result" => result})
20
26
 
21
- [200, {'Content-Type' => 'application/json', 'Content-Length' => "#{json_payload.length}"}, json_payload]
27
+ [200, {'Content-Type' => responsible_service.transport.encoder.content_type, 'Content-Length' => "#{encoded_result.length}"}, [encoded_result]]
22
28
  rescue Exception => e
23
- json_payload = JSON({'error' => e})
24
- [500, {'Content-Type' => 'application/json', 'Content-Length' => "#{json_payload.length}"}, json_payload]
29
+ Hoth::Logger.debug "e: #{e.message}"
30
+ if responsible_service
31
+ encoded_error = responsible_service.transport.encoder.encode({"error" => e})
32
+ [500, {'Content-Type' => service.transport.encoder.content_type, 'Content-Length' => "#{encoded_error.length}"}, [encoded_error]]
33
+ else
34
+ plain_error = "An error occuered! (#{e.message})"
35
+ [500, {'Content-Type' => "text/plain", 'Content-Length' => "#{plain_error.length}"}, [plain_error]]
36
+ end
25
37
  end
26
38
  else
27
39
  @app.call(env)
28
40
  end
29
41
  end
30
-
42
+
31
43
  end
32
44
  end
33
45
  end
@@ -1,6 +1,6 @@
1
1
  module Hoth
2
2
  class Service
3
- attr_accessor :name, :params_arity, :return_value, :module
3
+ attr_accessor :name, :params_arity, :module
4
4
 
5
5
  def initialize(name, &block)
6
6
  @name = name
@@ -13,7 +13,7 @@ module Hoth
13
13
  end
14
14
 
15
15
  def transport
16
- @transport ||= "hoth/transport/#{endpoint.transport_type}_transport".camelize.constantize.new(self)
16
+ @transport ||= Transport.create(endpoint.transport, self)
17
17
  end
18
18
 
19
19
  def impl_class
@@ -31,17 +31,12 @@ module Hoth
31
31
  end
32
32
 
33
33
  def execute(*args)
34
- if self.is_local?
35
- decoded_params = transport.decode_params(*args)
36
- result = impl_class.send(:execute, *decoded_params)
37
- return return_nothing? ? nil : result
38
- else
39
- transport.call_remote_with(*args)
40
- end
34
+ result = self.is_local? ? impl_class.send(:execute, *args) : transport.call_remote_with(*args)
35
+ return return_nothing? ? nil : result
41
36
  end
42
37
 
43
38
  def return_nothing?
44
- return_value == :nothing || return_value == :nil || return_value == nil
39
+ [:nothing, :nil, nil].include? @return_value
45
40
  end
46
41
 
47
42
  def via_endpoint(via = nil)
@@ -52,4 +47,4 @@ module Hoth
52
47
  @endpoint ||= self.module[Hoth.env][@via_endpoint]
53
48
  end
54
49
  end
55
- end
50
+ end
@@ -1,5 +1,16 @@
1
1
  module Hoth
2
2
  class ServiceDefinition
3
+
4
+ # create a new service with service_name and register it at the
5
+ # ServiceRegistry. The paramters of the block define the parameters of the
6
+ # defined service. Within the block you can describe the return value.
7
+ #
8
+ # Example:
9
+ #
10
+ # service :create_account do |account|
11
+ # returns :account_id
12
+ # end
13
+ #
3
14
  def service(service_name, &block)
4
15
  ServiceRegistry.add_service(Service.new(service_name, &block))
5
16
  end
@@ -31,17 +31,11 @@ module Hoth
31
31
  end
32
32
 
33
33
  def add_service(service_name, options = {})
34
- unless self.environments[Hoth.env]
35
- puts("no endpoint-definition for environment '#{Hoth.env}' and service '#{service_name}'")
36
- exit
37
- end
34
+ raise HothException.new("no endpoint-definition for environment '#{Hoth.env}' and service '#{service_name}'") unless self.environments[Hoth.env]
38
35
 
39
36
  service = ServiceRegistry.locate_service(service_name.to_sym)
40
37
 
41
- unless service
42
- puts("tried to add service '#{service_name}' but was not defined by service-definition")
43
- exit
44
- end
38
+ raise HothException.new("tried to add service '#{service_name}' but was not defined by service-definition") unless service
45
39
 
46
40
  service.module = self
47
41
  service.via_endpoint(options[:via])
@@ -1,26 +1,33 @@
1
1
  module Hoth
2
+
3
+ # The ServiceRegistry knows all registered services. You can register new
4
+ # services and locate existing services.
5
+
2
6
  class ServiceRegistry
3
7
  include Singleton
4
8
 
9
+ # add a service to the registry
5
10
  def self.add_service(service)
6
11
  instance.add_service(service)
7
12
  end
13
+ # alias_method :register_service, :add_service
8
14
 
15
+ # find a service with a given name
9
16
  def self.locate_service(service_name)
10
17
  instance.locate_service(service_name)
11
18
  end
12
19
 
13
- def add_service(service)
20
+ def add_service(service) # :nodoc:
14
21
  @registry[service.name.to_sym] = service
15
22
  end
16
23
 
17
- def locate_service(service_name)
24
+ def locate_service(service_name) # :nodoc:
18
25
  @registry[service_name.to_sym]
19
26
  end
20
27
 
21
28
  private
22
-
23
- def initialize
29
+
30
+ def initialize # :nodoc:
24
31
  @registry = {}
25
32
  end
26
33
  end
@@ -1,17 +1,45 @@
1
1
  module Hoth
2
2
  class Services
3
+
4
+ # With Hoth::Services.define followed by a block you define all your
5
+ # available services.
6
+ #
7
+ # Example:
8
+ #
9
+ # Hoth::Services.define do
10
+ #
11
+ # service :increment_counter do |counter|
12
+ # returns :nothing
13
+ # end
14
+ #
15
+ # service :value_of_counter do |counter|
16
+ # returns :fixnum
17
+ # end
18
+ #
19
+ # service :create_account do |account|
20
+ # returns :account_id
21
+ # end
22
+ #
23
+ # end
24
+ #
25
+ # after defining your services you can call each of them with
26
+ # <tt>Hoth::Services.service_name(params)</tt>
27
+ #
28
+ # Hoth::Services.increment_counter(counter)
29
+ # current_number = Hoth::Services.value_of_counter(counter)
30
+ # created_account = Hoth::Services.create_account(account)
31
+ #
32
+ # see Hoth::ServiceDefinition for further informations of the block
33
+ # content.
34
+
3
35
  def self.define(&block)
4
36
  ServiceDefinition.new.instance_eval(&block)
5
37
  end
6
38
 
7
39
  class <<self
8
- attr_writer :env
9
-
10
- def env
11
- @env.to_sym
12
- end
13
-
14
- def method_missing(meth, *args, &blk)
40
+
41
+ # this is where the services get called
42
+ def method_missing(meth, *args, &blk) # :nodoc:
15
43
  if _service = ServiceRegistry.locate_service(meth)
16
44
  _service.execute(*args)
17
45
  else
@@ -0,0 +1,42 @@
1
+ require 'hoth/transport/base'
2
+ require 'hoth/transport/http'
3
+ require 'hoth/transport/bert'
4
+ require 'hoth/transport/workling'
5
+
6
+ require 'hoth/encoding/json'
7
+ require 'hoth/encoding/no_op'
8
+
9
+ module Hoth
10
+ module Transport
11
+
12
+ POSSIBLE_TRANSPORTS = {
13
+ :json_via_http => {
14
+ :transport_class => Transport::Http,
15
+ :encoder => Encoding::Json
16
+ },
17
+ :http => :json_via_http,
18
+ :workling => {
19
+ :transport_class => Transport::Workling
20
+ }
21
+ }
22
+
23
+ class <<self
24
+ def create(transport_name, service)
25
+ new_transport_with_encoding(transport_name, service)
26
+ end
27
+
28
+ def new_transport_with_encoding(transport_name, service)
29
+ if POSSIBLE_TRANSPORTS[transport_name.to_sym]
30
+ if POSSIBLE_TRANSPORTS[transport_name.to_sym].kind_of?(Hash)
31
+ POSSIBLE_TRANSPORTS[transport_name.to_sym][:transport_class].new(service, :encoder => POSSIBLE_TRANSPORTS[transport_name.to_sym][:encoder])
32
+ else
33
+ new_transport_with_encoding(POSSIBLE_TRANSPORTS[transport_name.to_sym], service)
34
+ end
35
+ else
36
+ raise TransportException.new("specified transport '#{transport_name}' does not exist, use one of these: #{POSSIBLE_TRANSPORTS.keys.join(", ")}")
37
+ end
38
+ end
39
+ end
40
+
41
+ end
42
+ end
@@ -1,14 +1,17 @@
1
1
  require 'forwardable'
2
-
2
+
3
3
  module Hoth
4
4
  module Transport
5
- class HothTransport
5
+ class Base
6
6
  extend Forwardable
7
7
 
8
- def_delegators :@service_delegate, :name, :module, :endpoint, :params, :return_value
8
+ attr_reader :encoder
9
9
 
10
- def initialize(service_delegate)
10
+ def_delegators :@service_delegate, :name, :module, :endpoint, :params, :return_nothing?
11
+
12
+ def initialize(service_delegate, options = {})
11
13
  @service_delegate = service_delegate
14
+ @encoder = options[:encoder] || Encoding::NoOp
12
15
  end
13
16
 
14
17
  end
@@ -3,7 +3,7 @@ require 'bertrpc'
3
3
 
4
4
  module Hoth
5
5
  module Transport
6
- class BertTransport < HothTransport
6
+ class Bert < Base
7
7
 
8
8
  class TuplePreparer
9
9
  def self.prepare(obj)
@@ -0,0 +1,45 @@
1
+ require 'net/http'
2
+
3
+ module Hoth
4
+ module Transport
5
+ class Http < Base
6
+ def call_remote_with(*params)
7
+ unless return_nothing?
8
+ begin
9
+ handle_response post_payload(params)
10
+ rescue Exception => e
11
+ raise TransportError.wrap(e)
12
+ end
13
+ else
14
+ return nil
15
+ end
16
+ end
17
+
18
+ def handle_response(response)
19
+ case response
20
+ when Net::HTTPSuccess
21
+ Hoth::Logger.debug "response.body: #{response.body}"
22
+ encoder.decode(response.body)["result"]
23
+ when Net::HTTPServerError
24
+ begin
25
+ Hoth::Logger.debug "response.body: #{response.body}"
26
+ raise encoder.decode(response.body)["error"]
27
+ rescue JSON::ParserError => jpe
28
+ raise TransportError.wrap(jpe)
29
+ end
30
+ when Net::HTTPRedirection, Net::HTTPClientError, Net::HTTPInformation, Net::HTTPUnknownResponse
31
+ raise NotImplementedError, "code: #{response.code}, message: #{response.body}"
32
+ end
33
+ end
34
+
35
+ def post_payload(payload)
36
+ uri = URI.parse(self.endpoint.to_url)
37
+ return Net::HTTP.post_form(uri,
38
+ 'name' => self.name.to_s,
39
+ 'params' => encoder.encode(payload)
40
+ )
41
+ end
42
+
43
+ end
44
+ end
45
+ end
@@ -4,25 +4,17 @@ rescue LoadError
4
4
  STDERR.puts "You need the simple_publisher gem if you want to use Workling/Starling transport."
5
5
  end
6
6
 
7
-
8
-
9
-
10
-
11
7
  module Hoth
12
8
  module Transport
13
9
 
14
- class WorklingTransport < HothTransport
10
+ class Workling < Base
15
11
 
16
12
  def call_remote_with(*args)
17
13
  topic = SimplePublisher::Topic.new(:name => "#{self.module.name.to_s.underscore}_subscribers__#{name.to_s.underscore}")
18
14
  connection = SimplePublisher::StarlingConnection.new(:host => endpoint.host, :port => endpoint.port)
19
15
 
20
16
  publisher = SimplePublisher::Publisher.new(:topic => topic, :connection => connection)
21
- publisher.publish(args)
22
- end
23
-
24
- def decode_params(*params)
25
- params
17
+ publisher.publish(encoder.encode(args))
26
18
  end
27
19
 
28
20
  end
@@ -0,0 +1,25 @@
1
+ require File.expand_path(File.join(File.dirname(__FILE__), '../../', 'spec_helper'))
2
+
3
+ module Hoth
4
+ module Encoding
5
+
6
+ describe Json do
7
+
8
+ it "should decode a JSON string" do
9
+ decoded_json = Json.decode '{"test":23}'
10
+ decoded_json.should ==({"test" => 23})
11
+ end
12
+
13
+ it "should encode a JSON string" do
14
+ encoded_json = Json.encode({"test" => 23})
15
+ '{"test":23}'.should == encoded_json
16
+ end
17
+
18
+ it "should know its ContentType" do
19
+ Json.content_type.should == "application/json"
20
+ end
21
+
22
+ end
23
+
24
+ end
25
+ end
@@ -1,30 +1,34 @@
1
1
  require File.expand_path(File.join(File.dirname(__FILE__), '..', 'spec_helper'))
2
2
 
3
- describe Hoth::Endpoint do
3
+ module Hoth
4
4
 
5
- it "should have a port" do
6
- endpoint = Hoth::Endpoint.new { port 3000 }
7
- endpoint.port.should == 3000
8
- end
5
+ describe Endpoint do
9
6
 
10
- it "should have a host name" do
11
- endpoint = Hoth::Endpoint.new { host "example.com" }
12
- endpoint.host.should == "example.com"
13
- end
7
+ it "should have a port" do
8
+ endpoint = Endpoint.new { port 3000 }
9
+ endpoint.port.should == 3000
10
+ end
14
11
 
15
- it "should have a transport type" do
16
- endpoint = Hoth::Endpoint.new { transport_type :json }
17
- endpoint.transport_type.should == :json
18
- end
12
+ it "should have a host name" do
13
+ endpoint = Endpoint.new { host "example.com" }
14
+ endpoint.host.should == "example.com"
15
+ end
19
16
 
20
- it "should should cast itself to URL string" do
21
- endpoint = Hoth::Endpoint.new { port 3000; host "example.com" }
22
- endpoint.to_url.should == "http://example.com:3000/execute"
23
- end
17
+ it "should have a transport name" do
18
+ endpoint = Endpoint.new { transport :json_via_http }
19
+ endpoint.transport.should == :json_via_http
20
+ end
21
+
22
+ it "should should cast itself to URL string" do
23
+ endpoint = Endpoint.new { port 3000; host "example.com" }
24
+ endpoint.to_url.should == "http://example.com:3000/execute"
25
+ end
24
26
 
25
- it "should should know the deployment module this endpoint is associated to" do
26
- endpoint = Hoth::Endpoint.new { module_name "TestModule" }
27
- endpoint.module_name.should == "TestModule"
27
+ it "should should know the deployment module this endpoint is associated to" do
28
+ endpoint = Endpoint.new { module_name "TestModule" }
29
+ endpoint.module_name.should == "TestModule"
30
+ end
31
+
28
32
  end
29
33
 
30
34
  end
@@ -2,16 +2,48 @@ require File.expand_path(File.join(File.dirname(__FILE__), '../../', 'spec_help
2
2
 
3
3
  require File.expand_path(File.join(File.dirname(__FILE__), '../../../', 'lib', 'hoth', 'providers', 'rack_provider'))
4
4
 
5
- describe Hoth::Providers::RackProvider do
6
- it "should be able to handle exceptions" do
7
- app = stub("ApplicationStub").as_null_object
8
- middleware = Hoth::Providers::RackProvider.new app
9
- env = {"PATH_INFO" => "/execute/some_method", "other_params" => nil}
10
- Rack::Request.should_receive(:new).and_raise(RuntimeError)
5
+ require 'rack/mock'
6
+
7
+ module Hoth
8
+ module Providers
9
+
10
+ describe RackProvider do
11
+
12
+ it "should get transport and encoder based on called service" do
13
+ app = stub("ApplicationStub").as_null_object
14
+ middleware = Hoth::Providers::RackProvider.new(app)
15
+
16
+ encoder = mock("EncoderMock")
17
+ encoder.should_receive(:decode).with("some_parameter").and_return(decoded_params = "some_parameter_decoded")
18
+ encoder.should_receive(:content_type).and_return("application/json")
19
+
20
+ transport = mock("TransportMock")
21
+ transport.should_receive(:encoder).exactly(3).times.and_return(encoder)
22
+
23
+ service = mock("ServiceMock")
24
+ service.should_receive(:transport).exactly(3).times.and_return(transport)
25
+ ServiceRegistry.should_receive(:locate_service).with("service_name").and_return(service)
26
+
27
+ Hoth::Services.should_receive(:send).with("service_name", *decoded_params).and_return(service_result = "result")
28
+ encoder.should_receive(:encode).with({"result" => service_result}).and_return("result_encoded")
29
+
30
+ mock_request = Rack::MockRequest.new(middleware)
31
+ mock_request.post("http://localhost/execute?name=service_name&params=some_parameter")
32
+ end
33
+
34
+ it "should be able to handle exceptions" do
35
+ app = stub("ApplicationStub").as_null_object
36
+ middleware = Hoth::Providers::RackProvider.new app
37
+ env = {"PATH_INFO" => "/execute/some_method", "other_params" => nil}
38
+ Rack::Request.should_receive(:new).and_raise(RuntimeError)
39
+
40
+ rack_response = middleware.call env
41
+ rack_response.first.should == 500 #status code
42
+ rack_response.last.should be_a_kind_of(Array)
43
+ rack_response.last.first.should == "An error occuered! (RuntimeError)"
44
+ end
45
+
46
+ end
11
47
 
12
- rack_response = middleware.call env
13
- rack_response.first.should == 500 #status code
14
- rack_response.last.should match('json_class')
15
- rack_response.last.should match('RuntimeError')
16
48
  end
17
49
  end
@@ -14,7 +14,7 @@ module Hoth
14
14
  service = ServiceRegistry.locate_service(service_name)
15
15
  service.should_not be(nil)
16
16
  service.params_arity.should be(1)
17
- service.return_value.should be(:nothing)
17
+ service.return_nothing?.should be(true)
18
18
  end
19
19
 
20
20
  end
@@ -2,7 +2,6 @@ require File.expand_path(File.join(File.dirname(__FILE__), '..', 'spec_helper'))
2
2
 
3
3
  class TestServiceImpl
4
4
  def self.execute(param1, param2)
5
-
6
5
  end
7
6
  end
8
7
 
@@ -16,7 +15,7 @@ module Hoth
16
15
  service = Service.new("TestService", &service_block)
17
16
 
18
17
  service.params_arity.should be(3)
19
- service.return_value.should be(:some_data)
18
+ service.return_nothing?.should be(false)
20
19
  end
21
20
 
22
21
  it "should know its service-impl class" do
@@ -26,15 +25,22 @@ module Hoth
26
25
 
27
26
  it "should know that its service-impl class is not available" do
28
27
  service = Service.new("TestServiceWithoutImplClass") {}
29
- service.impl_class
28
+ service.impl_class.should be(false)
29
+ end
30
+
31
+ it "should know if it is local or not based on Impl-Class availability" do
32
+ service = Service.new("TestServiceWithoutImplClass") {}
33
+ service.is_local?.should be(false)
34
+
35
+ service = Service.new("test_service") {}
36
+ service.is_local?.should be(true)
30
37
  end
31
38
 
32
39
  it "should execute the service stub locally if its impl-class was found" do
33
40
  service = Service.new("test_service") { |p1, p2| returns :nothing }
34
41
 
35
- service.should_receive(:transport).and_return(transport = mock("TransportMock"))
36
- transport.should_receive(:decode_params).with(:arg1, :arg2).and_return(decoded_params = mock("DecodedParamsMock"))
37
- service.impl_class.should_receive(:execute).with(decoded_params)
42
+ service.should_receive(:is_local?).and_return(true)
43
+ service.impl_class.should_receive(:execute).with(:arg1, :arg2)
38
44
 
39
45
  service.execute(:arg1, :arg2)
40
46
  end
@@ -42,6 +48,7 @@ module Hoth
42
48
  it "should call the remote service if impl-class does not exist" do
43
49
  service = Service.new("test_service_without_impl") { |p1, p2| returns :nothing }
44
50
 
51
+ service.should_receive(:is_local?).and_return(false)
45
52
  service.should_receive(:transport).and_return(transport = mock("TransportMock"))
46
53
  transport.should_receive(:call_remote_with).with(:arg1, :arg2)
47
54
 
@@ -51,8 +58,8 @@ module Hoth
51
58
  it "should create transport instance based on endpoint" do
52
59
  service = Service.new("test_service") { |p1, p2| returns :nothing }
53
60
  service.should_receive(:endpoint).and_return(endpoint = mock("EndpointMock"))
54
- endpoint.should_receive(:transport_type).and_return(:http)
55
- Hoth::Transport::HttpTransport.should_receive(:new).with(service)
61
+ endpoint.should_receive(:transport).and_return(:http)
62
+ Hoth::Transport.should_receive(:create).with(:http, service)
56
63
  service.transport
57
64
  end
58
65
 
@@ -0,0 +1,43 @@
1
+ require File.expand_path(File.join(File.dirname(__FILE__), '../../', 'spec_helper'))
2
+
3
+ module Hoth
4
+ module Transport
5
+
6
+ describe "Base" do
7
+
8
+ it "should initialize with service-delegate" do
9
+ service = mock("ServiceMock")
10
+ transport = Base.new(service)
11
+ end
12
+
13
+ it "should use an NoOp encoder if no encoder class was given at all" do
14
+ service = mock("ServiceMock")
15
+ transport = Base.new(service)
16
+ transport.encoder.should == Encoding::NoOp
17
+ end
18
+
19
+ it "should delegate calls to service-delegate" do
20
+ service = mock("ServiceMock")
21
+ service.should_receive(:name)
22
+ service.should_receive(:module)
23
+ service.should_receive(:endpoint)
24
+ service.should_receive(:params)
25
+ service.should_receive(:return_nothing?)
26
+ transport = Base.new(service)
27
+
28
+ [:name, :module, :endpoint, :params, :return_nothing?].each do |method|
29
+ transport.send(method)
30
+ end
31
+ end
32
+
33
+ it "should have an encoder" do
34
+ service = mock("ServiceMock")
35
+ encoder = mock("EncoderMock")
36
+ transport = Base.new(service, :encoder => encoder)
37
+ transport.encoder.should be(encoder)
38
+ end
39
+
40
+ end
41
+
42
+ end
43
+ end
@@ -0,0 +1,77 @@
1
+ require File.expand_path(File.join(File.dirname(__FILE__), '../../', 'spec_helper'))
2
+
3
+ module Hoth
4
+ module Transport
5
+ describe Http do
6
+
7
+ before(:each) do
8
+ @service_mock = mock("ServiceMock")
9
+ end
10
+
11
+ it "should call a remote via http" do
12
+ @service_mock.should_receive(:return_nothing?).and_return(false)
13
+
14
+ params = {:first_name => "Seras", :last_name => "Victoria"}
15
+
16
+ transport = Http.new(@service_mock)
17
+ transport.should_receive(:post_payload).with([params])
18
+ transport.call_remote_with(params)
19
+ end
20
+
21
+ it "should post payload encoded with JSON" do
22
+ @service_mock.should_receive(:endpoint).and_return(endpoint = mock("EndpointMock"))
23
+ @service_mock.should_receive(:name).and_return("service_name")
24
+ endpoint.should_receive(:to_url).and_return("http://localhost:3000/execute")
25
+
26
+ encoder = mock("JsonEncoderMock")
27
+ encoder.should_receive(:encode).with("params").and_return("encoded_params")
28
+
29
+ URI.should_receive(:parse).with("http://localhost:3000/execute").and_return(uri = mock("URIMock"))
30
+ Net::HTTP.should_receive(:post_form).with(uri, {"name" => "service_name", "params" => "encoded_params"})
31
+
32
+ transport = Http.new(@service_mock, {:encoder => encoder})
33
+ transport.post_payload("params")
34
+ end
35
+
36
+ describe "Error Handling" do
37
+
38
+ before(:each) do
39
+ service_mock = mock("ServiceMock")
40
+ service_mock.should_receive(:return_nothing?).any_number_of_times.and_return(false)
41
+
42
+ @params = {:first_name => "Seras", :last_name => "Victoria"}
43
+ encoder = mock("JsonEncoderMock")
44
+ @transport = Http.new(service_mock, {:encoder => encoder})
45
+ end
46
+
47
+ it "should handle http connection error" do
48
+ @transport.should_receive(:post_payload).and_raise(Exception)
49
+ lambda { @transport.call_remote_with(@params) }.should raise_error(TransportError)
50
+ end
51
+
52
+ it "should handle successful response" do
53
+ @transport.should_receive(:post_payload).and_return(response = Net::HTTPSuccess.allocate)
54
+ response.should_receive(:body).twice.and_return(response_body = '{"result" : "return_value"}')
55
+ @transport.encoder.should_receive(:decode).with(response_body).and_return({"result" => "return_value"})
56
+ @transport.call_remote_with(@params)
57
+ end
58
+
59
+ it "should handle remote server error" do
60
+ @transport.should_receive(:post_payload).and_return(response = Net::HTTPServerError.allocate)
61
+ response.should_receive(:body).any_number_of_times.and_return(response_body = '{"error" : "ServerError"}')
62
+ @transport.encoder.should_receive(:decode).with(response_body).and_return({"result" => "return_value"})
63
+ lambda { @transport.call_remote_with(@params) }.should raise_error(TransportError)
64
+ end
65
+
66
+ it "should handle any other HTTP errors" do
67
+ [Net::HTTPRedirection, Net::HTTPClientError, Net::HTTPInformation, Net::HTTPUnknownResponse].each do |http_response|
68
+ @transport.should_receive(:post_payload).and_return(response = http_response.allocate)
69
+ response.should_receive(:body).any_number_of_times.and_return('{"error" : "ServerError"}')
70
+ lambda { @transport.call_remote_with(@params) }.should raise_error(TransportError)
71
+ end
72
+ end
73
+ end
74
+
75
+ end
76
+ end
77
+ end
@@ -3,7 +3,7 @@ require File.expand_path(File.join(File.dirname(__FILE__), '../../', 'spec_help
3
3
  module Hoth
4
4
  module Transport
5
5
 
6
- describe "WorklingTransport" do
6
+ describe "Workling" do
7
7
 
8
8
  it "should send publish a message via SimplePublisher" do
9
9
  endpoint = mock("EndpointMock")
@@ -32,17 +32,10 @@ module Hoth
32
32
 
33
33
  publisher.should_receive(:publish).with([uid, email_address])
34
34
 
35
- transport = WorklingTransport.new(service)
35
+ transport = Workling.new(service)
36
36
  transport.call_remote_with(uid, email_address)
37
37
  end
38
38
 
39
- it "should decode params from a message wrapper" do
40
- params_as_message = ["uid"]
41
-
42
- transport = WorklingTransport.new(mock("ServiceMock"))
43
- transport.decode_params(*params_as_message).should == ["uid"]
44
- end
45
-
46
39
  end
47
40
 
48
41
  end
@@ -0,0 +1,29 @@
1
+ require File.expand_path(File.join(File.dirname(__FILE__), '..', 'spec_helper'))
2
+
3
+ module Hoth
4
+ describe Transport do
5
+
6
+ it "should create a new transport by transport name for a given service" do
7
+ service = mock("ServiceMock")
8
+ Transport.should_receive(:new_transport_with_encoding).with(:transport_name, service)
9
+ Transport.create(:transport_name, service)
10
+ end
11
+
12
+ it "should create a new transport instance with encoding from a transport name" do
13
+ transport = Transport.new_transport_with_encoding :json_via_http, mock("ServiceMock")
14
+ transport.should be_kind_of(Transport::Http)
15
+ transport.encoder.should == Encoding::Json
16
+ end
17
+
18
+ it "should create a new transport instance with encoding from a transport alias" do
19
+ transport = Transport.new_transport_with_encoding :http, mock("ServiceMock")
20
+ transport.should be_kind_of(Transport::Http)
21
+ transport.encoder.should == Encoding::Json
22
+ end
23
+
24
+ it "should raise an exception if transport name does not exist" do
25
+ lambda { Transport.new_transport_with_encoding :not_existing_transport, mock("ServiceMock") }.should raise_error(TransportException)
26
+ end
27
+
28
+ end
29
+ end
metadata CHANGED
@@ -4,9 +4,9 @@ version: !ruby/object:Gem::Version
4
4
  prerelease: false
5
5
  segments:
6
6
  - 0
7
- - 2
8
- - 2
9
- version: 0.2.2
7
+ - 3
8
+ - 0
9
+ version: 0.3.0
10
10
  platform: ruby
11
11
  authors:
12
12
  - Dirk Breuer
@@ -14,7 +14,7 @@ autorequire:
14
14
  bindir: bin
15
15
  cert_chain: []
16
16
 
17
- date: 2010-03-30 00:00:00 +02:00
17
+ date: 2010-03-31 00:00:00 +02:00
18
18
  default_executable:
19
19
  dependencies:
20
20
  - !ruby/object:Gem::Dependency
@@ -89,10 +89,13 @@ extensions: []
89
89
  extra_rdoc_files:
90
90
  - LICENSE
91
91
  - README.rdoc
92
+ - TODO
92
93
  files:
93
94
  - README.rdoc
94
95
  - THANKS.md
95
96
  - lib/hoth.rb
97
+ - lib/hoth/encoding/json.rb
98
+ - lib/hoth/encoding/no_op.rb
96
99
  - lib/hoth/endpoint.rb
97
100
  - lib/hoth/exceptions.rb
98
101
  - lib/hoth/extension/core/exception.rb
@@ -104,13 +107,14 @@ files:
104
107
  - lib/hoth/service_module.rb
105
108
  - lib/hoth/service_registry.rb
106
109
  - lib/hoth/services.rb
107
- - lib/hoth/transport/amqp_transport.rb
108
- - lib/hoth/transport/bert_transport.rb
109
- - lib/hoth/transport/hoth_transport.rb
110
- - lib/hoth/transport/http_transport.rb
111
- - lib/hoth/transport/workling_transport.rb
110
+ - lib/hoth/transport.rb
111
+ - lib/hoth/transport/base.rb
112
+ - lib/hoth/transport/bert.rb
113
+ - lib/hoth/transport/http.rb
114
+ - lib/hoth/transport/workling.rb
112
115
  - lib/hoth/util/logger.rb
113
116
  - spec/spec_helper.rb
117
+ - spec/unit/encoding/json_spec.rb
114
118
  - spec/unit/endpoint_spec.rb
115
119
  - spec/unit/extension/core/exception_spec.rb
116
120
  - spec/unit/hoth_spec.rb
@@ -118,8 +122,12 @@ files:
118
122
  - spec/unit/service_definition_spec.rb
119
123
  - spec/unit/service_module_spec.rb
120
124
  - spec/unit/service_spec.rb
121
- - spec/unit/transport/workling_transport_spec.rb
125
+ - spec/unit/transport/base_spec.rb
126
+ - spec/unit/transport/http_spec.rb
127
+ - spec/unit/transport/workling_spec.rb
128
+ - spec/unit/transport_spec.rb
122
129
  - LICENSE
130
+ - TODO
123
131
  has_rdoc: true
124
132
  homepage: http://github.com/railsbros/hoth
125
133
  licenses: []
@@ -152,6 +160,7 @@ specification_version: 3
152
160
  summary: Registry and deployment description abstraction for SOA-Services
153
161
  test_files:
154
162
  - spec/spec_helper.rb
163
+ - spec/unit/encoding/json_spec.rb
155
164
  - spec/unit/endpoint_spec.rb
156
165
  - spec/unit/extension/core/exception_spec.rb
157
166
  - spec/unit/hoth_spec.rb
@@ -159,4 +168,7 @@ test_files:
159
168
  - spec/unit/service_definition_spec.rb
160
169
  - spec/unit/service_module_spec.rb
161
170
  - spec/unit/service_spec.rb
162
- - spec/unit/transport/workling_transport_spec.rb
171
+ - spec/unit/transport/base_spec.rb
172
+ - spec/unit/transport/http_spec.rb
173
+ - spec/unit/transport/workling_spec.rb
174
+ - spec/unit/transport_spec.rb
@@ -1,7 +0,0 @@
1
- module Hoth
2
- module Transport
3
- class AmqpTransport
4
-
5
- end
6
- end
7
- end
@@ -1,43 +0,0 @@
1
- require 'json'
2
- require 'net/http'
3
-
4
-
5
- module Hoth
6
- module Transport
7
- class HttpTransport < HothTransport
8
- def call_remote_with(*args)
9
- response = Net::HTTP.post_form(
10
- URI.parse(self.endpoint.to_url),
11
- { 'name' => self.name.to_s, 'params' => "#{args.to_json}" }
12
- )
13
-
14
- if return_value
15
- case response
16
- when Net::HTTPSuccess
17
- Hoth::Logger.debug "response.body: #{response.body}"
18
- JSON(response.body)["result"]
19
- when Net::HTTPServerError
20
- begin
21
- raise JSON(response.body)["error"]
22
- rescue JSON::ParserError => jpe
23
- raise TransportError.wrap(jpe)
24
- end
25
- when Net::HTTPRedirection, Net::HTTPClientError, Net::HTTPInformation, Net::HTTPUnknownResponse
26
- #TODO Handle redirects(3xx) and http errors(4xx), http information(1xx), unknown responses(xxx)
27
- raise NotImplementedError, "HTTP code: #{response.code}, message: #{response.message}"
28
- end
29
- else
30
- response.is_a?(Net::HTTPSuccess)
31
- end
32
- end
33
-
34
- def decode_params(params)
35
- Hoth::Logger.debug "Original params before decode: #{params.inspect}"
36
- JSON.parse(params)
37
- rescue JSON::ParserError => jpe
38
- raise TransportError.wrap(jpe)
39
- end
40
-
41
- end
42
- end
43
- end