webman如何实现把异步请求上游的结果发送给下游?

flycoo

问题描述

问题背景:
做了个api接口,使用到第三方的api,为避免进程阻塞,使用了异步请求第三方api,但是第三方的请求结果无法返回给客户端,这个要怎么解决?

代码如下:

use Workerman\Http\Client;
use support\Request;

class TestController
{

    public function test(Request $r)
    {
        $options = [
            'max_conn_per_addr' => 128, // 每个地址最多维持多少并发连接
            'keepalive_timeout' => 15,  // 连接多长时间不通讯就关闭
            'connect_timeout'   => 30,  // 连接超时时间
            'timeout'           => 30,  // 等待响应的超时时间
        ];
        $http = new Client($options);

        $url = $r->input('url');
        $http->get($url, function($response){

            $body = $response->getBody();

            return response($body);  // <------ 想要把这个结果发给客户端,请问如何实现呢?
        }, function($exception){
            echo $exception;
        });

        return 'sync ok'; // <----客户端只能收到这个结果
    }
}

为此你搜索到了哪些方案及不适用的原因

只搜索到这个文档
https://www.workerman.net/doc/workerman/components/workerman-http-client.html#Optinons%20%E9%80%89%E9%A1%B9

1319 3 5
3个回答

flycoo

实现了一个不太好的方案如下,但是该方案会有一个问题,不会关闭连接。请问官方有没有计划支持类似的场景

新建文件
support\HoldConnetion.php, 代码如下:

<?php

namespace support;

use Workerman\Connection\TcpConnection;

class HoldConnetion
{
    public static function encode($response, TcpConnection $connection)
    {
        return '';
    }
}

修改TestController的代码如下:

use support\HoldConnetion;
use support\Request;
use Webman\App;
use Workerman\Http\Client;

class TestController
{

    public function test(Request $r)
    {
        $options = [
            'max_conn_per_addr' => 128, // 每个地址最多维持多少并发连接
            'keepalive_timeout' => 15,  // 连接多长时间不通讯就关闭
            'connect_timeout'   => 30,  // 连接超时时间
            'timeout'           => 30,  // 等待响应的超时时间
        ];

        $http = new Client($options);
        $conn = App::connection();
        $url = $r->input('url');

        $protocol = $conn->protocol; // 保留旧的protocol
        $conn->protocol = new HoldConnetion();

        $http->get($url, function($response) use ($conn, $protocol) {
            $body = $response->getBody();

            $conn->protocol = $protocol; // 恢复旧的protocol

            $conn->send(response($body)); // <--- 异步发送给客户端

        }, function($exception){
            echo $exception;
        });

        return '不会发送到客户端';   // <-- 修改protocol后,这部分内容不会再发送到客户端
    }
}
  • 暂无评论
walkor

异步请请求建议用自定义进程去做。

process/api.php

<?php
/**
 * This file is part of webman.
 *
 * Licensed under The MIT License
 * For full copyright and license information, please see the MIT-LICENSE.txt
 * Redistributions of files must retain the above copyright notice.
 *
 * @author    walkor<walkor@workerman.net>
 * @copyright walkor<walkor@workerman.net>
 * @link      http://www.workerman.net/
 * @license   http://www.opensource.org/licenses/mit-license.php MIT License
 */

namespace process;

use Workerman\Connection\TcpConnection;
use Workerman\Http\Client;
use Workerman\Protocols\Http\Request;

class Api
{
    public function onMessage(TcpConnection $connection, Request $request)
    {
        if ($request->path() == '/test/test') {
            $options = [
                'max_conn_per_addr' => 128, // 每个地址最多维持多少并发连接
                'keepalive_timeout' => 15,  // 连接多长时间不通讯就关闭
                'connect_timeout'   => 30,  // 连接超时时间
                'timeout'           => 30,  // 等待响应的超时时间
            ];
            $http = new Client($options);

            $http->get('http://www.baidu.com', function($response) use ($connection) {
                $body = (string)$response->getBody();
                echo $body;
                $connection->send(response($body));
            }, function($exception){
                echo $exception;
            });
            return;
        }
        $connection->send(response('404 not found'), 404);
    }
}

config/process.php

return [
    // ....

    'api' => [
        'handler' => \process\Api::class,
        'listen' => 'http://0.0.0.0:8686',
        'count' => 4,
    ]
];

访问 http://127.0.0.1:8686/test/test

nginx做个代理,将需要异步的接口比如/test/test转发到端口8686即可

  • flycoo 2022-10-24

    感谢回复,使用自定义进程去做会存在以下问题:
    1.不可 复用路由 和 中间件 等配置(?)
    2.由于业务原因,几乎所有接口都会调用第三方 (第三方可能是公司内部其它服务) 的API, 改动起来较大

    从webman代码看,从框架层面支持该功能似乎改动不大,比如在 webman\App:send 方法里加个判断response类型
    即可。

    大佬是否考虑从框架层面增加支持,毕竟可以大大增加抗并发能力,场景也比较通用。

  • walkor 2022-10-25

    这个暂时不支持。后面workerman v5出来,可以做到框架和业务代码不用改动,支持异步调用url,以同步的方式返回结果。v5发布时间还没确定,你可以先用你现在的方案开发

tanhongbin

现在可以用php8.1 workerman V5 协程了,贼屌

  • ikun 2023-05-15

    v5发布了?

  • tanhongbin 2023-05-15

    v5.0.0-beta.5 这个版本没啥问题,我测试用了,协程很好用,windows 单进程 请求第三方不会阻塞,无敌

  • ikun 2023-05-15

    分享下 哈哈

  • tanhongbin 2023-05-15

    很简单 https://www.workerman.net/doc/workerman/components/workerman-http-client.html 协程用法,webman中写法也是一样的
    $http = new Client($options);
    $url = 'http://***';
    $response = $http->request($url, [
    'method' => 'POST',
    'version' => '1.1',
    'headers' => $ypHeader,
    'data' => json_encode($data),
    ]);
    $r = $response->getBody()->getContents();
    我压测,这个真不阻塞,能同时接受1000请求

  • flycoo 2023-05-19

    这个不太符合我的场景, 我的场景是 A->我->C , 我需要异步请求C,然后把结果再响应给A。
    你这个只能做到A->我, 我异步->C, 我异步->D,请求C、D时是没有阻塞的,但是结果无法响应给A了。

    搜索了一番发现在同步里调用异步实现我这种场景是做不到的,只能反过来,即异步里调用同步,英语好的可以看下

    https://journal.stuffwithstuff.com/2015/02/01/what-color-is-your-function/

  • xiaopi 2023-05-19

    我不太明白你的意思,但是我看这个协程是可以符合你的需求的啊。 你的需求是不是搭建web服务端提供api给A方调用,然后你的服务端程序调用C方的api,最终将C方的返回结果返回给A方? 如果是这样的话,在你的程序里使用异步客户端的方式调用C接口。这样你的服务端程序进程不会阻塞啊,A方可以进来更多的请求。可以试一下

  • xiaopi 2023-05-19

    这个是walkor这几天发的v5,一起学习下
    https://www.workerman.net/q/10564

  • tanhongbin 2023-05-19

    下面是我提问的,问完我就去测试了,redis 协程没成功,qps基本没差别,估计是redis足够快的原因,请求外网第三方协程方式,很好用,不阻塞,普通方式请求第三方开一个进程,qps基本几个,协程方式一个进程居然能跑好几百

  • xiaopi 2023-05-19

    我还没实验,不过异步协程webman中不是只有一个HTTP客户端:workerman/http-client吗,怎么还有异步redis?

  • tanhongbin 2023-05-19

    有可以使用 异步redis

年代过于久远,无法发表回答
🔝