The Perl client (see Example 3-1) against the Java predictions2 RESTful service, implemented as an HttpServlet
, makes
various calls that cover the CRUD operations. (The predictions2 service is from Chapter 2.)
Perl comes with most Unixy systems and is available on Windows (see, for
example, ActiveState). Although Perl has a quirky syntax—it has been called line-noise interpreted and even worse—its
excellent libraries, standard and
contributed, more than compensate. In any case, the point of the example is to provide a first look
at how a client interacts with a RESTful service. The curl clients from the two previous chapters illustrated all of the
CRUD operations against several services; all of the CRUD requests received at least a minimal document in return. Yet the
curl utility is meant for making HTTP requests, not processing HTTP responses.
The Perl client takes the second step; that is, this client performs all of the CRUD operations and then highlights the challenge of
processing response documents. In the case of response documents in XML, the Perl client parses the XML to extract usable
information, in this case the id
, who
, and what
of a specified Prediction
or of the entire PredictionsList
returned from
the service.
Example 3-1. A Perl client making CRUD calls against the RESTful predictions2 service
#
!/
usr
/
bin
/
perl
-
w
#
packages
use
strict
;
use
LWP
;
use
XML:
:
XPath
;
use
List:
:
MoreUtils
qw
(
each_array
);
my
$baseUrl
=
'
http:
//localhost:8080/predictions2/';
my
$ua
=
LWP:
:
UserAgent
->
new
;
runTests
();
##
Run
CRUD
tests
against
the
service
.
sub
runTests
{
getTest
(
$baseUrl
);
##
GET
all
(
xml
)
getTest
(
$baseUrl
.
'
?
id
=
4
'
);
##
GET
one
(
xml
)
getTestJson
(
$baseUrl
);
##
GET
all
(
json
)
getTestJson
(
$baseUrl
.
'
?
id
=
4
'
);
##
GET
one
(
json
)
postTest
(
$baseUrl
);
##
POST
getTest
(
$baseUrl
);
##
GET
all
(
xml
)
putTest
(
$baseUrl
);
##
PUT
getTest
(
$baseUrl
.
'
?
id
=
4
'
);
##
GET
one
(
xml
)
deleteTest
(
$baseUrl
.
'
?
id
=
31
'
);
##
DELETE
getTest
(
$baseUrl
);
##
GET
one
(
xml
)
}
sub
getTest
{
my
(
$url
)
=
@_
;
my
$request
=
HTTP:
:
Request
->
new
(
GET
=>
$url
);
my
$response
=
$ua
->
request
(
$request
);
handleResponse
(
$response
,
\
&
parseXml
);
#
pointer
to
a
function
}
sub
getTestJson
{
my
(
$url
)
=
@_
;
my
$request
=
HTTP:
:
Request
->
new
(
GET
=>
$url
,
HTTP:
:
Headers
->
new
(
'
Accept
'
=>
'
application
/
json
'
));
my
$response
=
$ua
->
request
(
$request
);
handleResponse
(
$response
,
\
&
parseJson
);
}
sub
postTest
{
my
(
$url
)
=
@_
;
my
$request
=
HTTP:
:
Request
->
new
(
POST
=>
$url
);
$request
->
content_type
(
'
application
/
x
-
www
-
form
-
urlencoded
'
);
$request
->
content
(
'
who
=
TS
Eliot
&
what
=
This
is
the
way
the
world
ends
.
'
);
my
$response
=
$ua
->
request
(
$request
);
handleResponse
(
$response
,
undef
);
}
sub
putTest
{
my
(
$url
)
=
@_
;
my
$request
=
HTTP:
:
Request
->
new
(
PUT
=>
$url
);
$request
->
content_type
(
'
application
/
x
-
www
-
form
-
urlencoded
'
);
$request
->
content
(
'
id
=
4
#
who
=
FOOBAR
'
);
my
$response
=
$ua
->
request
(
$request
);
handleResponse
(
$response
,
undef
);
}
sub
deleteTest
{
my
(
$url
)
=
@_
;
my
$request
=
HTTP:
:
Request
->
new
(
DELETE
=>
$url
);
my
$response
=
$ua
->
request
(
$request
);
handleResponse
(
$response
,
undef
);
}
sub
parseXml
{
my
(
$rawXml
)
=
@_
;
#
"Raw XML response:\n"
.
$rawXml
.
"\n"
;
#
Set
up
the
XPath
search
.
my
$xp
=
XML:
:
XPath
->
new
(
xml
=>
trim
(
$rawXml
));
#
Extract
a
list
apiece
of
ids
,
whos
,
and
whats
.
my
@ids
=
$xp
->
find
(
'
//object/void[@property="id"]/int')->get_nodelist;
my
@whos
=
$xp
->
find
(
'
//object/void[@property="who"]/string')->get_nodelist;
my
@whats
=
$xp
->
find
(
'
//object/void[@property="what"]/string')->get_nodelist;
#
Iterate
over
the
lists
to
the
data
.
my
$it
=
each_array
(
@ids
,
@whos
,
@whats
);
while
(
my
(
$id
,
$who
,
$what
)
=
$it
->())
{
sprintf
(
"%2d: "
,
$id
->
string_value
)
.
$who
->
string_value
.
" -> '"
.
$what
->
string_value
.
"'\n"
;
}
}
sub
parseJson
{
my
(
$json
)
=
@_
;
"JSON document:\n$json\n"
;
#
...
}
sub
trim
{
my
$string
=
shift
;
$string
=~
s
/^
\
s
+
//;
$string
=~
s
/
\
s
+
$
//;
return
$string
;
}
sub
handleResponse
{
my
(
$response
,
$callback
)
=
@_
;
if
(
$response
->
is_success
)
{
if
(
defined
$callback
)
{
$callback
->(
$response
->
content
);
}
else
{
$response
->
content
.
"\n"
;
}
}
else
{
STDERR
$response
->
status_line
.
"\n"
;
}
}
The Perl client can make a GET request for all predictions or just a specified one, and the client can
express a preference for XML or JSON in either case. Here is the function getTest
, which requests
all predictions in XML:
sub
getTest
{
my
(
$url
)
=
@_
;
![]()
"\nGET request against $url\n\n"
;
my
$request
=
HTTP:
:
Request
->
new
(
GET
=>
$url
);
![]()
my
$response
=
$ua
->
request
(
$request
);
![]()
handleResponse
(
$response
,
\
&
parseXml
);
![]()
}
In line 1 the $url
variable (in Perl, a scalar variable starts with a $
) has, as its value, a string URL that
targets the predictions2 service, whose
GET operation is the doGet
method in the HttpServlet
. The Perl client generates, in line 2, a request object (the reference
is $request
) and then issues this request through a LWP::UserAgent
instance, with $ua
as the
reference (line 3). The client then invokes, in line 4, the function named handleResponse
with two arguments: the $response
reference, which gives access to the entire HTTP response, including the status code, the headers, and the
body; and a reference to a response-processing function, in this case parseXml
, which parses the returned XML if
the request succeeds.
The function parseXml
illustrates the challenge of extracting usable information from the XML payload.
Perl, like most general-purpose languages, has various ways to parse XML. In this example, the Perl XPath
library is
used. Here is the parsing function without the comments:
sub
parseXml
{
my
(
$rawXml
)
=
@_
;
"Raw XML response:\n"
.
$rawXml
.
"\n"
;
#
raw
xml
![]()
my
$xp
=
XML:
:
XPath
->
new
(
xml
=>
trim
(
$rawXml
));
![]()
my
@ids
=
$xp
->
find
(
'
//object/void[@property="id"]/int')
->
get_nodelist
;
![]()
my
@whos
=
$xp
->
find
(
'
//object/void[@property="who"]/string')
->
get_nodelist
;
![]()
my
@whats
=
$xp
->
find
(
'
//object/void[@property="what"]/string')
->
get_nodelist
;
![]()
my
$it
=
each_array
(
@ids
,
@whos
,
@whats
);
while
(
my
(
$id
,
$who
,
$what
)
=
$it
->())
{
sprintf
(
"%2d: "
,
$id
->
string_value
)
.
$who
->
string_value
.
" -> '"
.
$what
->
string_value
.
"'\n"
;
}
}
The function prints the raw XML from the predictions2 service (line 1) and then uses XPath
(line 2) to
get three lists: @ids
, @whos
, and @whats
(lines 3, 4, and 5). (Perl lists begin with @
.) The
while
loop then prints the text representation of each Prediction
, giving the values of its
id
, who
, and what
properties. Here is a slice of the output:
1
:
Cornelius
Tillman
->
'
Managed
holistic
contingency
will
grow
killer
action
-
items
.
'
2
:
Conner
Kulas
->
'
Vision
-
oriented
zero
administration
timeframe
will
generate
backend
interfaces
.
'
...
23
:
Lottie
Marks
->
'
Open
-
source
multitasking
timeframe
will
monetize
rich
partnerships
.
'
In a production example, the application logic might be considerably more complicated than simply
printing the extracted information. The extracted Prediction
instances might be inserted into
a database, mined for relevant patterns, integrated with other information, sent out as email
greetings, and so on. Nonetheless,
the XML parsing would be the first step in support of any such additional processing.
The other requests from the Perl client are very similar to the curl commands used in Chapters 1 and 2. For example, the predictions2 service returns JSON rather than XML on a GET request if
the appropriate header element is in the HTTP message. The Perl client inserts the Accept
key/value
pair, issues the request, and then parses the returned JSON (line 1):
my
$request
=
HTTP:
:
Request
->
new
(
GET
=>
$url
,
HTTP:
:
Headers
->
new
(
'
Accept
'
=>
'
application
/
json
'
));
![]()
my
$response
=
$ua
->
request
(
$request
);
handleResponse
(
$response
,
\
&
parseJson
);
A POST or a PUT request requires that relevant data about a Prediction
be inserted into the
body of the HTTP request. Here is the POST test:
sub
postTest
{
my
(
$url
)
=
@_
;
my
$request
=
HTTP:
:
Request
->
new
(
POST
=>
$url
);
$request
->
content_type
(
'
application
/
x
-
www
-
form
-
urlencoded
'
);
![]()
$request
->
content
(
'
who
=
TS
Eliot
&
what
=
This
is
the
way
the
world
ends
.
'
);
![]()
my
$response
=
$ua
->
request
(
$request
);
![]()
handleResponse
(
$response
,
undef
);
#
undef
means
no
callback
function
![]()
}
Line 1 sets the content type of the HTTP request for x-www-form-urlencoded
, the standard type
for the body of a POST request; this type is commonly abbreviated as the HTTP form. The form holds
two key/value pairs, one for the who
and the other
for the what
property of a Prediction
(line 2). Line 3 sends the request and awaits the response. In line 4,
the call to handleResponse
, the Perl value undef
(short for undefined) serves roughly the same purpose
as null
in Java. In the call to handleResponse
, a second argument with undef
as its value signals that the
HTTP response should be
printed rather than processed further; for example, on a successful POST, the printed response would be similar to:
<?
xml
version
=
"1.0"
encoding
=
"UTF-8"
?>
<
java
version
=
"1.7.0"
class
=
"java.beans.XMLDecoder"
>
<
string
>
Prediction
36
created
.</
string
>
</
java
>
The Perl example illustrates language neutrality in RESTful web services because the predictions2 service is
written in Java. At the same time, this example
focuses on a central question in this chapter: How can a RESTful client minimize or even
avoid XML/JSON parsing? Even the relatively short Perl client makes clear that the tricky code involves XML
parsing, although the Perl XPath
library has an API that eases the task.
The next section addresses the issue of response-document parsing with two examples, each a Java client against the Amazon E-Commerce service.
The first client contends with the XML that the Amazon service returns, whereas the second client uses JAX-B to transform the XML into
native Java objects, which are then manipulated with the familiar get/set methods.