作为程序员一定要保持良好的睡眠,才能好编程

使用Yar 实现RPC框架

发布时间:2019-04-04

RPC 远程过程调用


Yar 是使用C语言扩展的一个RPC框架。


Yar是基于HTTP协议传输的。


Yar整个传输使用二进制流的形式传送


Yar的传输协议是Curl

yarinfo.png



Yar远程调用的实现原理

yar client 是通过 _call 这个魔术方法来实现远程调用的,在 Yar_Client 类里面 并没有任何方法,当我们再调用一个不存在的方法是就会自动调用 _call 这个方法。


Yar协议分析

yar.png


yar_response_t 中的 retval 这是 返回的结果值。




Yar 实现一个简单的RPC


Server.php




client.php




目录






sign算法:

1、所有数据 按 k 倒序

2、通过k=$val&k2=$val2 拼接成字符串

3、通过sign=秘钥 使用 - 与上边的字符连接

4、通过md5 加密后 生成。


这个sign的算法 服务器、客户端都保持通用。



数据先进行加减密:


倘若数组中出现了 _enctype='r' 这样的值,则系统自动按照 content 进行解密处理

$data=[
    'email'=>'574482856@qq.com',
    'title'=>'发送test邮件',
    'email_body'=>'这是一封邮件 请查收'
    
];

//定义一个方法,通过调用方法,自动填充需要验证的值,
//这个值,一经填充,不能再次修改,这是要发送到服务器的数据。
//会自动添加sign 数据

$content=AESEncode(json_encode($data),true);

$http_url='172.28.81.111:8888/message';
$client=Yar_client($http_url);

$sendData=[
    'content'=>$content,
    '_enctype'=>'r'
];

$result=$client->sendMessage($sendData);

print_r($result);


这种方法的设计有一种问题,就是很不清楚调用这项服务需要传递的参数。综合考虑吧!


解密处理后,自动赋值给数组。数组中的值经过sign 计算到 sign  与 get 中传递的sign进行对比,倘若一致,则放行。否则阻止。


我们在yaf_client 的时候,能否通过抓包工具,抓到发送的数据? 需要做一个测试


=============================

思考:

如果进行加减密传递数组的形式,进行传递,那么 sign 是否通过 $_data 这样的方式传递,不使用_GET 进行传递了?


如果是这样,所有的接口中



=============================



对sign的判断,这是一定要判断的,传出中不需要传递 秘钥,只需要加密的时候,带上即可。


这个验证非常好,客户来源IP,如果不在这个IP范围内,则不能访问

//验证IP
    if (!in_array(Yii::$app->request->userIP, $this->ipArr)) {
      return FALSE;
    }
 
    //有效时间
    if ((time() - $param['time']) > $this->activeTime) {
 
      return FALSE;
    }
    //验证密码
    if ($param['password'] !== $this->password) {
      return FALSE;
    }
    if (empty($param['class'])) {
      return FALSE;
    }




整个RPC框架的开发,不但需要考虑服务端如何部署开发,亦要开发响应客户端如何建立 ,如何应用。



一个yii2 框架开发结合yar 实现的RPC框架,看看实现步骤:




RPC服务端

 新建文件 RpcController.php,在这里展示出了怎样进行加解密的处理,以及如何访问。


RPC服务端做了什么事情:

1、声明了RPC控制器类,并声明了重要的参数 ,如:加解密的秘钥、有效时间、以及关闭csrf 攻击关闭

2、提供index 方法,对外提供服务

3、auth() 权限验证,是否授权用户?

     对密码、有效期、数据 进行了严格的判断

4、rpcDecode 方法 对传递字符进行解密处理

5、执行 yar_server 创建类,并执行handle() 方法对外提供服务


具体试下代码如下:

<?php

namespace backend\controllers;

use Yii;
use common\controllers\CommonController;
use yii\web\Controller;


/**
 * rpc controller
 */
class RpcController extends CommonController {
  /**
   * 关闭csft
   * @var string
   * @access public
   */
  public $enableCsrfValidation = FALSE;
  /**
   * ip
   * @var string
   * @access private
   */
  private $ipArr = ['127.0.0.1', '192.168.1.110'];
  /**
   * 密码
   * @var string
   * @access private
   */
  private $password = 'Add25f37';
  /**
   * 有效时间   秒
   * @var string
   * @access private
   */
  private $activeTime = 1440;

  /**
   * 暂无说明
   *
   * @author Zhiqiang Guo
   * @return void
   * @throws Exception
   * @access public
   */
  public function actionIndex() {
    $request = Yii::$app->request;
    //解密
    $data = $this->rpcDecode($request->get('rpctoken'));

    //权限认证
    if (!$this->auth($data)) {
      return;
    }

    try {

      $server = new \Yar_Server(new $data['class']());
      $server->handle();
    } catch (Exception $e) {
      return;
      $e->getMessage();
    }
    //        return $this->render('index');

  }

  /**
   * 权限认证
   *
   * @author Zhiqiang Guo
   * @return void
   * @throws Exception
   * @access private
   */
  private function auth($param) {
    if (!$param) {
      return FALSE;
    }
    //验证IP
    if (!in_array(Yii::$app->request->userIP, $this->ipArr)) {
      return FALSE;
    }

    //有效时间
    if ((time() - $param['time']) > $this->activeTime) {

      return FALSE;
    }
    //验证密码
    if ($param['password'] !== $this->password) {
      return FALSE;
    }
    if (empty($param['class'])) {
      return FALSE;
    }
    return TRUE;
  }

  /**
   * 解密
   *
   * @author Zhiqiang Guo
   * @return void
   * @throws Exception
   * @access private
   */
  private function rpcDecode($str) {
    if ($str) {
      return json_decode(base64_decode($str), TRUE);
    }
    return [];
  }
}






RPC客户端

YarApi.php

<?php

class YarApi {
  /**
   * 密码
   * @var string
   * @access private
   */
  private $password = 'Add25f37';
  /**
   * 暂无说明
   *
   * @author Zhiqiang Guo
   * @return void
   * @throws Exception
   * @access public
   */
  public function api(array $condition) {
    $defult = [
      'url' => 'http://localhost/rpc/index/', //服务器URL
      'class' => '', //class名称
    ];
    $condition = array_merge($defult, $condition);

    $data = [];
    $data['time'] = time();
    $data['password'] = $this->password;
    $data['class'] = $condition['class'];

    return new \Yar_Client("{$condition['url']}{$this->rpcEncode($data)}");

  }

  /**
   * 加密
   *
   * @author Zhiqiang Guo
   * @return void
   * @throws Exception
   * @access private
   */
  private function rpcEncode(array $data) {
    return base64_encode(json_encode($data));
  }
}




运行测试

<?php

namespace backend\controllers;

use Yii;
use common\controllers\CommonController;
use yii\web\Controller;
use common\rpc\YarApi;

/**
 * 测试
 *
 * @author Zhiqiang Guo
 * @date 2017-07-02
 */
class TestController extends CommonController {

  /**
   * No explanation
   *
   * @author Zhiqiang Guo
   * @return void
   * @throws Exception
   * @access public
   */
  public function actionIndex() {
    $condition = ['class' => '\backend\models\Per'];
    $yar = new YarApi();
    $model = $yar->api($condition);
    $query = $model->SelAll();
    echo "<pre>";
    var_dump($query);
    echo "</pre>";
    exit;
  }
}



解释说明

 $condition = ['class' => '\backend\models\Per'];

Per 这是服务端创建的一个服务对象,里面提供了一个SelAll()的方法,对外提供服务。


代码如下:

class Per extends ActiveRecord {
  /**
   * 暂无说明
   *
   * @author name
   * @return void
   * @throws Exception
   * @access public
   */
  public function rules() {
    return [
    ];
  }

  /**
   * 返回一个你要查询的表名
   *
   * @author name
   * @return void
   * @throws Exception
   * @access public
   */
  public static function tableName() {
    //表名
    return 'system_per';

  }

  /**
   * 查询权限的所有数据
   *
   * @author name
   * @return void
   * @throws Exception
   * @access public
   */
  public function SelAll() {

    $res = Per::find()->asArray()->All();

    return $res;

  }

}





这是一个成熟的RPC框架,整个项目基于swoole开发:

https://github.com/xcl3721/Dora-RPC