Good morning everyone!
I'm having a somewhat inconvenient problem using PHP's curl functions. Below is a brief description of the scenario before moving on to the code.
The script is executed via the command line and is responsible for importing information from an api web to the local database.
The class responsible for querying the web api performs three steps:
Requesting an Azure oAuth token, this request is functioning perfectly, this token is cached and renewed from time to time without major problems
A query is performed on the API that returns a json containing some objects
The array returned by item 2 is run and used to query another API method, which returns another json.
Finally, I hope you've managed to be clear on the scenario, unfortunately I can not go into too much detail about the API in question = /
Now the code, I am using the PHP curl functions for API access, as I mentioned before, the token works perfectly, the biggest problem is in steps 2 and 3, sometimes the return from step 2 comes empty, other than that in step 3, no error is ever generated in either curl_error, curl_errno or curl_info. (In this case, the HTTP code comes as 200).
<?php
namespace RestClient\Service;
/**
* Cliente abstrato de acesso ao webservice REST
* Este cliente é responsável por gerar o token para uso
* com os demais clientes.
*
* @author Rodrigo Teixeira Andreotti <[email protected]>
*/
abstract class AbstractClient
{
private $tokenUrl;
private $clientId;
private $secret;
private $serviceUrl;
private $resourceId;
private $tenantId;
private $apiKey;
private $cache;
/**
* Recebe em seu construtor uma instância da
* aplicação rodando e uma do handler de cache
*
* @param \Core\Application $app
* @param \Core\Cache\Cache $cache
*/
public function __construct(\Core\Application $app, \Core\Cache\Cache $cache)
{
$this->tokenUrl = $app->getConfig('api_token_url');
$this->clientId = $app->getConfig('api_clientId');
$this->secret = $app->getConfig('api_secret');
$this->serviceUrl = $app->getConfig('api_service_url');
$this->tenantId = $app->getConfig('api_tenantId');
$this->apiKey = $app->getConfig('api_key');
$this->resourceId = $app->getConfig('api_resourceId');
$this->cache = $cache;
$this->loadToken();
}
/**
* Verifica se existe um token válido em cache,
* caso haja o carrega, se não gera um novo token no webservice,
* o salva em cache e o retorna para uso pelo serviço.
*
* @uses AbstractClient::requestToken()
*
* @return string Token gerado / armazenado
*/
private function loadToken()
{
$cache = $this->cache;
$token = $cache->readCache('api_token');
if (!$token) {
$tokenData = $this->requestToken();
$cache->saveCache('api_token', $tokenData->access_token, 45); // <-- Converte o tempo do token para minutos
$token = $tokenData->access_token;
}
return $token;
}
/**
* Requisita ao webservice o token de acesso
*
* @return \stdClass Contém o json decodificado com as informações do token
*/
private function requestToken()
{
$ch = curl_init($this->tokenUrl . $this->tenantId . '/oauth2/token');
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, 0);
curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, 0);
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_POSTFIELDS, array(
'grant_type' => 'client_credentials',
'resource' => $this->resourceId,
'client_id' => $this->clientId,
'client_secret' => $this->secret
));
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
$data = json_decode(curl_exec($ch));
curl_close($ch);
return $data;
}
/**
* Realiza a consulta ao webserice
*
* @uses AbstractClient::buildUrlParams()
*
* @param string $method Método REST que será consultado
* @param array $params Paramentros adicionais que serão chamados
*
* @return \stdClass Retorno do json decodificado
*/
protected function callService($method, $params = null)
{
$ch = curl_init($this->serviceUrl . $method . ($params ? $this->buildUrlParams($params) : ''));
curl_setopt($ch, CURLOPT_FOLLOWLOCATION, 1);
curl_setopt($ch, CURLOPT_FRESH_CONNECT, TRUE);
// Linhas abaixo necessárias para usar o cUrl com windows sem certificado
//curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, 0);
//curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, 0);
curl_setopt_array($ch, array(
CURLOPT_HTTPGET => TRUE,
CURLOPT_RETURNTRANSFER => 1,
CURLOPT_HTTPHEADER => array(
'Authorization: Bearer ' . $this->loadToken(),
'X-Api-Key: ' . $this->apiKey,
)
));
$response = curl_exec($ch);
$httpCode = intval(curl_getinfo($ch, CURLINFO_HTTP_CODE));
if ($httpCode != 200) {
return $httpCode;
}
$data = json_decode($response);
curl_close($ch);
return $data;
}
/**
* Constrói os parâmetros em formato de URL
*
* @param array $params
* @return string Parametros de URL formatados
*/
private function buildUrlParams($params)
{
$urlParams = '';
if (count($params)) {
$urlParams .= '?';
$i = 0;
foreach ($params as $key => $param) {
$urlParams .= (($i == 0) ? '' : '&');
$urlParams .= $key . '=' . $param;
$i++;
}
}
return $urlParams;
}
}
As I'm using my own cache solution, follow my class too:
<?php
namespace Core\Cache;
/**
* Sistema de cache
*
* @author Rodrigo Teixeira Andreotti <[email protected]>
*/
class Cache
{
/**
*
* @var integer Tempo para o cache em minutos
*/
private $time = 60;
/**
*
* @var string Local onde o cache será salvo
*/
private $local;
/**
* Inicializa a classe e define o local onde o cache será armazenado
* @uses Cache::setLocal()
* @param string $local
*/
public function __construct($local)
{
$this->setLocal($local);
}
/**
* Define o local onde o cache será salvo
* @param string $local
* @return $this
*/
private function setLocal($local)
{
if (!file_exists($local)){
trigger_error('Diretório de cache não encontrado', E_USER_ERROR);
} elseif(!is_dir($local)) {
trigger_error('Caminho para diretório de cache não aponta para um diretório', E_USER_ERROR);
} elseif(!is_writable($local)){
trigger_error('Diretório de cache inacessível', E_USER_ERROR);
} else {
$this->local = $local;
}
return $this;
}
/**
* Gera o local onde o arquivo será salvo
* @param string $key
* @return string
*/
private function generateFileLocation($key)
{
return $this->local . DIRECTORY_SEPARATOR . sha1($key) . '.tmp';
}
/**
*
* Cria o arquivo de cache
*
* @uses Cache::generateFileLocation()
*
* @param string $key
* @param mixed $content
*
* @return boolean
*/
private function generateCacheFile($key, $content)
{
$file = $this->generateFileLocation($key);
return file_put_contents($file, $content) || trigger_error('Não foi possível criar o arquivo de cache', E_USER_ERROR);
}
/**
*
* Salva um valor em cache
*
* @uses Cache::generateCacheFiles
*
* @param string $key
* @param mixed $content
* @param integer $time Tempo em minutos
*
* @return boolean
*/
public function saveCache($key, $content, $time = null)
{
$time = strtotime(($time ? $time : $this->time) . ' minutes');
$content = serialize(array(
'expira' => $time,
'content' => $content
));
return $this->generateCacheFile($key, $content);
}
/**
* Recupera um valor salvo no cache
*
* @uses Cache::generateFileLocation()
*
* @param string $key
*
* @return mixed Valor do cache salvo ou null
*/
public function readCache($key)
{
$file = $this->generateFileLocation($key);
if (is_file($file) && is_readable($file)) {
$cache = unserialize(file_get_contents($file));
if ($cache['expira'] > time()) {
return $cache['content'];
} else {
unlink($file);
}
}
return null;
}
}
Below I also leave a print of curl_info var_dump on one of the times the problem occurs.
Generally has a specific order to occur the problem, on the first attempt item 2 described above does not work, on the second attempt is item 3 that does not work and on the third attempt works fine. kkk
Edit : I forgot to mention before, the problem usually occurs if I run the script after some time interval (thing of about 20 minutes or so)
From now on I thank you for the time and help of my colleagues.