2

I'm using the latest Google PHP Client library. How can I send the etag with this library?

I've already tried something like this:

$opt_params = array(
'etag' => 'F9iA7pnxqNgrkOutjQAa9F2k8HY/mOihMVEDLCvdSVCzwDmYHpSV');

$response = $youtube->channels->listChannels('snippet', array('id' => $channel_id), $opt_params);

Thanks!

Linda Lawton - DaImTo
  • 106,405
  • 32
  • 180
  • 449
root66
  • 477
  • 5
  • 17

3 Answers3

2

The Google API PHP Client does not have native support for etags. That said, you can still alter the curl request and apply an etag as a header.

Unfortunately Google_Http_REST throws an exception when it receives a 304 Not Modified header so you'll need to handle those seperately in a catch-block.

This is the resulting PHP-code (note, this is for version 1 of the Google API PHP Client):

class EmptyResponse { }

$DEVELOPER_KEY = '';

$client = new Google_Client();
$client->setDeveloperKey($DEVELOPER_KEY);

$youtube = new Google_Service_YouTube($client);

// Do some logic, catch some resources
// ..

// Set the "If-None-Match" header for the etag
// This assumes the Google API is using CURL
$client->getIo()->setOptions(array(
    CURLOPT_HTTPHEADER => array('If-None-Match: "4FSIjSQU83ZJMYAO0IqRYMvZX98/OPaxOhv3eeKc9owSnDQugGfL8ss"'),
));

try {
  $response = $youtube->playlists->listPlaylists('snippet', array(
      'id' => 'PLOIvOnfHWjIkiz6fd5KYUXJY6ZpHRqPfW',
  ));
}
catch (Google_Service_Exception $e) {
  if (304 == $e->getCode()) {
      // If the error code is 304 (Not Modified) return an empty response
      $response = new EmptyResponse;
  }
  else {
      // The request was unsuccesful
      $response = null;
  }
}

// After the request set the headers back to default
$client->getIo()->setOptions(array(
    CURLOPT_HTTPHEADER => array(),
));

var_dump($response);

// Catch some more resources
// ...
Coded Monkey
  • 604
  • 7
  • 19
2

This post is a little long (if not too long). If you just need the code scroll down and skip my discussions. Please proceed as you wish.

Background

I don't know if somebody has figured out a solution for this one. Coded Monkey's answer is a start. The problem is that the solution he presented assumes that the API makes request using only a developer key/api key and not an oauth access key. If you will try his code using an developer key you will get the following:

Error calling GET https://www.googleapis.com/youtube/v3/liveBroadcasts?part=id%2Csnippet%2Cstatus&mine=true&maxResults=50&key={developer key here}: (401) Login Required

A developer key is not enough to retrieve a user's youtube data and a developer key does not grant access to any account information. That includes youtube data. To access youtube data, one must use an oauth access key (read here: api keys)

Notice that the developer key becomes part of the query string. On the other hand when you use an oauth key, the key becomes a request header:

Authorization: Bearer {oauth key here}

However if you try to use an oauth key you will still get the same error as above. This is quite frustrating. And I guess that is why Coded Monkey's answer did not get an upvote. To understand why, one must understand how the google client send requests.

How the Google PHP client works

My explanation on how the google client work behind the scene is not complete and is only based on what I learned from my experience in figuring out how to solve this problem.

The google client, which I'll call $client, uses a Google_IO_Abstract class instance, I'll call $io, to send requests. One can get the current $io instance and change it using the $client->setIo() and $client->getIo() methods. Google_IO_Abstract is an abstract class. One of its concrete subclass available in the PHP Google API lib is Google_IO_Curl. As its name suggests the class uses curl.

From this point forward I shall assume that $io is a Google_IO_Curl instance. The following (from Code Mokey's solution):

$client->getIo()->setOptions(array(
    CURLOPT_HTTPHEADER => array('If-None-Match: "4FSIjSQU83ZJMYAO0IqRYMvZX98/OPaxOhv3eeKc9owSnDQugGfL8ss"'),
));

will add an If-None-Match HTTP header to curl's header option. The actual adding of header happens only when $io->executeRequest($request) is called. $request is the request $client needs to send.

Setting curl's header is done via curl_setopt($curl, CURLOPT_HTTPHEADER, $headers), where $curl is a curl instance and $headers is an array of headers.

If you will read executeRequestimplementation. You will see that most parts are for setting curl's options. One important part is the gathering of http headers from $requestinto an array called $curlHeaders as shown below (this is from Google_IO_Curl source code). The Authorization header is also added here.

    $requestHeaders = $request->getRequestHeaders();
    if ($requestHeaders && is_array($requestHeaders)) {
      $curlHeaders = array();
      foreach ($requestHeaders as $k => $v) {
        $curlHeaders[] = "$k: $v";
      }
      curl_setopt($curl, CURLOPT_HTTPHEADER, $curlHeaders);
    }

Notice that after gathering the headers, curl_setopt() is called to set curl's header options. Now comes the problem, after these lines of code comes the part where additional options are set. Options that are set using the setOptions() method, which means there will be another call for curl_setopt($curl, CURLOPT_HTTPHEADER, $headers) but this time $headers only contain the If-None-Match header. This second call will replace all previous headers. Voila! The Authorization header is gone. I am not sure if somebody has reported this yet. I am not sure if this is intentional. But this is really frustrating for beginners like me.

Anyway, I came up with a quick solution.

My Solution

I will stop my discussion here and instead give you the code (Google_IO_Curl_Mod.php):

<?php
/**
 * Wrapper class for Google_IO_Curl.
 *
 * This class fixes issues involving request headers added using setOptions()
 * 
 * @author Russel Villacarlos<cvsurussel_AT_gmail.com>
 */

if (!class_exists('Google_Client')) {
  require_once 'Google/autoload.php';
}

class Google_IO_Curl_Mod extends Google_IO_Curl
{

  //Google_IO_Curl instance
  private $io;

  //Array for additional headers added using setOptions()
  private $headers;

  public function __construct(Google_IO_Curl $io)
  { 
      $this->io = $io;
      $this->headers=[];
  }

  /**
   * Execute an HTTP Request
   *
   * @param Google_Http_Request $request the http request to be executed
   * @return array containing response headers, body, and http code
   * @throws Google_IO_Exception on curl or IO error
   */
  public function executeRequest(Google_Http_Request $request)
  {    
      foreach ($this->headers as $value) {
          if(is_string($value) && strpos($value,":")!==false){
            $header = split(":\s*",$value);

            $request->setRequestHeaders(array($header[0]=>$header[1]));              
          }
      }
      $result = $this->io->executeRequest($request); 
      return $result;
  }

  /**
   * Set options that update the transport implementation's behavior.
   * @param $options
   */
  public function setOptions($options)
  {
      if($options){
          if(array_key_exists(CURLOPT_HTTPHEADER, $options)){
              $this->headers= array_merge($this->headers, $options[CURLOPT_HTTPHEADER]);              
              unset($options[CURLOPT_HTTPHEADER]);
          }    
          $this->io->setOptions($options);
      }
  }

  /**
   * Set the maximum request time in seconds.
   * @param $timeout in seconds
   */
  public function setTimeout($timeout)
  {
    $this->io->setTimeout($timeout);
  }

  /**
   * Get the maximum request time in seconds.
   * @return timeout in seconds
   */
  public function getTimeout()
  {
    return $this->io->getTimeout();
  }

  /**
   * Test for the presence of a cURL header processing bug
   *
   * {@inheritDoc}
   *
   * @return boolean
   */
  protected function needsQuirk()
  {
    return $this->io->needsQuirk();
  }
}

To use this just do the following:

$client = new Google_Client();
//codes for setting the oauth credential appears here
$io = new Google_IO_Curl_Mod(new Google_IO_Curl($client));
$client->setIo($io);
$client->getIo()->setOptions(array(
                    CURLOPT_HTTPHEADER => 
                           array('If-None-Match: "NO6QTeg0-3ShswIeqLchQ_mzWJs/wtg8du-olFPZ73k6UW7Jk5JcwpQ"'),
                   ));
//codes for calling youtube api or any google api goes here

Take note that the pair of double quotes is part of the etag.

0

If you check the documentation for channels.list you will notice that etag is not one of the optional parameters.

So you cant send etag to list channels via the api its not a limitation of the client library.

Linda Lawton - DaImTo
  • 106,405
  • 32
  • 180
  • 449
  • Ok, but why does channels.list return an etag? Just for comparison purposes on my local cached data? – root66 Jan 06 '15 at 14:23
  • exactly its a way of your seeing if the data you have in your system has changed or not. – Linda Lawton - DaImTo Jan 06 '15 at 14:23
  • Will the etag change, if any "part" (i.e. brandingSettings->channel->featuredChannelsUrls) changes? I'm asking, because the "statistics" are permanently changing and the etag stays the same. – root66 Jan 06 '15 at 14:27
  • TBH I have never bothered with Etag I have never gotten it to work. I find it easer to just compare everything myself then rely upon it to have been changed. Which in my experience with several of the APIs it doesn't appear to get changed. – Linda Lawton - DaImTo Jan 06 '15 at 14:34
  • Ok, thanks. I'll also compare everything myself, because the etag is sometimes changing, although the data is exactly the same. – root66 Jan 06 '15 at 14:39
  • As mentioned in answers above, the ETag is an HTTP request header, not part of the HTTP body. – Lee Goddard Sep 16 '20 at 07:51
  • @LeeGee you are commenting on a five year old answer? agreeing? – Linda Lawton - DaImTo Sep 16 '20 at 07:59
  • @DaImTo - I was replying to a top hit from Google. You? – Lee Goddard Sep 16 '20 at 08:10
  • @LeeGee I answered this five years ago and get a notification when ever someone comments on one of my answers. I was wondering if you needed help. – Linda Lawton - DaImTo Sep 16 '20 at 08:13
  • 1
    From the above it seems you had not the etag working and think it unnecessary. But with the 10,000-a-day quota limit, the etag has become one of the best ways to preserve quota. It seems to be working for me. Thanks – Lee Goddard Sep 16 '20 at 08:30