hoth 0.2.2 → 0.3.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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