PHP CURL有时返回空结果

时间:2018-02-16 15:05:09

标签: php curl

使用PHP的curl函数我有一点不方便的问题。下面是在继续编写代码之前对场景的简要描述。

它在命令行上运行的脚本,负责将来自web api的信息导入本地数据库。

负责web api查询的类执行三个步骤:

  1. 请求Azure oAuth令牌(此请求正在运行 完美地),此令牌会不时被缓存和更新 没有重大问题。
  2. 执行API查询,返回包含某些内容的json 对象
  3. 遍历第2项返回的数组并用于查询另一个数组 API方法,返回另一个json。
  4. 无论如何,我希望你能够清楚地了解这个场景,遗憾的是我无法传递关于这个API的太多细节= /

    现在,代码,我正在使用PHP curl函数来访问API,正如我之前提到的,令牌工作正常,最大的问题在于步骤2和3,有时从步骤2返回是空的,其他从第3步开始,在curl_error,curl_errno或curl_info中都没有生成错误(即使在这一个中,生成的HTTP代码也是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 <ro.andriotti@gmail.com>
     */
    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;
        }
    
    }
    

    由于我使用自己的缓存解决方案,我的课程如下:

    <?php
    
    namespace Core\Cache;
    
    /**
     * Sistema de cache
     *
     * @author Rodrigo Teixeira Andreotti <ro.andriotti@gmail.com>
     */
    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;
        }
    
    }
    

    一般有一个特定的顺序发生问题,在上面描述的第一次尝试项目2不起作用,在第二次尝试是项目3不起作用和第三次尝试工作正常。洛尔

    提前感谢您的同事们的时间和帮助。

1 个答案:

答案 0 :(得分:0)

嗯,我想我可以自己解决这个问题,所以如果其他人遇到同样的问题,我想留下我在这里的解决方案。

显然问题出在callService方法的代码块中:

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,
    )
));

我无法找出确切的原因,但我将它与Postman生成的代码进行了比较,并更改了一些行以适应邮递员正在使用的请求,在更改后看起来像这样:

    // 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);
    $token = $this->loadToken();


    curl_setopt_array($ch, array(
        CURLOPT_RETURNTRANSFER => true,
        CURLOPT_ENCODING => "",
        CURLOPT_MAXREDIRS => 10,
        CURLOPT_TIMEOUT => 30,
        CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
        CURLOPT_CUSTOMREQUEST => "GET",
        CURLOPT_HTTPGET => TRUE,
        CURLOPT_HTTPHEADER => array(
            'Authorization: Bearer ' . $token,
            'Cache-Control: no-cache',
            'X-Api-Key: ' . $this->apiKey,
        )
    ));

对我来说,显然,它解决了这个问题。谢谢!