RPC 远程过程调用
Yar 是使用C语言扩展的一个RPC框架。
Yar是基于HTTP协议传输的。
Yar整个传输使用二进制流的形式传送
Yar的传输协议是Curl
Yar远程调用的实现原理
yar client 是通过 _call 这个魔术方法来实现远程调用的,在 Yar_Client 类里面 并没有任何方法,当我们再调用一个不存在的方法是就会自动调用 _call 这个方法。
Yar协议分析
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