wechatpay-apiv3 / wechatpay-guzzle-middleware

微信支付 APIv3 Guzzle HTTP Client中间件(middleware)

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

调试了一个晚上了,跟demo一模一样;

miwei230 opened this issue · comments

始终报这个错:

  1. HyperfTest\Cases\MediaTest::testUpload
    GuzzleHttp\Exception\RequestException: cURL error 0: The cURL request was retried 3 times and did not succeed. The most likely reason for the failure is that cURL was
    unable to rewind the body of the request and subsequent retries resulted in the same error. Turn on the debug option to see what went wrong. See https://bugs.php.net
    /bug.php?id=47204 for more information. (see https://curl.haxx.se/libcurl/c/libcurl-errors.html)

/opt/www/vendor/guzzlehttp/guzzle/src/Handler/CurlFactory.php:201
/opt/www/vendor/guzzlehttp/guzzle/src/Handler/CurlFactory.php:537
/opt/www/vendor/guzzlehttp/guzzle/src/Handler/CurlFactory.php:152
/opt/www/vendor/guzzlehttp/guzzle/src/Handler/CurlFactory.php:105
/opt/www/vendor/guzzlehttp/guzzle/src/Handler/CurlHandler.php:43
/opt/www/vendor/guzzlehttp/guzzle/src/Handler/CurlFactory.php:542
/opt/www/vendor/guzzlehttp/guzzle/src/Handler/CurlFactory.php:152
/opt/www/vendor/guzzlehttp/guzzle/src/Handler/CurlFactory.php:105
/opt/www/vendor/guzzlehttp/guzzle/src/Handler/CurlHandler.php:43
/opt/www/vendor/guzzlehttp/guzzle/src/Handler/CurlFactory.php:542
/opt/www/vendor/guzzlehttp/guzzle/src/Handler/CurlFactory.php:152
/opt/www/vendor/guzzlehttp/guzzle/src/Handler/CurlFactory.php:105
/opt/www/vendor/guzzlehttp/guzzle/src/Handler/CurlHandler.php:43
/opt/www/vendor/guzzlehttp/guzzle/src/Handler/Proxy.php:28
/opt/www/vendor/guzzlehttp/guzzle/src/Handler/Proxy.php:51

报文:
POST /v3/merchant/media/upload HTTP/1.1
Transfer-Encoding: chunked
Expect: 100-Continue
Host: api.mch.weixin.qq.com
Accept: application/json
content-type: multipart/form-data; boundary=6670df4d7516317f8c24338c2473dbaa569479be
Authorization: WECHATPAY2-SHA256-RSA2048 mchid="1604972733",nonce_str="SMC0WV5GqQsRid60TUeVYhzavr9ynT7H",timestamp="1609336633",serial_no="4A8E836B052723A58ACD2EE725102CF70100FDA4",signature="DYHnKqXnQVRJCTnxdXiEvLSzquvKsHfM6j9HkqLG2XMxG10wsXyi06kXSQ9E37O7jz1tjuyBoRc6d2UmUHW3dZ59orblFX1b42uwKO21zaUjRKtgkGbIQ0DgIs4r9JHY7bsmetp8oFc/iCvWwxcVZgy5M91dEzSSgTCyw3w4bzICqB/Gz/oLK4dbtaVlj6ceG/jYq/7/l8PkZuACz9OTJaGdRAVs7cpn8UMU6KeAUNpmodQFhmECDOoF/GlLd6XMmE4kjczaf1Kj2gsOH1cKae1Mqw2ennSqiCg/4+oExevLeiYuoEi2Q8uv9JLpHrEhqN4sG23ArlsIs7ZNvyyy2g=="
User-Agent: WechatPay-Guzzle/0.2.0 GuzzleHttp/6.5.5 curl/7.66.0 (Linux/4.19.76-linuxkit) PHP/7.4.13

使用postman或其他工具都有返回值,唯有这个guzzle 报错。

commented

是否额外设置了Curl的参数,或者限制了PHP无法访问tmp目录,可否把client初始化和发起POST请求的代码贴上来,我们结合情况定位下。
另外也可以先用 https://github.com/wechatpay-apiv3/wechatpay-guzzle-middleware/tree/master/tool 上的Certificate Downloader工具请求下证书API,验证网络与PHP环境是否OK

开发环境是alpine+swoole4.5+php7.4
代码copy是readme.md demo上的"上传媒体文件"( https://github.com/wechatpay-apiv3/wechatpay-guzzle-middleware#%E4%B8%8A%E4%BC%A0%E5%AA%92%E4%BD%93%E6%96%87%E4%BB%B6 )的代码, 上传图按接口文档的意思是不需要的API证书的,但官方sdk有要求也按要求做了,结果还是一样;后来放弃了guzzle使用 curl尝试竟然成功了! 什么原因暂时没有找出来,个人猜测可能是使用了swoole的原因。

调通了,按官方给的demo将body设为FnStream实例这种办法行不通,修改了下官方的jdk就跑通了;
$body不再由 bodyStream 的__toString方法获取而是由Header中获取;

protected function buildMessage($nonce, $timestamp, RequestInterface $request)
    {
        $body = '';
        $bodyStream = $request->getBody();
        // non-seekable stream need to be handled by the caller
        if ($bodyStream->isSeekable()) {
            $body = $request->hasHeader('tobody') ? $request->getHeader('tobody')[0] : (string)$bodyStream;
            if(is_array($body)) $body =  \GuzzleHttp\json_encode($body);
            $bodyStream->rewind();
        }
        return $request->getMethod()."\n".
            $request->getRequestTarget()."\n".
            $timestamp."\n".
            $nonce."\n".
            $body."\n";
    }

添加multipartData 属性,让外部直接使用

     /**
     * Compose the GuzzleHttp\Psr7\FnStream
     */
    private function composeStream()
    {
        $basename = \basename($this->filepath);
        $stream = isset($this->fileStream) ? $this->fileStream : new LazyOpenStream($this->filepath, 'r');
        if (!$stream->isSeekable()) {
            $stream = new CachingStream($stream);
        }

        $json = \GuzzleHttp\json_encode([
            'filename' => $basename,
            'sha256'   => \GuzzleHttp\Psr7\hash($stream, 'sha256'),
        ]);
        $this->meta = $json;

        $this->multipartData = [
            [
                'name'     => 'meta',
                'contents' => $json,
                'headers'  => [
                    'Content-Type' => 'application/json',
                ],
            ],
            [
                'name'     => 'file',
                'filename' => $basename,
                'contents' => $stream,
            ],
        ];
        $multipart = new MultipartStream($this->multipartData);
        $this->multipart = $multipart;

        $this->stream = FnStream::decorate($multipart, [
             // for signature
            '__toString' => function () use ($json) {
                return $json;
            },
             // let the `CURL` to use `CURLOPT_UPLOAD` context
            'getSize' => function () {
                return null;
            },
        ]);
    }

客户端调用

public function uploadByGuzzle(string $path, $returnResponse = false)
    {
        $media = new MediaUtil($path);
        $options = [
            'multipart' => $media->multipartData,
            'headers' => [
                'Accept' => 'application/json',
                'tobody' => $media->getMeta()
            ],
            'timeout' => 30
        ];
        $this->pushMiddleware($this->wechatPayMiddleware(), 'wechatPay');
        $this->pushMiddleware($this->logMiddleware(), 'log');
        $response = $this->performRequest('v3/merchant/media/upload', 'POST', $options);
        return $returnResponse ? $response : $this->castResponseToType($response, $this->app->config->get('response_type'));
    }

调通了,按官方给的demo将body设为FnStream实例这种办法行不通,修改了下官方的jdk就跑通了;
$body不再由 bodyStream 的__toString方法获取而是由Header中获取;

通过header传进去的方式很多人提出过。我好奇的问题是,为什么现在的方法行不通呢?能否提供进一步的信息或者协助定位一下。

查了下,这个问题可能是跟如下问题相关

建议楼主给一下swoole小版本号,我们来复现一下

@miwei230 你可以试一下swoole4.6.0,今天刚release的,增加了native-curl(swoole/swoole-src#3863)支持,我刚用最新master(看 swoole/swoole-src@34d1db2 提交,CURLOPT_FILE代码块修正了trigger_error)测试,0.2.0版本文档上的demo可以跑得通。

我的环境是 swoole/swoole-src@927a801

# cat /etc/os-release && php -v && composer -V && php --ri swoole
NAME="Alpine Linux"
ID=alpine
VERSION_ID=3.12.3
PRETTY_NAME="Alpine Linux v3.12"
HOME_URL="https://alpinelinux.org/"
BUG_REPORT_URL="https://bugs.alpinelinux.org/"
PHP 7.4.13 (cli) (built: Dec 28 2020 05:26:43) ( NTS )
Copyright (c) The PHP Group
Zend Engine v3.4.0, Copyright (c) Zend Technologies
    with Zend OPcache v7.4.13, Copyright (c), by Zend Technologies
Composer version 2.0.8 2020-12-03 17:20:38

swoole

Swoole => enabled
Author => Swoole Team <team@swoole.com>
Version => 4.6.0
Built => Jan  6 2021 13:53:08
coroutine => enabled
epoll => enabled
eventfd => enabled
signalfd => enabled
spinlock => enabled
rwlock => enabled
openssl => OpenSSL 1.1.1i  8 Dec 2020
dtls => enabled
http2 => enabled
json => enabled
curl-native => enabled
pcre => enabled
zlib => 1.2.11
mutex_timedlock => enabled
pthread_barrier => enabled
async_redis => enabled

Directive => Local Value => Master Value
swoole.enable_coroutine => On => On
swoole.enable_library => On => On
swoole.enable_preemptive_scheduler => Off => Off
swoole.display_errors => On => On
swoole.use_shortname => Off => Off
swoole.unixsock_buffer_size => 8388608 => 8388608

升级到swoole4.6后就能跑通了。

HTTP/1.1 200 OK
Server: nginx
Date: Thu, 07 Jan 2021 03:13:11 GMT
Content-Type: application/json; charset=utf-8
Content-Length: 122
Connection: keep-alive
Keep-Alive: timeout=8
Cache-Control: no-cache, must-revalidate
X-Content-Type-Options: nosniff
Request-ID: 08C7F8D9FF0510A50118FCC6EEA30620FA5328CFB301-0
Content-Language: zh-CN
Wechatpay-Nonce: 11cf42ec8ceafd7bd55905742175417a
Wechatpay-Signature: d8oiC6qyUaI2XOtTJL14PXGQdVfcKdfZL69HTckJa1tJt2jc7ZCz2r7A8CyOvrsvjE/VJqdqLo8q40lJU1YY9qQkDjvQnUbHb4CTlmfVXZUP1BtVJcvSdvyNF46fA6JDLSNnFYE57+PLKMOj7bEiHTYVhNdUfEfW3g1dxJSau0eXL3oFfjJbcPlreCoEDfz+KsxHdtT0+5Nh2jD3xkEUTRgKti9/reIlh6bo+fRDrUpX6JukEpC7qn2cwaSNYljxLlc8T2rOk9RY5jSF501XkoEo+RcNO1tHC7TZSTebvqPW8CxHd5hjjT7OFH7UCLSfCoPboSXca26Bq4BEGgLeIQ==
Wechatpay-Timestamp: 1609989191
Wechatpay-Serial: 585A45F5C9BF8B6CF530C853E3CC53DEB54E4DA2

{"media_id":"hyh0nvmtcOjCv8nKcqn47-SG6OcLizZlLfbJVAqfLrm4P6s6AzNl6KCbPEbOn7kui4NMgANmOvXPa3w2rcKJ7zI6Z5z0niVd9gG9oF1V8po"}
--------

针对这个我们更新下常见问题

已补充常见问题