- •Introduction
- •Rails Is Agile
- •Finding Your Way Around
- •Acknowledgments
- •Getting Started
- •Models, Views, and Controllers
- •Installing Rails
- •Installing on Windows
- •Installing on Mac OS X
- •Installing on Unix/Linux
- •Rails and Databases
- •Keeping Up-to-Date
- •Rails and ISPs
- •Creating a New Application
- •Hello, Rails!
- •Linking Pages Together
- •What We Just Did
- •Building an Application
- •The Depot Application
- •Incremental Development
- •What Depot Does
- •Task A: Product Maintenance
- •Iteration A1: Get Something Running
- •Iteration A2: Add a Missing Column
- •Iteration A4: Prettier Listings
- •Task B: Catalog Display
- •Iteration B1: Create the Catalog Listing
- •Iteration B2: Add Page Decorations
- •Task C: Cart Creation
- •Sessions
- •More Tables, More Models
- •Iteration C1: Creating a Cart
- •Iteration C3: Finishing the Cart
- •Task D: Checkout!
- •Iteration D2: Show Cart Contents on Checkout
- •Task E: Shipping
- •Iteration E1: Basic Shipping
- •Task F: Administrivia
- •Iteration F1: Adding Users
- •Iteration F2: Logging In
- •Iteration F3: Limiting Access
- •Finishing Up
- •More Icing on the Cake
- •Task T: Testing
- •Tests Baked Right In
- •Testing Models
- •Testing Controllers
- •Using Mock Objects
- •Test-Driven Development
- •Running Tests with Rake
- •Performance Testing
- •The Rails Framework
- •Rails in Depth
- •Directory Structure
- •Naming Conventions
- •Active Support
- •Logging in Rails
- •Debugging Hints
- •Active Record Basics
- •Tables and Classes
- •Primary Keys and IDs
- •Connecting to the Database
- •Relationships between Tables
- •Transactions
- •More Active Record
- •Acts As
- •Aggregation
- •Single Table Inheritance
- •Validation
- •Callbacks
- •Advanced Attributes
- •Miscellany
- •Action Controller and Rails
- •Context and Dependencies
- •The Basics
- •Routing Requests
- •Action Methods
- •Caching, Part One
- •The Problem with GET Requests
- •Action View
- •Templates
- •Builder templates
- •RHTML Templates
- •Helpers
- •Formatting Helpers
- •Linking to Other Pages and Resources
- •Pagination
- •Form Helpers
- •Layouts and Components
- •Adding New Templating Systems
- •Introducing AJAX
- •The Rails Way
- •Advanced Techniques
- •Action Mailer
- •Sending E-mail
- •Receiving E-mail
- •Testing E-mail
- •Web Services on Rails
- •Dispatching Modes
- •Using Alternate Dispatching
- •Method Invocation Interception
- •Testing Web Services
- •Protocol Clients
- •Securing Your Rails Application
- •SQL Injection
- •Cross-Site Scripting (CSS/XSS)
- •Avoid Session Fixation Attacks
- •Creating Records Directly from Form Parameters
- •Knowing That It Works
- •Deployment and Scaling
- •Picking a Production Platform
- •A Trinity of Environments
- •Iterating in the Wild
- •Maintenance
- •Finding and Dealing with Bottlenecks
- •Case Studies: Rails Running Daily
- •Appendices
- •Introduction to Ruby
- •Ruby Names
- •Regular Expressions
- •Source Code
- •Cross-Reference of Code Samples
- •Resources
- •Index
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 20
Web Services on Rails
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.
20.1 What AWS Is (and What It Isn’t)
AWS handles server-side support for the SOAP and XML-RPC protocols in our 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 try to 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
Prepared exclusively for Rida Al Barazi
THE API DEFINITION 412
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 blogging 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.
20.2 The 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 API definition 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.
1To paraphrase Larry Wall.
Prepared exclusively for Rida Al Barazi
Report erratum
|
|
THE API DEFINITION |
413 |
|
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 |
|
|
|
This generates a stub API definition. |
|
|
File 126 |
class BackendApi < ActionWebService::API::Base |
|
|
|
api_method :find_all_products |
|
|
|
api_method :find_product_by_id |
|
|
|
end |
|
|
|
And it generates a skeleton controller. |
|
|
File 133 |
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 20.6, |
|
|
|
Testing Web Services, on page 423. |
|
|
|
We’ll need to finish off the API definition. We’ll change its name to Product- |
|
|
|
Api and its filename to app/apis/product_api.rb. |
|
|
File 128 |
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 to the controller attach |
|
|
|
it to the controller explicitly. We also add some code to the method bodies |
|
|
|
and make the signatures match up with the API. |
|
|
File 132 |
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
Prepared exclusively for Rida Al Barazi
Report erratum
THE API DEFINITION 414
Figure 20.1: Web Service Scaffolding Lets You Test APIs
There are a couple of important things in the above example controller that may not immediately be obvious. 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 first parameter to web_service_scaffold( ) above). Figure 20.1 , shows the result of navigating to the scaffold in a browser.
Prepared exclusively for Rida Al Barazi
Report erratum
THE API DEFINITION |
415 |
Method Signatures
AWS API declarations use api_method( ) to declare each method in the
web service interface. These declarations use signatures to specify the signatures method’s calling convention and return type.
A signature is an array containing one or more parameter specifiers. The 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.
parameter specifiers
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 20.2, Structured Parameter Types, on page 417)
•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.
Prepared exclusively for Rida Al Barazi
Report erratum
THE API DEFINITION 416
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.
[: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.
Base Parameter Types
For simple types like 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 ambigious Class object.
We can use either the symbol or the corresponding 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.
Prepared exclusively for Rida Al Barazi
Report erratum