diff options
author | Gavin McDonald <gmcdonald@apache.org> | 2010-10-28 02:12:01 +0000 |
---|---|---|
committer | Gavin McDonald <gmcdonald@apache.org> | 2010-10-28 02:12:01 +0000 |
commit | 0b75e1ac7643787e201fd62628823e6d51ca6353 (patch) | |
tree | 4417fd3f2bc201f0f34c2344f0923df1485bc419 /lib/php/src | |
download | thrift-0.1.x.tar.gz |
Thrift now a TLP - INFRA-31160.1.x
git-svn-id: https://svn.apache.org/repos/asf/thrift/branches/0.1.x@1028168 13f79535-47bb-0310-9956-ffa450edef68
Diffstat (limited to 'lib/php/src')
-rw-r--r-- | lib/php/src/Thrift.php | 787 | ||||
-rw-r--r-- | lib/php/src/autoload.php | 51 | ||||
-rw-r--r-- | lib/php/src/ext/thrift_protocol/config.m4 | 13 | ||||
-rw-r--r-- | lib/php/src/ext/thrift_protocol/php_thrift_protocol.cpp | 999 | ||||
-rw-r--r-- | lib/php/src/ext/thrift_protocol/php_thrift_protocol.h | 26 | ||||
-rw-r--r-- | lib/php/src/protocol/TBinaryProtocol.php | 431 | ||||
-rw-r--r-- | lib/php/src/protocol/TProtocol.php | 377 | ||||
-rw-r--r-- | lib/php/src/transport/TBufferedTransport.php | 163 | ||||
-rw-r--r-- | lib/php/src/transport/TFramedTransport.php | 179 | ||||
-rw-r--r-- | lib/php/src/transport/THttpClient.php | 202 | ||||
-rw-r--r-- | lib/php/src/transport/TMemoryBuffer.php | 84 | ||||
-rw-r--r-- | lib/php/src/transport/TNullTransport.php | 48 | ||||
-rw-r--r-- | lib/php/src/transport/TPhpStream.php | 111 | ||||
-rw-r--r-- | lib/php/src/transport/TSocket.php | 312 | ||||
-rw-r--r-- | lib/php/src/transport/TSocketPool.php | 296 | ||||
-rw-r--r-- | lib/php/src/transport/TTransport.php | 108 |
16 files changed, 4187 insertions, 0 deletions
diff --git a/lib/php/src/Thrift.php b/lib/php/src/Thrift.php new file mode 100644 index 000000000..ef6ab8a49 --- /dev/null +++ b/lib/php/src/Thrift.php @@ -0,0 +1,787 @@ +<?php +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + * @package thrift + */ + + +/** + * Data types that can be sent via Thrift + */ +class TType { + const STOP = 0; + const VOID = 1; + const BOOL = 2; + const BYTE = 3; + const I08 = 3; + const DOUBLE = 4; + const I16 = 6; + const I32 = 8; + const I64 = 10; + const STRING = 11; + const UTF7 = 11; + const STRUCT = 12; + const MAP = 13; + const SET = 14; + const LST = 15; // N.B. cannot use LIST keyword in PHP! + const UTF8 = 16; + const UTF16 = 17; +} + +/** + * Message types for RPC + */ +class TMessageType { + const CALL = 1; + const REPLY = 2; + const EXCEPTION = 3; + const ONEWAY = 4; +} + +/** + * NOTE(mcslee): This currently contains a ton of duplicated code from TBase + * because we need to save CPU cycles and this is not yet in an extension. + * Ideally we'd multiply-inherit TException from both Exception and Base, but + * that's not possible in PHP and there are no modules either, so for now we + * apologetically take a trip to HackTown. + * + * Can be called with standard Exception constructor (message, code) or with + * Thrift Base object constructor (spec, vals). + * + * @param mixed $p1 Message (string) or type-spec (array) + * @param mixed $p2 Code (integer) or values (array) + */ +class TException extends Exception { + function __construct($p1=null, $p2=0) { + if (is_array($p1) && is_array($p2)) { + $spec = $p1; + $vals = $p2; + foreach ($spec as $fid => $fspec) { + $var = $fspec['var']; + if (isset($vals[$var])) { + $this->$var = $vals[$var]; + } + } + } else { + parent::__construct($p1, $p2); + } + } + + static $tmethod = array(TType::BOOL => 'Bool', + TType::BYTE => 'Byte', + TType::I16 => 'I16', + TType::I32 => 'I32', + TType::I64 => 'I64', + TType::DOUBLE => 'Double', + TType::STRING => 'String'); + + private function _readMap(&$var, $spec, $input) { + $xfer = 0; + $ktype = $spec['ktype']; + $vtype = $spec['vtype']; + $kread = $vread = null; + if (isset(TBase::$tmethod[$ktype])) { + $kread = 'read'.TBase::$tmethod[$ktype]; + } else { + $kspec = $spec['key']; + } + if (isset(TBase::$tmethod[$vtype])) { + $vread = 'read'.TBase::$tmethod[$vtype]; + } else { + $vspec = $spec['val']; + } + $var = array(); + $_ktype = $_vtype = $size = 0; + $xfer += $input->readMapBegin($_ktype, $_vtype, $size); + for ($i = 0; $i < $size; ++$i) { + $key = $val = null; + if ($kread !== null) { + $xfer += $input->$kread($key); + } else { + switch ($ktype) { + case TType::STRUCT: + $class = $kspec['class']; + $key = new $class(); + $xfer += $key->read($input); + break; + case TType::MAP: + $xfer += $this->_readMap($key, $kspec, $input); + break; + case TType::LST: + $xfer += $this->_readList($key, $kspec, $input, false); + break; + case TType::SET: + $xfer += $this->_readList($key, $kspec, $input, true); + break; + } + } + if ($vread !== null) { + $xfer += $input->$vread($val); + } else { + switch ($vtype) { + case TType::STRUCT: + $class = $vspec['class']; + $val = new $class(); + $xfer += $val->read($input); + break; + case TType::MAP: + $xfer += $this->_readMap($val, $vspec, $input); + break; + case TType::LST: + $xfer += $this->_readList($val, $vspec, $input, false); + break; + case TType::SET: + $xfer += $this->_readList($val, $vspec, $input, true); + break; + } + } + $var[$key] = $val; + } + $xfer += $input->readMapEnd(); + return $xfer; + } + + private function _readList(&$var, $spec, $input, $set=false) { + $xfer = 0; + $etype = $spec['etype']; + $eread = $vread = null; + if (isset(TBase::$tmethod[$etype])) { + $eread = 'read'.TBase::$tmethod[$etype]; + } else { + $espec = $spec['elem']; + } + $var = array(); + $_etype = $size = 0; + if ($set) { + $xfer += $input->readSetBegin($_etype, $size); + } else { + $xfer += $input->readListBegin($_etype, $size); + } + for ($i = 0; $i < $size; ++$i) { + $elem = null; + if ($eread !== null) { + $xfer += $input->$eread($elem); + } else { + $espec = $spec['elem']; + switch ($etype) { + case TType::STRUCT: + $class = $espec['class']; + $elem = new $class(); + $xfer += $elem->read($input); + break; + case TType::MAP: + $xfer += $this->_readMap($elem, $espec, $input); + break; + case TType::LST: + $xfer += $this->_readList($elem, $espec, $input, false); + break; + case TType::SET: + $xfer += $this->_readList($elem, $espec, $input, true); + break; + } + } + if ($set) { + $var[$elem] = true; + } else { + $var []= $elem; + } + } + if ($set) { + $xfer += $input->readSetEnd(); + } else { + $xfer += $input->readListEnd(); + } + return $xfer; + } + + protected function _read($class, $spec, $input) { + $xfer = 0; + $fname = null; + $ftype = 0; + $fid = 0; + $xfer += $input->readStructBegin($fname); + while (true) { + $xfer += $input->readFieldBegin($fname, $ftype, $fid); + if ($ftype == TType::STOP) { + break; + } + if (isset($spec[$fid])) { + $fspec = $spec[$fid]; + $var = $fspec['var']; + if ($ftype == $fspec['type']) { + $xfer = 0; + if (isset(TBase::$tmethod[$ftype])) { + $func = 'read'.TBase::$tmethod[$ftype]; + $xfer += $input->$func($this->$var); + } else { + switch ($ftype) { + case TType::STRUCT: + $class = $fspec['class']; + $this->$var = new $class(); + $xfer += $this->$var->read($input); + break; + case TType::MAP: + $xfer += $this->_readMap($this->$var, $fspec, $input); + break; + case TType::LST: + $xfer += $this->_readList($this->$var, $fspec, $input, false); + break; + case TType::SET: + $xfer += $this->_readList($this->$var, $fspec, $input, true); + break; + } + } + } else { + $xfer += $input->skip($ftype); + } + } else { + $xfer += $input->skip($ftype); + } + $xfer += $input->readFieldEnd(); + } + $xfer += $input->readStructEnd(); + return $xfer; + } + + private function _writeMap($var, $spec, $output) { + $xfer = 0; + $ktype = $spec['ktype']; + $vtype = $spec['vtype']; + $kwrite = $vwrite = null; + if (isset(TBase::$tmethod[$ktype])) { + $kwrite = 'write'.TBase::$tmethod[$ktype]; + } else { + $kspec = $spec['key']; + } + if (isset(TBase::$tmethod[$vtype])) { + $vwrite = 'write'.TBase::$tmethod[$vtype]; + } else { + $vspec = $spec['val']; + } + $xfer += $output->writeMapBegin($ktype, $vtype, count($var)); + foreach ($var as $key => $val) { + if (isset($kwrite)) { + $xfer += $output->$kwrite($key); + } else { + switch ($ktype) { + case TType::STRUCT: + $xfer += $key->write($output); + break; + case TType::MAP: + $xfer += $this->_writeMap($key, $kspec, $output); + break; + case TType::LST: + $xfer += $this->_writeList($key, $kspec, $output, false); + break; + case TType::SET: + $xfer += $this->_writeList($key, $kspec, $output, true); + break; + } + } + if (isset($vwrite)) { + $xfer += $output->$vwrite($val); + } else { + switch ($vtype) { + case TType::STRUCT: + $xfer += $val->write($output); + break; + case TType::MAP: + $xfer += $this->_writeMap($val, $vspec, $output); + break; + case TType::LST: + $xfer += $this->_writeList($val, $vspec, $output, false); + break; + case TType::SET: + $xfer += $this->_writeList($val, $vspec, $output, true); + break; + } + } + } + $xfer += $output->writeMapEnd(); + return $xfer; + } + + private function _writeList($var, $spec, $output, $set=false) { + $xfer = 0; + $etype = $spec['etype']; + $ewrite = null; + if (isset(TBase::$tmethod[$etype])) { + $ewrite = 'write'.TBase::$tmethod[$etype]; + } else { + $espec = $spec['elem']; + } + if ($set) { + $xfer += $output->writeSetBegin($etype, count($var)); + } else { + $xfer += $output->writeListBegin($etype, count($var)); + } + foreach ($var as $key => $val) { + $elem = $set ? $key : $val; + if (isset($ewrite)) { + $xfer += $output->$ewrite($elem); + } else { + switch ($etype) { + case TType::STRUCT: + $xfer += $elem->write($output); + break; + case TType::MAP: + $xfer += $this->_writeMap($elem, $espec, $output); + break; + case TType::LST: + $xfer += $this->_writeList($elem, $espec, $output, false); + break; + case TType::SET: + $xfer += $this->_writeList($elem, $espec, $output, true); + break; + } + } + } + if ($set) { + $xfer += $output->writeSetEnd(); + } else { + $xfer += $output->writeListEnd(); + } + return $xfer; + } + + protected function _write($class, $spec, $output) { + $xfer = 0; + $xfer += $output->writeStructBegin($class); + foreach ($spec as $fid => $fspec) { + $var = $fspec['var']; + if ($this->$var !== null) { + $ftype = $fspec['type']; + $xfer += $output->writeFieldBegin($var, $ftype, $fid); + if (isset(TBase::$tmethod[$ftype])) { + $func = 'write'.TBase::$tmethod[$ftype]; + $xfer += $output->$func($this->$var); + } else { + switch ($ftype) { + case TType::STRUCT: + $xfer += $this->$var->write($output); + break; + case TType::MAP: + $xfer += $this->_writeMap($this->$var, $fspec, $output); + break; + case TType::LST: + $xfer += $this->_writeList($this->$var, $fspec, $output, false); + break; + case TType::SET: + $xfer += $this->_writeList($this->$var, $fspec, $output, true); + break; + } + } + $xfer += $output->writeFieldEnd(); + } + } + $xfer += $output->writeFieldStop(); + $xfer += $output->writeStructEnd(); + return $xfer; + } + +} + +/** + * Base class from which other Thrift structs extend. This is so that we can + * cut back on the size of the generated code which is turning out to have a + * nontrivial cost just to load thanks to the wondrously abysmal implementation + * of PHP. Note that code is intentionally duplicated in here to avoid making + * function calls for every field or member of a container.. + */ +abstract class TBase { + + static $tmethod = array(TType::BOOL => 'Bool', + TType::BYTE => 'Byte', + TType::I16 => 'I16', + TType::I32 => 'I32', + TType::I64 => 'I64', + TType::DOUBLE => 'Double', + TType::STRING => 'String'); + + abstract function read($input); + + abstract function write($output); + + public function __construct($spec=null, $vals=null) { + if (is_array($spec) && is_array($vals)) { + foreach ($spec as $fid => $fspec) { + $var = $fspec['var']; + if (isset($vals[$var])) { + $this->$var = $vals[$var]; + } + } + } + } + + private function _readMap(&$var, $spec, $input) { + $xfer = 0; + $ktype = $spec['ktype']; + $vtype = $spec['vtype']; + $kread = $vread = null; + if (isset(TBase::$tmethod[$ktype])) { + $kread = 'read'.TBase::$tmethod[$ktype]; + } else { + $kspec = $spec['key']; + } + if (isset(TBase::$tmethod[$vtype])) { + $vread = 'read'.TBase::$tmethod[$vtype]; + } else { + $vspec = $spec['val']; + } + $var = array(); + $_ktype = $_vtype = $size = 0; + $xfer += $input->readMapBegin($_ktype, $_vtype, $size); + for ($i = 0; $i < $size; ++$i) { + $key = $val = null; + if ($kread !== null) { + $xfer += $input->$kread($key); + } else { + switch ($ktype) { + case TType::STRUCT: + $class = $kspec['class']; + $key = new $class(); + $xfer += $key->read($input); + break; + case TType::MAP: + $xfer += $this->_readMap($key, $kspec, $input); + break; + case TType::LST: + $xfer += $this->_readList($key, $kspec, $input, false); + break; + case TType::SET: + $xfer += $this->_readList($key, $kspec, $input, true); + break; + } + } + if ($vread !== null) { + $xfer += $input->$vread($val); + } else { + switch ($vtype) { + case TType::STRUCT: + $class = $vspec['class']; + $val = new $class(); + $xfer += $val->read($input); + break; + case TType::MAP: + $xfer += $this->_readMap($val, $vspec, $input); + break; + case TType::LST: + $xfer += $this->_readList($val, $vspec, $input, false); + break; + case TType::SET: + $xfer += $this->_readList($val, $vspec, $input, true); + break; + } + } + $var[$key] = $val; + } + $xfer += $input->readMapEnd(); + return $xfer; + } + + private function _readList(&$var, $spec, $input, $set=false) { + $xfer = 0; + $etype = $spec['etype']; + $eread = $vread = null; + if (isset(TBase::$tmethod[$etype])) { + $eread = 'read'.TBase::$tmethod[$etype]; + } else { + $espec = $spec['elem']; + } + $var = array(); + $_etype = $size = 0; + if ($set) { + $xfer += $input->readSetBegin($_etype, $size); + } else { + $xfer += $input->readListBegin($_etype, $size); + } + for ($i = 0; $i < $size; ++$i) { + $elem = null; + if ($eread !== null) { + $xfer += $input->$eread($elem); + } else { + $espec = $spec['elem']; + switch ($etype) { + case TType::STRUCT: + $class = $espec['class']; + $elem = new $class(); + $xfer += $elem->read($input); + break; + case TType::MAP: + $xfer += $this->_readMap($elem, $espec, $input); + break; + case TType::LST: + $xfer += $this->_readList($elem, $espec, $input, false); + break; + case TType::SET: + $xfer += $this->_readList($elem, $espec, $input, true); + break; + } + } + if ($set) { + $var[$elem] = true; + } else { + $var []= $elem; + } + } + if ($set) { + $xfer += $input->readSetEnd(); + } else { + $xfer += $input->readListEnd(); + } + return $xfer; + } + + protected function _read($class, $spec, $input) { + $xfer = 0; + $fname = null; + $ftype = 0; + $fid = 0; + $xfer += $input->readStructBegin($fname); + while (true) { + $xfer += $input->readFieldBegin($fname, $ftype, $fid); + if ($ftype == TType::STOP) { + break; + } + if (isset($spec[$fid])) { + $fspec = $spec[$fid]; + $var = $fspec['var']; + if ($ftype == $fspec['type']) { + $xfer = 0; + if (isset(TBase::$tmethod[$ftype])) { + $func = 'read'.TBase::$tmethod[$ftype]; + $xfer += $input->$func($this->$var); + } else { + switch ($ftype) { + case TType::STRUCT: + $class = $fspec['class']; + $this->$var = new $class(); + $xfer += $this->$var->read($input); + break; + case TType::MAP: + $xfer += $this->_readMap($this->$var, $fspec, $input); + break; + case TType::LST: + $xfer += $this->_readList($this->$var, $fspec, $input, false); + break; + case TType::SET: + $xfer += $this->_readList($this->$var, $fspec, $input, true); + break; + } + } + } else { + $xfer += $input->skip($ftype); + } + } else { + $xfer += $input->skip($ftype); + } + $xfer += $input->readFieldEnd(); + } + $xfer += $input->readStructEnd(); + return $xfer; + } + + private function _writeMap($var, $spec, $output) { + $xfer = 0; + $ktype = $spec['ktype']; + $vtype = $spec['vtype']; + $kwrite = $vwrite = null; + if (isset(TBase::$tmethod[$ktype])) { + $kwrite = 'write'.TBase::$tmethod[$ktype]; + } else { + $kspec = $spec['key']; + } + if (isset(TBase::$tmethod[$vtype])) { + $vwrite = 'write'.TBase::$tmethod[$vtype]; + } else { + $vspec = $spec['val']; + } + $xfer += $output->writeMapBegin($ktype, $vtype, count($var)); + foreach ($var as $key => $val) { + if (isset($kwrite)) { + $xfer += $output->$kwrite($key); + } else { + switch ($ktype) { + case TType::STRUCT: + $xfer += $key->write($output); + break; + case TType::MAP: + $xfer += $this->_writeMap($key, $kspec, $output); + break; + case TType::LST: + $xfer += $this->_writeList($key, $kspec, $output, false); + break; + case TType::SET: + $xfer += $this->_writeList($key, $kspec, $output, true); + break; + } + } + if (isset($vwrite)) { + $xfer += $output->$vwrite($val); + } else { + switch ($vtype) { + case TType::STRUCT: + $xfer += $val->write($output); + break; + case TType::MAP: + $xfer += $this->_writeMap($val, $vspec, $output); + break; + case TType::LST: + $xfer += $this->_writeList($val, $vspec, $output, false); + break; + case TType::SET: + $xfer += $this->_writeList($val, $vspec, $output, true); + break; + } + } + } + $xfer += $output->writeMapEnd(); + return $xfer; + } + + private function _writeList($var, $spec, $output, $set=false) { + $xfer = 0; + $etype = $spec['etype']; + $ewrite = null; + if (isset(TBase::$tmethod[$etype])) { + $ewrite = 'write'.TBase::$tmethod[$etype]; + } else { + $espec = $spec['elem']; + } + if ($set) { + $xfer += $output->writeSetBegin($etype, count($var)); + } else { + $xfer += $output->writeListBegin($etype, count($var)); + } + foreach ($var as $key => $val) { + $elem = $set ? $key : $val; + if (isset($ewrite)) { + $xfer += $output->$ewrite($elem); + } else { + switch ($etype) { + case TType::STRUCT: + $xfer += $elem->write($output); + break; + case TType::MAP: + $xfer += $this->_writeMap($elem, $espec, $output); + break; + case TType::LST: + $xfer += $this->_writeList($elem, $espec, $output, false); + break; + case TType::SET: + $xfer += $this->_writeList($elem, $espec, $output, true); + break; + } + } + } + if ($set) { + $xfer += $output->writeSetEnd(); + } else { + $xfer += $output->writeListEnd(); + } + return $xfer; + } + + protected function _write($class, $spec, $output) { + $xfer = 0; + $xfer += $output->writeStructBegin($class); + foreach ($spec as $fid => $fspec) { + $var = $fspec['var']; + if ($this->$var !== null) { + $ftype = $fspec['type']; + $xfer += $output->writeFieldBegin($var, $ftype, $fid); + if (isset(TBase::$tmethod[$ftype])) { + $func = 'write'.TBase::$tmethod[$ftype]; + $xfer += $output->$func($this->$var); + } else { + switch ($ftype) { + case TType::STRUCT: + $xfer += $this->$var->write($output); + break; + case TType::MAP: + $xfer += $this->_writeMap($this->$var, $fspec, $output); + break; + case TType::LST: + $xfer += $this->_writeList($this->$var, $fspec, $output, false); + break; + case TType::SET: + $xfer += $this->_writeList($this->$var, $fspec, $output, true); + break; + } + } + $xfer += $output->writeFieldEnd(); + } + } + $xfer += $output->writeFieldStop(); + $xfer += $output->writeStructEnd(); + return $xfer; + } +} + +class TApplicationException extends TException { + static $_TSPEC = + array(1 => array('var' => 'message', + 'type' => TType::STRING), + 2 => array('var' => 'code', + 'type' => TType::I32)); + + const UNKNOWN = 0; + const UNKNOWN_METHOD = 1; + const INVALID_MESSAGE_TYPE = 2; + const WRONG_METHOD_NAME = 3; + const BAD_SEQUENCE_ID = 4; + const MISSING_RESULT = 5; + + function __construct($message=null, $code=0) { + parent::__construct($message, $code); + } + + public function read($output) { + return $this->_read('TApplicationException', self::$_TSPEC, $output); + } + + public function write($output) { + $xfer = 0; + $xfer += $output->writeStructBegin('TApplicationException'); + if ($message = $this->getMessage()) { + $xfer += $output->writeFieldBegin('message', TType::STRING, 1); + $xfer += $output->writeString($message); + $xfer += $output->writeFieldEnd(); + } + if ($code = $this->getCode()) { + $xfer += $output->writeFieldBegin('type', TType::I32, 2); + $xfer += $output->writeI32($code); + $xfer += $output->writeFieldEnd(); + } + $xfer += $output->writeFieldStop(); + $xfer += $output->writeStructEnd(); + return $xfer; + } +} + +/** + * Set global THRIFT ROOT automatically via inclusion here + */ +if (!isset($GLOBALS['THRIFT_ROOT'])) { + $GLOBALS['THRIFT_ROOT'] = dirname(__FILE__); +} +include_once $GLOBALS['THRIFT_ROOT'].'/protocol/TProtocol.php'; +include_once $GLOBALS['THRIFT_ROOT'].'/transport/TTransport.php'; + +?> diff --git a/lib/php/src/autoload.php b/lib/php/src/autoload.php new file mode 100644 index 000000000..3a35545d1 --- /dev/null +++ b/lib/php/src/autoload.php @@ -0,0 +1,51 @@ +<?php +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + * @package thrift + */ + + +/** + * Include this file if you wish to use autoload with your PHP generated Thrift + * code. The generated code will *not* include any defined Thrift classes by + * default, except for the service interfaces. The generated code will populate + * values into $GLOBALS['THRIFT_AUTOLOAD'] which can be used by the autoload + * method below. If you have your own autoload system already in place, rename your + * __autoload function to something else and then do: + * $GLOBALS['AUTOLOAD_HOOKS'][] = 'my_autoload_func'; + * + * Generate this code using the --gen php:autoload Thrift generator flag. + */ + +$GLOBALS['THRIFT_AUTOLOAD'] = array(); +$GLOBALS['AUTOLOAD_HOOKS'] = array(); + +if (!function_exists('__autoload')) { + function __autoload($class) { + global $THRIFT_AUTOLOAD; + $classl = strtolower($class); + if (isset($THRIFT_AUTOLOAD[$classl])) { + include_once $GLOBALS['THRIFT_ROOT'].'/packages/'.$THRIFT_AUTOLOAD[$classl]; + } else if (!empty($GLOBALS['AUTOLOAD_HOOKS'])) { + foreach ($GLOBALS['AUTOLOAD_HOOKS'] as $hook) { + $hook($class); + } + } + } +} diff --git a/lib/php/src/ext/thrift_protocol/config.m4 b/lib/php/src/ext/thrift_protocol/config.m4 new file mode 100644 index 000000000..8cfb37d7f --- /dev/null +++ b/lib/php/src/ext/thrift_protocol/config.m4 @@ -0,0 +1,13 @@ +dnl Copyright (C) 2009 Facebook +dnl Copying and distribution of this file, with or without modification, +dnl are permitted in any medium without royalty provided the copyright +dnl notice and this notice are preserved. + +PHP_ARG_ENABLE(thrift_protocol, whether to enable the thrift_protocol extension, +[ --enable-thrift_protocol Enable the fbthrift_protocol extension]) + +if test "$PHP_THRIFT_PROTOCOL" != "no"; then + PHP_REQUIRE_CXX() + PHP_NEW_EXTENSION(thrift_protocol, php_thrift_protocol.cpp, $ext_shared) +fi + diff --git a/lib/php/src/ext/thrift_protocol/php_thrift_protocol.cpp b/lib/php/src/ext/thrift_protocol/php_thrift_protocol.cpp new file mode 100644 index 000000000..399cbe624 --- /dev/null +++ b/lib/php/src/ext/thrift_protocol/php_thrift_protocol.cpp @@ -0,0 +1,999 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include <sys/types.h> +#include <netinet/in.h> +#include <unistd.h> +#include <endian.h> +#include <byteswap.h> +#include <stdexcept> + +#if __BYTE_ORDER == __LITTLE_ENDIAN +#define htonll(x) bswap_64(x) +#define ntohll(x) bswap_64(x) +#else +#define htonll(x) x +#define ntohll(x) x +#endif + +enum TType { + T_STOP = 0, + T_VOID = 1, + T_BOOL = 2, + T_BYTE = 3, + T_I08 = 3, + T_I16 = 6, + T_I32 = 8, + T_U64 = 9, + T_I64 = 10, + T_DOUBLE = 4, + T_STRING = 11, + T_UTF7 = 11, + T_STRUCT = 12, + T_MAP = 13, + T_SET = 14, + T_LIST = 15, + T_UTF8 = 16, + T_UTF16 = 17 +}; + +const int32_t VERSION_MASK = 0xffff0000; +const int32_t VERSION_1 = 0x80010000; +const int8_t T_CALL = 1; +const int8_t T_REPLY = 2; +const int8_t T_EXCEPTION = 3; +// tprotocolexception +const int INVALID_DATA = 1; +const int BAD_VERSION = 4; + +#include "php.h" +#include "zend_interfaces.h" +#include "zend_exceptions.h" +#include "php_thrift_protocol.h" + +static function_entry thrift_protocol_functions[] = { + PHP_FE(thrift_protocol_write_binary, NULL) + PHP_FE(thrift_protocol_read_binary, NULL) + {NULL, NULL, NULL} +} ; + +zend_module_entry thrift_protocol_module_entry = { + STANDARD_MODULE_HEADER, + "thrift_protocol", + thrift_protocol_functions, + NULL, + NULL, + NULL, + NULL, + NULL, + "1.0", + STANDARD_MODULE_PROPERTIES +}; + +#ifdef COMPILE_DL_THRIFT_PROTOCOL +ZEND_GET_MODULE(thrift_protocol) +#endif + +class PHPExceptionWrapper : public std::exception { +public: + PHPExceptionWrapper(zval* _ex) throw() : ex(_ex) { + snprintf(_what, 40, "PHP exception zval=%p", ex); + } + const char* what() const throw() { return _what; } + ~PHPExceptionWrapper() throw() {} + operator zval*() const throw() { return const_cast<zval*>(ex); } // Zend API doesn't do 'const'... +protected: + zval* ex; + char _what[40]; +} ; + +class PHPTransport { +public: + zval* protocol() { return p; } + zval* transport() { return t; } +protected: + PHPTransport() {} + + void construct_with_zval(zval* _p, size_t _buffer_size) { + buffer = reinterpret_cast<char*>(emalloc(_buffer_size)); + buffer_ptr = buffer; + buffer_used = 0; + buffer_size = _buffer_size; + p = _p; + + // Get the transport for the passed protocol + zval gettransport; + ZVAL_STRING(&gettransport, "getTransport", 0); + MAKE_STD_ZVAL(t); + ZVAL_NULL(t); + TSRMLS_FETCH(); + call_user_function(EG(function_table), &p, &gettransport, t, 0, NULL TSRMLS_CC); + } + ~PHPTransport() { + efree(buffer); + zval_ptr_dtor(&t); + } + + char* buffer; + char* buffer_ptr; + size_t buffer_used; + size_t buffer_size; + + zval* p; + zval* t; +}; + + +class PHPOutputTransport : public PHPTransport { +public: + PHPOutputTransport(zval* _p, size_t _buffer_size = 8192) { + construct_with_zval(_p, _buffer_size); + } + + ~PHPOutputTransport() { + flush(); + directFlush(); + } + + void write(const char* data, size_t len) { + if ((len + buffer_used) > buffer_size) { + flush(); + } + if (len > buffer_size) { + directWrite(data, len); + } else { + memcpy(buffer_ptr, data, len); + buffer_used += len; + buffer_ptr += len; + } + } + + void writeI64(int64_t i) { + i = htonll(i); + write((const char*)&i, 8); + } + + void writeU32(uint32_t i) { + i = htonl(i); + write((const char*)&i, 4); + } + + void writeI32(int32_t i) { + i = htonl(i); + write((const char*)&i, 4); + } + + void writeI16(int16_t i) { + i = htons(i); + write((const char*)&i, 2); + } + + void writeI8(int8_t i) { + write((const char*)&i, 1); + } + + void writeString(const char* str, size_t len) { + writeU32(len); + write(str, len); + } + + void flush() { + if (buffer_used) { + directWrite(buffer, buffer_used); + buffer_ptr = buffer; + buffer_used = 0; + } + } + +protected: + void directFlush() { + zval ret; + ZVAL_NULL(&ret); + zval flushfn; + ZVAL_STRING(&flushfn, "flush", 0); + TSRMLS_FETCH(); + call_user_function(EG(function_table), &t, &flushfn, &ret, 0, NULL TSRMLS_CC); + zval_dtor(&ret); + } + void directWrite(const char* data, size_t len) { + zval writefn; + ZVAL_STRING(&writefn, "write", 0); + char* newbuf = (char*)emalloc(buffer_used + 1); + memcpy(newbuf, buffer, buffer_used); + newbuf[buffer_used] = '\0'; + zval *args[1]; + MAKE_STD_ZVAL(args[0]); + ZVAL_STRINGL(args[0], newbuf, buffer_used, 0); + TSRMLS_FETCH(); + zval ret; + ZVAL_NULL(&ret); + call_user_function(EG(function_table), &t, &writefn, &ret, 1, args TSRMLS_CC); + zval_ptr_dtor(args); + zval_dtor(&ret); + if (EG(exception)) { + zval* ex = EG(exception); + EG(exception) = NULL; + throw PHPExceptionWrapper(ex); + } + } +}; + +class PHPInputTransport : public PHPTransport { +public: + PHPInputTransport(zval* _p, size_t _buffer_size = 8192) { + construct_with_zval(_p, _buffer_size); + } + + ~PHPInputTransport() { + put_back(); + } + + void put_back() { + if (buffer_used) { + zval putbackfn; + ZVAL_STRING(&putbackfn, "putBack", 0); + + char* newbuf = (char*)emalloc(buffer_used + 1); + memcpy(newbuf, buffer_ptr, buffer_used); + newbuf[buffer_used] = '\0'; + + zval *args[1]; + MAKE_STD_ZVAL(args[0]); + ZVAL_STRINGL(args[0], newbuf, buffer_used, 0); + + TSRMLS_FETCH(); + + zval ret; + ZVAL_NULL(&ret); + call_user_function(EG(function_table), &t, &putbackfn, &ret, 1, args TSRMLS_CC); + zval_ptr_dtor(args); + zval_dtor(&ret); + } + buffer_used = 0; + buffer_ptr = buffer; + } + + void skip(size_t len) { + while (len) { + size_t chunk_size = MIN(len, buffer_used); + if (chunk_size) { + buffer_ptr = reinterpret_cast<char*>(buffer_ptr) + chunk_size; + buffer_used -= chunk_size; + len -= chunk_size; + } + if (! len) break; + refill(); + } + } + + void readBytes(void* buf, size_t len) { + while (len) { + size_t chunk_size = MIN(len, buffer_used); + if (chunk_size) { + memcpy(buf, buffer_ptr, chunk_size); + buffer_ptr = reinterpret_cast<char*>(buffer_ptr) + chunk_size; + buffer_used -= chunk_size; + buf = reinterpret_cast<char*>(buf) + chunk_size; + len -= chunk_size; + } + if (! len) break; + refill(); + } + } + + int8_t readI8() { + int8_t c; + readBytes(&c, 1); + return c; + } + + int16_t readI16() { + int16_t c; + readBytes(&c, 2); + return (int16_t)ntohs(c); + } + + uint32_t readU32() { + uint32_t c; + readBytes(&c, 4); + return (uint32_t)ntohl(c); + } + + int32_t readI32() { + int32_t c; + readBytes(&c, 4); + return (int32_t)ntohl(c); + } + +protected: + void refill() { + assert(buffer_used == 0); + zval retval; + ZVAL_NULL(&retval); + + zval *args[1]; + MAKE_STD_ZVAL(args[0]); + ZVAL_LONG(args[0], buffer_size); + + TSRMLS_FETCH(); + + zval funcname; + ZVAL_STRING(&funcname, "read", 0); + + call_user_function(EG(function_table), &t, &funcname, &retval, 1, args TSRMLS_CC); + zval_ptr_dtor(args); + + if (EG(exception)) { + zval_dtor(&retval); + zval* ex = EG(exception); + EG(exception) = NULL; + throw PHPExceptionWrapper(ex); + } + + buffer_used = Z_STRLEN(retval); + memcpy(buffer, Z_STRVAL(retval), buffer_used); + zval_dtor(&retval); + + buffer_ptr = buffer; + } + +}; + +void binary_deserialize_spec(zval* zthis, PHPInputTransport& transport, HashTable* spec); +void binary_serialize_spec(zval* zthis, PHPOutputTransport& transport, HashTable* spec); +void binary_serialize(int8_t thrift_typeID, PHPOutputTransport& transport, zval** value, HashTable* fieldspec); +void skip_element(long thrift_typeID, PHPInputTransport& transport); + +// Create a PHP object given a typename and call the ctor, optionally passing up to 2 arguments +void createObject(char* obj_typename, zval* return_value, int nargs = 0, zval* arg1 = NULL, zval* arg2 = NULL) { + TSRMLS_FETCH(); + size_t obj_typename_len = strlen(obj_typename); + zend_class_entry* ce = zend_fetch_class(obj_typename, obj_typename_len, ZEND_FETCH_CLASS_DEFAULT TSRMLS_CC); + if (! ce) { + php_error_docref(NULL TSRMLS_CC, E_ERROR, "Class %s does not exist", obj_typename); + RETURN_NULL(); + } + + object_and_properties_init(return_value, ce, NULL); + zend_function* constructor = zend_std_get_constructor(return_value TSRMLS_CC); + zval* ctor_rv = NULL; + zend_call_method(&return_value, ce, &constructor, NULL, 0, &ctor_rv, nargs, arg1, arg2 TSRMLS_CC); + zval_ptr_dtor(&ctor_rv); +} + +void throw_tprotocolexception(char* what, long errorcode) { + TSRMLS_FETCH(); + + zval *zwhat, *zerrorcode; + MAKE_STD_ZVAL(zwhat); + MAKE_STD_ZVAL(zerrorcode); + + ZVAL_STRING(zwhat, what, 1); + ZVAL_LONG(zerrorcode, errorcode); + + zval* ex; + MAKE_STD_ZVAL(ex); + createObject("TProtocolException", ex, 2, zwhat, zerrorcode); + zval_ptr_dtor(&zwhat); + zval_ptr_dtor(&zerrorcode); + throw PHPExceptionWrapper(ex); +} + +void binary_deserialize(int8_t thrift_typeID, PHPInputTransport& transport, zval* return_value, HashTable* fieldspec) { + zval** val_ptr; + Z_TYPE_P(return_value) = IS_NULL; // just in case + + switch (thrift_typeID) { + case T_STOP: + case T_VOID: + RETURN_NULL(); + return; + case T_STRUCT: { + if (zend_hash_find(fieldspec, "class", 6, (void**)&val_ptr) != SUCCESS) { + throw_tprotocolexception("no class type in spec", INVALID_DATA); + skip_element(T_STRUCT, transport); + RETURN_NULL(); + } + char* structType = Z_STRVAL_PP(val_ptr); + createObject(structType, return_value); + if (Z_TYPE_P(return_value) == IS_NULL) { + // unable to create class entry + skip_element(T_STRUCT, transport); + RETURN_NULL(); + } + TSRMLS_FETCH(); + zval* spec = zend_read_static_property(zend_get_class_entry(return_value TSRMLS_CC), "_TSPEC", 6, false TSRMLS_CC); + if (Z_TYPE_P(spec) != IS_ARRAY) { + char errbuf[128]; + snprintf(errbuf, 128, "spec for %s is wrong type: %d\n", structType, Z_TYPE_P(spec)); + throw_tprotocolexception(errbuf, INVALID_DATA); + RETURN_NULL(); + } + binary_deserialize_spec(return_value, transport, Z_ARRVAL_P(spec)); + return; + } break; + case T_BOOL: { + uint8_t c; + transport.readBytes(&c, 1); + RETURN_BOOL(c != 0); + } + //case T_I08: // same numeric value as T_BYTE + case T_BYTE: { + uint8_t c; + transport.readBytes(&c, 1); + RETURN_LONG(c); + } + case T_I16: { + uint16_t c; + transport.readBytes(&c, 2); + RETURN_LONG(ntohs(c)); + } + case T_I32: { + uint32_t c; + transport.readBytes(&c, 4); + RETURN_LONG(ntohl(c)); + } + case T_U64: + case T_I64: { + uint64_t c; + transport.readBytes(&c, 8); + RETURN_LONG(ntohll(c)); + } + case T_DOUBLE: { + union { + uint64_t c; + double d; + } a; + transport.readBytes(&(a.c), 8); + a.c = ntohll(a.c); + RETURN_DOUBLE(a.d); + } + //case T_UTF7: // aliases T_STRING + case T_UTF8: + case T_UTF16: + case T_STRING: { + uint32_t size = transport.readU32(); + if (size) { + char* strbuf = (char*) emalloc(size + 1); + transport.readBytes(strbuf, size); + strbuf[size] = '\0'; + ZVAL_STRINGL(return_value, strbuf, size, 0); + } else { + ZVAL_EMPTY_STRING(return_value); + } + return; + } + case T_MAP: { // array of key -> value + uint8_t types[2]; + transport.readBytes(types, 2); + uint32_t size = transport.readU32(); + array_init(return_value); + + zend_hash_find(fieldspec, "key", 4, (void**)&val_ptr); + HashTable* keyspec = Z_ARRVAL_PP(val_ptr); + zend_hash_find(fieldspec, "val", 4, (void**)&val_ptr); + HashTable* valspec = Z_ARRVAL_PP(val_ptr); + + for (uint32_t s = 0; s < size; ++s) { + zval *value; + MAKE_STD_ZVAL(value); + + zval* key; + MAKE_STD_ZVAL(key); + + binary_deserialize(types[0], transport, key, keyspec); + binary_deserialize(types[1], transport, value, valspec); + if (Z_TYPE_P(key) == IS_LONG) { + zend_hash_index_update(return_value->value.ht, Z_LVAL_P(key), &value, sizeof(zval *), NULL); + } + else { + if (Z_TYPE_P(key) != IS_STRING) convert_to_string(key); + zend_hash_update(return_value->value.ht, Z_STRVAL_P(key), Z_STRLEN_P(key) + 1, &value, sizeof(zval *), NULL); + } + zval_ptr_dtor(&key); + } + return; // return_value already populated + } + case T_LIST: { // array with autogenerated numeric keys + int8_t type = transport.readI8(); + uint32_t size = transport.readU32(); + zend_hash_find(fieldspec, "elem", 5, (void**)&val_ptr); + HashTable* elemspec = Z_ARRVAL_PP(val_ptr); + + array_init(return_value); + for (uint32_t s = 0; s < size; ++s) { + zval *value; + MAKE_STD_ZVAL(value); + binary_deserialize(type, transport, value, elemspec); + zend_hash_next_index_insert(return_value->value.ht, &value, sizeof(zval *), NULL); + } + return; + } + case T_SET: { // array of key -> TRUE + uint8_t type; + uint32_t size; + transport.readBytes(&type, 1); + transport.readBytes(&size, 4); + size = ntohl(size); + zend_hash_find(fieldspec, "elem", 5, (void**)&val_ptr); + HashTable* elemspec = Z_ARRVAL_PP(val_ptr); + + array_init(return_value); + + for (uint32_t s = 0; s < size; ++s) { + zval* key; + zval* value; + MAKE_STD_ZVAL(key); + MAKE_STD_ZVAL(value); + ZVAL_TRUE(value); + + binary_deserialize(type, transport, key, elemspec); + + if (Z_TYPE_P(key) == IS_LONG) { + zend_hash_index_update(return_value->value.ht, Z_LVAL_P(key), &value, sizeof(zval *), NULL); + } + else { + if (Z_TYPE_P(key) != IS_STRING) convert_to_string(key); + zend_hash_update(return_value->value.ht, Z_STRVAL_P(key), Z_STRLEN_P(key) + 1, &value, sizeof(zval *), NULL); + } + zval_ptr_dtor(&key); + } + return; + } + }; + + char errbuf[128]; + sprintf(errbuf, "Unknown thrift typeID %d", thrift_typeID); + throw_tprotocolexception(errbuf, INVALID_DATA); +} + +void skip_element(long thrift_typeID, PHPInputTransport& transport) { + switch (thrift_typeID) { + case T_STOP: + case T_VOID: + return; + case T_STRUCT: + while (true) { + int8_t ttype = transport.readI8(); // get field type + if (ttype == T_STOP) break; + transport.skip(2); // skip field number, I16 + skip_element(ttype, transport); // skip field payload + } + return; + case T_BOOL: + case T_BYTE: + transport.skip(1); + return; + case T_I16: + transport.skip(2); + return; + case T_I32: + transport.skip(4); + return; + case T_U64: + case T_I64: + case T_DOUBLE: + transport.skip(8); + return; + //case T_UTF7: // aliases T_STRING + case T_UTF8: + case T_UTF16: + case T_STRING: { + uint32_t len = transport.readU32(); + transport.skip(len); + } return; + case T_MAP: { + int8_t keytype = transport.readI8(); + int8_t valtype = transport.readI8(); + uint32_t size = transport.readU32(); + for (uint32_t i = 0; i < size; ++i) { + skip_element(keytype, transport); + skip_element(valtype, transport); + } + } return; + case T_LIST: + case T_SET: { + int8_t valtype = transport.readI8(); + uint32_t size = transport.readU32(); + for (uint32_t i = 0; i < size; ++i) { + skip_element(valtype, transport); + } + } return; + }; + + char errbuf[128]; + sprintf(errbuf, "Unknown thrift typeID %ld", thrift_typeID); + throw_tprotocolexception(errbuf, INVALID_DATA); +} + +void binary_serialize_hashtable_key(int8_t keytype, PHPOutputTransport& transport, HashTable* ht, HashPosition& ht_pos) { + bool keytype_is_numeric = (!((keytype == T_STRING) || (keytype == T_UTF8) || (keytype == T_UTF16))); + + char* key; + uint key_len; + long index = 0; + + zval* z; + MAKE_STD_ZVAL(z); + + int res = zend_hash_get_current_key_ex(ht, &key, &key_len, (ulong*)&index, 0, &ht_pos); + if (keytype_is_numeric) { + if (res == HASH_KEY_IS_STRING) { + index = strtol(key, NULL, 10); + } + ZVAL_LONG(z, index); + } else { + char buf[64]; + if (res == HASH_KEY_IS_STRING) { + key_len -= 1; // skip the null terminator + } else { + sprintf(buf, "%ld", index); + key = buf; key_len = strlen(buf); + } + ZVAL_STRINGL(z, key, key_len, 1); + } + binary_serialize(keytype, transport, &z, NULL); + zval_ptr_dtor(&z); +} + +inline bool ttype_is_int(int8_t t) { + return ((t == T_BYTE) || ((t >= T_I16) && (t <= T_I64))); +} + +inline bool ttypes_are_compatible(int8_t t1, int8_t t2) { + // Integer types of different widths are considered compatible; + // otherwise the typeID must match. + return ((t1 == t2) || (ttype_is_int(t1) && ttype_is_int(t2))); +} + +void binary_deserialize_spec(zval* zthis, PHPInputTransport& transport, HashTable* spec) { + // SET and LIST have 'elem' => array('type', [optional] 'class') + // MAP has 'val' => array('type', [optiona] 'class') + TSRMLS_FETCH(); + zend_class_entry* ce = zend_get_class_entry(zthis TSRMLS_CC); + while (true) { + zval** val_ptr = NULL; + + int8_t ttype = transport.readI8(); + if (ttype == T_STOP) return; + int16_t fieldno = transport.readI16(); + if (zend_hash_index_find(spec, fieldno, (void**)&val_ptr) == SUCCESS) { + HashTable* fieldspec = Z_ARRVAL_PP(val_ptr); + // pull the field name + // zend hash tables use the null at the end in the length... so strlen(hash key) + 1. + zend_hash_find(fieldspec, "var", 4, (void**)&val_ptr); + char* varname = Z_STRVAL_PP(val_ptr); + + // and the type + zend_hash_find(fieldspec, "type", 5, (void**)&val_ptr); + if (Z_TYPE_PP(val_ptr) != IS_LONG) convert_to_long(*val_ptr); + int8_t expected_ttype = Z_LVAL_PP(val_ptr); + + if (ttypes_are_compatible(ttype, expected_ttype)) { + zval* rv = NULL; + MAKE_STD_ZVAL(rv); + binary_deserialize(ttype, transport, rv, fieldspec); + zend_update_property(ce, zthis, varname, strlen(varname), rv TSRMLS_CC); + zval_ptr_dtor(&rv); + } else { + skip_element(ttype, transport); + } + } else { + skip_element(ttype, transport); + } + } +} + +void binary_serialize(int8_t thrift_typeID, PHPOutputTransport& transport, zval** value, HashTable* fieldspec) { + // At this point the typeID (and field num, if applicable) should've already been written to the output so all we need to do is write the payload. + switch (thrift_typeID) { + case T_STOP: + case T_VOID: + return; + case T_STRUCT: { + TSRMLS_FETCH(); + if (Z_TYPE_PP(value) != IS_OBJECT) { + throw_tprotocolexception("Attempt to send non-object type as a T_STRUCT", INVALID_DATA); + } + zval* spec = zend_read_static_property(zend_get_class_entry(*value TSRMLS_CC), "_TSPEC", 6, false TSRMLS_CC); + binary_serialize_spec(*value, transport, Z_ARRVAL_P(spec)); + } return; + case T_BOOL: + if (Z_TYPE_PP(value) != IS_BOOL) convert_to_boolean(*value); + transport.writeI8(Z_BVAL_PP(value) ? 1 : 0); + return; + case T_BYTE: + if (Z_TYPE_PP(value) != IS_LONG) convert_to_long(*value); + transport.writeI8(Z_LVAL_PP(value)); + return; + case T_I16: + if (Z_TYPE_PP(value) != IS_LONG) convert_to_long(*value); + transport.writeI16(Z_LVAL_PP(value)); + return; + case T_I32: + if (Z_TYPE_PP(value) != IS_LONG) convert_to_long(*value); + transport.writeI32(Z_LVAL_PP(value)); + return; + case T_I64: + case T_U64: + if (Z_TYPE_PP(value) != IS_LONG) convert_to_long(*value); + transport.writeI64(Z_LVAL_PP(value)); + return; + case T_DOUBLE: { + union { + int64_t c; + double d; + } a; + if (Z_TYPE_PP(value) != IS_DOUBLE) convert_to_double(*value); + a.d = Z_DVAL_PP(value); + transport.writeI64(a.c); + } return; + //case T_UTF7: + case T_UTF8: + case T_UTF16: + case T_STRING: + if (Z_TYPE_PP(value) != IS_STRING) convert_to_string(*value); + transport.writeString(Z_STRVAL_PP(value), Z_STRLEN_PP(value)); + return; + case T_MAP: { + if (Z_TYPE_PP(value) != IS_ARRAY) convert_to_array(*value); + if (Z_TYPE_PP(value) != IS_ARRAY) { + throw_tprotocolexception("Attempt to send an incompatible type as an array (T_MAP)", INVALID_DATA); + } + HashTable* ht = Z_ARRVAL_PP(value); + zval** val_ptr; + + zend_hash_find(fieldspec, "ktype", 6, (void**)&val_ptr); + if (Z_TYPE_PP(val_ptr) != IS_LONG) convert_to_long(*val_ptr); + uint8_t keytype = Z_LVAL_PP(val_ptr); + transport.writeI8(keytype); + zend_hash_find(fieldspec, "vtype", 6, (void**)&val_ptr); + if (Z_TYPE_PP(val_ptr) != IS_LONG) convert_to_long(*val_ptr); + uint8_t valtype = Z_LVAL_PP(val_ptr); + transport.writeI8(valtype); + + zend_hash_find(fieldspec, "val", 4, (void**)&val_ptr); + HashTable* valspec = Z_ARRVAL_PP(val_ptr); + + transport.writeI32(zend_hash_num_elements(ht)); + HashPosition key_ptr; + for (zend_hash_internal_pointer_reset_ex(ht, &key_ptr); zend_hash_get_current_data_ex(ht, (void**)&val_ptr, &key_ptr) == SUCCESS; zend_hash_move_forward_ex(ht, &key_ptr)) { + binary_serialize_hashtable_key(keytype, transport, ht, key_ptr); + binary_serialize(valtype, transport, val_ptr, valspec); + } + } return; + case T_LIST: { + if (Z_TYPE_PP(value) != IS_ARRAY) convert_to_array(*value); + if (Z_TYPE_PP(value) != IS_ARRAY) { + throw_tprotocolexception("Attempt to send an incompatible type as an array (T_LIST)", INVALID_DATA); + } + HashTable* ht = Z_ARRVAL_PP(value); + zval** val_ptr; + + zend_hash_find(fieldspec, "etype", 6, (void**)&val_ptr); + if (Z_TYPE_PP(val_ptr) != IS_LONG) convert_to_long(*val_ptr); + uint8_t valtype = Z_LVAL_PP(val_ptr); + transport.writeI8(valtype); + + zend_hash_find(fieldspec, "elem", 5, (void**)&val_ptr); + HashTable* valspec = Z_ARRVAL_PP(val_ptr); + + transport.writeI32(zend_hash_num_elements(ht)); + HashPosition key_ptr; + for (zend_hash_internal_pointer_reset_ex(ht, &key_ptr); zend_hash_get_current_data_ex(ht, (void**)&val_ptr, &key_ptr) == SUCCESS; zend_hash_move_forward_ex(ht, &key_ptr)) { + binary_serialize(valtype, transport, val_ptr, valspec); + } + } return; + case T_SET: { + if (Z_TYPE_PP(value) != IS_ARRAY) convert_to_array(*value); + if (Z_TYPE_PP(value) != IS_ARRAY) { + throw_tprotocolexception("Attempt to send an incompatible type as an array (T_SET)", INVALID_DATA); + } + HashTable* ht = Z_ARRVAL_PP(value); + zval** val_ptr; + + zend_hash_find(fieldspec, "etype", 6, (void**)&val_ptr); + if (Z_TYPE_PP(val_ptr) != IS_LONG) convert_to_long(*val_ptr); + uint8_t keytype = Z_LVAL_PP(val_ptr); + transport.writeI8(keytype); + + transport.writeI32(zend_hash_num_elements(ht)); + HashPosition key_ptr; + for (zend_hash_internal_pointer_reset_ex(ht, &key_ptr); zend_hash_get_current_data_ex(ht, (void**)&val_ptr, &key_ptr) == SUCCESS; zend_hash_move_forward_ex(ht, &key_ptr)) { + binary_serialize_hashtable_key(keytype, transport, ht, key_ptr); + } + } return; + }; + char errbuf[128]; + sprintf(errbuf, "Unknown thrift typeID %d", thrift_typeID); + throw_tprotocolexception(errbuf, INVALID_DATA); +} + + +void binary_serialize_spec(zval* zthis, PHPOutputTransport& transport, HashTable* spec) { + HashPosition key_ptr; + zval** val_ptr; + + TSRMLS_FETCH(); + zend_class_entry* ce = zend_get_class_entry(zthis TSRMLS_CC); + + for (zend_hash_internal_pointer_reset_ex(spec, &key_ptr); zend_hash_get_current_data_ex(spec, (void**)&val_ptr, &key_ptr) == SUCCESS; zend_hash_move_forward_ex(spec, &key_ptr)) { + ulong fieldno; + if (zend_hash_get_current_key_ex(spec, NULL, NULL, &fieldno, 0, &key_ptr) != HASH_KEY_IS_LONG) { + throw_tprotocolexception("Bad keytype in TSPEC (expected 'long')", INVALID_DATA); + return; + } + HashTable* fieldspec = Z_ARRVAL_PP(val_ptr); + + // field name + zend_hash_find(fieldspec, "var", 4, (void**)&val_ptr); + char* varname = Z_STRVAL_PP(val_ptr); + + // thrift type + zend_hash_find(fieldspec, "type", 5, (void**)&val_ptr); + if (Z_TYPE_PP(val_ptr) != IS_LONG) convert_to_long(*val_ptr); + int8_t ttype = Z_LVAL_PP(val_ptr); + + zval* prop = zend_read_property(ce, zthis, varname, strlen(varname), false TSRMLS_CC); + if (Z_TYPE_P(prop) != IS_NULL) { + transport.writeI8(ttype); + transport.writeI16(fieldno); + binary_serialize(ttype, transport, &prop, fieldspec); + } + } + transport.writeI8(T_STOP); // struct end +} + +// 6 params: $transport $method_name $ttype $request_struct $seqID $strict_write +PHP_FUNCTION(thrift_protocol_write_binary) { + int argc = ZEND_NUM_ARGS(); + if (argc < 6) { + WRONG_PARAM_COUNT; + } + + zval ***args = (zval***) emalloc(argc * sizeof(zval**)); + zend_get_parameters_array_ex(argc, args); + + if (Z_TYPE_PP(args[0]) != IS_OBJECT) { + php_error_docref(NULL TSRMLS_CC, E_ERROR, "1st parameter is not an object (transport)"); + efree(args); + RETURN_NULL(); + } + + if (Z_TYPE_PP(args[1]) != IS_STRING) { + php_error_docref(NULL TSRMLS_CC, E_ERROR, "2nd parameter is not a string (method name)"); + efree(args); + RETURN_NULL(); + } + + if (Z_TYPE_PP(args[3]) != IS_OBJECT) { + php_error_docref(NULL TSRMLS_CC, E_ERROR, "4th parameter is not an object (request struct)"); + efree(args); + RETURN_NULL(); + } + + PHPOutputTransport transport(*args[0]); + const char* method_name = Z_STRVAL_PP(args[1]); + convert_to_long(*args[2]); + int32_t msgtype = Z_LVAL_PP(args[2]); + zval* request_struct = *args[3]; + convert_to_long(*args[4]); + int32_t seqID = Z_LVAL_PP(args[4]); + convert_to_boolean(*args[5]); + bool strictWrite = Z_BVAL_PP(args[5]); + efree(args); + args = NULL; + + try { + if (strictWrite) { + int32_t version = VERSION_1 | msgtype; + transport.writeI32(version); + transport.writeString(method_name, strlen(method_name)); + transport.writeI32(seqID); + } else { + transport.writeString(method_name, strlen(method_name)); + transport.writeI8(msgtype); + transport.writeI32(seqID); + } + + zval* spec = zend_read_static_property(zend_get_class_entry(request_struct TSRMLS_CC), "_TSPEC", 6, false TSRMLS_CC); + binary_serialize_spec(request_struct, transport, Z_ARRVAL_P(spec)); + } catch (const PHPExceptionWrapper& ex) { + zend_throw_exception_object(ex TSRMLS_CC); + RETURN_NULL(); + } +} + +// 3 params: $transport $response_Typename $strict_read +PHP_FUNCTION(thrift_protocol_read_binary) { + int argc = ZEND_NUM_ARGS(); + + if (argc < 3) { + WRONG_PARAM_COUNT; + } + + zval ***args = (zval***) emalloc(argc * sizeof(zval**)); + zend_get_parameters_array_ex(argc, args); + + if (Z_TYPE_PP(args[0]) != IS_OBJECT) { + php_error_docref(NULL TSRMLS_CC, E_ERROR, "1st parameter is not an object (transport)"); + efree(args); + RETURN_NULL(); + } + + if (Z_TYPE_PP(args[1]) != IS_STRING) { + php_error_docref(NULL TSRMLS_CC, E_ERROR, "2nd parameter is not a string (typename of expected response struct)"); + efree(args); + RETURN_NULL(); + } + + PHPInputTransport transport(*args[0]); + char* obj_typename = Z_STRVAL_PP(args[1]); + convert_to_boolean(*args[2]); + bool strict_read = Z_BVAL_PP(args[2]); + efree(args); + args = NULL; + + try { + int8_t messageType = 0; + int32_t sz = transport.readI32(); + + if (sz < 0) { + // Check for correct version number + int32_t version = sz & VERSION_MASK; + if (version != VERSION_1) { + throw_tprotocolexception("Bad version identifier", BAD_VERSION); + } + messageType = (sz & 0x000000ff); + int32_t namelen = transport.readI32(); + // skip the name string and the sequence ID, we don't care about those + transport.skip(namelen + 4); + } else { + if (strict_read) { + throw_tprotocolexception("No version identifier... old protocol client in strict mode?", BAD_VERSION); + } else { + // Handle pre-versioned input + transport.skip(sz); // skip string body + messageType = transport.readI8(); + transport.skip(4); // skip sequence number + } + } + + if (messageType == T_EXCEPTION) { + zval* ex; + MAKE_STD_ZVAL(ex); + createObject("TApplicationException", ex); + zval* spec = zend_read_static_property(zend_get_class_entry(ex TSRMLS_CC), "_TSPEC", 6, false TSRMLS_CC); + binary_deserialize_spec(ex, transport, Z_ARRVAL_P(spec)); + throw PHPExceptionWrapper(ex); + } + + createObject(obj_typename, return_value); + zval* spec = zend_read_static_property(zend_get_class_entry(return_value TSRMLS_CC), "_TSPEC", 6, false TSRMLS_CC); + binary_deserialize_spec(return_value, transport, Z_ARRVAL_P(spec)); + } catch (const PHPExceptionWrapper& ex) { + zend_throw_exception_object(ex TSRMLS_CC); + RETURN_NULL(); + } +} + diff --git a/lib/php/src/ext/thrift_protocol/php_thrift_protocol.h b/lib/php/src/ext/thrift_protocol/php_thrift_protocol.h new file mode 100644 index 000000000..c9a3e00f0 --- /dev/null +++ b/lib/php/src/ext/thrift_protocol/php_thrift_protocol.h @@ -0,0 +1,26 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +#pragma once + +PHP_FUNCTION(thrift_protocol_write_binary); +PHP_FUNCTION(thrift_protocol_read_binary); + +extern zend_module_entry thrift_protocole_module_entry; + diff --git a/lib/php/src/protocol/TBinaryProtocol.php b/lib/php/src/protocol/TBinaryProtocol.php new file mode 100644 index 000000000..31bbbf9d0 --- /dev/null +++ b/lib/php/src/protocol/TBinaryProtocol.php @@ -0,0 +1,431 @@ +<?php +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + * @package thrift.protocol + */ + +include_once $GLOBALS['THRIFT_ROOT'].'/transport/TBufferedTransport.php'; + +/** + * Binary implementation of the Thrift protocol. + * + */ +class TBinaryProtocol extends TProtocol { + + const VERSION_MASK = 0xffff0000; + const VERSION_1 = 0x80010000; + + protected $strictRead_ = false; + protected $strictWrite_ = true; + + public function __construct($trans, $strictRead=false, $strictWrite=true) { + parent::__construct($trans); + $this->strictRead_ = $strictRead; + $this->strictWrite_ = $strictWrite; + } + + public function writeMessageBegin($name, $type, $seqid) { + if ($this->strictWrite_) { + $version = self::VERSION_1 | $type; + return + $this->writeI32($version) + + $this->writeString($name) + + $this->writeI32($seqid); + } else { + return + $this->writeString($name) + + $this->writeByte($type) + + $this->writeI32($seqid); + } + } + + public function writeMessageEnd() { + return 0; + } + + public function writeStructBegin($name) { + return 0; + } + + public function writeStructEnd() { + return 0; + } + + public function writeFieldBegin($fieldName, $fieldType, $fieldId) { + return + $this->writeByte($fieldType) + + $this->writeI16($fieldId); + } + + public function writeFieldEnd() { + return 0; + } + + public function writeFieldStop() { + return + $this->writeByte(TType::STOP); + } + + public function writeMapBegin($keyType, $valType, $size) { + return + $this->writeByte($keyType) + + $this->writeByte($valType) + + $this->writeI32($size); + } + + public function writeMapEnd() { + return 0; + } + + public function writeListBegin($elemType, $size) { + return + $this->writeByte($elemType) + + $this->writeI32($size); + } + + public function writeListEnd() { + return 0; + } + + public function writeSetBegin($elemType, $size) { + return + $this->writeByte($elemType) + + $this->writeI32($size); + } + + public function writeSetEnd() { + return 0; + } + + public function writeBool($value) { + $data = pack('c', $value ? 1 : 0); + $this->trans_->write($data, 1); + return 1; + } + + public function writeByte($value) { + $data = pack('c', $value); + $this->trans_->write($data, 1); + return 1; + } + + public function writeI16($value) { + $data = pack('n', $value); + $this->trans_->write($data, 2); + return 2; + } + + public function writeI32($value) { + $data = pack('N', $value); + $this->trans_->write($data, 4); + return 4; + } + + public function writeI64($value) { + // If we are on a 32bit architecture we have to explicitly deal with + // 64-bit twos-complement arithmetic since PHP wants to treat all ints + // as signed and any int over 2^31 - 1 as a float + if (PHP_INT_SIZE == 4) { + $neg = $value < 0; + + if ($neg) { + $value *= -1; + } + + $hi = (int)($value / 4294967296); + $lo = (int)$value; + + if ($neg) { + $hi = ~$hi; + $lo = ~$lo; + if (($lo & (int)0xffffffff) == (int)0xffffffff) { + $lo = 0; + $hi++; + } else { + $lo++; + } + } + $data = pack('N2', $hi, $lo); + + } else { + $hi = $value >> 32; + $lo = $value & 0xFFFFFFFF; + $data = pack('N2', $hi, $lo); + } + + $this->trans_->write($data, 8); + return 8; + } + + public function writeDouble($value) { + $data = pack('d', $value); + $this->trans_->write(strrev($data), 8); + return 8; + } + + public function writeString($value) { + $len = strlen($value); + $result = $this->writeI32($len); + if ($len) { + $this->trans_->write($value, $len); + } + return $result + $len; + } + + public function readMessageBegin(&$name, &$type, &$seqid) { + $result = $this->readI32($sz); + if ($sz < 0) { + $version = (int) ($sz & self::VERSION_MASK); + if ($version != (int) self::VERSION_1) { + throw new TProtocolException('Bad version identifier: '.$sz, TProtocolException::BAD_VERSION); + } + $type = $sz & 0x000000ff; + $result += + $this->readString($name) + + $this->readI32($seqid); + } else { + if ($this->strictRead_) { + throw new TProtocolException('No version identifier, old protocol client?', TProtocolException::BAD_VERSION); + } else { + // Handle pre-versioned input + $name = $this->trans_->readAll($sz); + $result += + $sz + + $this->readByte($type) + + $this->readI32($seqid); + } + } + return $result; + } + + public function readMessageEnd() { + return 0; + } + + public function readStructBegin(&$name) { + $name = ''; + return 0; + } + + public function readStructEnd() { + return 0; + } + + public function readFieldBegin(&$name, &$fieldType, &$fieldId) { + $result = $this->readByte($fieldType); + if ($fieldType == TType::STOP) { + $fieldId = 0; + return $result; + } + $result += $this->readI16($fieldId); + return $result; + } + + public function readFieldEnd() { + return 0; + } + + public function readMapBegin(&$keyType, &$valType, &$size) { + return + $this->readByte($keyType) + + $this->readByte($valType) + + $this->readI32($size); + } + + public function readMapEnd() { + return 0; + } + + public function readListBegin(&$elemType, &$size) { + return + $this->readByte($elemType) + + $this->readI32($size); + } + + public function readListEnd() { + return 0; + } + + public function readSetBegin(&$elemType, &$size) { + return + $this->readByte($elemType) + + $this->readI32($size); + } + + public function readSetEnd() { + return 0; + } + + public function readBool(&$value) { + $data = $this->trans_->readAll(1); + $arr = unpack('c', $data); + $value = $arr[1] == 1; + return 1; + } + + public function readByte(&$value) { + $data = $this->trans_->readAll(1); + $arr = unpack('c', $data); + $value = $arr[1]; + return 1; + } + + public function readI16(&$value) { + $data = $this->trans_->readAll(2); + $arr = unpack('n', $data); + $value = $arr[1]; + if ($value > 0x7fff) { + $value = 0 - (($value - 1) ^ 0xffff); + } + return 2; + } + + public function readI32(&$value) { + $data = $this->trans_->readAll(4); + $arr = unpack('N', $data); + $value = $arr[1]; + if ($value > 0x7fffffff) { + $value = 0 - (($value - 1) ^ 0xffffffff); + } + return 4; + } + + public function readI64(&$value) { + $data = $this->trans_->readAll(8); + + $arr = unpack('N2', $data); + + // If we are on a 32bit architecture we have to explicitly deal with + // 64-bit twos-complement arithmetic since PHP wants to treat all ints + // as signed and any int over 2^31 - 1 as a float + if (PHP_INT_SIZE == 4) { + + $hi = $arr[1]; + $lo = $arr[2]; + $isNeg = $hi < 0; + + // Check for a negative + if ($isNeg) { + $hi = ~$hi & (int)0xffffffff; + $lo = ~$lo & (int)0xffffffff; + + if ($lo == (int)0xffffffff) { + $hi++; + $lo = 0; + } else { + $lo++; + } + } + + // Force 32bit words in excess of 2G to pe positive - we deal wigh sign + // explicitly below + + if ($hi & (int)0x80000000) { + $hi &= (int)0x7fffffff; + $hi += 0x80000000; + } + + if ($lo & (int)0x80000000) { + $lo &= (int)0x7fffffff; + $lo += 0x80000000; + } + + $value = $hi * 4294967296 + $lo; + + if ($isNeg) { + $value = 0 - $value; + } + } else { + + // Upcast negatives in LSB bit + if ($arr[2] & 0x80000000) { + $arr[2] = $arr[2] & 0xffffffff; + } + + // Check for a negative + if ($arr[1] & 0x80000000) { + $arr[1] = $arr[1] & 0xffffffff; + $arr[1] = $arr[1] ^ 0xffffffff; + $arr[2] = $arr[2] ^ 0xffffffff; + $value = 0 - $arr[1]*4294967296 - $arr[2] - 1; + } else { + $value = $arr[1]*4294967296 + $arr[2]; + } + } + + return 8; + } + + public function readDouble(&$value) { + $data = strrev($this->trans_->readAll(8)); + $arr = unpack('d', $data); + $value = $arr[1]; + return 8; + } + + public function readString(&$value) { + $result = $this->readI32($len); + if ($len) { + $value = $this->trans_->readAll($len); + } else { + $value = ''; + } + return $result + $len; + } +} + +/** + * Binary Protocol Factory + */ +class TBinaryProtocolFactory implements TProtocolFactory { + private $strictRead_ = false; + private $strictWrite_ = false; + + public function __construct($strictRead=false, $strictWrite=false) { + $this->strictRead_ = $strictRead; + $this->strictWrite_ = $strictWrite; + } + + public function getProtocol($trans) { + return new TBinaryProtocol($trans, $this->strictRead, $this->strictWrite); + } +} + +/** + * Accelerated binary protocol: used in conjunction with the thrift_protocol + * extension for faster deserialization + */ +class TBinaryProtocolAccelerated extends TBinaryProtocol { + public function __construct($trans, $strictRead=false, $strictWrite=true) { + // If the transport doesn't implement putBack, wrap it in a + // TBufferedTransport (which does) + if (!method_exists($trans, 'putBack')) { + $trans = new TBufferedTransport($trans); + } + parent::__construct($trans, $strictRead, $strictWrite); + } + public function isStrictRead() { + return $this->strictRead_; + } + public function isStrictWrite() { + return $this->strictWrite_; + } +} + +?> diff --git a/lib/php/src/protocol/TProtocol.php b/lib/php/src/protocol/TProtocol.php new file mode 100644 index 000000000..e9ff41a32 --- /dev/null +++ b/lib/php/src/protocol/TProtocol.php @@ -0,0 +1,377 @@ +<?php +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + * @package thrift.protocol + */ + + +/** + * Protocol module. Contains all the types and definitions needed to implement + * a protocol encoder/decoder. + * + * @package thrift.protocol + */ + +/** + * Protocol exceptions + */ +class TProtocolException extends TException { + const UNKNOWN = 0; + const INVALID_DATA = 1; + const NEGATIVE_SIZE = 2; + const SIZE_LIMIT = 3; + const BAD_VERSION = 4; + + function __construct($message=null, $code=0) { + parent::__construct($message, $code); + } +} + +/** + * Protocol base class module. + */ +abstract class TProtocol { + // The below may seem silly, but it is to get around the problem that the + // "instanceof" operator can only take in a T_VARIABLE and not a T_STRING + // or T_CONSTANT_ENCAPSED_STRING. Using "is_a()" instead of "instanceof" is + // a workaround but is deprecated in PHP5. This is used in the generated + // deserialization code. + static $TBINARYPROTOCOLACCELERATED = 'TBinaryProtocolAccelerated'; + + /** + * Underlying transport + * + * @var TTransport + */ + protected $trans_; + + /** + * Constructor + */ + protected function __construct($trans) { + $this->trans_ = $trans; + } + + /** + * Accessor for transport + * + * @return TTransport + */ + public function getTransport() { + return $this->trans_; + } + + /** + * Writes the message header + * + * @param string $name Function name + * @param int $type message type TMessageType::CALL or TMessageType::REPLY + * @param int $seqid The sequence id of this message + */ + public abstract function writeMessageBegin($name, $type, $seqid); + + /** + * Close the message + */ + public abstract function writeMessageEnd(); + + /** + * Writes a struct header. + * + * @param string $name Struct name + * @throws TException on write error + * @return int How many bytes written + */ + public abstract function writeStructBegin($name); + + /** + * Close a struct. + * + * @throws TException on write error + * @return int How many bytes written + */ + public abstract function writeStructEnd(); + + /* + * Starts a field. + * + * @param string $name Field name + * @param int $type Field type + * @param int $fid Field id + * @throws TException on write error + * @return int How many bytes written + */ + public abstract function writeFieldBegin($fieldName, $fieldType, $fieldId); + + public abstract function writeFieldEnd(); + + public abstract function writeFieldStop(); + + public abstract function writeMapBegin($keyType, $valType, $size); + + public abstract function writeMapEnd(); + + public abstract function writeListBegin($elemType, $size); + + public abstract function writeListEnd(); + + public abstract function writeSetBegin($elemType, $size); + + public abstract function writeSetEnd(); + + public abstract function writeBool($bool); + + public abstract function writeByte($byte); + + public abstract function writeI16($i16); + + public abstract function writeI32($i32); + + public abstract function writeI64($i64); + + public abstract function writeDouble($dub); + + public abstract function writeString($str); + + /** + * Reads the message header + * + * @param string $name Function name + * @param int $type message type TMessageType::CALL or TMessageType::REPLY + * @parem int $seqid The sequence id of this message + */ + public abstract function readMessageBegin(&$name, &$type, &$seqid); + + /** + * Read the close of message + */ + public abstract function readMessageEnd(); + + public abstract function readStructBegin(&$name); + + public abstract function readStructEnd(); + + public abstract function readFieldBegin(&$name, &$fieldType, &$fieldId); + + public abstract function readFieldEnd(); + + public abstract function readMapBegin(&$keyType, &$valType, &$size); + + public abstract function readMapEnd(); + + public abstract function readListBegin(&$elemType, &$size); + + public abstract function readListEnd(); + + public abstract function readSetBegin(&$elemType, &$size); + + public abstract function readSetEnd(); + + public abstract function readBool(&$bool); + + public abstract function readByte(&$byte); + + public abstract function readI16(&$i16); + + public abstract function readI32(&$i32); + + public abstract function readI64(&$i64); + + public abstract function readDouble(&$dub); + + public abstract function readString(&$str); + + /** + * The skip function is a utility to parse over unrecognized date without + * causing corruption. + * + * @param TType $type What type is it + */ + public function skip($type) { + switch ($type) { + case TType::BOOL: + return $this->readBool($bool); + case TType::BYTE: + return $this->readByte($byte); + case TType::I16: + return $this->readI16($i16); + case TType::I32: + return $this->readI32($i32); + case TType::I64: + return $this->readI64($i64); + case TType::DOUBLE: + return $this->readDouble($dub); + case TType::STRING: + return $this->readString($str); + case TType::STRUCT: + { + $result = $this->readStructBegin($name); + while (true) { + $result += $this->readFieldBegin($name, $ftype, $fid); + if ($ftype == TType::STOP) { + break; + } + $result += $this->skip($ftype); + $result += $this->readFieldEnd(); + } + $result += $this->readStructEnd(); + return $result; + } + case TType::MAP: + { + $result = $this->readMapBegin($keyType, $valType, $size); + for ($i = 0; $i < $size; $i++) { + $result += $this->skip($keyType); + $result += $this->skip($valType); + } + $result += $this->readMapEnd(); + return $result; + } + case TType::SET: + { + $result = $this->readSetBegin($elemType, $size); + for ($i = 0; $i < $size; $i++) { + $result += $this->skip($elemType); + } + $result += $this->readSetEnd(); + return $result; + } + case TType::LST: + { + $result = $this->readListBegin($elemType, $size); + for ($i = 0; $i < $size; $i++) { + $result += $this->skip($elemType); + } + $result += $this->readListEnd(); + return $result; + } + default: + return 0; + } + } + + /** + * Utility for skipping binary data + * + * @param TTransport $itrans TTransport object + * @param int $type Field type + */ + public static function skipBinary($itrans, $type) { + switch ($type) { + case TType::BOOL: + return $itrans->readAll(1); + case TType::BYTE: + return $itrans->readAll(1); + case TType::I16: + return $itrans->readAll(2); + case TType::I32: + return $itrans->readAll(4); + case TType::I64: + return $itrans->readAll(8); + case TType::DOUBLE: + return $itrans->readAll(8); + case TType::STRING: + $len = unpack('N', $itrans->readAll(4)); + $len = $len[1]; + if ($len > 0x7fffffff) { + $len = 0 - (($len - 1) ^ 0xffffffff); + } + return 4 + $itrans->readAll($len); + case TType::STRUCT: + { + $result = 0; + while (true) { + $ftype = 0; + $fid = 0; + $data = $itrans->readAll(1); + $arr = unpack('c', $data); + $ftype = $arr[1]; + if ($ftype == TType::STOP) { + break; + } + // I16 field id + $result += $itrans->readAll(2); + $result += self::skipBinary($itrans, $ftype); + } + return $result; + } + case TType::MAP: + { + // Ktype + $data = $itrans->readAll(1); + $arr = unpack('c', $data); + $ktype = $arr[1]; + // Vtype + $data = $itrans->readAll(1); + $arr = unpack('c', $data); + $vtype = $arr[1]; + // Size + $data = $itrans->readAll(4); + $arr = unpack('N', $data); + $size = $arr[1]; + if ($size > 0x7fffffff) { + $size = 0 - (($size - 1) ^ 0xffffffff); + } + $result = 6; + for ($i = 0; $i < $size; $i++) { + $result += self::skipBinary($itrans, $ktype); + $result += self::skipBinary($itrans, $vtype); + } + return $result; + } + case TType::SET: + case TType::LST: + { + // Vtype + $data = $itrans->readAll(1); + $arr = unpack('c', $data); + $vtype = $arr[1]; + // Size + $data = $itrans->readAll(4); + $arr = unpack('N', $data); + $size = $arr[1]; + if ($size > 0x7fffffff) { + $size = 0 - (($size - 1) ^ 0xffffffff); + } + $result = 5; + for ($i = 0; $i < $size; $i++) { + $result += self::skipBinary($itrans, $vtype); + } + return $result; + } + default: + return 0; + } + } +} + +/** + * Protocol factory creates protocol objects from transports + */ +interface TProtocolFactory { + /** + * Build a protocol from the base transport + * + * @return TProtcol protocol + */ + public function getProtocol($trans); +} + + +?> diff --git a/lib/php/src/transport/TBufferedTransport.php b/lib/php/src/transport/TBufferedTransport.php new file mode 100644 index 000000000..cfae767ec --- /dev/null +++ b/lib/php/src/transport/TBufferedTransport.php @@ -0,0 +1,163 @@ +<?php +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + * @package thrift.transport + */ + + +/** + * Buffered transport. Stores data to an internal buffer that it doesn't + * actually write out until flush is called. For reading, we do a greedy + * read and then serve data out of the internal buffer. + * + * @package thrift.transport + */ +class TBufferedTransport extends TTransport { + + /** + * Constructor. Creates a buffered transport around an underlying transport + */ + public function __construct($transport=null, $rBufSize=512, $wBufSize=512) { + $this->transport_ = $transport; + $this->rBufSize_ = $rBufSize; + $this->wBufSize_ = $wBufSize; + } + + /** + * The underlying transport + * + * @var TTransport + */ + protected $transport_ = null; + + /** + * The receive buffer size + * + * @var int + */ + protected $rBufSize_ = 512; + + /** + * The write buffer size + * + * @var int + */ + protected $wBufSize_ = 512; + + /** + * The write buffer. + * + * @var string + */ + protected $wBuf_ = ''; + + /** + * The read buffer. + * + * @var string + */ + protected $rBuf_ = ''; + + public function isOpen() { + return $this->transport_->isOpen(); + } + + public function open() { + $this->transport_->open(); + } + + public function close() { + $this->transport_->close(); + } + + public function putBack($data) { + if (strlen($this->rBuf_) === 0) { + $this->rBuf_ = $data; + } else { + $this->rBuf_ = ($data . $this->rBuf_); + } + } + + /** + * The reason that we customize readAll here is that the majority of PHP + * streams are already internally buffered by PHP. The socket stream, for + * example, buffers internally and blocks if you call read with $len greater + * than the amount of data available, unlike recv() in C. + * + * Therefore, use the readAll method of the wrapped transport inside + * the buffered readAll. + */ + public function readAll($len) { + $have = strlen($this->rBuf_); + if ($have == 0) { + $data = $this->transport_->readAll($len); + } else if ($have < $len) { + $data = $this->rBuf_; + $this->rBuf_ = ''; + $data .= $this->transport_->readAll($len - $have); + } else if ($have == $len) { + $data = $this->rBuf_; + $this->rBuf_ = ''; + } else if ($have > $len) { + $data = substr($this->rBuf_, 0, $len); + $this->rBuf_ = substr($this->rBuf_, $len); + } + return $data; + } + + public function read($len) { + if (strlen($this->rBuf_) === 0) { + $this->rBuf_ = $this->transport_->read($this->rBufSize_); + } + + if (strlen($this->rBuf_) <= $len) { + $ret = $this->rBuf_; + $this->rBuf_ = ''; + return $ret; + } + + $ret = substr($this->rBuf_, 0, $len); + $this->rBuf_ = substr($this->rBuf_, $len); + return $ret; + } + + public function write($buf) { + $this->wBuf_ .= $buf; + if (strlen($this->wBuf_) >= $this->wBufSize_) { + $out = $this->wBuf_; + + // Note that we clear the internal wBuf_ prior to the underlying write + // to ensure we're in a sane state (i.e. internal buffer cleaned) + // if the underlying write throws up an exception + $this->wBuf_ = ''; + $this->transport_->write($out); + } + } + + public function flush() { + if (strlen($this->wBuf_) > 0) { + $this->transport_->write($this->wBuf_); + $this->wBuf_ = ''; + } + $this->transport_->flush(); + } + +} + +?> diff --git a/lib/php/src/transport/TFramedTransport.php b/lib/php/src/transport/TFramedTransport.php new file mode 100644 index 000000000..dc57392f7 --- /dev/null +++ b/lib/php/src/transport/TFramedTransport.php @@ -0,0 +1,179 @@ +<?php +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + * @package thrift.transport + */ + + +/** + * Framed transport. Writes and reads data in chunks that are stamped with + * their length. + * + * @package thrift.transport + */ +class TFramedTransport extends TTransport { + + /** + * Underlying transport object. + * + * @var TTransport + */ + private $transport_; + + /** + * Buffer for read data. + * + * @var string + */ + private $rBuf_; + + /** + * Buffer for queued output data + * + * @var string + */ + private $wBuf_; + + /** + * Whether to frame reads + * + * @var bool + */ + private $read_; + + /** + * Whether to frame writes + * + * @var bool + */ + private $write_; + + /** + * Constructor. + * + * @param TTransport $transport Underlying transport + */ + public function __construct($transport=null, $read=true, $write=true) { + $this->transport_ = $transport; + $this->read_ = $read; + $this->write_ = $write; + } + + public function isOpen() { + return $this->transport_->isOpen(); + } + + public function open() { + $this->transport_->open(); + } + + public function close() { + $this->transport_->close(); + } + + /** + * Reads from the buffer. When more data is required reads another entire + * chunk and serves future reads out of that. + * + * @param int $len How much data + */ + public function read($len) { + if (!$this->read_) { + return $this->transport_->read($len); + } + + if (strlen($this->rBuf_) === 0) { + $this->readFrame(); + } + + // Just return full buff + if ($len >= strlen($this->rBuf_)) { + $out = $this->rBuf_; + $this->rBuf_ = null; + return $out; + } + + // Return substr + $out = substr($this->rBuf_, 0, $len); + $this->rBuf_ = substr($this->rBuf_, $len); + return $out; + } + + /** + * Put previously read data back into the buffer + * + * @param string $data data to return + */ + public function putBack($data) { + if (strlen($this->rBuf_) === 0) { + $this->rBuf_ = $data; + } else { + $this->rBuf_ = ($data . $this->rBuf_); + } + } + + /** + * Reads a chunk of data into the internal read buffer. + */ + private function readFrame() { + $buf = $this->transport_->readAll(4); + $val = unpack('N', $buf); + $sz = $val[1]; + + $this->rBuf_ = $this->transport_->readAll($sz); + } + + /** + * Writes some data to the pending output buffer. + * + * @param string $buf The data + * @param int $len Limit of bytes to write + */ + public function write($buf, $len=null) { + if (!$this->write_) { + return $this->transport_->write($buf, $len); + } + + if ($len !== null && $len < strlen($buf)) { + $buf = substr($buf, 0, $len); + } + $this->wBuf_ .= $buf; + } + + /** + * Writes the output buffer to the stream in the format of a 4-byte length + * followed by the actual data. + */ + public function flush() { + if (!$this->write_) { + return $this->transport_->flush(); + } + + $out = pack('N', strlen($this->wBuf_)); + $out .= $this->wBuf_; + + // Note that we clear the internal wBuf_ prior to the underlying write + // to ensure we're in a sane state (i.e. internal buffer cleaned) + // if the underlying write throws up an exception + $this->wBuf_ = ''; + $this->transport_->write($out); + $this->transport_->flush(); + } + +} diff --git a/lib/php/src/transport/THttpClient.php b/lib/php/src/transport/THttpClient.php new file mode 100644 index 000000000..224d403b4 --- /dev/null +++ b/lib/php/src/transport/THttpClient.php @@ -0,0 +1,202 @@ +<?php +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + * @package thrift.transport + */ + + +/** + * HTTP client for Thrift + * + * @package thrift.transport + */ +class THttpClient extends TTransport { + + /** + * The host to connect to + * + * @var string + */ + protected $host_; + + /** + * The port to connect on + * + * @var int + */ + protected $port_; + + /** + * The URI to request + * + * @var string + */ + protected $uri_; + + /** + * The scheme to use for the request, i.e. http, https + * + * @var string + */ + protected $scheme_; + + /** + * Buffer for the HTTP request data + * + * @var string + */ + protected $buf_; + + /** + * Input socket stream. + * + * @var resource + */ + protected $handle_; + + /** + * Read timeout + * + * @var float + */ + protected $timeout_; + + /** + * Make a new HTTP client. + * + * @param string $host + * @param int $port + * @param string $uri + */ + public function __construct($host, $port=80, $uri='', $scheme = 'http') { + if ((strlen($uri) > 0) && ($uri{0} != '/')) { + $uri = '/'.$uri; + } + $this->scheme_ = $scheme; + $this->host_ = $host; + $this->port_ = $port; + $this->uri_ = $uri; + $this->buf_ = ''; + $this->handle_ = null; + $this->timeout_ = null; + } + + /** + * Set read timeout + * + * @param float $timeout + */ + public function setTimeoutSecs($timeout) { + $this->timeout_ = $timeout; + } + + /** + * Whether this transport is open. + * + * @return boolean true if open + */ + public function isOpen() { + return true; + } + + /** + * Open the transport for reading/writing + * + * @throws TTransportException if cannot open + */ + public function open() {} + + /** + * Close the transport. + */ + public function close() { + if ($this->handle_) { + @fclose($this->handle_); + $this->handle_ = null; + } + } + + /** + * Read some data into the array. + * + * @param int $len How much to read + * @return string The data that has been read + * @throws TTransportException if cannot read any more data + */ + public function read($len) { + $data = @fread($this->handle_, $len); + if ($data === FALSE || $data === '') { + $md = stream_get_meta_data($this->handle_); + if ($md['timed_out']) { + throw new TTransportException('THttpClient: timed out reading '.$len.' bytes from '.$this->host_.':'.$this->port_.'/'.$this->uri_, TTransportException::TIMED_OUT); + } else { + throw new TTransportException('THttpClient: Could not read '.$len.' bytes from '.$this->host_.':'.$this->port_.'/'.$this->uri_, TTransportException::UNKNOWN); + } + } + return $data; + } + + /** + * Writes some data into the pending buffer + * + * @param string $buf The data to write + * @throws TTransportException if writing fails + */ + public function write($buf) { + $this->buf_ .= $buf; + } + + /** + * Opens and sends the actual request over the HTTP connection + * + * @throws TTransportException if a writing error occurs + */ + public function flush() { + // God, PHP really has some esoteric ways of doing simple things. + $host = $this->host_.($this->port_ != 80 ? ':'.$this->port_ : ''); + + $headers = array('Host: '.$host, + 'Accept: application/x-thrift', + 'User-Agent: PHP/THttpClient', + 'Content-Type: application/x-thrift', + 'Content-Length: '.strlen($this->buf_)); + + $options = array('method' => 'POST', + 'header' => implode("\r\n", $headers), + 'max_redirects' => 1, + 'content' => $this->buf_); + if ($this->timeout_ > 0) { + $options['timeout'] = $this->timeout_; + } + $this->buf_ = ''; + + $contextid = stream_context_create(array('http' => $options)); + $this->handle_ = @fopen($this->scheme_.'://'.$host.$this->uri_, 'r', false, $contextid); + + // Connect failed? + if ($this->handle_ === FALSE) { + $this->handle_ = null; + $error = 'THttpClient: Could not connect to '.$host.$this->uri_; + throw new TTransportException($error, TTransportException::NOT_OPEN); + } + } + +} + +?> diff --git a/lib/php/src/transport/TMemoryBuffer.php b/lib/php/src/transport/TMemoryBuffer.php new file mode 100644 index 000000000..01eb0f5a6 --- /dev/null +++ b/lib/php/src/transport/TMemoryBuffer.php @@ -0,0 +1,84 @@ +<?php +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + * @package thrift.transport + */ + + +/** + * A memory buffer is a tranpsort that simply reads from and writes to an + * in-memory string buffer. Anytime you call write on it, the data is simply + * placed into a buffer, and anytime you call read, data is read from that + * buffer. + * + * @package thrift.transport + */ +class TMemoryBuffer extends TTransport { + + /** + * Constructor. Optionally pass an initial value + * for the buffer. + */ + public function __construct($buf = '') { + $this->buf_ = $buf; + } + + protected $buf_ = ''; + + public function isOpen() { + return true; + } + + public function open() {} + + public function close() {} + + public function write($buf) { + $this->buf_ .= $buf; + } + + public function read($len) { + if (strlen($this->buf_) === 0) { + throw new TTransportException('TMemoryBuffer: Could not read ' . + $len . ' bytes from buffer.', + TTransportException::UNKNOWN); + } + + if (strlen($this->buf_) <= $len) { + $ret = $this->buf_; + $this->buf_ = ''; + return $ret; + } + + $ret = substr($this->buf_, 0, $len); + $this->buf_ = substr($this->buf_, $len); + + return $ret; + } + + function getBuffer() { + return $this->buf_; + } + + public function available() { + return strlen($this->buf_); + } +} + +?> diff --git a/lib/php/src/transport/TNullTransport.php b/lib/php/src/transport/TNullTransport.php new file mode 100644 index 000000000..bada5dfb7 --- /dev/null +++ b/lib/php/src/transport/TNullTransport.php @@ -0,0 +1,48 @@ +<?php +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + * @package thrift.transport + */ + + +/** + * Transport that only accepts writes and ignores them. + * This is useful for measuring the serialized size of structures. + * + * @package thrift.transport + */ +class TNullTransport extends TTransport { + + public function isOpen() { + return true; + } + + public function open() {} + + public function close() {} + + public function read($len) { + throw new TTransportException("Can't read from TNullTransport."); + } + + public function write($buf) {} + +} + +?> diff --git a/lib/php/src/transport/TPhpStream.php b/lib/php/src/transport/TPhpStream.php new file mode 100644 index 000000000..3a1c80b8f --- /dev/null +++ b/lib/php/src/transport/TPhpStream.php @@ -0,0 +1,111 @@ +<?php +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + * @package thrift.transport + */ + + +/** + * Php stream transport. Reads to and writes from the php standard streams + * php://input and php://output + * + * @package thrift.transport + */ +class TPhpStream extends TTransport { + + const MODE_R = 1; + const MODE_W = 2; + + private $inStream_ = null; + + private $outStream_ = null; + + private $read_ = false; + + private $write_ = false; + + public function __construct($mode) { + $this->read_ = $mode & self::MODE_R; + $this->write_ = $mode & self::MODE_W; + } + + public function open() { + if ($this->read_) { + $this->inStream_ = @fopen(self::inStreamName(), 'r'); + if (!is_resource($this->inStream_)) { + throw new TException('TPhpStream: Could not open php://input'); + } + } + if ($this->write_) { + $this->outStream_ = @fopen('php://output', 'w'); + if (!is_resource($this->outStream_)) { + throw new TException('TPhpStream: Could not open php://output'); + } + } + } + + public function close() { + if ($this->read_) { + @fclose($this->inStream_); + $this->inStream_ = null; + } + if ($this->write_) { + @fclose($this->outStream_); + $this->outStream_ = null; + } + } + + public function isOpen() { + return + (!$this->read_ || is_resource($this->inStream_)) && + (!$this->write_ || is_resource($this->outStream_)); + } + + public function read($len) { + $data = @fread($this->inStream_, $len); + if ($data === FALSE || $data === '') { + throw new TException('TPhpStream: Could not read '.$len.' bytes'); + } + return $data; + } + + public function write($buf) { + while (strlen($buf) > 0) { + $got = @fwrite($this->outStream_, $buf); + if ($got === 0 || $got === FALSE) { + throw new TException('TPhpStream: Could not write '.strlen($buf).' bytes'); + } + $buf = substr($buf, $got); + } + } + + public function flush() { + @fflush($this->outStream_); + } + + private static function inStreamName() { + if (php_sapi_name() == 'cli') { + return 'php://stdin'; + } + return 'php://input'; + } + +} + +?> diff --git a/lib/php/src/transport/TSocket.php b/lib/php/src/transport/TSocket.php new file mode 100644 index 000000000..ba3a63184 --- /dev/null +++ b/lib/php/src/transport/TSocket.php @@ -0,0 +1,312 @@ +<?php +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + * @package thrift.transport + */ + + +/** + * Sockets implementation of the TTransport interface. + * + * @package thrift.transport + */ +class TSocket extends TTransport { + + /** + * Handle to PHP socket + * + * @var resource + */ + private $handle_ = null; + + /** + * Remote hostname + * + * @var string + */ + protected $host_ = 'localhost'; + + /** + * Remote port + * + * @var int + */ + protected $port_ = '9090'; + + /** + * Send timeout in milliseconds + * + * @var int + */ + private $sendTimeout_ = 100; + + /** + * Recv timeout in milliseconds + * + * @var int + */ + private $recvTimeout_ = 750; + + /** + * Is send timeout set? + * + * @var bool + */ + private $sendTimeoutSet_ = FALSE; + + /** + * Persistent socket or plain? + * + * @var bool + */ + private $persist_ = FALSE; + + /** + * Debugging on? + * + * @var bool + */ + protected $debug_ = FALSE; + + /** + * Debug handler + * + * @var mixed + */ + protected $debugHandler_ = null; + + /** + * Socket constructor + * + * @param string $host Remote hostname + * @param int $port Remote port + * @param bool $persist Whether to use a persistent socket + * @param string $debugHandler Function to call for error logging + */ + public function __construct($host='localhost', + $port=9090, + $persist=FALSE, + $debugHandler=null) { + $this->host_ = $host; + $this->port_ = $port; + $this->persist_ = $persist; + $this->debugHandler_ = $debugHandler ? $debugHandler : 'error_log'; + } + + /** + * Sets the send timeout. + * + * @param int $timeout Timeout in milliseconds. + */ + public function setSendTimeout($timeout) { + $this->sendTimeout_ = $timeout; + } + + /** + * Sets the receive timeout. + * + * @param int $timeout Timeout in milliseconds. + */ + public function setRecvTimeout($timeout) { + $this->recvTimeout_ = $timeout; + } + + /** + * Sets debugging output on or off + * + * @param bool $debug + */ + public function setDebug($debug) { + $this->debug_ = $debug; + } + + /** + * Get the host that this socket is connected to + * + * @return string host + */ + public function getHost() { + return $this->host_; + } + + /** + * Get the remote port that this socket is connected to + * + * @return int port + */ + public function getPort() { + return $this->port_; + } + + /** + * Tests whether this is open + * + * @return bool true if the socket is open + */ + public function isOpen() { + return is_resource($this->handle_); + } + + /** + * Connects the socket. + */ + public function open() { + + if ($this->persist_) { + $this->handle_ = @pfsockopen($this->host_, + $this->port_, + $errno, + $errstr, + $this->sendTimeout_/1000.0); + } else { + $this->handle_ = @fsockopen($this->host_, + $this->port_, + $errno, + $errstr, + $this->sendTimeout_/1000.0); + } + + // Connect failed? + if ($this->handle_ === FALSE) { + $error = 'TSocket: Could not connect to '.$this->host_.':'.$this->port_.' ('.$errstr.' ['.$errno.'])'; + if ($this->debug_) { + call_user_func($this->debugHandler_, $error); + } + throw new TException($error); + } + + stream_set_timeout($this->handle_, 0, $this->sendTimeout_*1000); + $this->sendTimeoutSet_ = TRUE; + } + + /** + * Closes the socket. + */ + public function close() { + if (!$this->persist_) { + @fclose($this->handle_); + $this->handle_ = null; + } + } + + /** + * Uses stream get contents to do the reading + * + * @param int $len How many bytes + * @return string Binary data + */ + public function readAll($len) { + if ($this->sendTimeoutSet_) { + stream_set_timeout($this->handle_, 0, $this->recvTimeout_*1000); + $this->sendTimeoutSet_ = FALSE; + } + // This call does not obey stream_set_timeout values! + // $buf = @stream_get_contents($this->handle_, $len); + + $pre = null; + while (TRUE) { + $buf = @fread($this->handle_, $len); + if ($buf === FALSE || $buf === '') { + $md = stream_get_meta_data($this->handle_); + if ($md['timed_out']) { + throw new TException('TSocket: timed out reading '.$len.' bytes from '. + $this->host_.':'.$this->port_); + } else { + throw new TException('TSocket: Could not read '.$len.' bytes from '. + $this->host_.':'.$this->port_); + } + } else if (($sz = strlen($buf)) < $len) { + $md = stream_get_meta_data($this->handle_); + if ($md['timed_out']) { + throw new TException('TSocket: timed out reading '.$len.' bytes from '. + $this->host_.':'.$this->port_); + } else { + $pre .= $buf; + $len -= $sz; + } + } else { + return $pre.$buf; + } + } + } + + /** + * Read from the socket + * + * @param int $len How many bytes + * @return string Binary data + */ + public function read($len) { + if ($this->sendTimeoutSet_) { + stream_set_timeout($this->handle_, 0, $this->recvTimeout_*1000); + $this->sendTimeoutSet_ = FALSE; + } + $data = @fread($this->handle_, $len); + if ($data === FALSE || $data === '') { + $md = stream_get_meta_data($this->handle_); + if ($md['timed_out']) { + throw new TException('TSocket: timed out reading '.$len.' bytes from '. + $this->host_.':'.$this->port_); + } else { + throw new TException('TSocket: Could not read '.$len.' bytes from '. + $this->host_.':'.$this->port_); + } + } + return $data; + } + + /** + * Write to the socket. + * + * @param string $buf The data to write + */ + public function write($buf) { + if (!$this->sendTimeoutSet_) { + stream_set_timeout($this->handle_, 0, $this->sendTimeout_*1000); + $this->sendTimeoutSet_ = TRUE; + } + while (strlen($buf) > 0) { + $got = @fwrite($this->handle_, $buf); + if ($got === 0 || $got === FALSE) { + $md = stream_get_meta_data($this->handle_); + if ($md['timed_out']) { + throw new TException('TSocket: timed out writing '.strlen($buf).' bytes from '. + $this->host_.':'.$this->port_); + } else { + throw new TException('TSocket: Could not write '.strlen($buf).' bytes '. + $this->host_.':'.$this->port_); + } + } + $buf = substr($buf, $got); + } + } + + /** + * Flush output to the socket. + */ + public function flush() { + $ret = fflush($this->handle_); + if ($ret === FALSE) { + throw new TException('TSocket: Could not flush: '. + $this->host_.':'.$this->port_); + } + } +} + +?> diff --git a/lib/php/src/transport/TSocketPool.php b/lib/php/src/transport/TSocketPool.php new file mode 100644 index 000000000..7f1157cb4 --- /dev/null +++ b/lib/php/src/transport/TSocketPool.php @@ -0,0 +1,296 @@ +<?php +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + * @package thrift.transport + */ + + +/** Inherits from Socket */ +include_once $GLOBALS['THRIFT_ROOT'].'/transport/TSocket.php'; + +/** + * This library makes use of APC cache to make hosts as down in a web + * environment. If you are running from the CLI or on a system without APC + * installed, then these null functions will step in and act like cache + * misses. + */ +if (!function_exists('apc_fetch')) { + function apc_fetch($key) { return FALSE; } + function apc_store($key, $var, $ttl=0) { return FALSE; } +} + +/** + * Sockets implementation of the TTransport interface that allows connection + * to a pool of servers. + * + * @package thrift.transport + */ +class TSocketPool extends TSocket { + + /** + * Remote servers. Array of associative arrays with 'host' and 'port' keys + */ + private $servers_ = array(); + + /** + * How many times to retry each host in connect + * + * @var int + */ + private $numRetries_ = 1; + + /** + * Retry interval in seconds, how long to not try a host if it has been + * marked as down. + * + * @var int + */ + private $retryInterval_ = 60; + + /** + * Max consecutive failures before marking a host down. + * + * @var int + */ + private $maxConsecutiveFailures_ = 1; + + /** + * Try hosts in order? or Randomized? + * + * @var bool + */ + private $randomize_ = TRUE; + + /** + * Always try last host, even if marked down? + * + * @var bool + */ + private $alwaysTryLast_ = TRUE; + + /** + * Socket pool constructor + * + * @param array $hosts List of remote hostnames + * @param mixed $ports Array of remote ports, or a single common port + * @param bool $persist Whether to use a persistent socket + * @param mixed $debugHandler Function for error logging + */ + public function __construct($hosts=array('localhost'), + $ports=array(9090), + $persist=FALSE, + $debugHandler=null) { + parent::__construct(null, 0, $persist, $debugHandler); + + if (!is_array($ports)) { + $port = $ports; + $ports = array(); + foreach ($hosts as $key => $val) { + $ports[$key] = $port; + } + } + + foreach ($hosts as $key => $host) { + $this->servers_ []= array('host' => $host, + 'port' => $ports[$key]); + } + } + + /** + * Add a server to the pool + * + * This function does not prevent you from adding a duplicate server entry. + * + * @param string $host hostname or IP + * @param int $port port + */ + public function addServer($host, $port) { + $this->servers_[] = array('host' => $host, 'port' => $port); + } + + /** + * Sets how many time to keep retrying a host in the connect function. + * + * @param int $numRetries + */ + public function setNumRetries($numRetries) { + $this->numRetries_ = $numRetries; + } + + /** + * Sets how long to wait until retrying a host if it was marked down + * + * @param int $numRetries + */ + public function setRetryInterval($retryInterval) { + $this->retryInterval_ = $retryInterval; + } + + /** + * Sets how many time to keep retrying a host before marking it as down. + * + * @param int $numRetries + */ + public function setMaxConsecutiveFailures($maxConsecutiveFailures) { + $this->maxConsecutiveFailures_ = $maxConsecutiveFailures; + } + + /** + * Turns randomization in connect order on or off. + * + * @param bool $randomize + */ + public function setRandomize($randomize) { + $this->randomize_ = $randomize; + } + + /** + * Whether to always try the last server. + * + * @param bool $alwaysTryLast + */ + public function setAlwaysTryLast($alwaysTryLast) { + $this->alwaysTryLast_ = $alwaysTryLast; + } + + + /** + * Connects the socket by iterating through all the servers in the pool + * and trying to find one that works. + */ + public function open() { + // Check if we want order randomization + if ($this->randomize_) { + shuffle($this->servers_); + } + + // Count servers to identify the "last" one + $numServers = count($this->servers_); + + for ($i = 0; $i < $numServers; ++$i) { + + // This extracts the $host and $port variables + extract($this->servers_[$i]); + + // Check APC cache for a record of this server being down + $failtimeKey = 'thrift_failtime:'.$host.':'.$port.'~'; + + // Cache miss? Assume it's OK + $lastFailtime = apc_fetch($failtimeKey); + if ($lastFailtime === FALSE) { + $lastFailtime = 0; + } + + $retryIntervalPassed = FALSE; + + // Cache hit...make sure enough the retry interval has elapsed + if ($lastFailtime > 0) { + $elapsed = time() - $lastFailtime; + if ($elapsed > $this->retryInterval_) { + $retryIntervalPassed = TRUE; + if ($this->debug_) { + call_user_func($this->debugHandler_, + 'TSocketPool: retryInterval '. + '('.$this->retryInterval_.') '. + 'has passed for host '.$host.':'.$port); + } + } + } + + // Only connect if not in the middle of a fail interval, OR if this + // is the LAST server we are trying, just hammer away on it + $isLastServer = FALSE; + if ($this->alwaysTryLast_) { + $isLastServer = ($i == ($numServers - 1)); + } + + if (($lastFailtime === 0) || + ($isLastServer) || + ($lastFailtime > 0 && $retryIntervalPassed)) { + + // Set underlying TSocket params to this one + $this->host_ = $host; + $this->port_ = $port; + + // Try up to numRetries_ connections per server + for ($attempt = 0; $attempt < $this->numRetries_; $attempt++) { + try { + // Use the underlying TSocket open function + parent::open(); + + // Only clear the failure counts if required to do so + if ($lastFailtime > 0) { + apc_store($failtimeKey, 0); + } + + // Successful connection, return now + return; + + } catch (TException $tx) { + // Connection failed + } + } + + // Mark failure of this host in the cache + $consecfailsKey = 'thrift_consecfails:'.$host.':'.$port.'~'; + + // Ignore cache misses + $consecfails = apc_fetch($consecfailsKey); + if ($consecfails === FALSE) { + $consecfails = 0; + } + + // Increment by one + $consecfails++; + + // Log and cache this failure + if ($consecfails >= $this->maxConsecutiveFailures_) { + if ($this->debug_) { + call_user_func($this->debugHandler_, + 'TSocketPool: marking '.$host.':'.$port. + ' as down for '.$this->retryInterval_.' secs '. + 'after '.$consecfails.' failed attempts.'); + } + // Store the failure time + apc_store($failtimeKey, time()); + + // Clear the count of consecutive failures + apc_store($consecfailsKey, 0); + } else { + apc_store($consecfailsKey, $consecfails); + } + } + } + + // Holy shit we failed them all. The system is totally ill! + $error = 'TSocketPool: All hosts in pool are down. '; + $hosts = array(); + foreach ($this->servers_ as $server) { + $hosts []= $server['host'].':'.$server['port']; + } + $hostlist = implode(',', $hosts); + $error .= '('.$hostlist.')'; + if ($this->debug_) { + call_user_func($this->debugHandler_, $error); + } + throw new TException($error); + } +} + +?> diff --git a/lib/php/src/transport/TTransport.php b/lib/php/src/transport/TTransport.php new file mode 100644 index 000000000..e24452596 --- /dev/null +++ b/lib/php/src/transport/TTransport.php @@ -0,0 +1,108 @@ +<?php +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + * @package thrift.transport + */ + + +/** + * Transport exceptions + */ +class TTransportException extends TException { + + const UNKNOWN = 0; + const NOT_OPEN = 1; + const ALREADY_OPEN = 2; + const TIMED_OUT = 3; + const END_OF_FILE = 4; + + function __construct($message=null, $code=0) { + parent::__construct($message, $code); + } +} + +/** + * Base interface for a transport agent. + * + * @package thrift.transport + */ +abstract class TTransport { + + /** + * Whether this transport is open. + * + * @return boolean true if open + */ + public abstract function isOpen(); + + /** + * Open the transport for reading/writing + * + * @throws TTransportException if cannot open + */ + public abstract function open(); + + /** + * Close the transport. + */ + public abstract function close(); + + /** + * Read some data into the array. + * + * @param int $len How much to read + * @return string The data that has been read + * @throws TTransportException if cannot read any more data + */ + public abstract function read($len); + + /** + * Guarantees that the full amount of data is read. + * + * @return string The data, of exact length + * @throws TTransportException if cannot read data + */ + public function readAll($len) { + // return $this->read($len); + + $data = ''; + $got = 0; + while (($got = strlen($data)) < $len) { + $data .= $this->read($len - $got); + } + return $data; + } + + /** + * Writes the given data out. + * + * @param string $buf The data to write + * @throws TTransportException if writing fails + */ + public abstract function write($buf); + + /** + * Flushes any pending data out of a buffer + * + * @throws TTransportException if a writing error occurs + */ + public function flush() {} +} + +?> |