Добавил:
Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
Agile Web Development With Rails, 2nd Edition (2006).pdf
Скачиваний:
30
Добавлен:
17.08.2013
Размер:
6.23 Mб
Скачать

DISPATCHING MODES 589

Using these lets external developers work with the native structured types for their platform when accessing our web services.

So what gets put into the structured type seen by remote callers? For ActionWebService::Struct, all the members defined with member do.

class Person < ActionWebService::Struct member :id, :int

member :name, :string end

An ActiveRecord::Base derivative exposes the columns defined in its corresponding database table.

25.3Dispatching Modes

Remote callers send their invocation requests to endpoint URLs. (Section 25.6,

External Client Applications (XML-RPC), on page 596, has the formats of endpoint URLs.) Dispatching is the process by which AWS maps these incoming requests to methods in objects that implement the services.

The default dispatching mode is direct dispatching and requires no additional configuration to set up. This is the mode we used for the example on page 584.

Direct Dispatching

With direct dispatching, the API definition is attached directly to the controller, and the API method implementations are placed in the controller as public instance methods.

The advantage of this approach is its simplicity. The drawback is that only one API definition can be attached to the controller; therefore, we can have only one API implementation for a unique endpoint URL. It also blurs the separation of model and controller code. It is shown in Figure 25.2.

NewPost(post)

Remote Caller

Controller

new_post(post)

Figure 25.2: Overview of Direct Dispatching

Report erratum

USING ALTERNATE DISPATCHING 590

Layered Dispatching

Layered dispatching allows us to implement multiple APIs with one controller, with one unique endpoint URL for all the APIs. This works well for overlapping XML-RPC–based APIs (such as the various blogging APIs), which have desktop client applications supporting only one endpoint URL. This is shown in Figure 25.3.

Delegated Dispatching

Delegated dispatching is identical to layered dispatching except that it uses a unique endpoint URL per contained API. Instead of embedding API identifiers in the method invocation messages, remote callers send the messages for a specific API to its associated endpoint URI.

We use the web_service_dispatching_mode method in a controller to select that controller’s dispatching mode.

Download e1/ws/dispatching_mode.rb

class RpcController < ActionController::Base web_service_dispatching_mode :layered

end

The valid modes are :direct, :layered, and :delegated.

25.4Using Alternate Dispatching

Because we’ve already used direct dispatching in our first example web service, let’s implement the same web service in one of the other modes.

Remote Caller

FindProductById(id)

ProductService

find_product_by_id(id)

Controller

OrderService

is_order_shipped(id)

IsOrderShipped(id)

Remote Caller

Figure 25.3: Overview of Layered Dispatching

Report erratum

USING ALTERNATE DISPATCHING 591

Layered Dispatching from a Remote Caller’s Perspective

Method invocation requests from remote callers differentiate between the APIs by sending an identifier indicating which to API the method call should go.

In the case of XML-RPC, remote callers use the standard XML-RPC serviceName.methodName convention, with serviceName being the identifier. For example, an XML-RPC method with a name in the XML-RPC message of blogger.newPost would be sent to a newPost method in whichever object is declared to implement the blogger service.

In the case of SOAP, this information is encoded in the SOAPAction HTTP header as declared by the generated WSDL. This has the implication that remote callers behind a proxy stripping off this HTTP header will not be able to call web services that use layered dispatching.

Layered Dispatching Mode

Since layered dispatching implements multiple APIs with one controller, it needs to create mappings for incoming method calls to the objects implementing them. We do this mapping using the web_service declaration in the controller.

Download e1/depot_ws/app/controllers/layered_backend_controller.rb

class LayeredBackendController < ApplicationController web_service_dispatching_mode :layered web_service_scaffold :invoke

web_service :product, ProductService.new web_service(:order) { OrderService.new }

end

You’ll notice that we no longer attach the API definition to the controller, because it no longer contains the API methods. Also notice the two different ways we called web_service.

The first call to web_service passed it a ProductService instance directly. This is sufficient if our web service doesn’t need to have anything to do with the controller. Because the instance is created at class definition time, though, it has no access to the instance variables of the controller, so it effectively operates in isolation from it.

The second call to web_service passes a block parameter. This has the effect of deferring OrderService instantiation to request time. The block we give it will be evaluated in controller instance context, so it will have access to all the instance variables and methods of the controller. This can be useful if we need to use helper methods such as url_for in our web service methods.

Report erratum

METHOD INVOCATION INTERCEPTION 592

Here’s the rest of our code. First, here’s the implementation of our productsearching service.

Download e1/depot_ws/app/apis/product_service.rb

class ProductService < ActionWebService::Base web_service_api ProductApi

def find_all_products

Product.find(:all).map{ |product| product.id } end

def find_product_by_id(id) Product.find(id)

end end

And here’s the implementation of the API to determine whether a product has been shipped.

Download e1/depot_ws/app/apis/order_service.rb

class OrderApi < ActionWebService::API::Base api_method :is_order_shipped,

:expects => [{:orderid => :int}], :returns => [:bool]

end

class OrderService < ActionWebService::Base web_service_api OrderApi

def is_order_shipped(orderid)

raise "No such order" unless order = Order.find_by_id(orderid) !order.shipped_at.nil?

end end

Implementing Delegated Dispatching

The implementation for delegated dispatching is identical to layered dispatching, except that we pass :delegated to web_service_dispatching_mode rather than :layered.

25.5Method Invocation Interception

To avoid duplicating the same code in multiple methods, AWS allows us to perform invocation interception, allowing us to register callbacks that will be invoked before and/or after the web service request.

AWS interception works similarly to Action Pack filters but includes additional information about the web service request that is not available through Action Pack filters, such as the method name and its decoded parameters.

Report erratum

METHOD INVOCATION INTERCEPTION 593

For example, if we wanted to allow only remote callers with an acceptable API key to access our product searching web service, we could add an extra parameter to each method call.

Download e1/depot_ws/app/apis/product_auth_api.rb

class ProductAuthApi < ActionWebService::API::Base

api_method :find_all_products,

:expects => [{:key=>:string}], :returns => [[:int]]

api_method :find_product_by_id,

:expects => [{:key=>:string}, {:id=>:int}], :returns => [Product]

end

And then create an invocation interceptor that validates this parameter without putting the code in every method.

Download e1/depot_ws/app/controllers/backend_auth_controller.rb

class BackendAuthController < ApplicationController wsdl_service_name 'Backend'

web_service_api ProductAuthApi web_service_scaffold :invoke

before_invocation :authenticate

def find_all_products(key) Product.find(:all).map{ |product| product.id }

end

def find_product_by_id(key, id) Product.find(id)

end

protected

def authenticate(name, args)

raise "Not authenticated" unless args[0] == 'secret' end

end

Like with Action Pack, if a before interceptor returns false, the method is never invoked, and an appropriate error message is sent back to the caller as an exception. If a before interceptor raises an exception, invocation of the web service method will also be aborted.

AWS interceptors are defined using before_invocation and after_invocation.

before_invocation(interceptor, options={}) after_invocation(interceptor, options={})

Report erratum