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 "
";
var_dump($query);
echo "
";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