This commit is contained in:
2021-09-02 11:26:10 +08:00
parent 7851259003
commit 2f68ae1757
2 changed files with 831 additions and 0 deletions
+780
View File
@@ -0,0 +1,780 @@
<?php
namespace Kiri\Rpc;
use Exception;
use Swoole\Coroutine\Http\Client as CoroutineClient;
class Client
{
// KV
const URI_PUT = 'kv/put';
const URI_RANGE = 'kv/range';
const URI_DELETE_RANGE = 'kv/deleterange';
const URI_TXN = 'kv/txn';
const URI_COMPACTION = 'kv/compaction';
// Lease
const URI_GRANT = 'lease/grant';
const URI_REVOKE = 'kv/lease/revoke';
const URI_KEEPALIVE = 'lease/keepalive';
const URI_TIMETOLIVE = 'kv/lease/timetolive';
// Role
const URI_AUTH_ROLE_ADD = 'auth/role/add';
const URI_AUTH_ROLE_GET = 'auth/role/get';
const URI_AUTH_ROLE_DELETE = 'auth/role/delete';
const URI_AUTH_ROLE_LIST = 'auth/role/list';
// Authenticate
const URI_AUTH_ENABLE = 'auth/enable';
const URI_AUTH_DISABLE = 'auth/disable';
const URI_AUTH_AUTHENTICATE = 'auth/authenticate';
// User
const URI_AUTH_USER_ADD = 'auth/user/add';
const URI_AUTH_USER_GET = 'auth/user/get';
const URI_AUTH_USER_DELETE = 'auth/user/delete';
const URI_AUTH_USER_CHANGE_PASSWORD = 'auth/user/changepw';
const URI_AUTH_USER_LIST = 'auth/user/list';
const URI_AUTH_ROLE_GRANT = 'auth/role/grant';
const URI_AUTH_ROLE_REVOKE = 'auth/role/revoke';
const URI_AUTH_USER_GRANT = 'auth/user/grant';
const URI_AUTH_USER_REVOKE = 'auth/user/revoke';
const PERMISSION_READ = 0;
const PERMISSION_WRITE = 1;
const PERMISSION_READWRITE = 2;
const DEFAULT_HTTP_TIMEOUT = 30;
/**
* @var string host:port
*/
protected string $host;
protected int $port;
/**
* @var string api version
*/
protected string $version;
/**
* @var array
*/
protected array $httpOptions;
/**
* @var bool
*/
protected bool $pretty = false;
/**
* @var string|null auth token
*/
protected ?string $token = null;
/**
* @param string $host
* @param int $port
* @param string $version
*/
public function __construct(string $host, int $port, string $version = 'v3alpha')
{
$this->host = $host;
$this->port = $port;
$this->version = trim($version);
}
public function setHttpOptions(array $options)
{
$this->httpOptions = $options;
}
public function setPretty($enabled)
{
$this->pretty = $enabled;
}
public function setToken($token)
{
$this->token = $token;
}
public function clearToken()
{
$this->token = null;
}
// region kv
/**
* Put puts the given key into the key-value store.
* A put request increments the revision of the key-value
* store\nand generates one event in the event history.
*
* @param string $key
* @param string $value
* @param array $options 可选参数
* int64 lease
* bool prev_kv
* bool ignore_value
* bool ignore_lease
* @return array
* @throws
*/
public function put(string $key, string $value, array $options = []): array
{
$params = ['value' => $value];
$key = ltrim($key,'/');
$body = $this->request('POST', "/v2/keys/$key", $params, $options);
$body = $this->decodeBodyForFields(
$body,
'prev_kv',
['key', 'value',]
);
if (isset($body['prev_kv']) && $this->pretty) {
return $this->convertFields($body['prev_kv']);
}
return $body;
}
/**
* Gets the key or a range of keys
*
* @param string $key
* @param array $options
* string range_end
* int limit
* int revision
* int sort_order
* int sort_target
* bool serializable
* bool keys_only
* bool count_only
* int64 min_mod_revision
* int64 max_mod_revision
* int64 min_create_revision
* int64 max_create_revision
* @return array
* @throws Exception
*/
public function get(string $key): array
{
$key = ltrim($key,'/');
$body = $this->request('GET', "/v2/keys/$key");
$body = $this->decodeBodyForFields(
$body,
'kvs',
['key', 'value',]
);
if (isset($body['kvs']) && $this->pretty) {
return $this->convertFields($body['kvs']);
}
return $body;
}
/**
* get all keys
*
* @return array
* @throws Exception
*/
public function getAllKeys(): array
{
return $this->get('/');
}
/**
* get all keys with prefix
*
* @param string $prefix
* @return array
* @throws Exception
*/
public function getKeysWithPrefix(string $prefix): array
{
$prefix = trim($prefix);
if (!$prefix) {
return [];
}
$lastIndex = strlen($prefix) - 1;
$lastChar = $prefix[$lastIndex];
$nextAsciiCode = ord($lastChar) + 1;
$rangeEnd = $prefix;
$rangeEnd[$lastIndex] = chr($nextAsciiCode);
return $this->get($prefix, ['range_end' => $rangeEnd]);
}
/**
* Removes the specified key or range of keys
*
* @param string $key
* @return array
* @throws Exception
*/
public function del(string $key): array
{
$key = ltrim($key,'/');
$body = $this->request('DELETE', "/v2/keys/$key");
$body = $this->decodeBodyForFields(
$body,
'prev_kvs',
['key', 'value',]
);
if (isset($body['prev_kvs']) && $this->pretty) {
return $this->convertFields($body['prev_kvs']);
}
return $body;
}
/**
* Compact compacts the event history in the etcd key-value store.
* The key-value\nstore should be periodically compacted
* or the event history will continue to grow\nindefinitely.
*
* @param int $revision
*
* @param bool|false $physical
*
* @return array
* @throws Exception
*/
public function compaction($revision, $physical = false)
{
$params = [
'revision' => $revision,
'physical' => $physical,
];
return $this->request(self::URI_COMPACTION, $params);
}
// endregion kv
// region lease
/**
* LeaseGrant creates a lease which expires if the server does not receive a
* keepAlive\nwithin a given time to live period. All keys attached to the lease
* will be expired and\ndeleted if the lease expires.
* Each expired key generates a delete event in the event history.",
*
* @param int $ttl TTL is the advisory time-to-live in seconds.
* @param int $id ID is the requested ID for the lease.
* If ID is set to 0, the lessor chooses an ID.
* @return array
*/
public function grant($ttl, $id = 0)
{
$params = [
'TTL' => $ttl,
'ID' => $id,
];
return $this->request(self::URI_GRANT, $params);
}
/**
* revokes a lease. All keys attached to the lease will expire and be deleted.
*
* @param int $id ID is the lease ID to revoke. When the ID is revoked,
* all associated keys will be deleted.
* @return array
*/
public function revoke($id)
{
$params = [
'ID' => $id,
];
return $this->request(self::URI_REVOKE, $params);
}
/**
* keeps the lease alive by streaming keep alive requests
* from the client\nto the server and streaming keep alive responses
* from the server to the client.
*
* @param int $id ID is the lease ID for the lease to keep alive.
* @return array
*/
public function keepAlive(int $id): array
{
$params = [
'ID' => $id,
];
$body = $this->request(self::URI_KEEPALIVE, $params);
if (!isset($body['result'])) {
return $body;
}
// response "result" field, etcd bug?
return [
'ID' => $body['result']['ID'],
'TTL' => $body['result']['TTL'],
];
}
/**
* retrieves lease information.
*
* @param int $id ID is the lease ID for the lease.
* @param bool|false $keys
* @return array
*/
public function timeToLive(int $id, bool $keys): array
{
$params = [
'ID' => $id,
'keys' => $keys,
];
$body = $this->request(self::URI_TIMETOLIVE, $params);
if (isset($body['keys'])) {
$body['keys'] = array_map(function ($value) {
return base64_decode($value);
}, $body['keys']);
}
return $body;
}
// endregion lease
// region auth
/**
* enable authentication
*
* @return array
*/
public function authEnable()
{
$body = $this->request(self::URI_AUTH_ENABLE);
$this->clearToken();
return $body;
}
/**
* disable authentication
*
* @return array
*/
public function authDisable()
{
$body = $this->request(self::URI_AUTH_DISABLE);
$this->clearToken();
return $body;
}
/**
* @param string $user
* @param string $password
* @return array
*/
public function authenticate($user, $password)
{
$params = [
'name' => $user,
'password' => $password,
];
$body = $this->request(self::URI_AUTH_AUTHENTICATE, $params);
if ($this->pretty && isset($body['token'])) {
return $body['token'];
}
return $body;
}
/**
* add a new role.
*
* @param string $name
* @return array
*/
public function addRole($name)
{
$params = [
'name' => $name,
];
$body = $this->request(self::URI_AUTH_ROLE_ADD, $params);
return $body;
}
/**
* get detailed role information.
*
* @param string $role
* @return array
*/
public function getRole($role)
{
$params = [
'role' => $role,
];
$body = $this->request(self::URI_AUTH_ROLE_GET, $params);
$body = $this->decodeBodyForFields(
$body,
'perm',
['key', 'range_end',]
);
if ($this->pretty && isset($body['perm'])) {
return $body['perm'];
}
return $body;
}
/**
* delete a specified role.
*
* @param string $role
* @return array
*/
public function deleteRole($role)
{
$params = [
'role' => $role,
];
return $this->request(self::URI_AUTH_ROLE_DELETE, $params);
}
/**
* get lists of all roles
*
* @return array
*/
public function roleList()
{
$body = $this->request(self::URI_AUTH_ROLE_LIST);
if ($this->pretty && isset($body['roles'])) {
return $body['roles'];
}
return $body;
}
/**
* add a new user
*
* @param string $user
* @param string $password
* @return array
*/
public function addUser($user, $password)
{
$params = [
'name' => $user,
'password' => $password,
];
return $this->request(self::URI_AUTH_USER_ADD, $params);
}
/**
* get detailed user information
*
* @param string $user
* @return array
* @throws \GuzzleHttp\Exception\BadResponseException
*/
public function getUser($user)
{
$params = [
'name' => $user,
];
$body = $this->request(self::URI_AUTH_USER_GET, $params);
if ($this->pretty && isset($body['roles'])) {
return $body['roles'];
}
return $body;
}
/**
* delete a specified user
*
* @param string $user
* @return array
* @throws \GuzzleHttp\Exception\BadResponseException
*/
public function deleteUser($user)
{
$params = [
'name' => $user,
];
$body = $this->request(self::URI_AUTH_USER_DELETE, $params);
return $body;
}
/**
* get a list of all users.
*
* @return array
* @throws \GuzzleHttp\Exception\BadResponseException
*/
public function userList()
{
$body = $this->request(self::URI_AUTH_USER_LIST);
if ($this->pretty && isset($body['users'])) {
return $body['users'];
}
return $body;
}
/**
* change the password of a specified user.
*
* @param string $user
* @param string $password
* @return array
* @throws \GuzzleHttp\Exception\BadResponseException
*/
public function changeUserPassword($user, $password)
{
$params = [
'name' => $user,
'password' => $password,
];
$body = $this->request(self::URI_AUTH_USER_CHANGE_PASSWORD, $params);
return $body;
}
/**
* grant a permission of a specified key or range to a specified role.
*
* @param string $role
* @param int $permType
* @param string $key
* @param string|null $rangeEnd
* @return array
* @throws \GuzzleHttp\Exception\BadResponseException
*/
public function grantRolePermission($role, $permType, $key, $rangeEnd = null)
{
$params = [
'name' => $role,
'perm' => [
'permType' => $permType,
'key' => base64_encode($key),
],
];
if ($rangeEnd !== null) {
$params['perm']['range_end'] = base64_encode($rangeEnd);
}
$body = $this->request(self::URI_AUTH_ROLE_GRANT, $params);
return $body;
}
/**
* revoke a key or range permission of a specified role.
*
* @param string $role
* @param string $key
* @param string|null $rangeEnd
* @return array
* @throws BadResponseException
*/
public function revokeRolePermission($role, $key, $rangeEnd = null)
{
$params = [
'role' => $role,
'key' => $key,
];
if ($rangeEnd !== null) {
$params['range_end'] = $rangeEnd;
}
$body = $this->request(self::URI_AUTH_ROLE_REVOKE, $params);
return $body;
}
/**
* grant a role to a specified user.
*
* @param string $user
* @param string $role
* @return array
* @throws BadResponseException
*/
public function grantUserRole($user, $role)
{
$params = [
'user' => $user,
'role' => $role,
];
$body = $this->request(self::URI_AUTH_USER_GRANT, $params);
return $body;
}
/**
* revoke a role of specified user.
*
* @param string $user
* @param string $role
* @return array
* @throws \GuzzleHttp\Exception\BadResponseException
*/
public function revokeUserRole($user, $role)
{
$params = [
'name' => $user,
'role' => $role,
];
$body = $this->request(self::URI_AUTH_USER_REVOKE, $params);
return $body;
}
// endregion auth
/**
* 发送HTTP请求
*
* @param string $method
* @param string $uri
* @param array $params 请求参数
* @param array $options 可选参数
* @return array
* @throws Exception
*/
protected function request(string $method, string $uri, array $params = [], array $options = []): array
{
if ($options) {
$params = array_merge($params, $options);
}
// 没有参数, 设置一个默认参数
if (!$params) {
$params['php-etcd-client'] = 1;
}
$client = new CoroutineClient('47.92.194.207', 2379);
if (!empty($params)) {
$client->setHeaders(['Content-Type' => 'application/x-www-form-urlencoded']);
$client->setData(http_build_query($params));
}
$client->setMethod($method);
$client->execute($uri);
$body = json_decode($client->body, true);
$client->close();
if (!in_array($client->getStatusCode(), [200, 201])) {
throw new Exception($client->body, $client->errCode);
}
return $body;
}
/**
* string类型key用base64编码
*
* @param array $data
* @return array
*/
protected function encode(array $data): array
{
foreach ($data as $key => $value) {
if (is_string($value)) {
$data[$key] = base64_encode($value);
}
}
return $data;
}
/**
* 指定字段base64解码
*
* @param array $body
* @param string $bodyKey
* @param array $fields 需要解码的字段
* @return array
*/
protected function decodeBodyForFields(array $body, string $bodyKey, array $fields): array
{
if (!isset($body[$bodyKey])) {
return $body;
}
$data = $body[$bodyKey];
if (!isset($data[0])) {
$data = array($data);
}
foreach ($data as $key => $value) {
foreach ($fields as $field) {
if (isset($value[$field])) {
$data[$key][$field] = base64_decode($value[$field]);
}
}
}
if (isset($body[$bodyKey][0])) {
$body[$bodyKey] = $data;
} else {
$body[$bodyKey] = $data[0];
}
return $body;
}
/**
* @param array $data
* @return mixed
*/
protected function convertFields(array $data): mixed
{
if (!isset($data[0])) {
return $data['value'];
}
$map = [];
foreach ($data as $value) {
$key = $value['key'];
$map[$key] = $value['value'];
}
return $map;
}
}
+51
View File
@@ -0,0 +1,51 @@
<?php
namespace Kiri\Rpc;
class Registry
{
// KV
const URI_PUT = 'kv/put';
const URI_RANGE = 'kv/range';
const URI_DELETE_RANGE = 'kv/deleterange';
const URI_TXN = 'kv/txn';
const URI_COMPACTION = 'kv/compaction';
// Lease
const URI_GRANT = 'lease/grant';
const URI_REVOKE = 'kv/lease/revoke';
const URI_KEEPALIVE = 'lease/keepalive';
const URI_TIMETOLIVE = 'kv/lease/timetolive';
// Role
const URI_AUTH_ROLE_ADD = 'auth/role/add';
const URI_AUTH_ROLE_GET = 'auth/role/get';
const URI_AUTH_ROLE_DELETE = 'auth/role/delete';
const URI_AUTH_ROLE_LIST = 'auth/role/list';
// Authenticate
const URI_AUTH_ENABLE = 'auth/enable';
const URI_AUTH_DISABLE = 'auth/disable';
const URI_AUTH_AUTHENTICATE = 'auth/authenticate';
// User
const URI_AUTH_USER_ADD = 'auth/user/add';
const URI_AUTH_USER_GET = 'auth/user/get';
const URI_AUTH_USER_DELETE = 'auth/user/delete';
const URI_AUTH_USER_CHANGE_PASSWORD = 'auth/user/changepw';
const URI_AUTH_USER_LIST = 'auth/user/list';
const URI_AUTH_ROLE_GRANT = 'auth/role/grant';
const URI_AUTH_ROLE_REVOKE = 'auth/role/revoke';
const URI_AUTH_USER_GRANT = 'auth/user/grant';
const URI_AUTH_USER_REVOKE = 'auth/user/revoke';
const PERMISSION_READ = 0;
const PERMISSION_WRITE = 1;
const PERMISSION_READWRITE = 2;
const DEFAULT_HTTP_TIMEOUT = 30;
}