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

Leon Breedt, the author of this chapter and the Action Web Service code, is an analyst/developer originally from the city of Cape Town, South Africa.

Chapter 25

Web Services on Rails

(This chapter is mildly controversial. With the advent of REST support in Rails, the core team is less interested in XML-RPC–based and SOAP-based web services. Action Web Service will be removed from the Rails core and made into a plugin for Rails 2.0. For new applications where you aren’t constrained by external interfaces, you might want to consider using a lighter-weight REST approach. However, if you need to interface to existing web services, this chapter should give you the information you need.)

With the Depot application up and running, we may want to let other developers write their own applications that can talk to it using standard web service protocols. To do that, we’ll need to get acquainted with Action Web Service (which we’ll call AWS from now on).

In this chapter, we’ll discuss how AWS is structured. We’ll see how to declare an API, write the code to implement it, and then make sure it works by writing tests for it.

25.1What AWS Is (and What It Isn’t)

AWS provides support for the SOAP and XML-RPC protocols in Rails application. It converts incoming method invocation requests into method calls on our web services and takes care of sending back the responses. This lets us focus on the work of writing the application-specific methods to service the requests.

AWS does not implement every facet of the W3C specifications for SOAP and WSDL or provide every possible feature of XML-RPC. Instead, it focuses on the functionality we can reasonably expect to use regularly in our web services.

Arbitrarily nested structured types

Typed arrays

Sending of exceptions and traces back over the wire when web service methods raise exceptions

THE API DEFINITION 584

Action Web Service lets us be liberal in the input we accept from remote callers, and strict in the output we emit,1 by coercing input and output values into the correct types.

Using Action Web Service, we could

add support for the Blogger or metaWeblog APIs to a Rails application,

implement our own custom API and have .NET developers be able to generate a class to use it from the Action Web Service–generated WSDL, and

support both SOAP and XML-RPC backends with the same code.

25.2The API Definition

The first step in creating a web services application is deciding the functionality we want to provide to remote callers and how much information we’re going to expose to them.

Ideally, it would then be enough to simply write a class implementing this functionality and make it available for invocation. However, this causes problems when we want to interoperate with languages that aren’t as dynamic as Ruby. A Ruby method can return an object of any type. This can cause things to blow up spectacularly when our remote callers get back something they didn’t expect.

AWS deals with this problem by performing type coercion. If a method parameter or return value is not of the correct type, AWS tries to convert it. This makes remote callers happy but also stops us from having to jump through hoops to get input parameters into the correct type if we have remote callers sending us bogus values, such as strings instead of proper integers.

Since Ruby can’t use method definitions to determine the expected method parameter types and return value types, we have to help it by creating an API definition class. Think of the API definition class as similar to a Java or C# interface: It contains no implementation code and cannot be instantiated. It just describes the API.

Enough talk, let’s see an example. We’ll use the generator to get started. We’ll create a web service that has two methods: one to return a list of all products and the other to return details of a particular product.

depot> ruby script/generate web_service Backend find_all_products find_product_by_id exists app/apis/

exists test/functional/

create app/apis/backend_api.rb

create app/controllers/backend_controller.rb create test/functional/backend_api_test.rb

1. To paraphrase Jon Postel (and, later, Larry Wall)

Report erratum

THE API DEFINITION 585

This generates a stub API definition.

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

class BackendApi < ActionWebService::API::Base api_method :find_all_products

api_method :find_product_by_id end

It generates a skeleton controller.

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

class BackendController < ApplicationController wsdl_service_name 'Backend'

def find_all_products end

def find_product_by_id end

end

And it generates a sample functional test that we’ll cover in Section 25.6,

Testing Web Services, on page 594.

We’ll need to finish off the API definition. We’ll change its name to ProductApi and its filename to app/apis/product_api.rb.

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

class ProductApi < ActionWebService::API::Base api_method :find_all_products,

:returns => [[:int]] api_method :find_product_by_id, :expects => [:int], :returns => [Product]

end

Since we changed the API definition name, the automatic loading of the API definition BackendApi (because it shares a prefix with the controller) will no longer work. So, we’ll add a web_service_api call to the controller to attach it to the controller explicitly. We also add some code to the method bodies and make the signatures match up with the API.

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

class BackendController < ApplicationController wsdl_service_name 'Backend'

web_service_api ProductApi web_service_scaffold :invoke

def find_all_products

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

def find_product_by_id(id) Product.find(id)

end end

Report erratum

THE API DEFINITION 586

Figure 25.1: Web Service Scaffolding Lets You Test APIs

There are a couple of important points to note in this example controller. The wsdl_service_name method associates a name with the service that will be used in generated Web Services Definition Language (WSDL). It is not necessary, but setting it is recommended. The web_service_scaffold call acts like the standard Action Pack scaffolding. This provides a way to execute web service methods from a web browser while in development and is something we will want to remove in production.

Now that we’ve implemented the service and the scaffolding is in place, we can test it by navigating to the scaffold action (we passed its name as the first parameter to web_service_scaffold). Figure 25.1, shows the result of navigating to the scaffold in a browser.

Method Signatures

AWS API declarations use api_method to declare each method in the web service interface. These declarations use signatures to specify the method’s calling convention and return type.

A signature is an array containing one or more parameter specifiers. The

Report erratum

THE API DEFINITION 587

parameter specifier tells AWS what type of value to expect for the corresponding parameter and, optionally, the name of the parameter.

api_method accepts the :expects and :returns options for specifying signatures. The :expects option indicates the type (and optionally the name) of each of our method’s parameters. The :returns option gives the type of the method’s return value.

If we omit :expects, AWS will raise an error if remote callers attempt to supply parameters. If we omit :returns, AWS will discard the method return value, returning nothing to the caller. The presence of either option will cause AWS to perform casting to ensure the following.

The method input parameters are of the correct type by the time the method executes.

The value returned by the method body is of the correct type before returning it to the remote caller.

Format of Parameter Specifiers

Parameter specifiers are one of the following.

A symbol or a string identifying one of the Action Web Service base types

The Class object of a custom structured type (such as an ActionWebService::Struct or ActiveRecord::Base; see Section 25.2, Structured Parameter Types, on the following page)

A single-element array containing an item from (1) or (2)

A single-element hash containing as a key the name of parameter and one of (1), (2), or (3) as a value

For example, the following are valid signatures.

[[:string]]

A string array parameter

[:bool]

A boolean parameter

[Person]

A Person structured-type parameter

[{:lastname=>:string}]

A string parameter, with a name of lastname in generated WSDL

[:int, :int]

Two integer parameters

Report erratum

THE API DEFINITION 588

Parameter Names

Notice that we didn’t name the method parameters in the :expects signature for the example ProductApi. Naming the parameters in :expects is not necessary, but without names, the generated WSDL will not have descriptive parameter names, making it less useful to external developers.

Base Parameter Types

For simple types such as numbers, strings, booleans, dates, and times, AWS defines a set of names that can be used to refer to the type in a signature instead of using the possibly ambiguous Class object.

We can use either a symbol or a string as a parameter specifier.

:int

An integer number parameter.

:string

A string value.

:base64

Use this to receive binary data. When the remote caller supplies a value using the protocol’s Base64 type and :base64 was used in the signature, the value will be decoded to binary by the time our method sees it.

:bool

A boolean value.

:float

A floating-point number.

:time

A time stamp value, containing both date and time. Coerced into the Ruby Time type.

:datetime

A time stamp value, containing both date and time. Coerced into the Ruby DateTime type.

:date

A date value, containing just the date. Coerced into the Ruby Date type.

Structured Parameter Types

In addition to the base types, AWS lets us use the Class objects of ActionWebService::Struct or ActiveRecord::Base in signatures.

Report erratum