11.4. API TransportsWe already have a transport layer in our networking stackusually TCP, sometimes UDP. When we talk about API transport layers, what we actually mean is a layer above the application layer at the top of the OSI seven-layer model. Our layer seven protocol for web services is always HTTP (otherwise, it wouldn't be web services), so we're talking about protocols to sit on top of HTTP. There tend to be three main contenders in this space, each of which we'll talk about separately. However, there are a limitless number of ways we can request and receive data over HTTP that would still fall under the guise of web services. In Chapter 7, we looked briefly at the REST, XML-RPC, and SOAP styles of passing XML messages. We're now going to look into each of these a little more closely, examining the mechanics of each and looking at how we can build a web service on top of each. 11.4.1. RESTREST stands for Representational State Transfer. The term was coined by Roy Fielding in his 2000 thesis Architectural Styles and the Design of Network-Based Software Architectures. Fielding was one of the principal authors of the HTTP specification (RFCs 1945 and 2616) and a cofounder of the Apache Foundation, which developed the Apache web server. The principles of REST involve using elements of HTTP to define the transport. The HTTP verbs describe the actions taking placedata is requested with GETs, modified with POSTs, created with PUTs, and removed with DELETEs. These actions roughly map to the CRUD database model of create, retrieve, update, and delete. In the REST model, HTTP status codes are used to return the status of the request. If a requested resource is not found, a 404 status would be returned, rather than the server returning a 200 response, with an error message in the response body. REST, like HTTP, is statelesseach request-response cycle happens independently of others, with neither the client nor server having to remember the state between calls. The addressing of all resources through REST is achieved via URIs, rather than request parameters within the body of the request. REST responses are formatted using hypermedia standards such as HTML or XML. REST has since been used to describe any web-based services that use XML over HTTP. This includes systems that don't use the full array of HTTP verbs (since PUT and DELETE are less well know than GET and POST), don't use HTTP status codes (since separating server errors from service errors is sometimes desirable, as well as describing errors in a more structured syntax), and aren't completely stateless (such as token-based authentication systems). What REST does include, however, is the lack of a governing MEP (Message Exchange Pattern) that is part of the core definition of the other two transports. REST responses have no fixed XML DTD or namespace and don't have to conform to any particular logical model. As a result, REST systems are typically very easy to understand and implement, both on the server and client side, at the cost of reduced interoperability and homogeneity. 11.4.2. XML-RPCMoving down the list from least structured to most, we next have the XML Remote Procedure Call protocol, or XML-RPC for short. XML-RPC was invented by Dave Winer in 1999, based on his own RPC protocol and previous work on the SOAP protocol. While the SOAP project was slow to get off the ground, Winer publicly released the XML-RPC specification to avoid delaying a release for potentially years (SOAP was, in fact, passed to the W3C two years later). The protocol is sometimes referred to as RPC2 (as the sequel to Winer's original RPC protocol), a fact that is often reflected in servers using an API endpoint of /RPC2. XML-RPC defines the request and response formats using a simple XML schema (http://www.xmlrpc.com/spec). The often-quoted phrase is that format is so short it fits onto two sheets of paper. For an RPC protocol using MEP formats, this is an extremely simple specification. XML-RPC servers have a single endpoint: a single URI to which all requests are sent, which differs from the REST model of a URI per resource. XML-RPC defines three different message formats: a request, a successful response, and an unsuccessful response. The request message looks like this: <?xml version="1.0"?> <methodCall> <methodName>my.method.name</methodName> <params> <param> ... </param> <param> ... </param> </params> </methodCall> The methodCall element must be the root element, and must contain a methodName element with the name of method to call. According to the specification, method names may only contain upper and lowercase letters, digits, underscores, dots, colons, and slashes. In practice, the convention is to use a period separated hierarchy, using only letters and numbers, specifying the most significant part first (much like Java and Perl package names). To fetch a list of topics from our fictional discussion application, you might call the method myApp.topics.fetchList. The use of lowerCamelCase has also become a convention. Each method may optionally have one or more positional parameters. To include parameters, you simply include a params element, which may contain zero or more param elements. Each param element can then contain a value. There are several value types defined by XML-RPC, which are also used in response messages. These values include basic and structured types, allowing you to express any data structure (albeit verbosely). The most important types are these two scalars and structures, or though there are a few more scalar types defined in the specification: <value><int>foo</int></value> <value><string>1234</String></value> <value> <struct> <member> <name>element key</name> ...value... </member> <member> <name>element key</name> ...value... </member> </struct> </value> <value> <array> <data> ...value... ...value... ...value... </data> </array> </value> The protocol also describes some odd extra constraints on requests. Both User-Agent and Host HTTP headers must be included with the request, so HTTP 1.0 clients need not apply. The content type must be specified as text/xml and must have a correct Content-Length header, so the request needs to be buffered before being sent (to determine its size). In practice, few XML-RPC servers have these arbitrary constraints. The response format is similarly simple, comprised of data specified using the various value types: <?xml version="1.0"?> <methodResponse> <params> <param>...value...</param> </params> </methodResponse> The outer methodResponse element must contain a params element to indicate sucess. As with the request, the params element can contain zero or more param elements containing positional response values. The failure response format also uses an outer methodResponse element, but contains a fault element: <?xml version="1.0"?> <methodResponse> <fault> <value> <struct> <member> <name>faultCode</name> <value><int>{code}</int></value> </member> <member> <name>faultString</name> <value><string>{message}</string></value> </member> </struct> </value> </fault> </methodResponse> This fault element must contain exactly one value, which must be a struct. This struct must contain two members, called faultCode (which must be an integer) and faultString (which must be a string). Because of the inflexibility of the error response format, more structured errors are sometimes returned using the success format, where the first position parameter specifies the success, while the second parameter specifies either to response structure or an error structure. XML-RPC is easy to implement on both the server and client sides, but suffers from being very verbose. As well as wasting bandwidth when performing a lot of requests, this makes large queries with many value types very difficult to read. XML-RPC was the protocol used to define all of the original blogging protocols (Blogger, Blogger2, and MetaWeblog), so is still in very heavy use and has well-supported client and server implementations. 11.4.3. SOAPSOAP was once short for Simple Object Access Protocol, but has since been de-acronymed and is now just plain old SOAP. SOAP was designed in 1998 by Don Box, Dave Winer, and Microsoft's Bob Atkinson and Mohsen Al-Ghosein. The protocol was slow to be released, due in part to the moving target XML was at the time. XML 1.0 had just become a W3C recommendation and XML Schema wasn't finished. The SOAP specification finally shipped at the end of 1999, with SOAP 1.1 being submitted to the W3C in 2001, where the protocol now resides. SOAP doesn't have to serialize over XML, but we'll only concern ourselves with the XML incarnation here. SOAP request and response messages have the concept of an envelope which can contain a header and a body. A simple SOAP request might look like this: <s:Envelope xmlns:s="http://www.w3.org/2001/06/soap-envelope" xmlns:xs="http://www.w3.org/1999/XMLSchema" xmlns:xsi="http://www.w3.org/1999/XMLSchema-instance" xmlns:my="http://www.myapp.com/ns/soap-stuff" > <s:Header> <my:Language s:mustUnderstand="0">en_US</my:Language> </s:Header> <s:Body> <my:ListOfStates> <my:State xsi:type="xsd:string">California</my:State> <my:State xsi:type="xsd:string">Florida</my:State> <my:State xsi:type="xsd:string">Ohio</my:State> </my:ListOfStates> </s:Body> </s:Envelope> SOAP messages are expressed using XML with namespaces. Typically, XML Schema namespaces are used to express content within messages; this is not required, but is recommended for simple interoperability. The XML Schema system defines types in a fairly similar way to XML-RPC, so transitioning between the two isn't a big mental leap. Error responses are returned by putting a fault element inside the message body: <s:Envelope xmlns:s="http://www.w3.org/2003/05/soap-envelope" xmlns:my="http://www.myapp.com/ns/soap-stuff" > <s:Body> <s:Fault> <s:Code> <s:Value>s:Sender</s:Value> </s:Code> <s:Reason> <s:Text>Too many frobs requested</s:Text> </s:Reason> <s:Detail> <my:explain>Too many frobs were requested for the given widget hamper</my:explain> </s:Detail> </s:Fault> </s:Body> </s:Envelope> Because SOAP uses namespaces, we can easily extend any part of any message with arbitrary data by adding our own namespace: <s:Envelope xmlns:s="http://www.w3.org/2003/05/soap-envelope" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:my="http://www.myapp.com/ns/soap-stuff" > <s:Body> <s:Fault> <my:suggestedaction>Run away</my:suggestedaction> <my:temperate>26 degrees</my:temperate> <dc:date>2005-11-14T19:22:41-08:00</dc:date> </s:Fault> </s:Body> </s:Envelope> The appeal of SOAP mainly comes from WSDL (Web Services Description Language, pronounced whiz-dull) files. WSDL files describe the services offered by a SOAP endpoint, to allow zero configuration interfacing. In SOAP-friendly environments (such as .NET or PHP 5), you simply need to point your SOAP client at the WSDL file for a service and all the methods the service defines will be immediately available for you to call as if defined natively, or to introspect programmatically. WSDL files are pretty terse; a WSDL file for a simple web service that takes one string and returns another can easily run to 100 lines. This example is a pretty pared down file showing a single method that takes one string parameter and passes back a different string: <definitions targetNamespace="http://www.myapp.com/frobs.wsdl" xmlns="http://schemas.xmlsoap.org/wsdl/" xmlns:soap="http://schemas.xmlsoap.org/wsdl/soap/" xmlns:xs="http://www.w3.org/1999/XMLSchema" xmlns:xsi="http://www.w3.org/1999/XMLSchema-instance" xmlns:tns="http://www.myapp.com/frobs.wsdl" > <message name="getFrobRequest"> <part name="frobname" xsi:type="xs:string"/> </message> <message name="getFrobResponse"> <part name="frobvalue" xsi:type="xs:string"/> </message> <portType name="frobPort"> <operation name="getFrob"> <input message="getFrobRequest"/> <output message="getFrobResponse"/> </operation> </portType> <binding type="frobPort" name="frobBinding" > <soap:binding style="document" transport="http://schemas.xmlsoap.org/soap/http" /> <operation name="getFrob"> <soap:operation soapAction="http://www.myapp.com/soap/getFrob" /> <input> <soap:body use="literal" /> </input> <output> <soap:body use="literal" /> </output> </operation> </binding> <service name="FrobService"> <documentation>My frob service</documentation> <port name="frobPort" binding="tns:frobBinding"> <soap:address location="http://www.myapp.com/soap/frobs"/> </port> </service> </definitions> WSDL files are ripe for autogeneration, but currently this step requires some manual work. PHP lacks any core support for WSDL generation, while Perl's WSDL::Generator is unmaintained and won't work with Perl 5.8. Without a WSDL file, SOAP services are largely useless.NET and Java developers won't be able to easily interface with your service, and users of the dynamic languages will find it easier to use REST or XML-RPC. 11.4.4. Transport AbstractionWhich transport should we pick for the interface to our API? They all have their advantages and disadvantages. REST is very flexible, but requires intimate knowledge of the particular method you're calling to know what to expect back. XML-RPC is easy to understand and parse, but takes a huge amount of markup to express complex data structures. SOAP is flexible and easy to use (with WSDL files) but large, unwieldy, and needs namespace support. The good news is that you don't necessarily need to choose just one. If we abstract the transport handling into a separate layer, we can support multiple protocols at once. Think back to our API layer diagram, shown in Figure 11-5. Our API-handling code exists in a layer separated from the business logic, but we can also abstract the request and response layers. A request needs to have some propertiesa method to call and some arguments. We can create API endpoints for REST, XML-RPC and SOAP requests, which all extract the needed information from requests and pass those details through to the actual API-handling code. Once we've processed the call, we generate a blob of data we want to return to the user. We then use another layer to serialize the data into a format the caller will understand: a simple blob of XML for REST callers, a structured piece of XML for XML-RPC, and a namespaced XML blob contained in an envelope for SOAP. Examples 11-1, 11-2, and 11-3 show responses in each of the three formats, expressing the same data. Example 11-1. REST example
Example 11-2. XML-RPC example
Example 11-3. SOAP example
So where's the benefit in being able to serialize to different formats? The main advantage is that we can present the format that everyone wants at the same time, allowing people to quickly and easily use our services. By abstracting it into a layer, we avoid having to do any extra work to support multiple serializers for each method. Each method will receive the parameters extracted by the endpoint the user called, perform its processing, and pass back a data object to the serializer. In fact, we don't have to only limit our responses to the three transport protocols. There's no reason we couldn't add serializers with no matching endpoint and select the desired serializer through a parameter. This allows us to create output formats for even easier development: PHP/Perl/JavaScript source code or serialized formats like PHP serialize, Perl's Storable.pm, or JSON (see Examples 11-4 and 11-5). Example 11-4. Serialized PHP example
Example 11-5. JSON example
If we want to make the experience totally seamless and transparent for developers, we just need to provide an API kit that uses the simplest protocol and serialization format to work with in that language, performs the method calls, and returns some kind of native object or data structure. Developers don't need to be concerned about what's going on in the background as long as they know how to make calls and use the results. |