king_soa 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
data/LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2009 Dirk Breuer
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.rdoc ADDED
@@ -0,0 +1,64 @@
1
+ = KingSoa
2
+
3
+ Creating a SOA requires a centralized location to define all services within the
4
+ SOA. Furthermore you want to know where those services live.
5
+
6
+ == Tech details
7
+
8
+ the soa registry is keeping a bunch of service objects which know where a method
9
+ lives. Methods are classes with an self.perform method and can be located local,
10
+ remote(http) or beeing put onto a queue
11
+
12
+
13
+
14
+ == Install
15
+
16
+ gem install king_soa
17
+
18
+ == Define services
19
+
20
+ === Low Level
21
+
22
+ You can define services anywhere in your app and add them to the service
23
+ registry.
24
+ Be aware that the registry is a singleton, so that the rack middleware and your
25
+ app are seeing the same :
26
+ # create the service
27
+ service = KingSoa::Service.new(:name=>:increment_usage,
28
+ :url=>'http://localhost:4567',
29
+ :auth_key=>'12345')
30
+ # register
31
+ KingSoa::Registry << service
32
+
33
+ # somewhere in your app just call your service method
34
+ KingSoa.increment_usage(12)
35
+
36
+ A service name is always required. If the service is to be called remotely you
37
+ must add a url and an auth_key.
38
+ The transport is done via http calls, so to make it secure you should either use
39
+ https in public or hide the servers somwhere on your farm.
40
+
41
+ == Integration
42
+
43
+ === Rails
44
+
45
+
46
+ === Sinatra
47
+
48
+ Take a look at spec/server/app where you can see a minimal sinatra implementation
49
+ The base is just:
50
+ require 'king_soa'
51
+ use KingSoa::Rack::Middleware
52
+
53
+
54
+ == Note on Patches/Pull Requests
55
+
56
+ * Fork the project.
57
+ * Make your feature addition or bug fix.
58
+ * Add tests for it. This is important so I don't break it in a future version unintentionally.
59
+ * Commit, do not mess with rakefile, version, or history. (if you want to have your own version, that is fine but bump version in a commit by itself I can ignore when I pull)
60
+ * Send me a pull request. Bonus points for topic branches.
61
+
62
+ == Copyright
63
+
64
+ Copyright (c) 2010 Georg Leciejewski. See LICENSE for details.
@@ -0,0 +1,47 @@
1
+ module KingSoa::Rack
2
+ class Middleware
3
+
4
+ def initialize(app)
5
+ @app = app
6
+ end
7
+
8
+ # Takes incoming soa requests and calls the passed in method with given params
9
+ def call(env)
10
+ # Hoth::Logger.debug "env: #{env.inspect}"
11
+ if env["PATH_INFO"] =~ /^\/soa/
12
+ begin
13
+ req = Rack::Request.new(env)
14
+ # find service
15
+ service = KingSoa.find(req.params["name"])
16
+ # authenticate
17
+ authenticated?(service, req.params["auth_key"])
18
+ # perform method with decoded params
19
+ result = service.perform(*service.decode( req.params["params"] ))
20
+ # encode result
21
+ encoded_result = service.encode({"result" => result})
22
+ # and return
23
+ [ 200,
24
+ {'Content-Type' => 'application/json',
25
+ 'Content-Length' => "#{encoded_result.length}"},
26
+ [encoded_result] ]
27
+ rescue Exception => e
28
+ #Hoth::Logger.debug "e: #{e.message}"
29
+ if service
30
+ encoded_error = service.encode({"error" => e})
31
+ [500, {'Content-Type' => 'application/json', 'Content-Length' => "#{encoded_error.length}"}, [encoded_error]]
32
+ else
33
+ encoded_error = {"error" => "An error occurred! (#{e.message})"}.to_json
34
+ [500, {'Content-Type' => "application/json", 'Content-Length' => "#{encoded_error.length}"}, [encoded_error]]
35
+ end
36
+ end
37
+ else
38
+ @app.call(env)
39
+ end
40
+ end
41
+
42
+ def authenticated?(service, key)
43
+ raise "Please provide a valid authentication key" unless service.auth_key == key
44
+ end
45
+
46
+ end
47
+ end
@@ -0,0 +1,55 @@
1
+ module KingSoa
2
+ class Registry
3
+ include Singleton
4
+
5
+ ############################################################################
6
+ # Class methods .. only use those since we are in a Singleton
7
+ ############################################################################
8
+
9
+ # Get a method by a given name
10
+ def self.[](service_name)
11
+ instance[service_name]
12
+ end
13
+ # add a service
14
+ def self.<<(service)
15
+ instance << service
16
+ end
17
+ # Return an array of defined services
18
+ def self.services
19
+ instance.services
20
+ end
21
+
22
+ # find a group of services identified by starting with the same string
23
+ #
24
+ def group(name)
25
+ instance.group(name)
26
+ end
27
+ ############################################################################
28
+ # Instance methods - not directly accessible => Singleton
29
+ ############################################################################
30
+
31
+ # returns all available methods
32
+ def services
33
+ @services ||= []
34
+ end
35
+
36
+ # Add a new method onto the stack
37
+ def <<(service)
38
+ (services || []) << service
39
+ end
40
+
41
+ # Get a method by a given name
42
+ def [](service_name)
43
+ name = service_name.to_sym
44
+ services.detect {|s| s.name == name }
45
+ end
46
+
47
+ def group(service_name)
48
+ name = service_name.to_sym
49
+ # srvs = []
50
+ services.collect {|s| s.name[/^#{name}/] }
51
+ end
52
+
53
+
54
+ end
55
+ end
@@ -0,0 +1,88 @@
1
+ module KingSoa
2
+ class Service
3
+ # endpoint url
4
+ attr_accessor :debug, :name, :auth_key, :queue
5
+ attr_reader :request
6
+
7
+ def initialize(opts)
8
+ self.name = opts[:name].to_sym
9
+ self.url = opts[:url] if opts[:url]
10
+ self.queue = opts[:queue] if opts[:queue]
11
+ self.auth_key = opts[:auth_key] if opts[:auth_key]
12
+ end
13
+
14
+ def call_remote(*args)
15
+ set_request_opts(args)
16
+ resp_code = @request.perform
17
+ case resp_code
18
+ when 200
19
+ return self.decode(@request.response_body)["result"]
20
+ else
21
+ return self.decode(@request.response_body)["error"]
22
+ end
23
+ end
24
+
25
+ def perform(*args)
26
+ result = local_class ? local_class.send(:perform, *args) : call_remote(*args)
27
+ return result
28
+ end
29
+
30
+
31
+ def local_class
32
+ @local_class ||= begin
33
+ "#{self.name.to_s.camelize}".constantize
34
+ rescue NameError => e # no local implementation
35
+ false
36
+ end
37
+ end
38
+
39
+ def request
40
+ @request ||= Typhoeus::Easy.new
41
+ end
42
+
43
+ def set_request_opts(args)
44
+ request.url = url
45
+ request.method = :post
46
+ request.timeout = 100 # milliseconds
47
+ request.params = params(args)
48
+ request.user_agent = 'KingSoa'
49
+ request.follow_location = true
50
+ request.verbose = 1 if debug
51
+ end
52
+
53
+ # Url receiving the request
54
+ # TODO. if not present try to grab from endpoint
55
+ def url
56
+ @url
57
+ end
58
+ def url=(url)
59
+ @url = "#{url}/soa"
60
+ end
61
+
62
+ # The params for each soa request consisnt of two values:
63
+ # name => the name of the method to call
64
+ # params => the parameters for the method
65
+ # ==== Parameter
66
+ # params<Hash|Array|String>:: will be json encoded
67
+ # === Returns
68
+ # <Hash{String=>String}>:: params added to the POST body
69
+ def params(payload)
70
+ { 'name' => name.to_s,
71
+ 'params' => encode(payload),
72
+ 'auth_key'=> auth_key }
73
+ end
74
+
75
+ def encode(string)
76
+ string.to_json
77
+ end
78
+
79
+ def decode(string)
80
+ begin
81
+ JSON.parse(string)
82
+ rescue JSON::ParserError => e
83
+ raise e
84
+ end
85
+ end
86
+
87
+ end
88
+ end
data/lib/king_soa.rb ADDED
@@ -0,0 +1,56 @@
1
+ require 'singleton'
2
+ require 'json'
3
+ require 'typhoeus'
4
+ require 'active_support/inflector'
5
+
6
+ require 'king_soa/registry'
7
+ require 'king_soa/service'
8
+ require 'king_soa/rack/middleware'
9
+
10
+ # Define available services.
11
+ #
12
+ # service:
13
+ # name: sign_document
14
+ # url: "https://msg.salesking.eu"
15
+ # auth: 'a-long-random-string'
16
+ # queue: a-queue-name
17
+ #
18
+ #
19
+ # method: save_signed_document
20
+ # url: "https://www.salesking.eu/soa"
21
+ #
22
+ #
23
+ # after defining your services you can call each of them with
24
+ # <tt>Hoth::Services.service_name(params)</tt>
25
+ #
26
+ # KingSoa.sign_document(counter)
27
+ # current_number = Hoth::Services.value_of_counter(counter)
28
+ # created_account = Hoth::Services.create_account(account)
29
+ #
30
+ module KingSoa
31
+
32
+ class << self
33
+
34
+ def init_from_hash(services)
35
+ # create service
36
+
37
+ end
38
+
39
+ # Locate service by a given name
40
+ # ==== Params
41
+ # service<String|Symbol>:: the name to lookup
42
+ def find(service)
43
+ Registry[service]
44
+ end
45
+
46
+ # this is where the services get called
47
+ def method_missing(meth, *args, &blk) # :nodoc:
48
+ if service = Registry[meth]
49
+ service.execute(*args)
50
+ else
51
+ super
52
+ end
53
+ end
54
+ end
55
+
56
+ end
@@ -0,0 +1,36 @@
1
+ require File.dirname(__FILE__) + '/../../spec_helper.rb'
2
+
3
+ describe KingSoa::Rack::Middleware do
4
+ include Rack::Test::Methods
5
+
6
+ before(:each) do
7
+ @service = KingSoa::Service.new(:url=>'localhost', :name=>'a_method')
8
+ KingSoa::Registry << @service
9
+ end
10
+
11
+ it "should be able to handle exceptions" do
12
+ app = stub("ApplicationStub").as_null_object
13
+ middleware = KingSoa::Rack::Middleware.new(app)
14
+ env = {"PATH_INFO" => "/soa", "name" => 'a_method'}
15
+
16
+ rack_response = middleware.call env
17
+ rack_response.first.should == 500 #status code
18
+ rack_response.last.should be_a_kind_of(Array)
19
+ rack_response.last.first.should == "{\"error\":\"An error occurred! (Missing rack.input)\"}"
20
+ end
21
+
22
+ xit "says hello" do
23
+ app = stub("ApplicationStub").as_null_object
24
+ middleware = Hoth::Providers::RackProvider.new(app)
25
+
26
+ get '/soa', :name=>'a_method', :params=> "#{{:number=>1}.to_json}"
27
+ last_response.should == 'ads'#be_ok
28
+ last_response.body.should == 'Hello World'
29
+ end
30
+
31
+ # def app
32
+ # dummy_app = lambda { |env| puts "in dummy"; [200, {}, ""] }
33
+ # KingSoa::Rack::Middleware.new(dummy_app)
34
+ # end
35
+
36
+ end
@@ -0,0 +1,28 @@
1
+ require File.dirname(__FILE__) + '/../spec_helper.rb'
2
+
3
+ describe KingSoa::Registry do
4
+ before(:each) do
5
+
6
+ end
7
+
8
+ it "should return empty services" do
9
+ reg = KingSoa::Registry.new
10
+ reg.services.should == []
11
+ end
12
+
13
+ it "should add service" do
14
+ reg = KingSoa::Registry.new
15
+ s = KingSoa::Service.new(:url=>'http://localhost')
16
+ reg << s
17
+ reg.services.should == [s]
18
+ end
19
+
20
+ it "should return a service by name" do
21
+ reg = KingSoa::Registry.new
22
+ s = KingSoa::Service.new(:name=>:save_document, :url=>'http://localhost')
23
+ reg << s
24
+ reg[:save_document].should == s
25
+ end
26
+
27
+
28
+ end
@@ -0,0 +1,46 @@
1
+ require File.dirname(__FILE__) + '/../spec_helper.rb'
2
+
3
+ describe KingSoa::Service do
4
+ before(:each) do
5
+ end
6
+
7
+ it "should init" do
8
+
9
+ end
10
+ end
11
+
12
+ describe KingSoa::Service, 'local request' do
13
+
14
+ it "should call service" do
15
+ s = KingSoa::Service.new(:name=>:local_soa_class)
16
+ res = s.perform(1,2,3)
17
+ res.should == [1,2,3]
18
+ end
19
+ end
20
+
21
+ # needs the local testserver !!!
22
+ # ruby spec/server/app
23
+ describe KingSoa::Service, 'remote request' do
24
+
25
+ it "should call a service remote" do
26
+ s = KingSoa::Service.new(:name=>:soa_test_service, :url=>'http://localhost:4567', :auth_key=>'12345')
27
+ s.perform(1,2,3).should == [1,2,3]
28
+ end
29
+
30
+ it "should call a service remote and return auth error" do
31
+ s = KingSoa::Service.new(:name=>:soa_test_service, :url=>'http://localhost:4567', :auth_key=>'wrong')
32
+ s.perform(1,2,3).should == "Please provide a valid authentication key"
33
+ end
34
+
35
+ it "should call a service remote and return auth error" do
36
+ s = KingSoa::Service.new(:name=>:wrong_service, :url=>'http://localhost:4567', :auth_key=>'12345')
37
+ s.perform(1,2,3).should == "An error occurred! (undefined method `auth_key' for nil:NilClass)"
38
+ end
39
+ end
40
+
41
+
42
+ class LocalSoaClass
43
+ def self.perform(param1, param2, param3)
44
+ return [param1, param2, param3]
45
+ end
46
+ end
@@ -0,0 +1,26 @@
1
+ #!/usr/bin/env ruby
2
+ require 'rubygems'
3
+ require 'sinatra'
4
+ require "#{File.dirname(__FILE__)}/../../lib/king_soa"
5
+
6
+ use KingSoa::Rack::Middleware
7
+
8
+
9
+ ###################################################
10
+ # method to kill this server instance
11
+ #'/die'
12
+ #######################################
13
+ # Somewhere in you app
14
+ #
15
+ # setup test registry
16
+ service = KingSoa::Service.new(:name=>'soa_test_service', :auth_key=>'12345')
17
+ KingSoa::Registry << service
18
+
19
+ # the class beeing called localy
20
+ class SoaTestService
21
+
22
+ def self.perform(param1, param2, param3)
23
+ return [param1, param2, param3]
24
+ end
25
+
26
+ end
@@ -0,0 +1,37 @@
1
+ require 'rubygems'
2
+
3
+ $LOAD_PATH.unshift(File.dirname(__FILE__))
4
+ $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
5
+ require 'king_soa'
6
+ require 'spec'
7
+ require 'spec/autorun'
8
+ # for mocking web requests
9
+ require 'webmock/rspec'
10
+ require 'rack/test'
11
+ include WebMock
12
+
13
+
14
+
15
+ def local_service
16
+ # Rack::Builder.app do
17
+ # use KingSoa::Rack::Middleware
18
+ # run super
19
+ # end
20
+
21
+ # Rack::Builder.new do
22
+ # use KingSoa::Rack::Middleware
23
+ # app = proc do |env|
24
+ # [ 200, {'Content-Type' => 'text/plain'}, "b" ]
25
+ # end
26
+ # run app
27
+ # end.to_app
28
+ # def app
29
+ # Rack::Builder.new {
30
+ # # URLs starting with /account (logged in users) go to Rails
31
+ # map "/soa" do
32
+ # run KingSoa::Rack::Middleware.new
33
+ # end
34
+ # }.to_app
35
+ # end
36
+ # app.run
37
+ end
metadata ADDED
@@ -0,0 +1,115 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: king_soa
3
+ version: !ruby/object:Gem::Version
4
+ prerelease: false
5
+ segments:
6
+ - 0
7
+ - 0
8
+ - 1
9
+ version: 0.0.1
10
+ platform: ruby
11
+ authors:
12
+ - Georg Leciejewski
13
+ autorequire:
14
+ bindir: bin
15
+ cert_chain: []
16
+
17
+ date: 2010-05-08 00:00:00 +02:00
18
+ default_executable:
19
+ dependencies:
20
+ - !ruby/object:Gem::Dependency
21
+ name: typhoeus
22
+ prerelease: false
23
+ requirement: &id001 !ruby/object:Gem::Requirement
24
+ requirements:
25
+ - - ">="
26
+ - !ruby/object:Gem::Version
27
+ segments:
28
+ - 0
29
+ version: "0"
30
+ type: :runtime
31
+ version_requirements: *id001
32
+ - !ruby/object:Gem::Dependency
33
+ name: json
34
+ prerelease: false
35
+ requirement: &id002 !ruby/object:Gem::Requirement
36
+ requirements:
37
+ - - ">="
38
+ - !ruby/object:Gem::Version
39
+ segments:
40
+ - 0
41
+ version: "0"
42
+ type: :runtime
43
+ version_requirements: *id002
44
+ - !ruby/object:Gem::Dependency
45
+ name: rspec
46
+ prerelease: false
47
+ requirement: &id003 !ruby/object:Gem::Requirement
48
+ requirements:
49
+ - - ">="
50
+ - !ruby/object:Gem::Version
51
+ segments:
52
+ - 0
53
+ version: "0"
54
+ type: :development
55
+ version_requirements: *id003
56
+ description: |
57
+ Creating a SOA requires a centralized location to define all services within the
58
+ SOA. Furthermore you want to know where to deploy those services.
59
+
60
+ email: gl@salesking.eu
61
+ executables: []
62
+
63
+ extensions: []
64
+
65
+ extra_rdoc_files:
66
+ - LICENSE
67
+ - README.rdoc
68
+ files:
69
+ - README.rdoc
70
+ - lib/king_soa.rb
71
+ - lib/king_soa/rack/middleware.rb
72
+ - lib/king_soa/registry.rb
73
+ - lib/king_soa/service.rb
74
+ - spec/king_soa/rack/middleware_spec.rb
75
+ - spec/king_soa/registry_spec.rb
76
+ - spec/king_soa/service_spec.rb
77
+ - spec/server/app.rb
78
+ - spec/spec_helper.rb
79
+ - LICENSE
80
+ has_rdoc: true
81
+ homepage: http://github.com/salesking/king_soa
82
+ licenses: []
83
+
84
+ post_install_message:
85
+ rdoc_options:
86
+ - --charset=UTF-8
87
+ require_paths:
88
+ - lib
89
+ required_ruby_version: !ruby/object:Gem::Requirement
90
+ requirements:
91
+ - - ">="
92
+ - !ruby/object:Gem::Version
93
+ segments:
94
+ - 0
95
+ version: "0"
96
+ required_rubygems_version: !ruby/object:Gem::Requirement
97
+ requirements:
98
+ - - ">="
99
+ - !ruby/object:Gem::Version
100
+ segments:
101
+ - 0
102
+ version: "0"
103
+ requirements: []
104
+
105
+ rubyforge_project:
106
+ rubygems_version: 1.3.6
107
+ signing_key:
108
+ specification_version: 3
109
+ summary: Registry and deployment description abstraction for SOA-Services by SalesKing
110
+ test_files:
111
+ - spec/spec_helper.rb
112
+ - spec/king_soa/registry_spec.rb
113
+ - spec/king_soa/service_spec.rb
114
+ - spec/king_soa/rack/middleware_spec.rb
115
+ - spec/server/app.rb