【輕知識】Oss Sdk request 使用 Guzzle & guzzle-swoole ,協(xié)程化

環(huán)境: hyperf框架

需求

減少阻塞調(diào)用。支持swoole 協(xié)程。

Cos sdk 用的是guzzle。用guzzle-swoole開啟協(xié)程。

Oss sdk 用的是curl。但是swoole 一鍵協(xié)程。跟Cos sdk 沖突。于是把Oss sdk改成使用guzzle。對比改Cos成本比較小。

改動點

主要改動點如下。

1.prep_request方法。改成返回一個 guzzle的request對象。
2.send_request方法,guzzle client 發(fā)送request。
3.process_response方法,處理響應體。
4.框架代碼不用改命名空間。單獨拎出到一個目錄(這個可靈活處理,或者弄個composer包都可以)。composer.json。放到autoload 里面。

autoload": {
        "psr-4": {
            "App\\": "app/",
            "OSS\\": "lib/aliyuncs/oss-sdk-php/src/OSS"
        },
        "files": []
    },

注意點

1.guzzle 對非200操作會拋異常。但是oss是restful設計。其他狀態(tài)碼也表示接口語義。所以拋異常catch下。賦值response。

        try {
               $client->send(xxxxxx);
        } catch (GuzzleHttp\Exception\ClientException $e) {
            // guzzle 非200會拋異常。所以restful接口。下面賦值不做其他處理
            $this->response = $e->getResponse();
        }

2.對接口返回100狀態(tài)碼的處理。

  $temp_headers['Expect'] = '';

3.響應內(nèi)容拼接。參考下面的getRawResponse方法。

4.對項目中用到的接口功能進行測試。確保兼容功能沒有異常。

貼一個自測代碼。可能不常用fopen。測試getObject寫入文件。

    $resource = fopen($downPath, 'rw+');
    $options = [
        OssClient::OSS_FILE_DOWNLOAD => $resource,
    ];
    $getResult = $ossClient->getObject('xes-private-test', $object,$options);
    var_dump(file_get_contents($downPath));

貼代碼,也不多。

<?php
namespace OSS\Http;
use Swoole\Coroutine;
use Swoole\Curl\Handler;
use GuzzleHttp;
use GuzzleHttp\Psr7\Request;
class RequestCore
{
    public $request_url;

    public $request_headers;

    public $response_raw_headers;

    public $response_error_body;

    public $write_file_handle;

    public $request_body;

    public $response;

    public $response_headers;

    public $response_body;

    public $response_code;

    public $response_info;

    public $method;

    public $proxy = null;

    public $username = null;

    public $password = null;

    public $curlopts = null;

    public $debug_mode = false;

    public $request_class = 'OSS\Http\RequestCore';

    public $response_class = 'OSS\Http\ResponseCore';

    public $useragent = 'RequestCore/1.4.3';

    public $read_file = null;

    public $read_stream = null;

    public $read_stream_size = null;

    public $read_stream_read = 0;

    public $write_file = null;

    public $write_stream = null;

    public $seek_position = null;

    public $cacert_location = false;

    public $ssl_verification = true;

    public $registered_streaming_read_callback = null;

    public $registered_streaming_write_callback = null;

    public $timeout = 5184000;

    public $connect_timeout = 10;

    // CONSTANTS

    const HTTP_GET = 'GET';

    const HTTP_POST = 'POST';

    const HTTP_PUT = 'PUT';

    const HTTP_DELETE = 'DELETE';

    const HTTP_HEAD = 'HEAD';


    private $requestAddInfo  = []; // 請求的附加信息。

    // CONSTRUCTOR/DESTRUCTOR

    public function __construct($url = null, $proxy = null, $helpers = null)
    {
        // Set some default values.
        $this->request_url = $url;
        $this->method = self::HTTP_GET;
        $this->request_headers = array();
        $this->request_body = '';

        // Set a new Request class if one was set.
        if (isset($helpers['request']) && !empty($helpers['request'])) {
            $this->request_class = $helpers['request'];
        }

        // Set a new Request class if one was set.
        if (isset($helpers['response']) && !empty($helpers['response'])) {
            $this->response_class = $helpers['response'];
        }

        if ($proxy) {
            $this->set_proxy($proxy);
        }

        return $this;
    }

    public function __destruct()
    {


//        if (isset($this->read_file) && isset($this->read_stream)) {
//            fclose($this->read_stream);
//        }

        if (isset($this->write_file) && isset($this->write_stream)) {
             fclose($this->write_stream);
        }

        return $this;
    }


    // REQUEST METHODS

    public function set_credentials($user, $pass)
    {
        $this->username = $user;
        $this->password = $pass;
        return $this;
    }

    public function add_header($key, $value)
    {
        $this->request_headers[$key] = $value;
        return $this;
    }

    public function remove_header($key)
    {
        if (isset($this->request_headers[$key])) {
            unset($this->request_headers[$key]);
        }
        return $this;
    }

    public function set_method($method)
    {
        $this->method = strtoupper($method);
        return $this;
    }

    public function set_useragent($ua)
    {
        $this->useragent = $ua;
        return $this;
    }

    public function set_body($body)
    {
        $this->request_body = $body;
        return $this;
    }

    public function set_request_url($url)
    {
        $this->request_url = $url;
        return $this;
    }

    public function set_curlopts($curlopts)
    {
        $this->curlopts = $curlopts;
        return $this;
    }

    public function set_read_stream_size($size)
    {
        $this->read_stream_size = $size;

        return $this;
    }

    public function set_read_stream($resource, $size = null)
    {
        if (!isset($size) || $size < 0) {
            $stats = fstat($resource);

            if ($stats && $stats['size'] >= 0) {
                $position = ftell($resource);

                if ($position !== false && $position >= 0) {
                    $size = $stats['size'] - $position;
                }
            }
        }

        $this->read_stream = $resource;

        return $this->set_read_stream_size($size);
    }

    public function set_read_file($location)
    {
        $this->read_file = $location;
        $read_file_handle = fopen($location, 'r');

        return $this->set_read_stream($read_file_handle);
    }

    public function set_write_stream($resource)
    {
        $this->write_stream = $resource;

        return $this;
    }

    public function set_write_file($location)
    {
        $this->write_file = $location;
    }

    public function set_proxy($proxy)
    {
        $proxy = parse_url($proxy);
        $proxy['user'] = isset($proxy['user']) ? $proxy['user'] : null;
        $proxy['pass'] = isset($proxy['pass']) ? $proxy['pass'] : null;
        $proxy['port'] = isset($proxy['port']) ? $proxy['port'] : null;
        $this->proxy = $proxy;
        return $this;
    }

    public function set_seek_position($position)
    {
        $this->seek_position = isset($position) ? (integer)$position : null;

        return $this;
    }

    public function streaming_header_callback($curl_handle, $header_content)
    {
        $code = curl_getinfo($curl_handle, CURLINFO_HTTP_CODE);

        if (isset($this->write_file) && intval($code) / 100 == 2 && !isset($this->write_file_handle))
        {
            $this->write_file_handle = fopen($this->write_file, 'w');
            $this->set_write_stream($this->write_file_handle);
        }

        $this->response_raw_headers .= $header_content;
        return strlen($header_content);
    }


    public function register_streaming_read_callback($callback)
    {
        $this->registered_streaming_read_callback = $callback;

        return $this;
    }

    public function register_streaming_write_callback($callback)
    {
        $this->registered_streaming_write_callback = $callback;

        return $this;
    }


    // PREPARE, SEND, AND PROCESS REQUEST

    public function streaming_read_callback($curl_handle, $file_handle, $length)
    {
        // Once we've sent as much as we're supposed to send...
        if ($this->read_stream_read >= $this->read_stream_size) {
            // Send EOF
            return '';
        }

        // If we're at the beginning of an upload and need to seek...
        if ($this->read_stream_read == 0 && isset($this->seek_position) && $this->seek_position !== ftell($this->read_stream)) {
            if (fseek($this->read_stream, $this->seek_position) !== 0) {
                throw new RequestCore_Exception('The stream does not support seeking and is either not at the requested position or the position is unknown.');
            }
        }

        $read = fread($this->read_stream, min($this->read_stream_size - $this->read_stream_read, $length)); // Remaining upload data or cURL's requested chunk size
        $this->read_stream_read += strlen($read);

        $out = $read === false ? '' : $read;

        // Execute callback function
        if ($this->registered_streaming_read_callback) {
            call_user_func($this->registered_streaming_read_callback, $curl_handle, $file_handle, $out);
        }

        return $out;
    }

    public function streaming_write_callback($curl_handle, $data)
    {
        $code = curl_getinfo($curl_handle, CURLINFO_HTTP_CODE);

        if (intval($code) / 100 != 2)
        {
            $this->response_error_body .= $data;
            return strlen($data);
        }

        $length = strlen($data);
        $written_total = 0;
        $written_last = 0;

        while ($written_total < $length) {
            $written_last = fwrite($this->write_stream, substr($data, $written_total));

            if ($written_last === false) {
                return $written_total;
            }

            $written_total += $written_last;
        }

        // Execute callback function
        if ($this->registered_streaming_write_callback) {
            call_user_func($this->registered_streaming_write_callback, $curl_handle, $written_total);
        }

        return $written_total;
    }

    public function prep_request()
    {

        $this->requestAddInfo = [
            'url'=>$this->request_url,
        ];

        // Process custom headers
        if (isset($this->request_headers) && count($this->request_headers)) {
            $temp_headers = array();

            foreach ($this->request_headers as $k => $v) {
                $temp_headers[$k] =  $v;
            }
        }
        $temp_headers['Expect'] = '';
        $temp_headers['Referer'] =  $this->request_url;
        $temp_headers['User-Agent'] =  $this->useragent;
        $body = null;
        switch ($this->method) {
            case self::HTTP_POST:
            case self::HTTP_PUT:
                if (isset($this->read_stream)) {
                    if (!isset($this->read_stream_size) || $this->read_stream_size < 0) {
                        throw new RequestCore_Exception('The stream size for the streaming upload cannot be determined.');
                    }
                    $this->requestAddInfo['size_upload'] = $this->read_stream_size;
                } else {
                    $this->requestAddInfo['size_upload'] = strlen($this->request_body);
                }
                break;

            case self::HTTP_HEAD:
                $this->request_body = null;
                break;

            default: // Assumed GET
//                if (isset($this->write_stream) || isset($this->write_file)) {
//
//                } else {
//                }
                break;
        }
        $this->requestAddInfo['method'] = $this->method;
        if (isset($this->read_stream)) {// 使用 stream 的方式
            if (!isset($this->read_stream_size) || $this->read_stream_size < 0) {
                throw new RequestCore_Exception('The stream size for the streaming upload cannot be determined.');
            }

            $bodyStream = \GuzzleHttp\Psr7\stream_for($this->read_stream);
            $request = new Request($this->method, $this->request_url, $temp_headers, $bodyStream);
        } else {
            $request = new Request($this->method, $this->request_url, $temp_headers, $this->request_body);
        }
        return $request;
    }

    public function process_response(Request $request = null, GuzzleHttp\Psr7\Response $response = null)
    {
        // Accept a custom one if it's passed.
        if ($request && $response) {
            $this->response = $this->getRawResponse($response);
        }

        $this->response_headers = $response->getHeaders();
        $this->response_code = $response->getStatusCode();
        $this->requestAddInfo['http_code'] = $response->getStatusCode();

        $this->response_info = $this->requestAddInfo; // 為空白
        $header_assoc = [];
        foreach ($this->response_headers as $key=>$header) {
            $header_assoc[strtolower($key)] = $header[0] ?? '';
        }

        // Reset the headers to the appropriate property.
        $this->response_headers = $header_assoc;
        $this->response_headers['info'] = $this->response_info;
        $this->response_headers['info']['method'] = $this->method;

        if ($request && $response) {
            return new ResponseCore($this->response_headers, $this->response_body, $this->response_code);
        }

        if (intval($this->response_code) / 100 != 2 && isset($this->write_file))
        {
            $this->response_headers = $this->response_raw_headers;
            $this->response_body = $this->response_error_body;
        }

        return false;
    }

    public function send_request($parse = false)
    {
        set_time_limit(0);
        $request = $this->prep_request();
        $client = new GuzzleHttp\Client();
        try {
            if ($this->method === self::HTTP_GET && (isset($this->write_stream) || isset($this->write_file))) {
                if (isset($this->write_file)) {
                    $this->response = $client->send($request, [
                        'save_to'=>$this->write_file
                    ]);
                }
                if (isset($this->write_stream)) {
                    $this->response = $client->send($request, [
                        'save_to'=> GuzzleHttp\Psr7\stream_for($this->write_stream)
                    ]);
                }

            } else {
                $this->response = $client->send($request);
            }
        } catch (GuzzleHttp\Exception\ClientException $e) {
            // guzzle 非200會拋異常。所以restful接口。下面賦值不做其他處理
            $this->response = $e->getResponse();
        }

        $parsed_response = $this->process_response($request, $this->response);

        if ($parse) {
            return $parsed_response;
        }
        return $this->response;
    }

    // RESPONSE METHODS

    public function get_response_header($header = null)
    {
        if ($header) {
            return $this->response_headers[strtolower($header)];
        }
        return $this->response_headers;
    }

    public function get_response_body()
    {
        return $this->response_body;
    }

    public function get_response_code()
    {
        return $this->response_code;
    }


    // 拼接 響應報文
    //string(335) "HTTP/1.1 100 Continue
//
//HTTP/1.1 200 OK
//Server: AliyunOSS
//Date: Wed, 26 Aug 2020 21:25:37 GMT
//Content-Length: 0
//Connection: keep-alive
//x-oss-request-id: 5F46D3509EB80732383B67E1
//ETag: "8FA01A1F7C0811B252FF4DF1973F32CE"
//Content-MD5: j6AaH3wIEbJS/03xlz8yzg==
//x-oss-hash-crc64ecma: 16545178446529799215
//x-oss-server-time: 38
    public function getRawResponse(GuzzleHttp\Psr7\Response $response) {

        $str = sprintf("HTTP/%s %s %s",$response->getProtocolVersion(),$response->getStatusCode(), $response->getReasonPhrase());
        $str .= "\r\n";
        if (!empty($response->getHeaders())) {
            foreach ($response->getHeaders() as $key=>$header) {
                $str.= sprintf("%s: %s\r\n", $key, $header[0] ?? '');
            }
            $str .= "\r\n";
        }else {
            $str .= "\r\n";
        }
        $body = $response->getBody()->getContents();
        $this->response_body = $body;
        $str.= $body;
        return $str;
    }
}

參考資料

最后編輯于
?著作權歸作者所有,轉載或內(nèi)容合作請聯(lián)系作者
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,333評論 6 531
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 98,491評論 3 416
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 176,263評論 0 374
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經(jīng)常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,946評論 1 309
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,708評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 55,186評論 1 324
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,255評論 3 441
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,409評論 0 288
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 48,939評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 40,774評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,976評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,518評論 5 359
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 44,209評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,641評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,872評論 1 286
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,650評論 3 391
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,958評論 2 373