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

METHOD INVOCATION INTERCEPTION 421

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

File 127

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.

 

20.5 Method 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 addi-

 

tional information about the web service request that is not available

 

through Action Pack filters, such as the method name and its decoded

 

parameters.

 

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.

File 129

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.

Prepared exclusively for Rida Al Barazi

Report erratum

METHOD INVOCATION INTERCEPTION 422

File 131

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 the methods before_invocation( ) and after_invocation( ).

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

An interceptor can be a symbol, in which case it is expected to refer to an instance method. It can also be a block or an object instance. When it’s an object instance, it is expected to have an intercept( ) method.

Instance method before interceptors receive two parameters when called, the method name of the intercepted method and its parameters as an array.

def interceptor(method_name, method_params) false

end

Block and object instance before interceptors receive three parameters. The first is the object containing the web service method, the second the the intercepted method name, and the third its parameters as an array.

before_invocation do |obj, method_name, method_params| false

end

After interceptors receive the same initial parameters as before interceptors but receive an additional parameter at the end. This contains the intercepted method return value, since after interceptors execute after the intercepted method has completed.

Prepared exclusively for Rida Al Barazi

Report erratum

TESTING WEB SERVICES 423

The before_invocation( ) and after_invocation( ) methods support the :except and :only options. These options take as argument an array of symbols identifying the method names to limit interceptions to.

before_invocation :intercept_before, :except => [:some_method]

The previous example applies the :intercept_before interceptor to all web service methods except the :some_method method.

20.6 Testing Web Services

Action Web Service integrates with the Rails testing framework, so we can use the standard Rails testing idioms to ensure our web services are working correctly.

When we used the web_service generator for the first example, a skeleton functional test was created for us in test/functional/backend_api_test.rb.

This is our functional test, modified to pass on the parameters expected by the example on page 412.

File 135

require File.dirname(__FILE__) + '/../test_helper'

 

require 'backend_controller'

 

class BackendController

 

def rescue_action(e)

 

raise e

 

 

end

 

 

end

 

 

class BackendControllerApiTest < Test::Unit::TestCase

 

fixtures :products

 

def setup

 

 

@controller = BackendController.new

 

@request

= ActionController::TestRequest.new

 

@response

= ActionController::TestResponse.new

 

end

 

def test_find_all_products

result = invoke :find_all_products assert result[0].is_a?(Integer)

end

def test_find_product_by_id

product = invoke :find_product_by_id, 2 assert_equal 'Product 2', product.description

end end

This tests the web service methods in BackendController. It performs a complete Action Pack request/response cycle, emulating how our web service will get called in the real world.

The tests use invoke(method_name, *args) to call the web service. The paramater method_name is a symbol identifying the method to invoke, and *args

Prepared exclusively for Rida Al Barazi

Report erratum

TESTING WEB SERVICES 424

are zero or more parameters to be passed to that method.

The invoke( ) method can be used to test controllers using direct dispatching only. For layered and delegated dispatching, we use invoke_layered( ) and invoke_delegated( ) to perform the test invocations. They have identical signatures.

invoke_layered(service_name, method_name, *args) invoke_delegated(service_name, method_name, *args)

In both cases, the service_name parameter refers to the first parameter passed to web_service( ) when declaring the service in the controller.

External Client Applications (SOAP)

When we want to test with external applications on platforms that have a SOAP stack, we’ll want to create our clients from the WSDL that AWS can generate.

The WSDL file AWS generates declares our web service to use RPC-encoded messages, as this gives us stronger typing. These are also the only type of message AWS supports: Document/Literal messages are not supported.

The default Rails config/routes.rb file creates a route named service.wsdl on our controller. To get the WSDL for that controller, we’d download the file

http://my.app.com/CONTROLLER/service.wsdl

and use an IDE such as Visual Studio or the appropriate command-line tools like wsdl.exe to generate the client class files. Should we remove the service.wsdl route, an action named wsdl( ) will still exist in the controller.

External Client Applications (XML-RPC)

If our web service uses XML-RPC instead, we have to know what the endpoint URL for it is going to be, as XML-RPC does not have a WSDL equivalent with information on where to send protocol requests. For direct and layered dispatching, the endpoint URL is

http://my.app.com/PATH/TO/CONTROLLER/api

For delegated dispatching, the endpoint URL is

http://my.app.com/PATH/TO/CONTROLLER/SERVICE_NAME

In this case, SERVICE_NAME refers to the name given as the first parameter to web_service( ) in the controller.

Prepared exclusively for Rida Al Barazi

Report erratum