天天看點

hyperf| hyperf/guzzle 下載下傳檔案問題詳解

使用

hyperf/guzzle

遇到下載下傳檔案的問題, 過程雖波折, 卻頗有意思, 分享給大家.

業務上有一個下載下傳檔案的任務, 太簡單啦

業務上要下載下傳一個 oss 檔案, 假設位址為

oss_url

. 先撸一遍 guzzle 的文檔

下載下傳需要在 request option 中設定

sink

參數: http://docs.guzzlephp.org/en/stable/request-options.html#sink
$oss_url = 'oss_url';
$file_path = 'xxx';
$client = new \GuzzleHttp\Client([
    'verify' => false,
    'decode_content' => false,
]);
$client->get($oss_url, [
    'sink' => $file_path,
]);           

換成 hyperf/guzzle 來寫:

// 使用 clientFactory 擷取到協程版 client 即可
$container = ApplicationContext::getContainer();
$clientFactory = $container->get(ClientFactory::class);
$client = $clientFactory->create([
    'verify' => false,
    'decode_content' => false,
]);           

開心的執行, 劇情按照預期的方向發展~

并沒有!!! 下載下傳沒反應!!!

下載下傳位址的問題?

使用 curl/wget 等指令行工具驗證

wget oss_url
curl -O oss_url           

下載下傳位址沒問題

檔案有 300M, 會不會是逾時了?

$client = $clientFactory->create([
    'timeout' => 600, // 逾時設定為 10 分鐘
    'verify' => false,
    'decode_content' => false,
]);           

噗, 還是一樣沒效果?

翻 hyperf 文檔

hyperf/guzzle: https://doc.hyperf.io/#/zh/guzzle

哦, 原來使用 swoole 配置要這樣:

<?php
use GuzzleHttp\Client;
use Hyperf\Guzzle\CoroutineHandler;
use GuzzleHttp\HandlerStack;

$client = new Client([
    'base_uri' => 'http://127.0.0.1:8080',
    'handler' => HandlerStack::create(new CoroutineHandler()),
    'timeout' => 5,
    'swoole' => [ // 看這裡看這裡
        'timeout' => 10,
        'socket_buffer_size' => 1024 * 1024 * 2,
    ],
]);

$response = $client->get('/');           

這次應該行了吧 !?

噗, 還是一樣沒效果.

直接用 guzzle 行不行

$oss_url = 'oss_url';
$file_path = 'xxx';
$client = new \GuzzleHttp\Client([
    'verify' => false,
    'decode_content' => false,
]);
$client->get($oss_url, [
    'sink' => $file_path,
]);           

終于看了需要下載下傳的問題. 是時候刷鍋一波給 hyperf,

什麼渣渣架構, 檔案下載下傳居然都不支援

.

我們看看鍋到底在哪

老規矩, 看源碼, 既然是使用

\Hyperf\Guzzle\ClientFactory->create()

建立的, 看看源碼漲啥樣:

public function create(array $options = []): Client
{
    $stack = null;
    if (Coroutine::getCid() > 0) {
        $stack = HandlerStack::create(new CoroutineHandler());
    }

    $config = array_replace(['handler' => $stack], $options);

    if (method_exists($this->container, 'make')) {
        // Create by DI for AOP.
        return $this->container->make(Client::class, ['config' => $config]);
    }
    return new Client($config);
}           

協程環境下使用的

new CoroutineHandler

, 來看看廬山真面目(檔案有點長, 不要輕言放棄):

// \Hyperf\Guzzle\CoroutineHandler
// 關鍵在這句, 這裡其實是 \Swoole\Coroutine\Http\Client
$client = new Client($host, $port, $ssl);           

原來這裡用的

\Swoole\Coroutine\Http\Client

. 這時候我靈機一動, 會不會是?

來看 swoole 的文檔

下載下傳方法在這裡: https://wiki.swoole.com/wiki/page/938.html
$host = 'www.swoole.com';
$cli = new \Swoole\Coroutine\Http\Client($host, 443, true);
$cli->set(['timeout' => -1]);
$cli->setHeaders([
    'Host' => $host,
    "User-Agent" => 'Chrome/49.0.2587.3',
    'Accept' => '*',
    'Accept-Encoding' => 'gzip'
]);
$cli->download('/static/files/swoole-logo.svg', __DIR__ . '/logo.svg');           

這 api 和 guzzle 完全不一樣呀 !!! 坑爹呢這是, 用

swoole + hyperf

, 連個檔案下載下傳都搞不定 ?!

swoole + hyperf 表示我這麼強大, 你居然不會用 !

swoole v4.4.6

的版本更新中, 就增加了對 curl hook 的支援, 添加

SWOOLE_HOOK_FLAGS

即可, hyperf v1.1.0 版本中已經提供了支援:

// bin/hyperf.php:11
! defined('SWOOLE_HOOK_FLAGS') && define('SWOOLE_HOOK_FLAGS', SWOOLE_HOOK_ALL);           

開啟後, swoole 就會無縫将 curl hook 為協程版.

無形加速, 最為緻命 !

可是 hyperf/guzzle 預設還是使用的

\Swoole\Coroutine\Http\Client

怎麼辦? 這就太簡單了:

  • 如果你喜歡靜态類風格:
<?php

namespace App\Util;

use GuzzleHttp\Client;

class Guzzle
{
    /**
     * @param array $config
     * @return Client
     */
    public static function create(array $config)
    {
        return make(Client::class, ['config' => $config]);
    }
}           
  • 如果你喜歡 ClientFactory 風格
<?php

namespace App\Util;

use GuzzleHttp\Client;

class ClientFactory
{
    /**
     * @param array $config
     * @return Client
     */
    public function create(array $config)
    {
        return make(Client::class, ['config' => $config]);
    }
}           

寫在最後

沒事不要亂甩鍋, 多問問問題, 多找找原因, 收獲就在這不經意間

繼續閱讀