Back to News for Developers

How-To: Optimize the Graph API data fetch using ETags

January 9, 2012ByVikas Gupta

Imagine if you could only get data from an API when things have changed or there is new data? Well, you do not have to imagine anymore, the Facebook Graph API now supports HTTP ETags. ETags support on the Facebook Platform can help you reduce bandwidth consumption and client-side overhead by suppressing output when making Graph API calls. In addition, clients (especially mobile devices on slow connections) can increase performance and reduce data usage when calling the Graph API with ETags.

This is how it works:

  1. When you make a Graph API call, the response header includes ETag with a value that is the hash of the data returned in the API call. Pull this ETag value and use in Step-2.
  2. Next time you make the same API call, include the If-None-Match request header with ETag value pulled from step-1 for this API call.
  3. If the data hasn’t changed, the response status code would be 304 – Not Modified and no data is returned.
  4. If the data has changed since last pull, the data is returned as usual with a new ETag. Pull the new ETag value and use for subsequent calls.

Note: While ETags help reduce the data traffic, the If-None-Match GET will still count against the throttling limits for your app and must not be performed at a frequent rate. We recommend only do it for data that doesn’t change frequently like user’s friends, likes, photo albums, photos etc. Do not use it for news feed, messages etc.

The ETag is calculated using the entire response from the API call including its formatting. Developers should be aware that the formatting of API response output may be impacted by the user agent string. Therefore, calls originating from the same client should keep the user agent consistent between calls.

Example:

Install Firebug and Modify Headers add-ons on Firefox.

Fetching user’s friends: https://graph.facebook.com/me/friends?access_token=<token> returns friends data in JSON output and ETag in the response header. You can quickly test by visiting the Graph API Doc and clicking on the Friends sample link that auto generates access token for the session user and makes this API call.

Next create the If-None-Match request header in the Modify Header add-on tool with the ETag value in the quotes. Make sure the header is enabled (green lit).

Now call the friend Graph API again - https://graph.facebook.com/me/friends?access_token=<token>. Note that the API returns no data and the status code is ‘304- Not Modified’:

PHP Code Example:

The php code demonstrates ETags while fetching user’s friends. The code pulls the ETag from the response header and then call the same API again while passing the ETag value in the If-None-Match request header. Notice that the response for the second call is 304 – Not Modified.

  <?php

    $app_id = 'YOUR_APP_ID';
    $app_secret = 'YOUR_APP_SECRET';
    $my_url = 'YOUR_REDIRECT_URL';

    $code = $_REQUEST["code"];

    echo '<html><body>';

    // Auth the app using the server side OAuth 2.0
    if(!$code) {
        // get permission from the user to publish to their page.
        $dialog_url = "http://www.facebook.com/dialog/oauth?client_id="
         . $app_id . "&redirect_uri=" . urlencode($my_url);
        echo('<script>top.location.href="' . $dialog_url . '";</script>');
    } else {
        // get access token for the user
        $token_url = "https://graph.facebook.com/oauth/access_token?"
         . "client_id=" . $app_id 
         . "&redirect_uri=" . urlencode($my_url)
         . "&client_secret=" . $app_secret
         . "&code=" . $code;
        $access_token = file_get_contents($token_url);

        // get user's friends now we have the access token
        $friend_url = 'https://graph.facebook.com/me/friends?' . 
                    $access_token;

        // initialize curl if not yet initialized
        if (!$ch) {
          $ch = curl_init();
        }

        /*
         * Disable the 'Expect: 100-continue' behaviour. This causes CURL
         * to wait for 2 seconds if the server does not support this 
         * header.
         */
        curl_setopt($ch, CURLOPT_HTTPHEADER, 'Expect:');
        // set the URL to Graph API URL
        curl_setopt($ch, CURLOPT_URL, $friend_url);
        // we want header in the output
        curl_setopt($ch, CURLOPT_HEADER, true);
        // but do not display the curl output
        curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
        // execute the curl and get the info
        $response = curl_exec($ch);
        $info = curl_getinfo($ch);
        
        // extract the http code, header and body
        $http_code = $info['http_code'];
        $headers = substr($response, 0, $info['header_size']);
        $body = substr($response, -$info['download_content_length']);
        
        // convert header string into an associate array and extract the ETag
        $headers_arr = http_parse_headers($headers);
        $etag = $headers_arr['Etag'];

        echo '<a href="' . $friend_url . '"/>' . $friend_url . 
                '</a/> <br /><br />';
        echo 'HTTP Code: ' . $http_code . '<br />';
        echo 'ETag: ' . $etag . '<br /><br />';

        /*
         * Execute the same API curl call while passing the
         * ETag value in If-None-Match request header
         */
        echo 'Executing the same API call with request header <br/> 
                <b/>If-None-Match: '. $etag . '</b/> 
                returns : <br/><br/>';
        curl_setopt($ch, CURLOPT_HTTPHEADER, array('If-None-Match: ' . 
                    $etag));
        $result = curl_exec($ch);
        $http_code = curl_getinfo( $ch, CURLINFO_HTTP_CODE );

        echo 'HTTP Code: ' . $http_code . '<br />';

        curl_close($ch);

    }
    echo '</body> </html>';

    /*
     * Function to convert header string into associative array
     * Source - http://php.net/manual/en/function.http-parse-headers.php
     */
     
     function http_parse_headers( $header )
     {
        $retVal = array();
        $fields = explode("\r\n", preg_replace('/\x0D\x0A[\x09\x20]+/', 
                    ' ', $header));
        foreach( $fields as $field ) {
            if( preg_match('/([^:]+): (.+)/m', $field, $match) ) {
                $match[1] = preg_replace('/(?=>^|[\x09\x20\x2D])./e', 'strtoupper("\0")', strtolower(trim($match[1])));
                if( isset($retVal[$match[1]]) ) {
                    $retVal[$match[1]] = array($retVal[$match[1]], 
                                $match[2]);
                } else {
                    $retVal[$match[1]] = trim($match[2]);
                }
            }
        }
        return $retVal;
     }
  ?>

Sample Code Output:

https://graph.facebook.com/me/friends?access_token=<access_token>

HTTP Code: 200
ETag: "84a290085a1bb6df44534ac017bfed800c407a15"

Executing the same API call with request header 
If-None-Match: "84a290085a1bb6df44534ac017bfed800c407a15" returns : HTTP Code: 304

Tags: