Combining HTTP and JavaScript APIs with php

Christian Harms's picture

The implementation of the ip to geo location script started as a quick hack in php. Sometimes I use php scripts because everyone can include it in his own website account (but I am not an active php programmer). It's much easier to write a simple php script instead of deploying a more complex web application. The quick result was this implementation as an example. After getting the php script to work I rewrote it in python, transformed it into a google appengine application and deployed it. Both worked fine and now there are two examples for the API integration available.

technorati code jxzmcfgp74

php and http API / RESTfull web services

To fetch http resources use the the curl module. It's robust and there aren't too many possibilities to break it. If you want to use more than one service provider's API you can access them in a parallel fashion. So the total execution time for the request will be equal to the slowest provider's roundtrip time instead of the sum of all roundtrip times together.

  1.   function readUrls($urls) {
  2.     $req = array();
  3.     $mh = curl_multi_init();
  4.     for($i = 0; $i<count($urls); $i++) {
  5.       $req[$i] = curl_init();
  6.       curl_setopt($req[$i], CURLOPT_URL, $urls[$i]);
  7.       curl_setopt($req[$i], CURLOPT_RETURNTRANSFER, 1);
  8.       curl_multi_add_handle($mh, $req[$i]);
  9.     }
  10.     $running=null;
  11.     do {
  12.         curl_multi_exec($mh,$running);
  13.     } while ($running > 0);
  14.  
  15.     //read the content
  16.     $ret = array();
  17.     for($i = 0; $i<count($urls); $i++) {
  18.       $ret[$i] = curl_multi_getcontent($req[$i]);
  19.       curl_multi_remove_handle($mh, $req[$i]);
  20.     }
  21.     curl_multi_close($mh);
  22.     return $ret;
  23.   }

This simple function gets an array of urls and returns an array of the content. Ok - only the optimistic way without error detection!

Basic XML parsing with PHP

First you have to check if the Content-encoding is set correctly. If not you'll have to convert the http-content to the encoding declared within the xml declaration, which is UTF-8 in the example below.

  1. <?xml version="1.0" encoding="UTF-8"?>
  2. <Response>
  3.         <Ip>74.125.45.100</Ip>
  4.         <Status>OK</Status>
  5.         <CountryCode>US</CountryCode>
  6.         <CountryName>United States</CountryName>
  7.         <RegionCode>06</RegionCode>
  8.         <RegionName>California</RegionName>
  9.         <City>Mountain View</City>
  10.         <ZipPostalCode>94043</ZipPostalCode>
  11.         <Latitude>37.4192</Latitude>
  12.         <Longitude>-122.057</Longitude>
  13. </Response>

And now the simple php code for xml parsing:

  1. if (substr($content,1,5)=='?xml') {
  2.     $xml = simplexml_load_string(utf8_encode($content));
  3.     if ($xml && $xml->City) {
  4.     //Add the geo data to the result set
  5.     $result =  array("name" => "ipinfodb",
  6.                       "country" => strval($xml->CountryName),
  7.                       "city" => strval($xml->City),
  8.                       "long" => floatval($xml->Longitude),
  9.                       "lat" => floatval($xml->Latitude)  );
  10. }

php and parsing non-structured data

For non-structured data regular expressions are the best solution if you can beat the beast. Otherwise try it with several split-actions.

  1. Country: UNITED STATES (US)
  2. City: Sugar Grove, IL
  3. Latitude: 41.7696
  4. Longitude: -88.4588

And now the php code for text parsing:

  1. if (preg_match("/Country:\s+(.*?)\(\w+\)\nCity:\s+(.*?)\nLatitude: (-*\d+\.\d+)\nLongitude: (-*\d+\.\d+)/", $content, $ma)==1) {
  2.     $result = array("name" => "hostip",
  3.                               "country" => $ma[1],
  4.                               "city" => $ma[2],
  5.                               "long" => floatval($ma[4]),
  6.                               "lat" => floatval($ma[3])  
  7. };

php and parsing JSON data

Json is defined as the JavaScript object notation and is an universal key-value transfer format, especially in web applications. So many API (like the IPInfoDB from the xml example) offer these formats and php has the build-in function to parse it. The encoding is defined as utf-8!

  1. if (substr($content,0,1)=='{') {
  2.     $json =  json_decode($content);
  3.     if ($json && $json->{'City'}) {
  4.         $result = array("name" => "ipinfodb",
  5.                                   "country" => $json->{'CountryName'},
  6.                                   "city" => $json->{'City'},    
  7.                                   "long" => floatval($json->{'Longitude'}),
  8.                                   "lat" => floatval($json->{'Latitude'})));
  9.     }
  10. }

Getting the javascripts data together

The described php code should produce a simple javascript (after running the above script) containing the fetched data and including the other javascripts via document.write function like this output:

  1. var com = com||{};
  2. com.unitedCoders = com.unitedCoders||{};
  3. com.unitedCoders.geo = com.unitedCoders.geo||{};
  4. <?php
  5.   //build your result
  6.   echo "com.unitedCoders.geo.ll = [".json_encode($result)."];\n";
  7. ?>
  8. document.write('<script type="text/javascript" src="http://j.maxmind.com/app/geoip.js"></script>');
  9. document.write('<script type="text/javascript" src="http://api.wipmania.com/wip.js"></script>');
  10. document.write('<script type="text/javascript" src="geo.js"></script>');

After the other javascript sources are loaded (sequential) the geo_funcs.js javascript can map from the internal structures to the global com.unitedCoders.geo.ll list and add some helper functions. For example the "get the middle position" of all lat/long values.

add the external javascript data to the result

The maxmind javascript api has as result a set of global functions. Here the maxmind part of geo_funcs.js:

  1. if (window['geoip_country_name']) {
  2.     com.unitedCoders.geo.ll.push({
  3.             "name": "maxmind",
  4.             "country": geoip_country_name(),
  5.             "city": geoip_city(),
  6.             "long": parseFloat(geoip_longitude()),
  7.             "lat": parseFloat(geoip_latitude()) });
  8. };
  9.  
  10. //calculate the middle point of all position where city is set
  11. (function() {
  12.     var lat = 0.0;
  13.     var long = 0.0;
  14.     var count = 0;
  15.     for (var i=0; i<com.unitedCoders.geo.ll.length;i++) {
  16.         var service=com.unitedCoders.geo.ll[i]
  17.         if (service.long && service.lat && service.city) {
  18.             lat += parseFloat(service.lat);
  19.             long += parseFloat(service.long);
  20.             count ++;
  21.        }
  22.     }
  23.     com.unitedCoders.geo.lat = lat/count;
  24.     com.unitedCoders.geo.long = long/count;
  25. })();

The function wrapper chooses all positions with a set city, hoping these lat/long value pairs are the most accurate positions and calculate the middle point. After testing I noticed the google positions are accurat (but no city information is available).

In the next article I will describe the same functionality but implemented in python for the google appengine.

AttachmentSize
geo_php.txt3.06 KB

Comments

Anonymous's picture

very cool & good tip, thank you very much for sharing.

Can I share this snippet on my JavaScript library?

Awaiting your response. Thank

Combining HTTP and JavaScript APIs with python on google app's picture

[...] the php stand alone implementation [...]

3 caching steps to boost your webservice by x10 | united-cod's picture

[...] the php stand alone implementation [...]