* @link http://www.pradosoft.com/
* @copyright 2010 Bigpoint GmbH
* @license http://www.pradosoft.com/license/
* @since 3.2
* @package Prado\Web\Services
*/
namespace Prado\Web\Services;
use Prado\Exceptions\TConfigurationException;
use Prado\Exceptions\THttpException;
use Prado\Prado;
use Prado\Xml\TXmlElement;
/**
* TRpcService class
*
* The TRpcService class is a generic class that can be extended and used to implement
* rpc services using different servers and protocols.
*
* A server is a {@link TModule} that must subclass {@link TRpcServer}: its role is
* to be an intermediate, moving data between the service and the provider. The base
* {@link TRpcServer} class should suit the most common needs, but can be sublassed for
* logging and debugging purposes, or to filter and modify the request/response on the fly.
*
* A protocol is a {@link TModule} that must subclass {@link TRpcProtocol}: its role is
* to implement the protocol that exposes the rpc api. Prado already implements two
* protocols: {@link TXmlRpcProtocol} for Xml-Rpc request and {@link TJsonRpcProtocol} for
* JSON-Rpc requests.
*
* A provider is a {@link TModule} that must subclass {@link TRpcApiProvider}: its role is
* to implement the methods that are available through the api. Each defined api must be
* a sublass of the abstract class {@link TRpcApiProvider} and implement its methods.
*
* The flow of requests and reponses is the following:
* Request <-> TRpcService <-> TRpcServer <-> TRpcProtocol <-> TRpcApiProvider <-> Response
*
* To define an rpc service, add the proper application configuration:
*
*
*
*
*
*
*
*
*
*
* An api can be registered adding a proper definition inside the service
* configuration. Each api definition must contain an id property and a class name
* expressed in namespace format. When the service receives a request for that api,
* the specified class will be instanciated in order to satisfy the request.
*
* @author Robin J. Rogge
* @package Prado\Web\Services
* @since 3.2
**/
class TRpcService extends \Prado\TService
{
/**
* const string base api provider class which every API must extend
*/
const BASE_API_PROVIDER = 'TRpcApiProvider';
/**
* const string base RPC server implementation
*/
const BASE_RPC_SERVER = 'TRpcServer';
/**
* @var array containing mimetype to protocol handler mappings
*/
protected $protocolHandlers = array(
'application/json' => 'TJsonRpcProtocol',
'text/xml' => 'TXmlRpcProtocol'
);
/**
* @var array containing API provider and their configured properties
*/
protected $apiProviders = array();
// methods
/**
* Creates the API provider instance for the current request
* @param TRpcProtocol $protocolHandler instance
* @param string $providerId
*/
public function createApiProvider(TRpcProtocol $protocolHandler, $providerId)
{
$_properties = $this->apiProviders[$providerId];
if(($_providerClass = $_properties->remove('class')) === null)
throw new TConfigurationException('rpcservice_apiprovider_required');
Prado::using($_providerClass);
$_providerClassName = ($_pos = strrpos($_providerClass, '.')) !== false ? substr($_providerClass, $_pos + 1) : $_providerClass;
if(!is_subclass_of($_providerClassName, self::BASE_API_PROVIDER))
throw new TConfigurationException('rpcservice_apiprovider_invalid');
if(($_rpcServerClass = $_properties->remove('server')) === null)
$_rpcServerClass = self::BASE_RPC_SERVER;
Prado::using($_rpcServerClass);
$_rpcServerClassName = ($_pos = strrpos($_rpcServerClass, '.')) !== false ? substr($_rpcServerClass, $_pos + 1) : $_rpcServerClass;
if($_rpcServerClassName!==self::BASE_RPC_SERVER && !is_subclass_of($_rpcServerClassName, self::BASE_RPC_SERVER))
throw new TConfigurationException('rpcservice_rpcserver_invalid');
$_apiProvider = new $_providerClassName(new $_rpcServerClassName($protocolHandler));
$_apiProvider->setId($providerId);
foreach($_properties as $_key => $_value)
$_apiProvider->setSubProperty($_key, $_value);
return $_apiProvider;
}
/**
* Initializes the service
* @param TXmlElement $config containing the module configuration
*/
public function init($config)
{
$this->loadConfig($config);
}
/**
* Loads the service configuration
* @param TXmlElement $xml configuration
*/
public function loadConfig(TXmlElement $xml)
{
foreach($xml->getElementsByTagName('rpcapi') as $_apiProviderXml)
{
$_properties = $_apiProviderXml->getAttributes();
if(($_id = $_properties->remove('id')) === null || $_id == "")
throw new TConfigurationException('rpcservice_apiproviderid_required');
if(isset($this->apiProviders[$_id]))
throw new TConfigurationException('rpcservice_apiproviderid_duplicated');
$this->apiProviders[$_id] = $_properties;
}
}
/**
* Runs the service
*/
public function run()
{
$_request = $this->getRequest();
if(($_providerId = $_request->getServiceParameter()) == "")
throw new THttpException(400, 'RPC API-Provider id required');
if(($_method = $_request->getRequestType()) != 'POST')
throw new THttpException(405, 'Invalid request method "'.$_method.'"!'); // TODO Exception muss "Allow POST" Header setzen
if(($_mimeType = $_request->getContentType()) === null)
throw new THttpException(406, 'Content-Type is missing!'); // TODO Exception muss gültige Content-Type werte zurück geben
if(!in_array($_mimeType, array_keys($this->protocolHandlers)))
throw new THttpException(406, 'Unsupported Content-Type!'); // TODO see previous
$_protocolHandlerClass = $this->protocolHandlers[$_mimeType];
$_protocolHandler = new $_protocolHandlerClass;
if(($_result = $this->createApiProvider($_protocolHandler, $_providerId)->processRequest()) !== null)
{
$_response = $this->getResponse();
$_protocolHandler->createResponseHeaders($_response);
$_response->write($_result);
}
}
}