This commit is contained in:
2020-10-09 10:58:37 +08:00
parent 8147a57fa0
commit c4bde06838
37 changed files with 7920 additions and 2 deletions
+226
View File
@@ -0,0 +1,226 @@
<?php
/* vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4 foldmethod=marker: */
// +---------------------------------------------------------------------------
// | SWAN [ $_SWANBR_SLOGAN_$ ]
// +---------------------------------------------------------------------------
// | Copyright $_SWANBR_COPYRIGHT_$
// +---------------------------------------------------------------------------
// | Version $_SWANBR_VERSION_$
// +---------------------------------------------------------------------------
// | Licensed ( $_SWANBR_LICENSED_URL_$ )
// +---------------------------------------------------------------------------
// | $_SWANBR_WEB_DOMAIN_$
// +---------------------------------------------------------------------------
namespace Kafka\Protocol;
/**
+------------------------------------------------------------------------------
* Kafka protocol for commit offset api
+------------------------------------------------------------------------------
*
* @package
* @version $_SWANBR_VERSION_$
* @copyright Copyleft
* @author $_SWANBR_AUTHOR_$
+------------------------------------------------------------------------------
*/
class CommitOffset extends Protocol
{
// {{{ functions
// {{{ public function encode()
/**
* commit offset request encode
*
* @param array $payloads
* @access public
* @return string
*/
public function encode($payloads)
{
if (!isset($payloads['group_id'])) {
throw new \Kafka\Exception\Protocol('given commit offset data invalid. `group_id` is undefined.');
}
if (!isset($payloads['data'])) {
throw new \Kafka\Exception\Protocol('given commit data invalid. `data` is undefined.');
}
if (!isset($payloads['generation_id'])) {
$payloads['generation_id'] = -1;
}
if (!isset($payloads['member_id'])) {
$payloads['member_id'] = '';
}
if (!isset($payloads['retention_time'])) {
$payloads['retention_time'] = -1;
}
$version = $this->getApiVersion(self::OFFSET_COMMIT_REQUEST);
$header = $this->requestHeader('kafka-php', self::OFFSET_COMMIT_REQUEST, self::OFFSET_COMMIT_REQUEST);
$data = self::encodeString($payloads['group_id'], self::PACK_INT16);
if ($version == self::API_VERSION1) {
$data .= self::pack(self::BIT_B32, $payloads['generation_id']);
$data .= self::encodeString($payloads['member_id'], self::PACK_INT16);
}
if ($version == self::API_VERSION2) {
$data .= self::pack(self::BIT_B32, $payloads['generation_id']);
$data .= self::encodeString($payloads['member_id'], self::PACK_INT16);
$data .= self::pack(self::BIT_B64, $payloads['retention_time']);
}
$data .= self::encodeArray($payloads['data'], array($this, 'encodeTopic'));
$data = self::encodeString($header . $data, self::PACK_INT32);
return $data;
}
// }}}
// {{{ public function decode()
/**
* decode group response
*
* @access public
* @return array
*/
public function decode($data)
{
$offset = 0;
$version = $this->getApiVersion(self::OFFSET_REQUEST);
$topics = $this->decodeArray(substr($data, $offset), array($this, 'decodeTopic'), $version);
$offset += $topics['length'];
return $topics['data'];
}
// }}}
// {{{ protected function encodeTopic()
/**
* encode commit offset topic array
*
* @param array $values
* @access public
* @return array
*/
protected function encodeTopic($values)
{
if (!isset($values['topic_name'])) {
throw new \Kafka\Exception\Protocol('given commit offset data invalid. `topic_name` is undefined.');
}
if (!isset($values['partitions'])) {
throw new \Kafka\Exception\Protocol('given commit offset data invalid. `partitions` is undefined.');
}
$data = self::encodeString($values['topic_name'], self::PACK_INT16);
$data .= self::encodeArray($values['partitions'], array($this, 'encodePartition'));
return $data;
}
// }}}
// {{{ protected function encodePartition()
/**
* encode commit offset partition array
*
* @param array $values
* @access public
* @return array
*/
protected function encodePartition($values)
{
if (!isset($values['partition'])) {
throw new \Kafka\Exception\Protocol('given commit offset data invalid. `partition` is undefined.');
}
if (!isset($values['offset'])) {
throw new \Kafka\Exception\Protocol('given commit offset data invalid. `offset` is undefined.');
}
if (!isset($values['metadata'])) {
$values['metadata'] = '';
}
if (!isset($values['timestamp'])) {
$values['timestamp'] = time() * 1000;
}
$version = $this->getApiVersion(self::OFFSET_COMMIT_REQUEST);
$data = self::pack(self::BIT_B32, $values['partition']);
$data .= self::pack(self::BIT_B64, $values['offset']);
if ($version == self::API_VERSION1) {
$data .= self::pack(self::BIT_B64, $values['timestamp']);
}
$data .= self::encodeString($values['metadata'], self::PACK_INT16);
return $data;
}
// }}}
// {{{ protected function decodeTopic()
/**
* decode commit offset topic response
*
* @param byte[] $data
* @param string $version
* @access protected
* @return array
*/
protected function decodeTopic($data, $version)
{
$offset = 0;
$topicInfo = $this->decodeString(substr($data, $offset), self::BIT_B16);
$offset += $topicInfo['length'];
$partitions = $this->decodeArray(substr($data, $offset), array($this, 'decodePartition'), $version);
$offset += $partitions['length'];
return array(
'length' => $offset,
'data' => array(
'topicName' => $topicInfo['data'],
'partitions' => $partitions['data'],
)
);
}
// }}}
// {{{ protected function decodePartition()
/**
* decode commit offset partition response
*
* @param byte[] $data
* @param string $version
* @access protected
* @return array
*/
protected function decodePartition($data, $version)
{
$offset = 0;
$partitionId = self::unpack(self::BIT_B32, substr($data, $offset, 4));
$offset += 4;
$errorCode = self::unpack(self::BIT_B16_SIGNED, substr($data, $offset, 2));
$offset += 2;
return array(
'length' => $offset,
'data' => array(
'partition' => $partitionId,
'errorCode' => $errorCode,
)
);
}
// }}}
// }}}
}
+201
View File
@@ -0,0 +1,201 @@
<?php
/* vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4 foldmethod=marker: */
// +---------------------------------------------------------------------------
// | SWAN [ $_SWANBR_SLOGAN_$ ]
// +---------------------------------------------------------------------------
// | Copyright $_SWANBR_COPYRIGHT_$
// +---------------------------------------------------------------------------
// | Version $_SWANBR_VERSION_$
// +---------------------------------------------------------------------------
// | Licensed ( $_SWANBR_LICENSED_URL_$ )
// +---------------------------------------------------------------------------
// | $_SWANBR_WEB_DOMAIN_$
// +---------------------------------------------------------------------------
namespace Kafka\Protocol;
/**
+------------------------------------------------------------------------------
* Kafka protocol for describe group api
+------------------------------------------------------------------------------
*
* @package
* @version $_SWANBR_VERSION_$
* @copyright Copyleft
* @author $_SWANBR_AUTHOR_$
+------------------------------------------------------------------------------
*/
class DescribeGroups extends Protocol
{
// {{{ functions
// {{{ public function encode()
/**
* describe group request encode
*
* @param array $payloads
* @access public
* @return string
*/
public function encode($payloads)
{
if (!is_array($payloads)) {
$payloads = array($payloads);
}
$header = $this->requestHeader('kafka-php', self::DESCRIBE_GROUPS_REQUEST, self::DESCRIBE_GROUPS_REQUEST);
$data = self::encodeArray($payloads, array($this, 'encodeString'), self::PACK_INT16);
$data = self::encodeString($header . $data, self::PACK_INT32);
return $data;
}
// }}}
// {{{ public function decode()
/**
* decode group response
*
* @access public
* @return array
*/
public function decode($data)
{
$offset = 0;
$groups = $this->decodeArray(substr($data, $offset), array($this, 'describeGroup'));
$offset += $groups['length'];
return $groups['data'];
}
// }}}
// {{{ protected function describeGroup()
/**
* decode describe group response
*
* @access protected
* @return array
*/
protected function describeGroup($data)
{
$offset = 0;
$errorCode = self::unpack(self::BIT_B16_SIGNED, substr($data, $offset, 2));
$offset += 2;
$groupId = $this->decodeString(substr($data, $offset), self::BIT_B16);
$offset += $groupId['length'];
$state = $this->decodeString(substr($data, $offset), self::BIT_B16);
$offset += $state['length'];
$protocolType = $this->decodeString(substr($data, $offset), self::BIT_B16);
$offset += $protocolType['length'];
$protocol = $this->decodeString(substr($data, $offset), self::BIT_B16);
$offset += $protocol['length'];
$members = $this->decodeArray(substr($data, $offset), array($this, 'describeMember'));
$offset += $members['length'];
return array(
'length' => $offset,
'data' => array(
'errorCode' => $errorCode,
'groupId' => $groupId['data'],
'state' => $state['data'],
'protocolType' => $protocolType['data'],
'protocol' => $protocol['data'],
'members' => $members['data']
)
);
}
// }}}
// {{{ protected function describeMember()
/**
* decode describe members response
*
* @access protected
* @return array
*/
protected function describeMember($data)
{
$offset = 0;
$memberId = $this->decodeString(substr($data, $offset), self::BIT_B16);
$offset += $memberId['length'];
$clientId = $this->decodeString(substr($data, $offset), self::BIT_B16);
$offset += $clientId['length'];
$clientHost = $this->decodeString(substr($data, $offset), self::BIT_B16);
$offset += $clientHost['length'];
$metadata = $this->decodeString(substr($data, $offset), self::BIT_B32);
$offset += $metadata['length'];
$assignment = $this->decodeString(substr($data, $offset), self::BIT_B32);
$offset += $assignment['length'];
$memberAssignment = $assignment['data'];
$memberAssignmentOffset = 0;
$version = self::unpack(self::BIT_B16_SIGNED, substr($memberAssignment, $memberAssignmentOffset, 2));
$memberAssignmentOffset += 2;
$partitionAssignments = $this->decodeArray(substr($memberAssignment, $memberAssignmentOffset),
array($this, 'describeResponsePartition'));
$memberAssignmentOffset += $partitionAssignments['length'];
$userData = $this->decodeString(substr($memberAssignment, $memberAssignmentOffset), self::BIT_B32);
$metaData = $metadata['data'];
$metaOffset = 0;
$version = self::unpack(self::BIT_B16, substr($metaData, $metaOffset, 2));
$metaOffset += 2;
$topics = $this->decodeArray(substr($metaData, $metaOffset), array($this, 'decodeString'), self::BIT_B16);
$metaOffset += $topics['length'];
$metaUserData = $this->decodeString(substr($metaData, $metaOffset), self::BIT_B32);
return array(
'length' => $offset,
'data' => array(
'memberId' => $memberId['data'],
'clientId' => $clientId['data'],
'clientHost' => $clientHost['data'],
'metadata' => array(
'version' => $version,
'topics' => $topics['data'],
'userData' => $metaUserData['data'],
),
'assignment' => array(
'version' => $version,
'partitions' => $partitionAssignments['data'],
'userData' => $userData['data']
)
)
);
}
// }}}
// {{{ protected function describeResponsePartition()
/**
* decode describe group partition response
*
* @access protected
* @return array
*/
protected function describeResponsePartition($data)
{
$offset = 0;
$topicName = $this->decodeString(substr($data, $offset), self::BIT_B16);
$offset += $topicName['length'];
$partitions = $this->decodePrimitiveArray(substr($data, $offset), self::BIT_B32);
$offset += $partitions['length'];
return array(
'length' => $offset,
'data' => array(
'topicName' => $topicName['data'],
'partitions' => $partitions['data'],
)
);
}
// }}}
// }}}
}
+377
View File
@@ -0,0 +1,377 @@
<?php
/* vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4 foldmethod=marker: */
// +---------------------------------------------------------------------------
// | SWAN [ $_SWANBR_SLOGAN_$ ]
// +---------------------------------------------------------------------------
// | Copyright $_SWANBR_COPYRIGHT_$
// +---------------------------------------------------------------------------
// | Version $_SWANBR_VERSION_$
// +---------------------------------------------------------------------------
// | Licensed ( $_SWANBR_LICENSED_URL_$ )
// +---------------------------------------------------------------------------
// | $_SWANBR_WEB_DOMAIN_$
// +---------------------------------------------------------------------------
namespace Kafka\Protocol;
/**
+------------------------------------------------------------------------------
* Kafka protocol for consumer fetch api
+------------------------------------------------------------------------------
*
* @package
* @version $_SWANBR_VERSION_$
* @copyright Copyleft
* @author $_SWANBR_AUTHOR_$
+------------------------------------------------------------------------------
*/
class Fetch extends Protocol
{
// {{{ functions
// {{{ public function encode()
/**
* consumer fetch request encode
*
* @param array $payloads
* @access public
* @return string
*/
public function encode($payloads)
{
if (!isset($payloads['data'])) {
throw new \Kafka\Exception\Protocol('given fetch kafka data invalid. `data` is undefined.');
}
if (!isset($payloads['replica_id'])) {
$payloads['replica_id'] = -1;
}
if (!isset($payloads['max_wait_time'])) {
$payloads['max_wait_time'] = 100; // default timeout 100ms
}
if (!isset($payloads['min_bytes'])) {
$payloads['min_bytes'] = 64 * 1024; // 64k
}
$header = $this->requestHeader('kafka-php', self::FETCH_REQUEST, self::FETCH_REQUEST);
$data = self::pack(self::BIT_B32, $payloads['replica_id']);
$data .= self::pack(self::BIT_B32, $payloads['max_wait_time']);
$data .= self::pack(self::BIT_B32, $payloads['min_bytes']);
$data .= self::encodeArray($payloads['data'], array($this, 'encodeFetchTopic'));
$data = self::encodeString($header . $data, self::PACK_INT32);
return $data;
}
// }}}
// {{{ public function decode()
/**
* decode fetch response
*
* @access public
* @return array
*/
public function decode($data)
{
$offset = 0;
$version = $this->getApiVersion(self::FETCH_REQUEST);
$throttleTime = 0;
if ($version != self::API_VERSION0) {
$throttleTime = self::unpack(self::BIT_B32, substr($data, $offset, 4));
$offset += 4;
}
$topics = $this->decodeArray(substr($data, $offset), array($this, 'fetchTopic'), $version);
$offset += $topics['length'];
return array(
'throttleTime' => $throttleTime,
'topics' => $topics['data'],
);
}
// }}}
// {{{ protected function fetchTopic()
/**
* decode fetch topic response
*
* @access protected
* @return array
*/
protected function fetchTopic($data, $version)
{
$offset = 0;
$topicInfo = $this->decodeString(substr($data, $offset), self::BIT_B16);
$offset += $topicInfo['length'];
$partitions = $this->decodeArray(substr($data, $offset), array($this, 'fetchPartition'), $version);
$offset += $partitions['length'];
return array(
'length' => $offset,
'data' => array(
'topicName' => $topicInfo['data'],
'partitions' => $partitions['data'],
)
);
}
// }}}
// {{{ protected function fetchPartition()
/**
* decode fetch partition response
*
* @access protected
* @return array
*/
protected function fetchPartition($data, $version)
{
$offset = 0;
$partitionId = self::unpack(self::BIT_B32, substr($data, $offset, 4));
$offset += 4;
$errorCode = self::unpack(self::BIT_B16_SIGNED, substr($data, $offset, 2));
$offset += 2;
$highwaterMarkOffset = self::unpack(self::BIT_B64, substr($data, $offset, 8));
$offset += 8;
$messageSetSize = self::unpack(self::BIT_B32, substr($data, $offset, 4));
$offset += 4;
if ($offset < strlen($data) && $messageSetSize) {
$messages = $this->decodeMessageSetArray(substr($data, $offset, $messageSetSize), array($this, 'decodeMessageSet'), $messageSetSize);
$offset += $messages['length'];
}
return array(
'length' => $offset,
'data' => array(
'partition' => $partitionId,
'errorCode' => $errorCode,
'highwaterMarkOffset' => $highwaterMarkOffset,
'messageSetSize' => $messageSetSize,
'messages' => isset($messages['data']) ? $messages['data'] : array(),
)
);
}
// }}}
// {{{ protected function decodeMessageSetArray()
/**
* decode message Set
*
* @param array $array
* @param Callable $func
* @param null $options
* @return string
* @access protected
*/
protected function decodeMessageSetArray($data, $func, $messageSetSize = null)
{
$offset = 0;
if (!is_callable($func, false)) {
throw new \Kafka\Exception\Protocol('Decode array failed, given function is not callable.');
}
$result = array();
while ($offset < strlen($data)) {
$value = substr($data, $offset);
if (!is_null($messageSetSize)) {
$ret = call_user_func($func, $value, $messageSetSize);
} else {
$ret = call_user_func($func, $value);
}
if (!is_array($ret) && $ret === false) {
break;
}
if (!isset($ret['length']) || !isset($ret['data'])) {
throw new \Kafka\Exception\Protocol('Decode array failed, given function return format is invliad');
}
if ($ret['length'] == 0) {
continue;
}
$offset += $ret['length'];
$result[] = $ret['data'];
}
if ($offset < $messageSetSize) {
$offset = $messageSetSize;
}
return array('length' => $offset, 'data' => $result);
}
// }}}
// {{{ protected function decodeMessageSet()
/**
* decode message set
* N.B., MessageSets are not preceded by an int32 like other array elements
* in the protocol.
*
* @param array $messages
* @param int $compression
* @return string
* @access protected
*/
protected function decodeMessageSet($data)
{
if (strlen($data) <= 12) {
return false;
}
$offset = 0;
$roffset = self::unpack(self::BIT_B64, substr($data, $offset, 8));
$offset += 8;
$messageSize = self::unpack(self::BIT_B32, substr($data, $offset, 4));
$offset += 4;
$ret = $this->decodeMessage(substr($data, $offset), $messageSize);
if (!is_array($ret) && $ret == false) {
return false;
}
$offset += $ret['length'];
return array(
'length' => $offset,
'data' => array(
'offset' => $roffset,
'size' => $messageSize,
'message' => $ret['data'],
)
);
}
// }}}
// {{{ protected function decodeMessage()
/**
* decode message
* N.B., MessageSets are not preceded by an int32 like other array elements
* in the protocol.
*
* @param array $messages
* @param int $compression
* @return string
* @access protected
*/
protected function decodeMessage($data, $messageSize)
{
if (strlen($data) < $messageSize || !$messageSize) {
return false;
}
$offset = 0;
$crc = self::unpack(self::BIT_B32, substr($data, $offset, 4));
$offset += 4;
$magic = self::unpack(self::BIT_B8, substr($data, $offset, 1));
$offset += 1;
$attr = self::unpack(self::BIT_B8, substr($data, $offset, 1));
$offset += 1;
$timestamp = 0;
$backOffset = $offset;
try { // try unpack message format v0 and v1, if use v1 unpack fail, try unpack v0
$version = $this->getApiVersion(self::FETCH_REQUEST);
if ($version == self::API_VERSION2) {
$timestamp = self::unpack(self::BIT_B64, substr($data, $offset, 8));
$offset += 8;
}
$keyRet = $this->decodeString(substr($data, $offset), self::BIT_B32);
$offset += $keyRet['length'];
$valueRet = $this->decodeString(substr($data, $offset), self::BIT_B32);
$offset += $valueRet['length'];
if ($offset != $messageSize) {
throw new \Kafka\Exception('pack message fail, message len:' . $messageSize . ' , data unpack offset :' . $offset);
}
} catch (\Kafka\Exception $e) { // try unpack message format v0
$offset = $backOffset;
$timestamp = 0;
$keyRet = $this->decodeString(substr($data, $offset), self::BIT_B32);
$offset += $keyRet['length'];
$valueRet = $this->decodeString(substr($data, $offset), self::BIT_B32);
$offset += $valueRet['length'];
}
return array(
'length' => $offset,
'data' => array(
'crc' => $crc,
'magic' => $magic,
'attr' => $attr,
'timestamp' => $timestamp,
'key' => $keyRet['data'],
'value' => $valueRet['data'],
)
);
}
// }}}
// {{{ protected function encodeFetchPartion()
/**
* encode signal part
*
* @param partions
* @access protected
* @return string
*/
protected function encodeFetchPartion($values)
{
if (!isset($values['partition_id'])) {
throw new \Kafka\Exception\Protocol('given fetch data invalid. `partition_id` is undefined.');
}
if (!isset($values['offset'])) {
$values['offset'] = 0;
}
if (!isset($values['max_bytes'])) {
$values['max_bytes'] = 2 * 1024 * 1024;
}
$data = self::pack(self::BIT_B32, $values['partition_id']);
$data .= self::pack(self::BIT_B64, $values['offset']);
$data .= self::pack(self::BIT_B32, $values['max_bytes']);
return $data;
}
// }}}
// {{{ protected function encodeFetchTopic()
/**
* encode signal topic
*
* @param partions
* @access protected
* @return string
*/
protected function encodeFetchTopic($values)
{
if (!isset($values['topic_name'])) {
throw new \Kafka\Exception\Protocol('given fetch data invalid. `topic_name` is undefined.');
}
if (!isset($values['partitions']) || empty($values['partitions'])) {
throw new \Kafka\Exception\Protocol('given fetch data invalid. `partitions` is undefined.');
}
$topic = self::encodeString($values['topic_name'], self::PACK_INT16);
$partitions = self::encodeArray($values['partitions'], array($this, 'encodeFetchPartion'));
return $topic . $partitions;
}
// }}}
// }}}
}
+185
View File
@@ -0,0 +1,185 @@
<?php
/* vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4 foldmethod=marker: */
// +---------------------------------------------------------------------------
// | SWAN [ $_SWANBR_SLOGAN_$ ]
// +---------------------------------------------------------------------------
// | Copyright $_SWANBR_COPYRIGHT_$
// +---------------------------------------------------------------------------
// | Version $_SWANBR_VERSION_$
// +---------------------------------------------------------------------------
// | Licensed ( $_SWANBR_LICENSED_URL_$ )
// +---------------------------------------------------------------------------
// | $_SWANBR_WEB_DOMAIN_$
// +---------------------------------------------------------------------------
namespace Kafka\Protocol;
/**
+------------------------------------------------------------------------------
* Kafka protocol for fetch offset api
+------------------------------------------------------------------------------
*
* @package
* @version $_SWANBR_VERSION_$
* @copyright Copyleft
* @author $_SWANBR_AUTHOR_$
+------------------------------------------------------------------------------
*/
class FetchOffset extends Protocol
{
// {{{ functions
// {{{ public function encode()
/**
* fetch offset request encode
*
* @param array $payloads
* @access public
* @return string
*/
public function encode($payloads)
{
if (!isset($payloads['data'])) {
throw new \Kafka\Exception\Protocol('given fetch offset data invalid. `data` is undefined.');
}
if (!isset($payloads['group_id'])) {
throw new \Kafka\Exception\Protocol('given fetch offset data invalid. `group_id` is undefined.');
}
$header = $this->requestHeader('kafka-php', self::OFFSET_FETCH_REQUEST, self::OFFSET_FETCH_REQUEST);
$data = self::encodeString($payloads['group_id'], self::PACK_INT16);
$data .= self::encodeArray($payloads['data'], array($this, 'encodeOffsetTopic'));
$data = self::encodeString($header . $data, self::PACK_INT32);
return $data;
}
// }}}
// {{{ public function decode()
/**
* decode group response
*
* @param byte[] $data
* @access public
* @return array
*/
public function decode($data)
{
$offset = 0;
$version = $this->getApiVersion(self::OFFSET_REQUEST);
$topics = $this->decodeArray(substr($data, $offset), array($this, 'offsetTopic'), $version);
$offset += $topics['length'];
return $topics['data'];
}
// }}}
// {{{ protected function encodeOffsetPartion()
/**
* encode signal part
*
* @param byte[] values partions data
* @access protected
* @return string
*/
protected function encodeOffsetPartion($values)
{
return self::pack(self::BIT_B32, $values);
}
// }}}
// {{{ protected function encodeOffsetTopic()
/**
* encode signal topic
*
* @param partions
* @access protected
* @return string
*/
protected function encodeOffsetTopic($values)
{
if (!isset($values['topic_name'])) {
throw new \Kafka\Exception\Protocol('given fetch offset data invalid. `topic_name` is undefined.');
}
if (!isset($values['partitions']) || empty($values['partitions'])) {
throw new \Kafka\Exception\Protocol('given fetch offset data invalid. `partitions` is undefined.');
}
$topic = self::encodeString($values['topic_name'], self::PACK_INT16);
$partitions = self::encodeArray($values['partitions'], array($this, 'encodeOffsetPartion'));
return $topic . $partitions;
}
// }}}
// {{{ protected function offsetTopic()
/**
* decode offset topic response
*
* @access protected
* @return array
*/
protected function offsetTopic($data, $version)
{
$offset = 0;
$topicInfo = $this->decodeString(substr($data, $offset), self::BIT_B16);
$offset += $topicInfo['length'];
$partitions = $this->decodeArray(substr($data, $offset), array($this, 'offsetPartition'), $version);
$offset += $partitions['length'];
return array(
'length' => $offset,
'data' => array(
'topicName' => $topicInfo['data'],
'partitions' => $partitions['data'],
)
);
}
// }}}
// {{{ protected function offsetPartition()
/**
* decode offset partition response
*
* @access protected
* @return array
*/
protected function offsetPartition($data, $version)
{
$offset = 0;
$partitionId = self::unpack(self::BIT_B32, substr($data, $offset, 4));
$offset += 4;
$roffset = self::unpack(self::BIT_B64, substr($data, $offset, 8));
$offset += 8;
$metadata = $this->decodeString(substr($data, $offset), self::BIT_B16);
$offset += $metadata['length'];
$errorCode = self::unpack(self::BIT_B16_SIGNED, substr($data, $offset, 2));
$offset += 2;
return array(
'length' => $offset,
'data' => array(
'partition' => $partitionId,
'errorCode' => $errorCode,
'metadata' => $metadata['data'],
'offset' => $roffset,
)
);
}
// }}}
// }}}
}
+85
View File
@@ -0,0 +1,85 @@
<?php
/* vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4 foldmethod=marker: */
// +---------------------------------------------------------------------------
// | SWAN [ $_SWANBR_SLOGAN_$ ]
// +---------------------------------------------------------------------------
// | Copyright $_SWANBR_COPYRIGHT_$
// +---------------------------------------------------------------------------
// | Version $_SWANBR_VERSION_$
// +---------------------------------------------------------------------------
// | Licensed ( $_SWANBR_LICENSED_URL_$ )
// +---------------------------------------------------------------------------
// | $_SWANBR_WEB_DOMAIN_$
// +---------------------------------------------------------------------------
namespace Kafka\Protocol;
/**
+------------------------------------------------------------------------------
* Kafka protocol for group coordinator api
+------------------------------------------------------------------------------
*
* @package
* @version $_SWANBR_VERSION_$
* @copyright Copyleft
* @author $_SWANBR_AUTHOR_$
+------------------------------------------------------------------------------
*/
class GroupCoordinator extends Protocol
{
// {{{ functions
// {{{ public function encode()
/**
* group coordinator request encode
*
* @param array $payloads
* @access public
* @return string
*/
public function encode($payloads)
{
if (!isset($payloads['group_id'])) {
throw new \Kafka\Exception\Protocol('given group coordinator invalid. `group_id` is undefined.');
}
$header = $this->requestHeader('kafka-php', self::GROUP_COORDINATOR_REQUEST, self::GROUP_COORDINATOR_REQUEST);
$data = self::encodeString($payloads['group_id'], self::PACK_INT16);
$data = self::encodeString($header . $data, self::PACK_INT32);
return $data;
}
// }}}
// {{{ public function decode()
/**
* decode group response
*
* @access public
* @return array
*/
public function decode($data)
{
$offset = 0;
$errorCode = self::unpack(self::BIT_B16_SIGNED, substr($data, $offset, 2));
$offset += 2;
$coordinatorId = self::unpack(self::BIT_B32, substr($data, $offset, 4));
$offset += 4;
$hosts = $this->decodeString(substr($data, $offset), self::BIT_B16);
$offset += $hosts['length'];
$coordinatorPort = self::unpack(self::BIT_B32, substr($data, $offset, 4));
$offset += 4;
return array(
'errorCode' => $errorCode,
'coordinatorId' => $coordinatorId,
'coordinatorHost' => $hosts['data'],
'coordinatorPort' => $coordinatorPort
);
}
// }}}
// }}}
}
+85
View File
@@ -0,0 +1,85 @@
<?php
/* vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4 foldmethod=marker: */
// +---------------------------------------------------------------------------
// | SWAN [ $_SWANBR_SLOGAN_$ ]
// +---------------------------------------------------------------------------
// | Copyright $_SWANBR_COPYRIGHT_$
// +---------------------------------------------------------------------------
// | Version $_SWANBR_VERSION_$
// +---------------------------------------------------------------------------
// | Licensed ( $_SWANBR_LICENSED_URL_$ )
// +---------------------------------------------------------------------------
// | $_SWANBR_WEB_DOMAIN_$
// +---------------------------------------------------------------------------
namespace Kafka\Protocol;
/**
+------------------------------------------------------------------------------
* Kafka protocol for heart beat api
+------------------------------------------------------------------------------
*
* @package
* @version $_SWANBR_VERSION_$
* @copyright Copyleft
* @author $_SWANBR_AUTHOR_$
+------------------------------------------------------------------------------
*/
class Heartbeat extends Protocol
{
// {{{ functions
// {{{ public function encode()
/**
* heartbeat request encode
*
* @param array $payloads
* @access public
* @return string
*/
public function encode($payloads)
{
if (!isset($payloads['group_id'])) {
throw new \Kafka\Exception\Protocol('given heartbeat data invalid. `group_id` is undefined.');
}
if (!isset($payloads['generation_id'])) {
throw new \Kafka\Exception\Protocol('given heartbeat data invalid. `generation_id` is undefined.');
}
if (!isset($payloads['member_id'])) {
throw new \Kafka\Exception\Protocol('given heartbeat data invalid. `member_id` is undefined.');
}
$header = $this->requestHeader('kafka-php', self::HEART_BEAT_REQUEST, self::HEART_BEAT_REQUEST);
$data = self::encodeString($payloads['group_id'], self::PACK_INT16);
$data .= self::pack(self::BIT_B32, $payloads['generation_id']);
$data .= self::encodeString($payloads['member_id'], self::PACK_INT16);
$data = self::encodeString($header . $data, self::PACK_INT32);
return $data;
}
// }}}
// {{{ public function decode()
/**
* decode heart beat response
*
* @access public
* @return array
*/
public function decode($data)
{
$offset = 0;
$errorCode = self::unpack(self::BIT_B16_SIGNED, substr($data, $offset, 2));
$offset += 2;
return array(
'errorCode' => $errorCode,
);
}
// }}}
// }}}
}
+204
View File
@@ -0,0 +1,204 @@
<?php
/* vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4 foldmethod=marker: */
// +---------------------------------------------------------------------------
// | SWAN [ $_SWANBR_SLOGAN_$ ]
// +---------------------------------------------------------------------------
// | Copyright $_SWANBR_COPYRIGHT_$
// +---------------------------------------------------------------------------
// | Version $_SWANBR_VERSION_$
// +---------------------------------------------------------------------------
// | Licensed ( $_SWANBR_LICENSED_URL_$ )
// +---------------------------------------------------------------------------
// | $_SWANBR_WEB_DOMAIN_$
// +---------------------------------------------------------------------------
namespace Kafka\Protocol;
/**
+------------------------------------------------------------------------------
* Kafka protocol for join group api
+------------------------------------------------------------------------------
*
* @package
* @version $_SWANBR_VERSION_$
* @copyright Copyleft
* @author $_SWANBR_AUTHOR_$
+------------------------------------------------------------------------------
*/
class JoinGroup extends Protocol
{
// {{{ functions
// {{{ public function encode()
/**
* join group request encode
*
* @param array $payloads
* @access public
* @return string
*/
public function encode($payloads)
{
if (!isset($payloads['group_id'])) {
throw new \Kafka\Exception\Protocol('given join group data invalid. `group_id` is undefined.');
}
if (!isset($payloads['session_timeout'])) {
throw new \Kafka\Exception\Protocol('given join group data invalid. `session_timeout` is undefined.');
}
if (!isset($payloads['member_id'])) {
throw new \Kafka\Exception\Protocol('given join group data invalid. `member_id` is undefined.');
}
if (!isset($payloads['data'])) {
throw new \Kafka\Exception\Protocol('given join group data invalid. `data` is undefined.');
}
if (!isset($payloads['protocol_type'])) {
$payloads['protocol_type'] = 'consumer';
}
if (!isset($payloads['rebalance_timeout'])) {
$payloads['rebalance_timeout'] = $payloads['session_timeout'];
}
$header = $this->requestHeader('kafka-php', self::JOIN_GROUP_REQUEST, self::JOIN_GROUP_REQUEST);
$data = self::encodeString($payloads['group_id'], self::PACK_INT16);
$data .= self::pack(self::BIT_B32, $payloads['session_timeout']);
if ($this->getApiVersion(self::JOIN_GROUP_REQUEST) == self::API_VERSION1) {
$data .= self::pack(self::BIT_B32, $payloads['rebalance_timeout']);
}
$data .= self::encodeString($payloads['member_id'], self::PACK_INT16);
$data .= self::encodeString($payloads['protocol_type'], self::PACK_INT16);
$data .= self::encodeArray($payloads['data'], array($this, 'encodeGroupProtocol'));
$data = self::encodeString($header . $data, self::PACK_INT32);
return $data;
}
// }}}
// {{{ public function decode()
/**
* decode join group response
*
* @access public
* @return array
*/
public function decode($data)
{
$offset = 0;
$errorCode = self::unpack(self::BIT_B16_SIGNED, substr($data, $offset, 2));
$offset += 2;
$generationId = self::unpack(self::BIT_B32, substr($data, $offset, 4));
$offset += 4;
$groupProtocol = $this->decodeString(substr($data, $offset), self::BIT_B16);
$offset += $groupProtocol['length'];
$leaderId = $this->decodeString(substr($data, $offset), self::BIT_B16);
$offset += $leaderId['length'];
$memberId = $this->decodeString(substr($data, $offset), self::BIT_B16);
$offset += $memberId['length'];
$members = $this->decodeArray(substr($data, $offset), array($this, 'joinGroupMember'));
$offset += $memberId['length'];
return array(
'errorCode' => $errorCode,
'generationId' => $generationId,
'groupProtocol' => $groupProtocol['data'],
'leaderId' => $leaderId['data'],
'memberId' => $memberId['data'],
'members' => $members['data'],
);
}
// }}}
// {{{ protected function encodeGroupProtocol()
/**
* encode group protocol
*
* @param partions
* @access protected
* @return string
*/
protected function encodeGroupProtocol($values)
{
if (!isset($values['protocol_name'])) {
throw new \Kafka\Exception\Protocol('given join group data invalid. `protocol_name` is undefined.');
}
$protocolName = self::encodeString($values['protocol_name'], self::PACK_INT16);
if (!isset($values['version'])) {
throw new \Kafka\Exception\Protocol('given data invalid. `version` is undefined.');
}
if (!isset($values['subscription']) || empty($values['subscription'])) {
throw new \Kafka\Exception\Protocol('given data invalid. `subscription` is undefined.');
}
if (!isset($values['user_data'])) {
$values['user_data'] = '';
}
$data = self::pack(self::BIT_B16, 0);
$data .= self::encodeArray($values['subscription'], array($this, 'encodeGroupProtocolMetaTopic'));
$data .= self::encodeString($values['user_data'], self::PACK_INT32);
return $protocolName . self::encodeString($data, self::PACK_INT32);
}
// }}}
// {{{ protected function encodeGroupProtocolMetaTopic()
/**
* encode group protocol metadata topic
*
* @param partions
* @access protected
* @return string
*/
protected function encodeGroupProtocolMetaTopic($values)
{
$topic = self::encodeString($values, self::PACK_INT16);
return $topic;
}
// }}}
// {{{ protected function joinGroupMember()
/**
* decode join group member response
*
* @access protected
* @return array
*/
protected function joinGroupMember($data)
{
$offset = 0;
$memberId = $this->decodeString(substr($data, $offset), self::BIT_B16);
$offset += $memberId['length'];
$memberMeta = $this->decodeString(substr($data, $offset), self::BIT_B32);
$offset += $memberMeta['length'];
$metaData = $memberMeta['data'];
$metaOffset = 0;
$version = self::unpack(self::BIT_B16, substr($metaData, $metaOffset, 2));
$metaOffset += 2;
$topics = $this->decodeArray(substr($metaData, $metaOffset), array($this, 'decodeString'), self::BIT_B16);
$metaOffset += $topics['length'];
$userData = $this->decodeString(substr($metaData, $metaOffset), self::BIT_B32);
return array(
'length' => $offset,
'data' => array(
'memberId' => $memberId['data'],
'memberMeta' => array(
'version' => $version,
'topics' => $topics['data'],
'userData' => $userData['data'],
),
)
);
}
// }}}
// }}}
}
+79
View File
@@ -0,0 +1,79 @@
<?php
/* vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4 foldmethod=marker: */
// +---------------------------------------------------------------------------
// | SWAN [ $_SWANBR_SLOGAN_$ ]
// +---------------------------------------------------------------------------
// | Copyright $_SWANBR_COPYRIGHT_$
// +---------------------------------------------------------------------------
// | Version $_SWANBR_VERSION_$
// +---------------------------------------------------------------------------
// | Licensed ( $_SWANBR_LICENSED_URL_$ )
// +---------------------------------------------------------------------------
// | $_SWANBR_WEB_DOMAIN_$
// +---------------------------------------------------------------------------
namespace Kafka\Protocol;
/**
+------------------------------------------------------------------------------
* Kafka protocol for leave group api
+------------------------------------------------------------------------------
*
* @package
* @version $_SWANBR_VERSION_$
* @copyright Copyleft
* @author $_SWANBR_AUTHOR_$
+------------------------------------------------------------------------------
*/
class LeaveGroup extends Protocol
{
// {{{ functions
// {{{ public function encode()
/**
* leave group request encode
*
* @param array $payloads
* @access public
* @return string
*/
public function encode($payloads)
{
if (!isset($payloads['group_id'])) {
throw new \Kafka\Exception\Protocol('given leave group data invalid. `group_id` is undefined.');
}
if (!isset($payloads['member_id'])) {
throw new \Kafka\Exception\Protocol('given leave group data invalid. `member_id` is undefined.');
}
$header = $this->requestHeader('kafka-php', self::LEAVE_GROUP_REQUEST, self::LEAVE_GROUP_REQUEST);
$data = self::encodeString($payloads['group_id'], self::PACK_INT16);
$data .= self::encodeString($payloads['member_id'], self::PACK_INT16);
$data = self::encodeString($header . $data, self::PACK_INT32);
return $data;
}
// }}}
// {{{ public function decode()
/**
* decode group response
*
* @access public
* @return array
*/
public function decode($data)
{
$errorCode = self::unpack(self::BIT_B16_SIGNED, substr($data, 0, 2));
return array(
'errorCode' => $errorCode,
);
}
// }}}
// }}}
}
+99
View File
@@ -0,0 +1,99 @@
<?php
/* vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4 foldmethod=marker: */
// +---------------------------------------------------------------------------
// | SWAN [ $_SWANBR_SLOGAN_$ ]
// +---------------------------------------------------------------------------
// | Copyright $_SWANBR_COPYRIGHT_$
// +---------------------------------------------------------------------------
// | Version $_SWANBR_VERSION_$
// +---------------------------------------------------------------------------
// | Licensed ( $_SWANBR_LICENSED_URL_$ )
// +---------------------------------------------------------------------------
// | $_SWANBR_WEB_DOMAIN_$
// +---------------------------------------------------------------------------
namespace Kafka\Protocol;
/**
+------------------------------------------------------------------------------
* Kafka protocol for list group api
+------------------------------------------------------------------------------
*
* @package
* @version $_SWANBR_VERSION_$
* @copyright Copyleft
* @author $_SWANBR_AUTHOR_$
+------------------------------------------------------------------------------
*/
class ListGroup extends Protocol
{
// {{{ functions
// {{{ public function encode()
/**
* list group request encode
*
* @param array $payloads
* @access public
* @return string
*/
public function encode($payloads)
{
$header = $this->requestHeader('kafka-php', self::LIST_GROUPS_REQUEST, self::LIST_GROUPS_REQUEST);
$data = self::encodeString($header, self::PACK_INT32);
return $data;
}
// }}}
// {{{ public function decode()
/**
* decode group response
*
* @access public
* @return array
*/
public function decode($data)
{
$offset = 0;
$errorCode = self::unpack(self::BIT_B16_SIGNED, substr($data, $offset, 2));
$offset += 2;
$groups = $this->decodeArray(substr($data, $offset), array($this, 'listGroup'));
return array(
'errorCode' => $errorCode,
'groups' => $groups['data'],
);
}
// }}}
// {{{ protected function listGroup()
/**
* decode list group response
*
* @access protected
* @return array
*/
protected function listGroup($data)
{
$offset = 0;
$groupId = $this->decodeString(substr($data, $offset), self::BIT_B16);
$offset += $groupId['length'];
$protocolType = $this->decodeString(substr($data, $offset), self::BIT_B16);
$offset += $protocolType['length'];
return array(
'length' => $offset,
'data' => array(
'groupId' => $groupId['data'],
'protocolType' => $protocolType['data'],
)
);
}
// }}}
// }}}
}
+179
View File
@@ -0,0 +1,179 @@
<?php
/* vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4 foldmethod=marker: */
// +---------------------------------------------------------------------------
// | SWAN [ $_SWANBR_SLOGAN_$ ]
// +---------------------------------------------------------------------------
// | Copyright $_SWANBR_COPYRIGHT_$
// +---------------------------------------------------------------------------
// | Version $_SWANBR_VERSION_$
// +---------------------------------------------------------------------------
// | Licensed ( $_SWANBR_LICENSED_URL_$ )
// +---------------------------------------------------------------------------
// | $_SWANBR_WEB_DOMAIN_$
// +---------------------------------------------------------------------------
namespace Kafka\Protocol;
/**
+------------------------------------------------------------------------------
* Kafka protocol for meta data api
+------------------------------------------------------------------------------
*
* @package
* @version $_SWANBR_VERSION_$
* @copyright Copyleft
* @author $_SWANBR_AUTHOR_$
+------------------------------------------------------------------------------
*/
class Metadata extends Protocol
{
// {{{ functions
// {{{ public function encode()
/**
* meta data request encode
*
* @param array $payloads
* @access public
* @return string
*/
public function encode($topics)
{
if (!is_array($topics)) {
$topics = array($topics);
}
foreach ($topics as $topic) {
if (!is_string($topic)) {
throw new \Kafka\Exception\Protocol('request metadata topic array have invalid value. ');
}
}
$header = $this->requestHeader('kafka-php', self::METADATA_REQUEST, self::METADATA_REQUEST);
$data = self::encodeArray($topics, array($this, 'encodeString'), self::PACK_INT16);
$data = self::encodeString($header . $data, self::PACK_INT32);
return $data;
}
// }}}
// {{{ public function decode()
/**
* decode metadata response
*
* @access public
* @return array
*/
public function decode($data)
{
$offset = 0;
$version = $this->getApiVersion(self::METADATA_REQUEST);
$brokerRet = $this->decodeArray(substr($data, $offset), array($this, 'metaBroker'), $version);
$offset += $brokerRet['length'];
$topicMetaRet = $this->decodeArray(substr($data, $offset), array($this, 'metaTopicMetaData'), $version);
$offset += $topicMetaRet['length'];
$result = array(
'brokers' => $brokerRet['data'],
'topics' => $topicMetaRet['data'],
);
return $result;
}
// }}}
// {{{ protected function metaBroker()
/**
* decode meta broker response
*
* @access protected
* @return array
*/
protected function metaBroker($data, $version)
{
$offset = 0;
$nodeId = self::unpack(self::BIT_B32, substr($data, $offset, 4));
$offset += 4;
$hostNameInfo = $this->decodeString(substr($data, $offset), self::BIT_B16);
$offset += $hostNameInfo['length'];
$port = self::unpack(self::BIT_B32, substr($data, $offset, 4));
$offset += 4;
return array(
'length' => $offset,
'data' => array(
'host' => $hostNameInfo['data'],
'port' => $port,
'nodeId' => $nodeId
)
);
}
// }}}
// {{{ protected function metaTopicMetaData()
/**
* decode meta topic meta data response
*
* @access protected
* @return array
*/
protected function metaTopicMetaData($data, $version)
{
$offset = 0;
$topicErrCode = self::unpack(self::BIT_B16_SIGNED, substr($data, $offset, 2));
$offset += 2;
$topicInfo = $this->decodeString(substr($data, $offset), self::BIT_B16);
$offset += $topicInfo['length'];
$partionsMetaRet = $this->decodeArray(substr($data, $offset), array($this, 'metaPartitionMetaData'), $version);
$offset += $partionsMetaRet['length'];
return array(
'length' => $offset,
'data' => array(
'topicName' => $topicInfo['data'],
'errorCode' => $topicErrCode,
'partitions' => $partionsMetaRet['data'],
)
);
}
// }}}
// {{{ protected function metaPartitionMetaData()
/**
* decode meta partition meta data response
*
* @access protected
* @return array
*/
protected function metaPartitionMetaData($data, $version)
{
$offset = 0;
$errcode = self::unpack(self::BIT_B16_SIGNED, substr($data, $offset, 2));
$offset += 2;
$partId = self::unpack(self::BIT_B32, substr($data, $offset, 4));
$offset += 4;
$leader = self::unpack(self::BIT_B32, substr($data, $offset, 4));
$offset += 4;
$replicas = $this->decodePrimitiveArray(substr($data, $offset), self::BIT_B32);
$offset += $replicas['length'];
$isr = $this->decodePrimitiveArray(substr($data, $offset), self::BIT_B32);
$offset += $isr['length'];
return array(
'length' => $offset,
'data' => array(
'partitionId' => $partId,
'errorCode' => $errcode,
'replicas' => $replicas['data'],
'leader' => $leader,
'isr' => $isr['data'],
)
);
}
// }}}
// }}}
}
+203
View File
@@ -0,0 +1,203 @@
<?php
/* vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4 foldmethod=marker: */
// +---------------------------------------------------------------------------
// | SWAN [ $_SWANBR_SLOGAN_$ ]
// +---------------------------------------------------------------------------
// | Copyright $_SWANBR_COPYRIGHT_$
// +---------------------------------------------------------------------------
// | Version $_SWANBR_VERSION_$
// +---------------------------------------------------------------------------
// | Licensed ( $_SWANBR_LICENSED_URL_$ )
// +---------------------------------------------------------------------------
// | $_SWANBR_WEB_DOMAIN_$
// +---------------------------------------------------------------------------
namespace Kafka\Protocol;
/**
+------------------------------------------------------------------------------
* Kafka protocol for offset api
+------------------------------------------------------------------------------
*
* @package
* @version $_SWANBR_VERSION_$
* @copyright Copyleft
* @author $_SWANBR_AUTHOR_$
+------------------------------------------------------------------------------
*/
class Offset extends Protocol
{
// {{{ functions
// {{{ public function encode()
/**
* offset request encode
*
* @param array $payloads
* @access public
* @return string
*/
public function encode($payloads)
{
if (!isset($payloads['data'])) {
throw new \Kafka\Exception\Protocol('given offset data invalid. `data` is undefined.');
}
if (!isset($payloads['replica_id'])) {
$payloads['replica_id'] = -1;
}
$header = $this->requestHeader('kafka-php', self::OFFSET_REQUEST, self::OFFSET_REQUEST);
$data = self::pack(self::BIT_B32, $payloads['replica_id']);
$data .= self::encodeArray($payloads['data'], array($this, 'encodeOffsetTopic'));
$data = self::encodeString($header . $data, self::PACK_INT32);
return $data;
}
// }}}
// {{{ public function decode()
/**
* decode group response
*
* @access public
* @return array
*/
public function decode($data)
{
$offset = 0;
$version = $this->getApiVersion(self::OFFSET_REQUEST);
$topics = $this->decodeArray(substr($data, $offset), array($this, 'offsetTopic'), $version);
$offset += $topics['length'];
return $topics['data'];
}
// }}}
// {{{ protected function encodeOffsetPartion()
/**
* encode signal part
*
* @param partions
* @access protected
* @return string
*/
protected function encodeOffsetPartion($values)
{
if (!isset($values['partition_id'])) {
throw new \Kafka\Exception\Protocol('given offset data invalid. `partition_id` is undefined.');
}
if (!isset($values['time'])) {
$values['time'] = -1; // -1
}
if (!isset($values['max_offset'])) {
$values['max_offset'] = 100000;
}
$data = self::pack(self::BIT_B32, $values['partition_id']);
$data .= self::pack(self::BIT_B64, $values['time']);
if ($this->getApiVersion(self::OFFSET_REQUEST) == self::API_VERSION0) {
$data .= self::pack(self::BIT_B32, $values['max_offset']);
}
return $data;
}
// }}}
// {{{ protected function encodeOffsetTopic()
/**
* encode signal topic
*
* @param partions
* @access protected
* @return string
*/
protected function encodeOffsetTopic($values)
{
if (!isset($values['topic_name'])) {
throw new \Kafka\Exception\Protocol('given offset data invalid. `topic_name` is undefined.');
}
if (!isset($values['partitions']) || empty($values['partitions'])) {
throw new \Kafka\Exception\Protocol('given offset data invalid. `partitions` is undefined.');
}
$topic = self::encodeString($values['topic_name'], self::PACK_INT16);
$partitions = self::encodeArray($values['partitions'], array($this, 'encodeOffsetPartion'));
return $topic . $partitions;
}
// }}}
// {{{ protected function offsetTopic()
/**
* decode offset topic response
*
* @access protected
* @return array
*/
protected function offsetTopic($data, $version)
{
$offset = 0;
$topicInfo = $this->decodeString(substr($data, $offset), self::BIT_B16);
$offset += $topicInfo['length'];
$partitions = $this->decodeArray(substr($data, $offset), array($this, 'offsetPartition'), $version);
$offset += $partitions['length'];
return array(
'length' => $offset,
'data' => array(
'topicName' => $topicInfo['data'],
'partitions' => $partitions['data'],
)
);
}
// }}}
// {{{ protected function offsetPartition()
/**
* decode offset partition response
*
* @access protected
* @return array
*/
protected function offsetPartition($data, $version)
{
$offset = 0;
$partitionId = self::unpack(self::BIT_B32, substr($data, $offset, 4));
$offset += 4;
$errorCode = self::unpack(self::BIT_B16_SIGNED, substr($data, $offset, 2));
$offset += 2;
$timestamp = 0;
if ($version != self::API_VERSION0) {
$timestamp = self::unpack(self::BIT_B64, substr($data, $offset, 8));
$offset += 8;
}
$offsets = $this->decodePrimitiveArray(substr($data, $offset), self::BIT_B64);
$offset += $offsets['length'];
return array(
'length' => $offset,
'data' => array(
'partition' => $partitionId,
'errorCode' => $errorCode,
'timestamp' => $timestamp,
'offsets' => $offsets['data'],
)
);
}
// }}}
// }}}
}
+276
View File
@@ -0,0 +1,276 @@
<?php
/* vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4 foldmethod=marker: */
// +---------------------------------------------------------------------------
// | SWAN [ $_SWANBR_SLOGAN_$ ]
// +---------------------------------------------------------------------------
// | Copyright $_SWANBR_COPYRIGHT_$
// +---------------------------------------------------------------------------
// | Version $_SWANBR_VERSION_$
// +---------------------------------------------------------------------------
// | Licensed ( $_SWANBR_LICENSED_URL_$ )
// +---------------------------------------------------------------------------
// | $_SWANBR_WEB_DOMAIN_$
// +---------------------------------------------------------------------------
namespace Kafka\Protocol;
/**
+------------------------------------------------------------------------------
* Kafka protocol for produce api
+------------------------------------------------------------------------------
*
* @package
* @version $_SWANBR_VERSION_$
* @copyright Copyleft
* @author $_SWANBR_AUTHOR_$
+------------------------------------------------------------------------------
*/
class Produce extends Protocol
{
// {{{ functions
// {{{ public function encode()
/**
* produce request encode
*
* @param array $payloads
* @access public
* @return string
*/
public function encode($payloads)
{
if (!isset($payloads['data'])) {
throw new \Kafka\Exception\Protocol('given procude data invalid. `data` is undefined.');
}
if (!isset($payloads['required_ack'])) {
// default server will not send any response
// (this is the only case where the server will not reply to a request)
$payloads['required_ack'] = 0;
}
if (!isset($payloads['timeout'])) {
$payloads['timeout'] = 100; // default timeout 100ms
}
$header = $this->requestHeader('kafka-php', 0, self::PRODUCE_REQUEST);
$data = self::pack(self::BIT_B16, $payloads['required_ack']);
$data .= self::pack(self::BIT_B32, $payloads['timeout']);
$data .= self::encodeArray($payloads['data'], array($this, 'encodeProcudeTopic'), self::COMPRESSION_NONE);
$data = self::encodeString($header . $data, self::PACK_INT32);
return $data;
}
// }}}
// {{{ public function decode()
/**
* decode produce response
*
* @access public
* @return array
*/
public function decode($data)
{
$offset = 0;
$version = $this->getApiVersion(self::PRODUCE_REQUEST);
$ret = $this->decodeArray(substr($data, $offset), array($this, 'produceTopicPair'), $version);
$offset += $ret['length'];
$throttleTime = 0;
if ($version == self::API_VERSION2) {
$throttleTime = self::unpack(self::BIT_B32, substr($data, $offset, 4));
}
return array('throttleTime' => $throttleTime, 'data' => $ret['data']);
}
// }}}
// {{{ protected function encodeMessageSet()
/**
* encode message set
* N.B., MessageSets are not preceded by an int32 like other array elements
* in the protocol.
*
* @param array $messages
* @param int $compression
* @return string
* @static
* @access public
*/
protected function encodeMessageSet($messages, $compression = self::COMPRESSION_NONE)
{
if (!is_array($messages)) {
$messages = array($messages);
}
$data = '';
$next = 0;
foreach ($messages as $message) {
$tmpMessage = $this->encodeMessage($message, $compression);
// int64 -- message offset Message
//This is the offset used in kafka as the log sequence number. When the producer is sending non compressed messages, it can set the offsets to anything. When the producer is sending compressed messages, to avoid server side recompression, each compressed message should have offset starting from 0 and increasing by one for each inner message in the compressed message. (see more details about compressed messages in Kafka below)
$data .= self::pack(self::BIT_B64, $next) . self::encodeString($tmpMessage, self::PACK_INT32);
$next++;
}
return $data;
}
// }}}
// {{{ protected function encodeMessage()
/**
* encode signal message
*
* @param string $message
* @param int $compression
* @return string
* @static
* @access protected
*/
protected function encodeMessage($message, $compression = self::COMPRESSION_NONE)
{
// int8 -- magic int8 -- attribute
$version = $this->getApiVersion(self::PRODUCE_REQUEST);
$magic = ($version == self::API_VERSION2) ? self::MESSAGE_MAGIC_VERSION0 : self::MESSAGE_MAGIC_VERSION1;
$data = self::pack(self::BIT_B8, $magic);
$data .= self::pack(self::BIT_B8, $compression);
$key = '';
if (is_array($message)) {
$key = $message['key'];
$message = $message['value'];
}
// message key
$data .= self::encodeString($key, self::PACK_INT32);
// message value
$data .= self::encodeString($message, self::PACK_INT32, $compression);
$crc = crc32($data);
// int32 -- crc code string data
$message = self::pack(self::BIT_B32, $crc) . $data;
return $message;
}
// }}}
// {{{ protected function encodeProcudePartion()
/**
* encode signal part
*
* @param $values
* @param $compression
* @return string
* @internal param $partions
* @access protected
*/
protected function encodeProcudePartion($values, $compression)
{
if (!isset($values['partition_id'])) {
throw new \Kafka\Exception\Protocol('given produce data invalid. `partition_id` is undefined.');
}
if (!isset($values['messages']) || empty($values['messages'])) {
throw new \Kafka\Exception\Protocol('given produce data invalid. `messages` is undefined.');
}
$data = self::pack(self::BIT_B32, $values['partition_id']);
$data .= self::encodeString($this->encodeMessageSet($values['messages'], $compression), self::PACK_INT32);
return $data;
}
// }}}
// {{{ protected function encodeProcudeTopic()
/**
* encode signal topic
*
* @param $values
* @param $compression
* @return string
* @internal param $partions
* @access protected
*/
protected function encodeProcudeTopic($values, $compression)
{
if (!isset($values['topic_name'])) {
throw new \Kafka\Exception\Protocol('given produce data invalid. `topic_name` is undefined.');
}
if (!isset($values['partitions']) || empty($values['partitions'])) {
throw new \Kafka\Exception\Protocol('given produce data invalid. `partitions` is undefined.');
}
$topic = self::encodeString($values['topic_name'], self::PACK_INT16);
$partitions = self::encodeArray($values['partitions'], array($this, 'encodeProcudePartion'), $compression);
return $topic . $partitions;
}
// }}}
// {{{ protected function produceTopicPair()
/**
* decode produce topic pair response
*
* @access protected
* @return array
*/
protected function produceTopicPair($data, $version)
{
$offset = 0;
$topicInfo = $this->decodeString($data, self::BIT_B16);
$offset += $topicInfo['length'];
$ret = $this->decodeArray(substr($data, $offset), array($this, 'producePartitionPair'), $version);
$offset += $ret['length'];
return array('length' => $offset, 'data' => array(
'topicName' => $topicInfo['data'],
'partitions'=> $ret['data'],
));
}
// }}}
// {{{ protected function producePartitionPair()
/**
* decode produce partition pair response
*
* @access protected
* @return array
*/
protected function producePartitionPair($data, $version)
{
$offset = 0;
$partitionId = self::unpack(self::BIT_B32, substr($data, $offset, 4));
$offset += 4;
$errorCode = self::unpack(self::BIT_B16_SIGNED, substr($data, $offset, 2));
$offset += 2;
$partitionOffset = self::unpack(self::BIT_B64, substr($data, $offset, 8));
$offset += 8;
$timestamp = 0;
if ($version == self::API_VERSION2) {
$timestamp = self::unpack(self::BIT_B64, substr($data, $offset, 8));
$offset += 8;
}
return array(
'length' => $offset,
'data' => array(
'partition' => $partitionId,
'errorCode' => $errorCode,
'offset' => $offset,
'timestamp' => $timestamp,
)
);
}
// }}}
// }}}
}
+679
View File
@@ -0,0 +1,679 @@
<?php
/* vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4 foldmethod=marker: */
// +---------------------------------------------------------------------------
// | SWAN [ $_SWANBR_SLOGAN_$ ]
// +---------------------------------------------------------------------------
// | Copyright $_SWANBR_COPYRIGHT_$
// +---------------------------------------------------------------------------
// | Version $_SWANBR_VERSION_$
// +---------------------------------------------------------------------------
// | Licensed ( $_SWANBR_LICENSED_URL_$ )
// +---------------------------------------------------------------------------
// | $_SWANBR_WEB_DOMAIN_$
// +---------------------------------------------------------------------------
namespace Kafka\Protocol;
/**
+------------------------------------------------------------------------------
* Kafka protocol since Kafka v0.8
+------------------------------------------------------------------------------
*
* @package
* @version $_SWANBR_VERSION_$
* @copyright Copyleft
* @author $_SWANBR_AUTHOR_$
+------------------------------------------------------------------------------
*/
abstract class Protocol
{
use \Psr\Log\LoggerAwareTrait;
use \Kafka\LoggerTrait;
// {{{ consts
/**
* Default kafka broker verion
*/
const DEFAULT_BROKER_VERION = '0.9.0.0';
/**
* Kafka server protocol version0
*/
const API_VERSION0 = 0;
/**
* Kafka server protocol version 1
*/
const API_VERSION1 = 1;
/**
* Kafka server protocol version 2
*/
const API_VERSION2 = 2;
/**
* use encode message, This is a version id used to allow backwards
* compatible evolution of the message binary format.
*/
const MESSAGE_MAGIC_VERSION0 = 0;
/**
* use encode message, This is a version id used to allow backwards
* compatible evolution of the message binary format.
*/
const MESSAGE_MAGIC_VERSION1 = 1;
/**
* message no compression
*/
const COMPRESSION_NONE = 0;
/**
* Message using gzip compression
*/
const COMPRESSION_GZIP = 1;
/**
* Message using Snappy compression
*/
const COMPRESSION_SNAPPY = 2;
/**
* pack int32 type
*/
const PACK_INT32 = 0;
/**
* pack int16 type
*/
const PACK_INT16 = 1;
/**
* protocol request code
*/
const PRODUCE_REQUEST = 0;
const FETCH_REQUEST = 1;
const OFFSET_REQUEST = 2;
const METADATA_REQUEST = 3;
const OFFSET_COMMIT_REQUEST = 8;
const OFFSET_FETCH_REQUEST = 9;
const GROUP_COORDINATOR_REQUEST = 10;
const JOIN_GROUP_REQUEST = 11;
const HEART_BEAT_REQUEST = 12;
const LEAVE_GROUP_REQUEST = 13;
const SYNC_GROUP_REQUEST = 14;
const DESCRIBE_GROUPS_REQUEST = 15;
const LIST_GROUPS_REQUEST = 16;
// unpack/pack bit
const BIT_B64 = 'N2';
const BIT_B32 = 'N';
const BIT_B16 = 'n';
const BIT_B16_SIGNED = 's';
const BIT_B8 = 'C';
// }}}
// {{{ members
/**
* kafka broker version
*
* @var mixed
* @access protected
*/
protected $version = self::DEFAULT_BROKER_VERION;
/**
* isBigEndianSystem
*
* gets set to true if the computer this code is running is little endian,
* gets set to false if the computer this code is running on is big endian.
*
* @var null|bool
* @access private
*/
private static $isLittleEndianSystem = null;
// }}}
// {{{ functions
// {{{ public function __construct()
/**
* __construct
*
* @param string version
* @access public
*/
public function __construct($version = self::DEFAULT_BROKER_VERION)
{
$this->version = $version;
}
// }}}
// {{{ public static function Khex2bin()
/**
* hex to bin
*
* @param string $string
* @static
* @access protected
* @return string (raw)
*/
public static function Khex2bin($string)
{
if (function_exists('\hex2bin')) {
return \hex2bin($string);
} else {
$bin = '';
$len = strlen($string);
for ($i = 0; $i < $len; $i += 2) {
$bin .= pack('H*', substr($string, $i, 2));
}
return $bin;
}
}
// }}}
// {{{ public static function unpack()
/**
* Unpack a bit integer as big endian long
*
* @static
* @access public
* @param $type
* @param $bytes
* @return int
*/
public static function unpack($type, $bytes)
{
$result = array();
self::checkLen($type, $bytes);
if ($type == self::BIT_B64) {
$set = unpack($type, $bytes);
$result = ($set[1] & 0xFFFFFFFF) << 32 | ($set[2] & 0xFFFFFFFF);
} elseif ($type == self::BIT_B16_SIGNED) {
// According to PHP docs: 's' = signed short (always 16 bit, machine byte order)
// So lets unpack it..
$set = unpack($type, $bytes);
// But if our system is little endian
if (self::isSystemLittleEndian()) {
// We need to flip the endianess because coming from kafka it is big endian
$set = self::convertSignedShortFromLittleEndianToBigEndian($set);
}
$result = $set;
} else {
$result = unpack($type, $bytes);
}
return is_array($result) ? array_shift($result) : $result;
}
// }}}
// {{{ public static function pack()
/**
* pack a bit integer as big endian long
*
* @static
* @access public
* @param $type
* @param $data
* @return int
*/
public static function pack($type, $data)
{
if ($type == self::BIT_B64) {
if ($data == -1) { // -1L
$data = self::Khex2bin('ffffffffffffffff');
} elseif ($data == -2) { // -2L
$data = self::Khex2bin('fffffffffffffffe');
} else {
$left = 0xffffffff00000000;
$right = 0x00000000ffffffff;
$l = ($data & $left) >> 32;
$r = $data & $right;
$data = pack($type, $l, $r);
}
} else {
$data = pack($type, $data);
}
return $data;
}
// }}}
// {{{ protected static function checkLen()
/**
* check unpack bit is valid
*
* @param string $type
* @param string(raw) $bytes
* @static
* @access protected
* @return void
*/
protected static function checkLen($type, $bytes)
{
$len = 0;
switch ($type) {
case self::BIT_B64:
$len = 8;
break;
case self::BIT_B32:
$len = 4;
break;
case self::BIT_B16:
$len = 2;
break;
case self::BIT_B16_SIGNED:
$len = 2;
break;
case self::BIT_B8:
$len = 1;
break;
}
if (strlen($bytes) != $len) {
throw new \Kafka\Exception\Protocol('unpack failed. string(raw) length is ' . strlen($bytes) . ' , TO ' . $type);
}
}
// }}}
// {{{ public static function isSystemLittleEndian()
/**
* Determines if the computer currently running this code is big endian or little endian.
*
* @access public
* @return bool - false if big endian, true if little endian
*/
public static function isSystemLittleEndian()
{
// If we don't know if our system is big endian or not yet...
if (is_null(self::$isLittleEndianSystem)) {
// Lets find out
list($endiantest) = array_values(unpack('L1L', pack('V', 1)));
if ($endiantest != 1) {
// This is a big endian system
self::$isLittleEndianSystem = false;
} else {
// This is a little endian system
self::$isLittleEndianSystem = true;
}
}
return self::$isLittleEndianSystem;
}
// }}}
// {{{ public static function convertSignedShortFromLittleEndianToBigEndian()
/**
* Converts a signed short (16 bits) from little endian to big endian.
*
* @param int[] $bits
* @access public
* @return array
*/
public static function convertSignedShortFromLittleEndianToBigEndian($bits)
{
foreach ($bits as $index => $bit) {
// get LSB
$lsb = $bit & 0xff;
// get MSB
$msb = $bit >> 8 & 0xff;
// swap bytes
$bit = $lsb <<8 | $msb;
if ($bit >= 32768) {
$bit -= 65536;
}
$bits[$index] = $bit;
}
return $bits;
}
// }}}
// {{{ public function getApiVersion()
/**
* Get kafka api version according to specifiy kafka broker version
*
* @param int kafka api key
* @access public
* @return int
*/
public function getApiVersion($apikey)
{
switch ($apikey) {
case self::METADATA_REQUEST:
return self::API_VERSION0;
case self::PRODUCE_REQUEST:
if (version_compare($this->version, '0.10.0') >= 0) {
return self::API_VERSION2;
} elseif (version_compare($this->version, '0.9.0') >= 0) {
return self::API_VERSION1;
} else {
return self::API_VERSION0;
}
case self::FETCH_REQUEST:
if (version_compare($this->version, '0.10.0') >= 0) {
return self::API_VERSION2;
} elseif (version_compare($this->version, '0.9.0') >= 0) {
return self::API_VERSION1;
} else {
return self::API_VERSION0;
}
case self::OFFSET_REQUEST:
// todo
return self::API_VERSION0;
if (version_compare($this->version, '0.10.1.0') >= 0) {
return self::API_VERSION1;
} else {
return self::API_VERSION0;
}
case self::GROUP_COORDINATOR_REQUEST:
return self::API_VERSION0;
case self::OFFSET_COMMIT_REQUEST:
if (version_compare($this->version, '0.9.0') >= 0) {
return self::API_VERSION2;
} elseif (version_compare($this->version, '0.8.2') >= 0) {
return self::API_VERSION1;
} else {
return self::API_VERSION0; // supported in 0.8.1 or later
}
case self::OFFSET_FETCH_REQUEST:
if (version_compare($this->version, '0.8.2') >= 0) {
return self::API_VERSION1; // Offset Fetch Request v1 will fetch offset from Kafka
} else {
return self::API_VERSION0;//Offset Fetch Request v0 will fetch offset from zookeeper
}
case self::JOIN_GROUP_REQUEST:
if (version_compare($this->version, '0.10.1.0') >= 0) {
return self::API_VERSION1;
} else {
return self::API_VERSION0; // supported in 0.9.0.0 and greater
}
case self::SYNC_GROUP_REQUEST:
return self::API_VERSION0;
case self::HEART_BEAT_REQUEST:
return self::API_VERSION0;
case self::LEAVE_GROUP_REQUEST:
return self::API_VERSION0;
case self::LIST_GROUPS_REQUEST:
return self::API_VERSION0;
case self::DESCRIBE_GROUPS_REQUEST:
return self::API_VERSION0;
}
// default
return self::API_VERSION0;
}
// }}}
// {{{ public static function getApiText()
/**
* Get kafka api text
*
* @param int kafka api key
* @access public
* @return string
*/
public static function getApiText($apikey)
{
$apis = array(
self::PRODUCE_REQUEST => 'ProduceRequest',
self::FETCH_REQUEST => 'FetchRequest',
self::OFFSET_REQUEST => 'OffsetRequest',
self::METADATA_REQUEST => 'MetadataRequest',
self::OFFSET_COMMIT_REQUEST => 'OffsetCommitRequest',
self::OFFSET_FETCH_REQUEST => 'OffsetFetchRequest',
self::GROUP_COORDINATOR_REQUEST => 'GroupCoordinatorRequest',
self::JOIN_GROUP_REQUEST => 'JoinGroupRequest',
self::HEART_BEAT_REQUEST => 'HeartbeatRequest',
self::LEAVE_GROUP_REQUEST => 'LeaveGroupRequest',
self::SYNC_GROUP_REQUEST => 'SyncGroupRequest',
self::DESCRIBE_GROUPS_REQUEST => 'DescribeGroupsRequest',
self::LIST_GROUPS_REQUEST => 'ListGroupsRequest',
);
return $apis[$apikey];
}
// }}}
// {{{ public function requestHeader()
/**
* get request header
*
* @param string $clientId
* @param integer $correlationId
* @param integer $apiKey
* @access public
* @return string
*/
public function requestHeader($clientId, $correlationId, $apiKey)
{
// int16 -- apiKey int16 -- apiVersion int32 correlationId
$binData = self::pack(self::BIT_B16, $apiKey);
$binData .= self::pack(self::BIT_B16, $this->getApiVersion($apiKey));
$binData .= self::pack(self::BIT_B32, $correlationId);
// concat client id
$binData .= self::encodeString($clientId, self::PACK_INT16);
$msg = sprintf('ClientId: %s ApiKey: %s ApiVersion: %s', $clientId, self::getApiText($apiKey), $this->getApiVersion($apiKey));
$this->debug('Start Request ' . $msg);
return $binData;
}
// }}}
// {{{ public static function encodeString()
/**
* encode pack string type
*
* @param string $string
* @param int $bytes self::PACK_INT32: int32 big endian order. self::PACK_INT16: int16 big endian order.
* @param int $compression
* @return string
* @static
* @access public
*/
public static function encodeString($string, $bytes, $compression = self::COMPRESSION_NONE)
{
$packLen = ($bytes == self::PACK_INT32) ? self::BIT_B32 : self::BIT_B16;
switch ($compression) {
case self::COMPRESSION_NONE:
break;
case self::COMPRESSION_GZIP:
$string = \gzencode($string);
break;
case self::COMPRESSION_SNAPPY:
// todo
throw new \Kafka\Exception\NotSupported('SNAPPY compression not yet implemented');
default:
throw new \Kafka\Exception\NotSupported('Unknown compression flag: ' . $compression);
}
return self::pack($packLen, strlen($string)) . $string;
}
// }}}
// {{{ public static function encodeArray()
/**
* encode key array
*
* @param array $array
* @param Callable $func
* @param null $options
* @return string
* @static
* @access public
*/
public static function encodeArray(array $array, $func, $options = null)
{
if (!is_callable($func, false)) {
throw new \Kafka\Exception\Protocol('Encode array failed, given function is not callable.');
}
$arrayCount = count($array);
$body = '';
foreach ($array as $value) {
if (!is_null($options)) {
$body .= call_user_func($func, $value, $options);
} else {
$body .= call_user_func($func, $value);
}
}
return self::pack(self::BIT_B32, $arrayCount) . $body;
}
// }}}
// {{{ public function decodeString()
/**
* decode unpack string type
*
* @param bytes $data
* @param int $bytes self::BIT_B32: int32 big endian order. self::BIT_B16: int16 big endian order.
* @param int $compression
* @return string
* @access public
*/
public function decodeString($data, $bytes, $compression = self::COMPRESSION_NONE)
{
$offset = ($bytes == self::BIT_B32) ? 4 : 2;
$packLen = self::unpack($bytes, substr($data, 0, $offset)); // int16 topic name length
if ($packLen == 4294967295) { // uint32(4294967295) is int32 (-1)
$packLen = 0;
}
if ($packLen == 0) {
return array('length' => $offset, 'data' => '');
}
$data = substr($data, $offset, $packLen);
$offset += $packLen;
switch ($compression) {
case self::COMPRESSION_NONE:
break;
case self::COMPRESSION_GZIP:
$data = \gzdecode($data);
break;
case self::COMPRESSION_SNAPPY:
// todo
throw new \Kafka\Exception\NotSupported('SNAPPY compression not yet implemented');
default:
throw new \Kafka\Exception\NotSupported('Unknown compression flag: ' . $compression);
}
return array('length' => $offset, 'data' => $data);
}
// }}}
// {{{ public function decodeArray()
/**
* decode key array
*
* @param array $array
* @param Callable $func
* @param null $options
* @return string
* @access public
*/
public function decodeArray($data, $func, $options = null)
{
$offset = 0;
$arrayCount = self::unpack(self::BIT_B32, substr($data, $offset, 4));
$offset += 4;
if (!is_callable($func, false)) {
throw new \Kafka\Exception\Protocol('Decode array failed, given function is not callable.');
}
$result = array();
for ($i = 0; $i < $arrayCount; $i++) {
$value = substr($data, $offset);
if (!is_null($options)) {
$ret = call_user_func($func, $value, $options);
} else {
$ret = call_user_func($func, $value);
}
if (!is_array($ret) && $ret === false) {
break;
}
if (!isset($ret['length']) || !isset($ret['data'])) {
throw new \Kafka\Exception\Protocol('Decode array failed, given function return format is invliad');
}
if ($ret['length'] == 0) {
continue;
}
$offset += $ret['length'];
$result[] = $ret['data'];
}
return array('length' => $offset, 'data' => $result);
}
// }}}
// {{{ public function decodePrimitiveArray()
/**
* decode primitive type array
*
* @param bytes[] $data
* @param bites $bites
* @return array
* @access public
*/
public function decodePrimitiveArray($data, $bites)
{
$offset = 0;
$arrayCount = self::unpack(self::BIT_B32, substr($data, $offset, 4));
$offset += 4;
if ($arrayCount == 4294967295) {
$arrayCount = 0;
}
$result = array();
for ($i = 0; $i < $arrayCount; $i++) {
if ($bites == self::BIT_B64) {
$result[] = self::unpack(self::BIT_B64, substr($data, $offset, 8));
$offset += 8;
} elseif ($bites == self::BIT_B32) {
$result[] = self::unpack(self::BIT_B32, substr($data, $offset, 4));
$offset += 4;
} elseif (in_array($bites, array(self::BIT_B16, self::BIT_B16_SIGNED))) {
$result[] = self::unpack($bites, substr($data, $offset, 2));
$offset += 2;
} elseif ($bites == self::BIT_B8) {
$result[] = self::unpack($bites, substr($data, $offset, 1));
$offset += 1;
}
}
return array('length' => $offset, 'data' => $result);
}
// }}}
// }}}
}
+212
View File
@@ -0,0 +1,212 @@
<?php
/* vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4 foldmethod=marker: */
// +---------------------------------------------------------------------------
// | SWAN [ $_SWANBR_SLOGAN_$ ]
// +---------------------------------------------------------------------------
// | Copyright $_SWANBR_COPYRIGHT_$
// +---------------------------------------------------------------------------
// | Version $_SWANBR_VERSION_$
// +---------------------------------------------------------------------------
// | Licensed ( $_SWANBR_LICENSED_URL_$ )
// +---------------------------------------------------------------------------
// | $_SWANBR_WEB_DOMAIN_$
// +---------------------------------------------------------------------------
namespace Kafka\Protocol;
/**
+------------------------------------------------------------------------------
* Kafka protocol for sync group api
+------------------------------------------------------------------------------
*
* @package
* @version $_SWANBR_VERSION_$
* @copyright Copyleft
* @author $_SWANBR_AUTHOR_$
+------------------------------------------------------------------------------
*/
class SyncGroup extends Protocol
{
// {{{ functions
// {{{ public function encode()
/**
* sync group request encode
*
* @param array $payloads
* @access public
* @return string
*/
public function encode($payloads)
{
if (!isset($payloads['group_id'])) {
throw new \Kafka\Exception\Protocol('given sync group data invalid. `group_id` is undefined.');
}
if (!isset($payloads['generation_id'])) {
throw new \Kafka\Exception\Protocol('given sync group data invalid. `generation_id` is undefined.');
}
if (!isset($payloads['member_id'])) {
throw new \Kafka\Exception\Protocol('given sync group data invalid. `member_id` is undefined.');
}
if (!isset($payloads['data'])) {
throw new \Kafka\Exception\Protocol('given sync group data invalid. `data` is undefined.');
}
$header = $this->requestHeader('kafka-php', self::SYNC_GROUP_REQUEST, self::SYNC_GROUP_REQUEST);
$data = self::encodeString($payloads['group_id'], self::PACK_INT16);
$data .= self::pack(self::BIT_B32, $payloads['generation_id']);
$data .= self::encodeString($payloads['member_id'], self::PACK_INT16);
$data .= self::encodeArray($payloads['data'], array($this, 'encodeGroupAssignment'));
$data = self::encodeString($header . $data, self::PACK_INT32);
return $data;
}
// }}}
// {{{ public function decode()
/**
* decode group response
*
* @access public
* @return array
*/
public function decode($data)
{
$offset = 0;
$errorCode = self::unpack(self::BIT_B16_SIGNED, substr($data, $offset, 2));
$offset += 2;
$memberAssignments = $this->decodeString(substr($data, $offset), self::BIT_B32);
$offset += $memberAssignments['length'];
$memberAssignment = $memberAssignments['data'];
if (strlen($memberAssignment)) {
$memberAssignmentOffset = 0;
$version = self::unpack(self::BIT_B16_SIGNED, substr($memberAssignment, $memberAssignmentOffset, 2));
$memberAssignmentOffset += 2;
$partitionAssignments = $this->decodeArray(substr($memberAssignment, $memberAssignmentOffset),
array($this, 'syncGroupResponsePartition'));
$memberAssignmentOffset += $partitionAssignments['length'];
$userData = $this->decodeString(substr($memberAssignment, $memberAssignmentOffset), self::BIT_B32);
} else {
return array(
'errorCode' => $errorCode,
);
}
return array(
'errorCode' => $errorCode,
'partitionAssignments' => $partitionAssignments['data'],
'version' => $version,
'userData' => $userData['data'],
);
}
// }}}
// {{{ protected function encodeGroupAssignment()
/**
* encode group assignment protocol
*
* @param partions
* @access protected
* @return string
*/
protected function encodeGroupAssignment($values)
{
if (!isset($values['version'])) {
throw new \Kafka\Exception\Protocol('given data invalid. `version` is undefined.');
}
if (!isset($values['member_id'])) {
throw new \Kafka\Exception\Protocol('given data invalid. `member_id` is undefined.');
}
if (!isset($values['assignments'])) {
throw new \Kafka\Exception\Protocol('given data invalid. `assignments` is undefined.');
}
if (!isset($values['user_data'])) {
$values['user_data'] = '';
}
$memberId = self::encodeString($values['member_id'], self::PACK_INT16);
$data = self::pack(self::BIT_B16, 0);
$data .= self::encodeArray($values['assignments'], array($this, 'encodeGroupAssignmentTopic'));
$data .= self::encodeString($values['user_data'], self::PACK_INT32);
return $memberId . self::encodeString($data, self::PACK_INT32);
}
// }}}
// {{{ protected function encodeGroupAssignmentTopic()
/**
* encode group assignment topic protocol
*
* @param partions
* @access protected
* @return string
*/
protected function encodeGroupAssignmentTopic($values)
{
if (!isset($values['topic_name'])) {
throw new \Kafka\Exception\Protocol('given data invalid. `topic_name` is undefined.');
}
if (!isset($values['partitions'])) {
throw new \Kafka\Exception\Protocol('given data invalid. `partitions` is undefined.');
}
$topicName = self::encodeString($values['topic_name'], self::PACK_INT16);
$partitions = self::encodeArray($values['partitions'], array($this, 'encodeGroupAssignmentTopicPartition'));
return $topicName . $partitions;
}
// }}}
// {{{ protected function encodeGroupAssignmentTopicPartition()
/**
* encode group assignment topic protocol
*
* @param partions
* @access protected
* @return string
*/
protected function encodeGroupAssignmentTopicPartition($values)
{
return self::pack(self::BIT_B32, $values);
}
// }}}
// {{{ protected function syncGroupResponsePartition()
/**
* decode sync group partition response
*
* @access protected
* @return array
*/
protected function syncGroupResponsePartition($data)
{
$offset = 0;
$topicName = $this->decodeString(substr($data, $offset), self::BIT_B16);
$offset += $topicName['length'];
$partitions = $this->decodePrimitiveArray(substr($data, $offset), self::BIT_B32);
$offset += $partitions['length'];
return array(
'length' => $offset,
'data' => array(
'topicName' => $topicName['data'],
'partitions' => $partitions['data'],
)
);
}
// }}}
// }}}
}