Permalink
Browse files

allow non KingSoa service endpoints, returning plain text. Configure …

…rack middleware path for detecting incoming soa calls.

added documentation
1 parent ebbbbde commit 8c9f1b56d32af54968849788049a2a33f1457735 @schorsch schorsch committed Jun 25, 2010
View
@@ -1,4 +1,4 @@
-Copyright (c) 2009 Dirk Breuer
+Copyright (c) 2010 Georg Leciejewski
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
View
@@ -66,7 +66,7 @@ to provide some minimal access restriction. To make it secure you should either
use https in public or hide the endpoints somwhere on your farm.
Service endpoints receiving such calls need to use the provided rack middleware.
-The middleware is doing the authentication, executing the service class and
+As the middleware is doing the authentication, executing the service class and
returns values or errors.
=== Local Services
@@ -80,6 +80,12 @@ The service is put onto a resque queue and somewhere in your cloud you should
have a worker looking for it. The service class should also have the resque
@queue attribute set so the job can be resceduled if it fails.
+=== Gotchas
+
+* make sure to define the service on the sender side with the appropriate url
+* define the local service(called from remote) with the right auth key
+* double check your auth keys(a string is not an int), to be save use "strings" esp. when loading from yml
+
== Integration
Just think of a buch of small sinatra or specialized rails apps, each having
@@ -119,13 +125,18 @@ The base is just:
use KingSoa::Rack::Middleware
The service definition should of course also be done, see rails example.
-
+
+== ToDo
+
+* better error logging
+* a central server & clients getting definitions from it
+
== Note on Patches/Pull Requests
* Fork the project.
* Make your feature addition or bug fix.
* Add tests for it. This is important so I don't break it in a future version unintentionally.
-* 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)
+* 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 branch or commit by itself so I can ignore when I pull)
* Send me a pull request. Bonus points for topic branches.
== Copyright
View
@@ -3,6 +3,9 @@
require 'json'
require 'typhoeus'
require 'active_support/inflector'
+# Rails 3.0
+#require 'active_support'
+#require 'active_support/core_ext/string'
require 'king_soa/registry'
require 'king_soa/service'
@@ -27,7 +30,7 @@ def method_missing(meth, *args, &blk) # :nodoc:
if service = Registry[meth]
service.perform(*args)
else
- super
+ super(meth, *args, &blk)
end
end
end
@@ -1,14 +1,22 @@
module KingSoa::Rack
class Middleware
- def initialize(app)
+ # === Params
+ # app:: Application to call next
+ # config<Hash{Symbol=>String}>::
+ # === config hash
+ # :endpoint_path<RegEx>:: Path which is getting all incoming soa requests.
+ # Defaults to /^\/soa/ => /soa
+ # Make sure your service url's have it set too.
+ def initialize(app, config={})
@app = app
+ @config = config
+ @config[:endpoint_path] ||= /^\/soa/
end
# Takes incoming soa requests and calls the passed in method with given params
def call(env)
-# Hoth::Logger.debug "env: #{env.inspect}"
- if env["PATH_INFO"] =~ /^\/soa/
+ if env["PATH_INFO"] =~ @config[:endpoint_path]
begin
req = Rack::Request.new(env)
# find service
@@ -29,7 +37,6 @@ def call(env)
]
rescue Exception => e
- #Hoth::Logger.debug "e: #{e.message}"
if service
encoded_error = service.encode({"error" => e})
[500, {'Content-Type' => 'application/json', 'Content-Length' => "#{encoded_error.length}"}, [encoded_error]]
View
@@ -1,11 +1,21 @@
module KingSoa
class Service
- # endpoint url
- attr_accessor :debug, :name, :auth, :queue
+
+ # name<String/Symbol>:: name of the service class to call
+ # auth<String/Int>:: password for the remote service. Used by rack middleware
+ # to authentify the callee
+ # url<String>:: Url where the service is located. If the rack middleware is
+ # used on the receiving side, make sure to append /soa to the url so the
+ # middleware can grab the incoming call. A custom endpoint path can be set in
+ # the middleware.
+ # queue<Boolean>:: turn on queueing for this service call. The incoming
+ # request(className+parameter) will be put onto a resque queue
+ # debug<Boolean>:: turn on verbose outpur for typhoeus request
+ attr_accessor :debug, :name, :auth, :queue, :url
def initialize(opts)
self.name = opts[:name].to_sym
- [:url, :queue,:auth, :debug ].each do |opt|
+ [:url, :queue, :auth, :debug ].each do |opt|
self.send("#{opt}=", opts[opt]) if opts[opt]
end
end
@@ -18,9 +28,19 @@ def call_remote(*args)
resp_code = request.perform
case resp_code
when 200
- return self.decode(request.response_body)["result"]
+ if request.response_header.include?('Content-Type: application/json')
+ #decode incoming json .. most likely from KingSoa's rack middleware
+ return self.decode(request.response_body)["result"]
+ else # return plain body
+ return request.response_body
+ end
else
- return self.decode(request.response_body)["error"]
+ if request.response_header.include?('Content-Type: application/json')
+ #decode incoming json .. most likely from KingSoa's rack middleware
+ return self.decode(request.response_body)["error"]
+ else # return plain body
+ return request.response_body
+ end
end
end
@@ -36,13 +56,17 @@ def add_to_queue(*args)
# * local by calling perform method on a class
# * put a job onto a queue
# === Parameter
- # args:: whatever arguments the service methods recieves. Those are later json
- # encoded for remote or queued methods
+ # args:: whatever arguments the service methods receives. A local service/method
+ # gets thems as splatted params. For a remote service they are converted to
+ # json
+ # === Returns
+ # <nil> for queued services dont answer
+ # <mixed> Whatever the method/service
def perform(*args)
if queue
add_to_queue(*args)
return nil
- else
+ else # call the local class if present, else got remote
result = local_class ? local_class.send(:perform, *args) : call_remote(*args)
return result
end
@@ -57,16 +81,17 @@ def local_class
end
end
- # Return the classname infered from the camelized service name.
- # A service named: save_attachment => class SaveAttachment
+ # Return the class name infered from the camelized service name.
+ # === Example
+ # save_attachment => class SaveAttachment
def local_class_name
self.name.to_s.camelize
end
# Set options for the typhoeus curl request
# === Parameter
# req<Typhoeus::Easy>:: request object
- # args<Array[]>:: the arguments for the soa method, will be json encoded and added to post body
+ # args<Array[]>:: arguments for the soa method, added to post body json encoded
def set_request_opts(req, args)
req.url = url
req.method = :post
@@ -77,20 +102,12 @@ def set_request_opts(req, args)
req.verbose = 1 if debug
end
- # Url receiving the request
- # TODO. if not present try to grab from endpoint
- def url
- @url
- end
- def url=(url)
- @url = "#{url}/soa"
- end
-
- # The params for each soa request consist of following values:
- # name => the name of the method to call
- # args => the arguments for the soa class method
- # auth => an authentication key. something like a api key or pass. To make
- # it really secure you MUST use https or do not expose your soa endpoints
+ # Params for a soa request consist of following values:
+ # name => name of the soa class to call
+ # args => arguments for the soa class method -> Class.perform(args)
+ # auth => an authentication key. something like a api key or pass. To make
+ # it really secure you MUST use https or hide your soa endpoints from public
+ # web acces
#
# ==== Parameter
# payload<Hash|Array|String>:: will be json encoded
@@ -25,4 +25,12 @@
env = {"PATH_INFO" => "/soa"}
# KingSoa.should_receive(:find).and_return(@service)
end
-end
+end
+
+#
+# von bumi
+#referer = "http://google.com"
+# env = Rack::MockRequest.env_for "http://payango.com", :method => "GET", "HTTP_REFERER" => referer
+# app = mock('app')
+# app.expects(:call).with(has_entry("X_EXTERNAL_REFERER", referer))
+# response = Middleware::Referer.new(app).call(env)
@@ -1,11 +1,14 @@
require File.dirname(__FILE__) + '/../spec_helper.rb'
-describe KingSoa::Service do
+describe KingSoa::Service, 'in general' do
+
before(:each) do
end
- it "should init" do
-
+ it "should not init without name" do
+ lambda {
+ s = KingSoa::Service.new()
+ }.should raise_error
end
end
@@ -33,18 +36,23 @@
stop_test_server
end
- it "should call a service remote" do
- s = KingSoa::Service.new(:name=>:soa_test_service, :url=>test_url, :auth=>'12345')
+ it "should call a remote service" do
+ s = KingSoa::Service.new(:name=>:soa_test_service, :url=>test_soa_url, :auth=>'12345')
s.perform(1,2,3).should == [1,2,3]
end
- it "should call a service remote and return auth error" do
- s = KingSoa::Service.new(:name=>:soa_test_service, :url=>test_url, :auth=>'wrong')
+ it "should call a remote service and return auth error" do
+ s = KingSoa::Service.new(:name=>:soa_test_service, :url=>test_soa_url, :auth=>'wrong')
s.perform(1,2,3).should == "Please provide a valid authentication key"
end
- it "should call a service remote and return auth error" do
- s = KingSoa::Service.new(:name=>:wrong_service, :url=>test_url, :auth=>'12345')
+ it "should call a service remote and return not found error" do
+ s = KingSoa::Service.new(:name=>:wrong_service, :url=>test_soa_url, :auth=>'12345')
s.perform(1,2,3).should include("The service: wrong_service could not be found")
end
+
+ it "should call a remote service without using middleware and returning plain text" do
+ s = KingSoa::Service.new(:name=>:non_soa_test_service, :url=> "#{test_url}/non_json_response")
+ s.perform().should == "<h1>hello World</h1>"
+ end
end
View
@@ -14,15 +14,22 @@
exit!
end
-#######################################
-# Somewhere in you app define services
+# A non king_soa method => rack middleware is not used
+# Still such methods can be called and their result is returned as plain text
+post '/non_json_response' do
+ "<h1>hello World</h1>"
+end
+
+################################################################################
+# Somewhere in you app you define a local service, receiving the incoming call
#
# setup test registry
service = KingSoa::Service.new(:name=>'soa_test_service', :auth => '12345')
KingSoa::Registry << service
# the local soa class beeing called
class SoaTestService
+ #simply return all given parameters
def self.perform(param1, param2, param3)
return [param1, param2, param3]
end
View
@@ -9,14 +9,18 @@
# for starting the test sinatra server
require 'open3'
+def test_soa_url
+ "#{test_url}/soa"
+end
+
def test_url
'http://localhost:4567'
end
# Start a local test sinatra app receiving the real requests .. no mocking here
# the server could also be started manually with:
# ruby spec/server/app
-def start_test_server(wait_time=1)
+def start_test_server(wait_time=3)
# start sinatra test server
Dir.chdir(File.dirname(__FILE__) + '/server/') do
@in, @rackup, @err = Open3.popen3("ruby app.rb")

0 comments on commit 8c9f1b5

Please sign in to comment.