新建会话
Ctrl
K
Kimi Code
历史会话
查看手机应用

Webman YAR 调用与实现

如果 用 webman 实现 yar 服务端呢?
搜索
webman 实现 YAR 服务端
31 个结果
在 Webman 里“实现一个 YAR 服务端”= 让 Webman 能看懂 YAR 协议,把请求解包→路由到类→把结果按 YAR 格式回给客户端。
核心只有三步:
  1. 识别 YAR 协议头(X-YAR-*);
  2. 用 msgpack/json 把 body 解成 [method, params];
  3. 把返回值再按同样格式打包返回,并带上 X-YAR-* 响应头。
下面给出一份「零依赖、开箱即用」的 Webman 路由级实现,不装 yar 扩展也能跑。

  1. 新建路由文件 route/yar.php
php
复制
use Webman\Route;
use app\yar\YarServer;      // 后面会创建

# 只接管 POST,GET 留给 info 页面
Route::post('/yar', [YarServer::class, 'handle']);
Route::get ('/yar', [YarServer::class, 'info']);

  1. 实现协议解析器 app/yar/YarProtocol.php
php
复制
<?php
namespace app\yar;

/**
 * 把 YAR 请求/响应打包、解包
 * 只实现最常用 msgpack 格式,json 同理
 */
class YarProtocol
{
    const HEADER_SIZE = 80;            // yar_header_t 固定 80 B
    const MAGIC_NUM   = 0x80dfec60;    // 网络字节序

    /* 解析一次请求,返回 ['method'=>xx,'params'=>[...]] */
    public static function unpack(string $raw): array
    {
        if (strlen($raw) < self::HEADER_SIZE) {
            throw new \Exception('bad yar header');
        }
        $header = unpack('Nmagic/Nid/Nversion/Nbody_len/Npackager/Nreserved', substr($raw, 0, 24));
        if ($header['magic'] !== self::MAGIC_NUM) {
            throw new \Exception('magic number mismatch');
        }
        $body   = substr($raw, self::HEADER_SIZE, $header['body_len']);
        $data   = match ($header['packager']) {
            0       => msgpack_unpack($body),   // msgpack
            1       => json_decode($body, true),// json
            default => throw new \Exception('unsupported packager'),
        };
        return $data;   // ['method'=>'add','params'=>[1,2]]
    }

    /* 把返回值打包成 YAR 响应体 */
    public static function pack($ret, string $packager = 'msgpack'): string
    {
        $body = match ($packager) {
            'msgpack' => msgpack_pack(['ret'=>$ret]),
            'json'    => json_encode(['ret'=>$ret]),
        };
        $head = pack('NNNNNN',
            self::MAGIC_NUM,
            mt_rand(),   // id
            0,           // version
            strlen($body),
            $packager==='msgpack' ? 0 : 1,
            0
        );
        return $head . str_pad('', self::HEADER_SIZE - strlen($head), "\0") . $body;
    }
}

  1. 服务端入口 app/yar/YarServer.php
php
复制
<?php
namespace app\yar;

use Webman\Http\Request;
use Webman\Http\Response;

class YarServer
{
    protected static $executor;   // 要暴露的类实例

    /* 注册业务对象(Webman 启动时调用一次) */
    public static function setExecutor(object $obj): void
    {
        self::$executor = $obj;
    }

    /** 真正处理 RPC 调用 */
    public static function handle(Request $request): Response
    {
        if (!$request->isPost()) {
            return response('only POST', 405);
        }
        $packager = $request->header('x-yar-packager', 'msgpack');
        try {
            $raw   = $request->rawBody();
            $data  = YarProtocol::unpack($raw);
            $ret   = call_user_func_array([self::$executor, $data['method']], $data['params']);
            $body  = YarProtocol::pack($ret, $packager);
            return response($body, 200, [
                'Content-Type'    => 'application/octet-stream',
                'X-YAR-Packager'  => $packager,
            ]);
        } catch (\Throwable $e) {
            return response(YarProtocol::pack(['error'=>$e->getMessage()], $packager),
                            200, ['Content-Type'=>'application/octet-stream']);
        }
    }

    /** 浏览器 GET 访问时,返回 HTML API 列表 */
    public static function info(Request $request): Response
    {
        $ref  = new \ReflectionObject(self::$executor);
        $html = '<h1>'.get_class(self::$executor).'</h1><ul>';
        foreach ($ref->getMethods(\ReflectionMethod::IS_PUBLIC) as $m) {
            if ($m->name[0] !== '_') {
                $html .= '<li>'.$m->name.'('.implode(',', $m->getParameters()).')</li>';
            }
        }
        return response($html.'</ul>');
    }
}

  1. 注册业务类(在 process/bootstrap.php 或自定义启动文件里)
php
复制
use app\yar\YarServer;
use app\service\CalcService;   // 你自己的业务类

 YarServer::setExecutor(new CalcService());

  1. 业务类示例 app/service/CalcService.php
php
复制
<?php
namespace app\service;

class CalcService
{
    public function add($a, $b) { return $a + $b; }
    public function mul($a, $b) { return $a * $b; }
}

  1. 客户端调用(任意 yar-client / 上一问的纯 PHP 客户端均可)
php
复制
$cli = new Yar_Client('http://127.0.0.1:8787/yar');
echo $cli->add(3, 5);   // 8

要点回顾
  1. Webman 本身不需要装 yar 扩展,只要按协议收包/发包即可;
  2. 路由里把 /yar 的 POST 流量交给 YarServer::handle,其余流量正常走业务;
  3. 打包格式只支持 msgpack/json 就足够与官方 yar-client 互通;
  4. 如果想暴露 TCP 版,把上面协议解析搬到 workerman/tcp-server 的 onMessage 里即可,思路完全一样。