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!
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 executeRequest
implementation. You will see that most parts are for setting curl's options. One important part is the gathering of http headers from $request
into 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.
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
// ...
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.