Building a Server-­Side Proxy

In the previous section, you learned how to use XHR to talk to a local weather.php file that in turn calls the Yahoo! Weather API. You might wonder why XHR doesn’t go directly to the Yahoo! Weather API. It turns out that because of cross-­domain security issues in the browser, you can’t use the XHR object to make a request to a server that is different from the originating server of the JavaScript code. That would apply to the Flickr API as it does to the Yahoo! Weather API. To get around this issue, you will need a little help from a server-­side proxy in the form of a PHP script whose job it is to take a tag and bounding box as input, call the Flickr API to get photos, and return that in XML or JSON to the calling script.

I’ll show you how to write a server-­side proxy to the Flickr API to get geotagged photos, but first I’ll prove to you that you can’t use XHR to go directly to the Yahoo! Weather API.

What Happens with XHR and Direct API Calls?

Let’s see why weather.html can’t just call Yahoo! directly. You can find out what happens by running the following code, which instead of calling the local weather.php goes directly to http://xml.weather.yahoo.com/forecastrss?p=94720:[134]

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
  <head>
    <title>Direct connect</title>
    <script type="text/javascript" src="/lib/yui/build/yahoo/yahoo.js"></script>
    <script type="text/javascript" src="/lib/yui/build/event/event.js"></script>
    <script type="text/javascript"
            src="/lib/yui/build/connection/connection.js"></script>
  </head>
  <body>
  <div id="status"></div>
  <script>
    div = document.getElementById('status');

    var handleSuccess = function(o){

      function parseHeaders(headerStr){

        var headers = headerStr.split("\n");
        for(var i=0; i < headers.length; i++){
          var delimitPos = headers[i].indexOf(':');
          if(delimitPos != -1){
            headers[i] = "<p>" +
            headers[i].substring(0,delimitPos) + ":"+
            headers[i].substring(delimitPos+1) + "</p>";
          }
        return headers;
        }
      }

      if(o.responseText !== undefined){
        div.innerHTML = "Transaction id: " + o.tId;
        div.innerHTML += "HTTP status: " + o.status;
        div.innerHTML += "Status code message: " + o.statusText;
        div.innerHTML += "HTTP headers: " + parseHeaders(o.getAllResponseHeaders);
        div.innerHTML += "Server response: " + o.responseText;
        div.innerHTML += "Argument object: property foo = " + o.argument.foo +
                 "and property bar = " + o.argument.bar;
      }

   }

    var handleFailure = function(o){
      if(o.responseText !== undefined){
        div.innerHTML = "<li>Transaction id: " + o.tId + "</li>";
        div.innerHTML += "<li>HTTP status: " + o.status + "</li>";
        div.innerHTML += "<li>Status code message: " + o.statusText + "</li>";
      }
    }

    var callback =
    {
      success:handleSuccess, failure: handleFailure,
      argument: { foo:"foo", bar:"bar" }
    };

    var sUrl = "http://xml.weather.yahoo.com/forecastrss?p=94720";
    var request = YAHOO.util.Connect.asyncRequest('GET', sUrl, callback);
  </script>
  <div id="status"></div>
</body>
</html>
         

If you try to run this, you will get a JavaScript error. In Firefox, if you look in the Firefox error console, you’ll see the following:

Error: uncaught exception: Permission denied to call method XMLHttpRequest.open

The main lesson here is that XHR lets you access URLs only from the same domain—for security reasons. Let’s prove that by making a new HTML file in the same directory as a local copy of weather.php. This security issue, and the workaround by the server-­side proxy, is explained here:

http://developer.yahoo.com/javascript/howto-proxy.html

In case you are still skeptical, you can change the JavaScript in your HTML to access weather.php from this:

var sUrl = "http://xml.weather.yahooapis.com/forecastrss?p=94720";

to this:

var sUrl = "./weather.php?p=94720";

When you load weather.proxy.html,[135]<div>—but that’s not very nice. Let’s now move toward getting Flickr information.

Building a Server-­Side Script for Geolocated Photos

Based on what you just learned, you now know that you need to get results about Flickr geotagged photos from the Flickr API into the browser using XHR. Hence, you’ll need a server-­side proxy for bridging any client-­side script with Flickr. That’s the aim of this section.

As an exercise, I recommend you write this code yourself before studying the solution presented. Think about how weather.php works and how you can use flickr.photos.search to look for geotagged photos. You can imagine a PHP script that gives access to the full range of input parameters for flickr.photos.search in searches of public photos and returns the search results in a variety of useful formats. You can find a list of the input parameters for flickr.photos.search here:

http://www.flickr.com/services/api/flickr.photos.search.html

A script that I wrote to serve as a server-­side proxy for flickr.photos.search is flickrgeo.php. You can run the script here:

http://examples.mashupguide.net/ch10/flickrgeo.php

The code is listed here:

http://examples.mashupguide.net/ch10/flickrgeo.php.txt

Moreover, you will find a complete listing of the code in Chapter 13, including a description of how it handles KML and KML network links (which is beyond what is covered here). In this section, I’ll describe the overall structure of flickrgeo.php and discuss some example usage.

With several exceptions, all the parameters for flickr.photos.search are also parameters for flickrgeo.php:

  • user_id

  • tags

  • tag_mode

  • text

  • min_upload_date

  • max_upload_date

  • min_taken_date

  • max_taken_date

  • license

  • sort

  • privacy_filter

  • accuracy

  • safe_search

  • content_type

  • machine_tags

  • machine_tag_mode

  • group_id

  • place_id

  • extras

  • per_page

  • page

There are three differences between the parameters for flickr.photos.search and for flickrgeo.php. First, the api_key is hardwired for flickrgeo.php. Second, instead of using the single bbox parameter from flickr.photos.search to specify the bounding box for geotagged photos, flickrgeo.php takes four parameters: lat0, lon0, lat1, and lon1 where lat0, lon0 and lat1, lon1 are, respectively, the southwest and northeast corners of the bounding box. Hence, the value of the bbox parameter for flickr.photos.search is {lon0},{lat0},{lon1},{lat1}.

Second, instead of using the format parameter for Flickr API methods, which takes one of rest (the default value), xml-rpc, soap, json, or php, flickrgeo.php uses an o_format parameter to control the output of the script. These are the values recognized by the script:

  • rest returns the default (rest) output from the Flickr API.

  • json returns the JSON output from the Flickr API.

  • html returns an HTML form and list of photos.

  • kml returns the search results as KML (see Chapter 13 for more details).

  • nl returns the results as a KML network link (see Chapter 13 for more details).

If the o_format is not set or is equal to html, then you want to return the HTML form and a display of the photos. If the o_format is rest, return the default output from the Flickr API (rest). If it’s json, you want to return the JSON output with no callback.

For example, a sample invocation of this script shows the first page of geotagged photos tagged with cat from all over the world:

http://examples.mashupguide.net/ch10/flickrgeo.php?tags=cat&lat0=-90&lon0=-180&lat1=90&lon1=180&page=1&per_page=10&o_format=html

If you change the o_format to json, you get JSON output:

http://examples.mashupguide.net/ch10/flickrgeo.php?tags=cat&lat0=-90&lon0=-180&lat1=90&lon1=180&page=1&per_page=10&o_format=json

This script generates a simple user interface so that you can test the input parameters. That is, you can use the html interface to see what photos are coming back and then switch the output to json, rest, kml, or nl to be used in your server-­side proxy. Much of the code is devoted to generating KML and KML network links, functionality used in Chapter 13. There’s also some other convenience functionality: automatic form generation, error checking, and some useful default values for the bbox parameter. Again, consult Chapter 13 for more details.