Changeset View
Changeset View
Standalone View
Standalone View
src/channel/PhutilJSONProtocolChannel.php
- This file was added.
| <?php | |||||
| /** | |||||
| * Channel that transmits dictionaries of primitives using JSON serialization. | |||||
| * This channel is not binary safe. | |||||
| * | |||||
| * This protocol is implemented by the Phabricator Aphlict realtime notification | |||||
| * server. | |||||
| * | |||||
| * @task protocol Protocol Implementation | |||||
| */ | |||||
| final class PhutilJSONProtocolChannel extends PhutilProtocolChannel { | |||||
| const MODE_LENGTH = 'length'; | |||||
| const MODE_OBJECT = 'object'; | |||||
| /** | |||||
| * Size of the "length" frame of the protocol in bytes. | |||||
| */ | |||||
| const SIZE_LENGTH = 8; | |||||
| private $mode = self::MODE_LENGTH; | |||||
| private $byteLengthOfNextChunk = self::SIZE_LENGTH; | |||||
| private $buf = ''; | |||||
| /* -( Protocol Implementation )-------------------------------------------- */ | |||||
| /** | |||||
| * Encode a message for transmission over the channel. The message should | |||||
| * be any serializable as JSON. | |||||
| * | |||||
| * Objects are transmitted as: | |||||
| * | |||||
| * <len><json> | |||||
| * | |||||
| * ...where <len> is an 8-character, zero-padded integer written as a string. | |||||
| * For example, this is a valid message: | |||||
| * | |||||
| * 00000015{"key":"value"} | |||||
| * | |||||
| * @task protocol | |||||
| */ | |||||
| protected function encodeMessage($message) { | |||||
| $message = json_encode($message); | |||||
| $len = sprintf( | |||||
| '%0'.self::SIZE_LENGTH.'.'.self::SIZE_LENGTH.'d', | |||||
| strlen($message)); | |||||
| return "{$len}{$message}"; | |||||
| } | |||||
| /** | |||||
| * Decode a message received from the other end of the channel. Messages are | |||||
| * decoded as associative arrays. | |||||
| * | |||||
| * @task protocol | |||||
| */ | |||||
| protected function decodeStream($data) { | |||||
| $this->buf .= $data; | |||||
| $objects = array(); | |||||
| while (strlen($this->buf) >= $this->byteLengthOfNextChunk) { | |||||
| switch ($this->mode) { | |||||
| case self::MODE_LENGTH: | |||||
| $len = substr($this->buf, 0, self::SIZE_LENGTH); | |||||
| $this->buf = substr($this->buf, self::SIZE_LENGTH); | |||||
| $this->mode = self::MODE_OBJECT; | |||||
| $this->byteLengthOfNextChunk = (int)$len; | |||||
| break; | |||||
| case self::MODE_OBJECT: | |||||
| $data = substr($this->buf, 0, $this->byteLengthOfNextChunk); | |||||
| $this->buf = substr($this->buf, $this->byteLengthOfNextChunk); | |||||
| try { | |||||
| $objects[] = phutil_json_decode($data); | |||||
| } catch (PhutilJSONParserException $ex) { | |||||
| throw new PhutilProxyException( | |||||
| pht('Failed to decode JSON object.'), | |||||
| $ex); | |||||
| } | |||||
| $this->mode = self::MODE_LENGTH; | |||||
| $this->byteLengthOfNextChunk = self::SIZE_LENGTH; | |||||
| break; | |||||
| } | |||||
| } | |||||
| return $objects; | |||||
| } | |||||
| } | |||||