Now that you have used the Flickr API Explorer and documentation to make sense of the
details of a given API method and to package a call in the browser, you will now learn
how to make a call from a simple third-party application that you write. In this
section, I return to the flickr.photos.search
example I used earlier in
this chapter:
http://api.flickr.com/services/rest/?method=flickr.photos.search&api_key={api_key}&tags={tag}&per_page={per_page}
Specifically, the following:
http://api.flickr.com/services/rest/?method=flickr.photos.search&api_key={api_key}&tags=puppy&per_page=3
generates a response similar to this:
<?xml version="1.0" encoding="utf-8"> <rsp stat="ok"> <photos page="1" pages="96293" perpage="3" total="288877"> <photo id="1153699093" owner="7841384@N07" secret="d1fba451c9" server="1023" farm="2" title="willy after bath and haircut" ispublic="1" isfriend="0" isfamily="0" /> <photo id="1154506492" owner="7841384@N07" secret="881ff7c4bc" server="1058" farm="2" title="rocky with broken leg" ispublic="1" isfriend="0" isfamily="0" /> <photo id="1153588011" owner="90877382@N00" secret="8a7a559e68" server="1288" farm="2" title="DSC 6503" ispublic="1" isfriend="0" isfamily="0" /> </photos> </rsp>
In the earlier narrative, I described to you how you can extract from the XML response
such quantities as the total number of photos and how to derive from a
photo
element such as this:
<photo id="1153699093" owner="7841384@N07" secret="d1fba451c9" server="1023" farm="2" title="willy after bath and haircut" ispublic="1" isfriend="0" isfamily="0" />
Here are the URLs for the corresponding photo:
http://www.flickr.com/photos/7841384@N07/1153699093/
http://farm2.static.flickr.com/1023/1153699093_d1fba451c9.jpg
In the following sections, I’ll show you how to instantiate that logic into code. Specifically, we will write a simple third-party Flickr app in PHP that makes a Flickr API call and converts the response to HTML. We’ll use two important sets of techniques that I will elaborate on in some detail, HTTP clients and XML processing, after which I describe how to use these techniques to make the call to Flickr. Here I focus on PHP, but you can apply these ideas to your language of choice.
![]() | Tip |
---|---|
When debugging web services, I have found it helpful to use a network protocol
analyzer such as Wireshark ( |
Let’s consider first the issue of how to perform an HTTP GET
request and retrieve the response in PHP. The function
file_get_contents
takes a URL and returns the corresponding content
in a string, provided the allow_url_fopen
option is set to
true
in the system-wide php.ini
. For
example:
<?php // retrieve Atom feed of recent flower-tagged photos in Flickr $url = "http://api.flickr.com/services/feeds/photos_public.gne?tags=flower&lang=en-us&format=atom"; $content = file_get_contents($url); echo $content; ?>
If you are using an instance of PHP for which URL access for
file_get_contents
is disabled (which is not uncommon for shared
hosting facilities with security concerns), then you might still be able to use the
cURL extension for PHP (libcurl
) to perform the same function.
libcurl
is documented here:
The following getResource
function does what
file_get_contents
does. Note the four steps in using the
curl
library: initializing the call, configuring options, executing
the call, and closing down the handle:
<?php function getResource($url){ // initialize a handle $chandle = curl_init(); // set URL curl_setopt($chandle, CURLOPT_URL, $url); // return results a s string curl_setopt($chandle, CURLOPT_RETURNTRANSFER, 1); // execute the call $result = curl_exec($chandle); curl_close($chandle); return $result; } ?>
The many options you can configure in libcurl
are documented
here:
http://us3.php.net/manual/en/function.curl-setopt.php
In this book, I use libcurl
for HTTP access in PHP. Should you not be
able to use libcurl
, you can use the libcurl
Emulator, a
pure-PHP implementation of libcurl
:
http://code.blitzaffe.com/pages/phpclasses/files/libcurl_emulator_52-7
![]() | Note |
---|---|
I will often use |
So, how would you configure the many options of a library such as
libcurl
? Doing so requires some understanding of HTTP. Although
HTTP is a foundational protocol, it’s really quite easy to get along, even as
programmers, without knowing the subtleties of HTTP. My goal here is not to describe
HTTP in great detail. When you need to understand the protocol in depth, I suggest
reading the official specifications; here’s the URL for HTTP 1.0 (RFC
1945):
http://tools.ietf.org/html/rfc1945
And here’s the URL for HTTP 1.1:
http://tools.ietf.org/html/rfc2616
You can also consult the official W3C page:
Reading and digesting the specification is not the best way to learn HTTP for most
of us, however. Instead of formally learning HTTP all in one go in its formal glory,
I’ve learned different aspects of HTTP at different times, and because that
partial knowledge was sufficient for the situation at hand, I felt no need to
explore the subtleties of the protocol. It was a new situation that prompted me to
learn more. My first encounter with HTTP was simply surfing the Web and using URLs
that had http
for their prefix. For a little while, I didn’t even
know that http
wasn’t the only possible scheme in a URI—and
that, technically, http://
is not a redundant part of a URI, even if
on business cards it might be. (People understand www.apress.com
means
an address for a page in web browser—and the prefix http://
just
looks geeky.)
Later when I learned about HTML forms, I learned that there are two possible
values for the method
attribute for FORM
: get
and post
.[99]get
and put
since put
is often the
complement to get
.) For a long time, the only difference I perceived
between get
and post
was that a form that uses the
get
method generates URLs that include the name/value pairs of the
submitted form elements, whereas post
doesn’t. The practical
upshot for me was that get
produces addressable URLs, whereas
post
doesn’t. I thought I had post
figured out
as a way of changing the state of resources (as opposed to using get
for asking for information)—and then I learned about the formal way of
distinguishing between safe
and idempotent
methods (see
the “Safe Methods and Idempotent Methods” sidebar for a further
explanation of these terms). Even with post
, it turns out that there is
a difference between two different forms of form encoding stated in the
FORM
enctype
attribute (application/x-www-form-urlencoded
vs.
multipart/form-data
), a distinction that is not technically part of
HTTP but that will have a practical effect on how you programmatically make certain
HTTP requests.[100]
When I moved from writing HTML to writing basic web applications, I then
learned more about the formal structure of HTTP—and how what I had learned
fit within a larger structure of what HTTP is capable of doing. For instance, I
learned that, in addition to GET
and POST
, HTTP
defines six other methods, among which was a PUT
after all.
(It’s just that few, if any, web browsers support PUT
.) Let
me describe the parts of HTTP 1.1 request and response messages. (I draw some
terminology in the following discussion from the excellent presentation of HTTP
by Leonard Richardson and Sam Ruby in Restful Web
Services.)
An HTTP request is composed of the following pieces:
The method (also known as
verb or action). In
addition to GET
and POST
, there are six others
defined in the HTTP specification: OPTIONS
,
HEAD
, PUT
, DELETE
,
TRACE
, and CONNECT
. GET
and
POST
are widely used and supported in web browsers and
programming libraries.
The path—the part of the URL to the right of the hostname.
A series of request headers. See http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html
.
A request body, which may be empty.
The parts of the HTTP response include the following:
A response code. You can find a long list of
codes at http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html
.
Examples include 200 OK, 400 Bad Request, and 500 Internal Server
Error.
Response headers. See
http://www.w3.org/Protocols/rfc2616/rfc2616-sec6.html
.
A response body.
Let’s consider the following example:
http://api.flickr.com/services/rest/?method=flickr.photos.search&api_key={api-key}&tags=puppy&per_page=3
To track the HTTP traffic, I’m using curl
(with the
verbose
option) to make the call. (You can also use Wireshark
to read the parameters of the HTTP request and response):
curl --verbose "http://api.flickr.com/services/rest/?method=flickr.photos.search&api_key={api-key}&tags=puppy&per_page=3"
This is an edited version of what I get:
* About to connect() to api.flickr.com port 80 * Trying 68.142.214.24... * connected * Connected to api.flickr.com (68.142.214.24) port 80 > GET /services/rest/?method=flickr.photos.search&api_key={api-key}&tags=puppy&per_page=3 HTTP/1.1 User-Agent: curl/7.13.2 (i386-pc-linux-gnu) libcurl/7.13.2 OpenSSL/0.9.7e zlib/1.2.2 libidn/0.5.13 Host: api.flickr.com Pragma: no-cache Accept: */* < HTTP/1.1 200 OK < Date: Tue, 21 Aug 2007 20:42:54 GMT < Server: Apache/2.0.52 < Set-Cookie: cookie_l10n=en-us%3Bus; expires=Friday, 20-Aug-10 20:42:54 GMT; path=/; domain=flickr.com < Set-Cookie: cookie_intl=deleted; expires=Monday, 21-Aug-06 20:42:53 GMT; path=/; domain=flickr.com < Content-Length: 570 < Connection: close < Content-Type: text/xml; charset=utf-8 <?xml version="1.0" encoding="utf-8" ?> <rsp stat="ok"> <photos page="1" pages="97168" perpage="3" total="291503"> <photo id="1196703288" owner="69161261@N00" secret="d4e5a75664" server="1412" farm="2" title="Pomeranian" ispublic="1" isfriend="0" isfamily="0" /> <photo id="1196707012" owner="58944004@N00" secret="9d88253b87" server="1200" farm="2" title="Fraggle" ispublic="1" isfriend="0" isfamily="0" /> <photo id="1195805641" owner="21877391@N00" secret="311d276ec7" server="1177" farm="2" title="Blue" ispublic="1" isfriend="0" isfamily="0" /> </photos> </rsp>
Let’s break down the specifics of this request/response exchange, as shown in Table 6-2 and Table 6-3, respectively.
Parameter | Value |
method | GET |
path | /services/rest/?method=flickr.photos.search&api_key={api-key}&tags=puppy&per_page=3 |
headers | Four headers of the following types: User-Agent, Host (which identifies api.flickr.com), Pragma, and Accept |
response | Empty (typical of GET requests) |
Parameter | Value |
Response | code 200 OK |
Response headers | Seven headers of the following types: Date, Server, Set-Cookie (twice), Content-Length, Connection, Content-Type |
Response body | The XML document representing the photos that match the query |
Keep this example in mind to see how the HTTP request and response are broken down as you continue through this chapter. Notice the structure of having a document (in the body) and a set of headers in both the HTTP request and response structure.
Even though you now understand the basic structure of HTTP, the point is to not have to understand the intricacies of the protocol. You can shield yourself from the details while still taking advantage of the rich functionality of HTTP with the right choice of tools and libraries. Richardson and Ruby provide a helpful shopping list of desirable features in an HTTP client library:
Support for HTTPS and SSL certificate validation.
Support for what they consider to be the five main HTTP methods:
GET
, HEAD
, POST
,
PUT
, and DELETE
. Some give you only
GET
. Others let you use GET
and
POST
.
Lets you customize the request body of POST
and
PUT
requests.
Lets you customize the HTTP request headers.
Gives you access to the response code and HTTP response headers—not just the body of the response.
Lets you communicate through an HTTP proxy.
They list the following features as nice options:
Lets you request and handle data compression. The
relevant HTTP request/response headers are Accept-Encoding
and Encoding
.
Lets you deal with caching. The relevant HTTP
headers are ETag
and If-Modified-Since
and
ETag
and Last-Modified
.
Lets you deal with the most common forms of HTTP
authentication: Basic
,
Digest
, and WSSE
.
Lets you deal with HTTP redirects.
Helps you deal with HTTP cookies.
They also make specific recommendations for what to use in various languages, including the following:
The httplib2
library (http://code.google.com/p/httplib2/
) for
Python
HttpClient
in the Apache Jakarta project (http://jakarta.apache.org/commons/?httpclient/
)
rest-open-uri
, a modification of Ruby’s
open-uri
to support more than the GET
method (http://rubyforge.org/projects/rest-open-uri/
)
Once you have made the HTTP request to the Flickr API, you are left with the second big task of processing the XML document contained in the response body. The topic of how to process XML is a large subject, especially when you consider techniques in multiple languages. What I show you here is one way of parsing XML in PHP 5 through an example involving a reasonably complicated XML document (with namespaces and attributes).
The simpleXML
library is built into PHP 5, which is documented
here:
I found the following article particularly helpful to me in understanding how to
handle namespaces and mixed content in simpleXML
:
http://devzone.zend.com/node/view/id/688
In the following example, I parse an Atom feed (an example from Chapter 4) and
print various parts of the document. I access XML elements as though they are PHP
object properties (using ->element-name
) and the attributes as
though they are members of an array (using ["attribute-name"]
), for
example, $xml->title
and $entry->link["href"]
. First
I list the code and then the output from the code:
<?php // An example to show how to parse an Atom feed (with multiple namespaces) // with SimpleXML # create the XML document in the $feed string $feed=<<<EOT <?xml version="1.0" encoding="utf-8"?> <feed xmlns="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/"> <title>Apress :: The Expert's Voice</title> <subtitle>Welcome to Apress.com. Books for Professionals, by Professionals(TM)...with what the professional needs to know(TM)</subtitle> <link rel="alternate" type="text/html" href="http://www.apress.com/"/> <link rel="self" href="http://examples.mashupguide.net/ch06/Apress.Atom.with.DC.xml"/> <updated>2007-07-25T12:57:02Z</updated> <author> <name>Apress, Inc.</name> <email>support@apress.com</email> </author> <id>http://apress.com/</id> <entry> <title>Excel 2007: Beyond the Manual</title> <link href="http://www.apress.com/book/bookDisplay.html?bID=10232"/> <id>http://www.apress.com/book/bookDisplay.html?bID=10232</id> <updated>2007-07-25T12:57:02Z</updated> <dc:date>2007-03</dc:date> <summary type="html" ><p><i>Excel 2007: Beyond the Manual</i> will introduce those who are already familiar with Excel basics to more advanced features, like consolidation, ?what-?if analysis, PivotTables, sorting and filtering, and some commonly used functions. You'll learn how to maximize your efficiency at producing professional-looking spreadsheets and charts and become competent at analyzing data using a variety of tools. The book includes practical examples to illustrate advanced features.</p></summary> </entry> <entry> <title>Word 2007: Beyond the Manual</title> <link href="http://www.apress.com/book/bookDisplay.html?bID=10249"/> <id>http://www.apress.com/book/bookDisplay.html?bID=10249</id> <updated>2007-07-25T12:57:10Z</updated> <dc:date>2007-03-01</dc:date> <summary type="html" ><p><i>Word 2007: Beyond the Manual</i> focuses on new features of Word 2007 as well as older features that were once less accessible than they are now. This book also makes a point to include examples of practical applications for all the new features. The book assumes familiarity with Word 2003 or earlier versions, so you can focus on becoming a confident 2007 user.</p></summary> </entry> </feed> EOT; # instantiate a simpleXML object based on the $feed XML $xml = simplexml_load_string($feed); # access the title and subtitle elements print "title: {$xml->title}\n"; print "subtitle: {$xml->subtitle}\n"; # loop through the two link elements, printing all the attributes for each link. print "processing links\n"; foreach ($xml->link as $link) { print "attribute:\t"; foreach ($link->attributes() as $a => $b) { print "{$a}=>{$b}\t"; } print "\n"; } print "author: {$xml->author->name}\n"; # let's check out the namespace situation $ns_array = $xml->getDocNamespaces(true); # display the namespaces that are in the document print "namespaces in the document\n"; foreach ($ns_array as $ns_prefix=>$ns_uri) { print "namespace: ${ns_prefix}->${ns_uri}\n"; } print "\n"; # loop over all the entry elements foreach ($xml->entry as $entry) { print "entry has the following elements in the global namespace: \t"; // won't be able to access tags that aren't in the global namespace. foreach ($entry->children() as $child) { print $child->getName(). " "; } print "\n"; print "entry title: {$entry->title}\t link: {$entry->link["href"]}\n"; // show how to use xpath to get date // note dc is registered already to $xml. $date = $entry->xpath("./dc:date"); print "date (via XPath): {$date[0]}\n"; // use children() to get at date $date1 = $entry->children("http://purl.org/dc/elements/1.1/"); print "date (from children()): {$date[0]}\n"; } # add <category term="books" /> to feed -- adding the element will work # but the tag is in the wrong place to make a valid Atom feed. # It is supposed to go before the entry elements $category = $xml->addChild("category"); $category->addAttribute('term','books'); # output the XML to show that category has been added. $newxmlstring = $xml->asXML(); print "new xml (with category tag): \n$newxmlstring\n"; ?>
The output from the code is as follows:
title: Apress :: The Expert's Voice subtitle: Welcome to Apress.com. Books for Professionals, by Professionals(TM)...with what the professional needs to know(TM) processing links attribute: rel=>alternate type=>text/html href=>http://www.apress.com/ attribute: rel=>self href=>http://examples.mashupguide.net/ch06/Apress.Atom.with.DC.xml author: Apress, Inc. namespaces in the document namespace: ->http://www.w3.org/2005/Atom namespace: dc->http://purl.org/dc/elements/1.1/ entry has the following elements in the global namespace: title link id updated summary entry title: Excel 2007: Beyond the Manual link: http://www.apress.com/book/bookDisplay.html?bID=10232 date (via XPath): 2007-03 date (from children()): 2007-03 entry has the following elements in the global namespace: title link id updated summary entry title: Word 2007: Beyond the Manual link: http://www.apress.com/book/bookDisplay.html?bID=10249 date (via XPath): 2007-03-01 date (from children()): 2007-03-01 new xml (with category tag): <?xml version="1.0" encoding="utf-8"?> <feed xmlns="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/"> <title>Apress :: The Expert's Voice</title> <subtitle>Welcome to Apress.com. Books for Professionals, by Professionals(TM)...with what the professional needs to know(TM)</subtitle> <link rel="alternate" type="text/html" href="http://www.apress.com/"/> <link rel="self" href="http://examples.mashupguide.net/ch06/Apress.Atom.with.DC.xml"/> <updated>2007-07-25T12:57:02Z</updated> <author> <name>Apress, Inc.</name> <email>support@apress.com</email> </author> <id>http://apress.com/</id> <entry> <title>Excel 2007: Beyond the Manual</title> <link href="http://www.apress.com/book/bookDisplay.html?bID=10232"/> <id>http://www.apress.com/book/bookDisplay.html?bID=10232</id> <updated>2007-07-25T12:57:02Z</updated> <dc:date>2007-03</dc:date> <summary type="html"><p><i>Excel 2007: Beyond the Manual</i> will introduce those who are already familiar with Excel basics to more advanced features, like consolidation, what-if analysis, PivotTables, sorting and filtering, and some commonly used functions. You'll learn how to maximize your efficiency at producing professional-looking spreadsheets and charts and become competent at analyzing data using a variety of tools. The book includes practical examples to illustrate advanced features.</p></summary> </entry> <entry> <title>Word 2007: Beyond the Manual</title> <link href="http://www.apress.com/book/bookDisplay.html?bID=10249"/> <id>http://www.apress.com/book/bookDisplay.html?bID=10249</id> <updated>2007-07-25T12:57:10Z</updated> <dc:date>2007-03-01</dc:date> <summary type="html"><p><i>Word 2007: Beyond the Manual</i> focuses on new features of Word 2007 as well as older features that were once less accessible than they are now. This book also makes a point to include examples of practical applications for all the new features. The book assumes familiarity with Word 2003 or earlier versions, so you can focus on becoming a confident 2007 user.</p></summary> </entry> <category term="books"/></feed>
There are certainly alternatives to simpleXML
for processing XML in
PHP 5, but it provides a comfortable interface for a PHP programmer to XML
documents.
![]() | Note |
---|---|
When trying to figure out the structures of PHP objects, consider using one of
the following functions: |
Now we have the two pieces of technology to send an HTTP request to Flickr and parse the XML in the response:
The getResource
function I displayed earlier that uses the
libcurl
library of PHP 5
The simpleXML
library to parse the XML response
I’ll now show you a PHP script that uses these two pieces of functionality to prompt a user for a tag and that returns the list of five HTML-formatted photos for that tag.
Here’s a breakdown of the logical steps that take place in the following script:
It displays the total number of pictures
($xml->photos['total']
).
It iterates through the array of photos through an elaboration of the following loop:
foreach ($xml->photos->photo as $photo) { $id = $photo['id']; }
It forms the URL of the thumbnail and the URL of the photo page through the logic contained in the following line:
$thumb_url ="http://farm{$farmid}.static.flickr.com/{$serverid}/{$id}_{$secret}_t.jpg";
The following is one possible version of such a script.[101]Content-Type
HTTP response header of text/html
to keep Internet Explorer happy with XHTML, but the output is XHTML 1.0
Strict.)
<?php header("Content-Type:text/html"); echo '<?xml version="1.0" encoding="utf-8"?>'; ?> <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"> <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en"> <head> <meta http-equiv="Content-Type" content="text/html;charset=utf-8" /> <title>flickrsearch.php</title> </head> <body> <?php if (isset($_GET['tag'])) { do_search($_GET['tag']); } else { ?> <form action="<?php echo $_SERVER['PHP_SELF']?>" method="get"> <p>Search for photos with the following tag: <input type="text" size="20" name="tag"/> <input type="submit" value="Go!"/></p> </form> <?php } ?> <?php # uses libcurl to return the response body of a GET request on $url function getResource($url){ $chandle = curl_init(); curl_setopt($chandle, CURLOPT_URL, $url); curl_setopt($chandle, CURLOPT_RETURNTRANSFER, 1); $result = curl_exec($chandle); curl_close($chandle); return $result; } function do_search($tag) { $tag = urlencode($tag); #insert your own Flickr API KEY here $api_key = "[API-Key]"; $per_page="5"; $url = "http://api.flickr.com/services/rest/?method=flickr.photos.search&api_key={$api_key}&tags={$tag}&per_page={$per_page}"; $feed = getResource($url); $xml = simplexml_load_string($feed); print "<p>Total number of photos for {$tag}: {$xml->photos['total']}</p>"; # http://www.flickr.com/services/api/misc.urls.html # http://farm{farm-id}.static.flickr.com/{server-id}/{id}_{secret}.jpg foreach ($xml->photos->photo as $photo) { $title = $photo['title']; $farmid = $photo['farm']; $serverid = $photo['server']; $id = $photo['id']; $secret = $photo['secret']; $owner = $photo['owner']; $thumb_url = "http://farm{$farmid}.static.flickr.com/{$serverid}/{$id}_{$secret}_t.jpg"; $page_url = "http://www.flickr.com/photos/{$owner}/{$id}"; $image_html= "<a href='{$page_url}'><img alt='{$title}' src='{$thumb_url}'/></a>"; print "<p>$image_html</p>"; } } # do_search ?> </body> </html>
This code allows you to search and display some pictures from Flickr. More
important, it is an example of a class of Flickr methods: those that require neither
signing nor authorization to be called. You will see in the next section how to
determine which of the Flickr API methods fall in that category. In the following
sections, you’ll look at generalizing the techniques you have used in studying
flickr.photos.search
to the other capabilities of the Flickr API.