Offer new REST services
Prerequisite: You must be familiar with the Syntax used in Tutorials and have already created an extension.
- learning:
- Create new REST services
- level:
- Intermediate
- domains:
- PHP
- min version:
- 2.3.0
In this tutorial, we will see how we can enhanced the iTop Web Services by offering new services not yet covered by the core ones.
When defining web services,
be aware that you can easily open security breaches
Knowing existing REST services in iTop, we can be limited
-
to access classes with category having neither
grant-by-profile
norbusiness
. Examples of such classes: InlineImage, Attachment, TemplateFieldData,… -
to execute a query which cannot be performed (easily) with the standard verbs, or require additional treatments.
-
for example get the mapping between a synchro-replica and the iTop object key,
-
For example, for getting a lot of data from iTop in order to compute a value and then store it in another iTop object with another REST call. You could instead create a new verb which does all in one call.
-
Be clear on what you want:
-
provided parameters,
-
returned data (think about access right),
-
volumes
Strategy
Within an itop extension XXXX
-
Create a class which implements
iRestServiceProvider
(core/restservices.class.inc.php) with 2 methods:-
ListOperations($sVersion)
-
ExecOperation($sVersion, $sVerb, $aParams)
-
-
Define the services (=Operation =Verb)
-
For each Service, code the behavior
-
Define one or multiple classes extending
RestResult
class, depending on your service needs in terms of returned data.
Rules
-
The verb must be unique, so not colliding with any other verb brought by any other extension
-
iTop convention is to use <service_group>/<verb>
-
<service_group> must not be core which is reserved for iTop core.
-
For extending REST for a source application or partner, you can use the source application combined with your company name, to be sure to not collide with other extension, so for eg ldap-itomig
-
<verb> any name but make it meaningful
Entry parameters
-
Each verb decides which parameters are expected in entry and in which format
-
operation is a mandatory parameter
-
There is no order in the provided parameters
-
string, integer, boolean and array are the only possible type of param
-
A parameter can be optional or mandatory, but it's only visible in the PHP code of the service.
-
$sMandatoryParam1 = RestUtils::GetMandatoryPara🤦$aParams, 'param1');
-
$sOptionalParam2 = RestUtils::GetOptionalPara🤦$aParams, 'param2');
-
If mandatory parameter is not provided, the error treatment is handle by iRestServiceProvider for you.
-
-
Example
core/get
has 4 parameters operation, class,
Returned data
-
code: see standard values for allowed values
-
message can be empty
-
objects … a specific JSON structure containing the results for your service
The class RestResultWithObjects
will handle the
json data generation, here an example returning a set of objects
$oObjectSet
- ExecOperation
-
$oResult = new RestResultWithObjects(); while ($oObject = $oObjectSet->Fetch()) { $oResult->AddObject(0, '', $oObject, array('id','name','org_id','priority',...), false); } $oResult->message = "Found: ".$oObjectSet->Count(); return $oResult; }
If you returned a lot of
objects and if they contains blobs, then the returned packet can
quickly exceed memory_limit or other size constrains for the
requestor
Caneva
As a proposal, you can reuse the caneva below, in which case
-
Enter in
$aVerbs = [
the list of operations withdescription
and phpmethod
to call -
Keep
ListOperations()
andExecOperation()
as is -
For each new Web Service that we want to offer, we will write a private PHP method to handle that Service
-
Read params
-
Check if the user is allowed to perform this action
-
Perform the requested service on iTop
-
Build Result object and return it
-
<?php namespace Combodo\iTop\Extension\XXXX\Service; // Add the classes you need for your services use iRestServiceProvider; use RestResult; use RestUtils; use Exception; class RestService implements iRestServiceProvider { //----------------- Define in this table your own web Services ------------ private static $aVerbs = [ 'datasynchro/get_mappings' => [ 'description' => 'Return primary_key / dest_id mappings for a given DataSynchro', 'method' => 'GetMappings', ], 'request_template/get_details' => [ 'description' => 'Return service_details for a given Ticket', 'method' => 'TemplateDetails', ], ]; //----------------- Keep the below 2 methods as is --------------------------- /** * Enumerate services delivered by this class * @param string $sVersion The version (e.g. 1.0) supported by the services * @return array An array of hash 'verb' => verb, 'description' => description */ public function ListOperations($sVersion) { $aResult = []; foreach(static::$aVerbs as $sVerb => $aVerbDef) { $aResult[] = [ 'verb' => $sVerb, 'description' => $aVerbDef['description'], ]; } return $aResult; } /** * Executes the services delivered by this class * * @param string $sVersion The version (e.g. 1.0) supported by the services * @param string $sVerb * @param array $aParams * * @return RestResult The standardized result structure (at least a message) */ public function ExecOperation($sVersion, $sVerb, $aParams) { if (!array_key_exists($sVerb, self::$aVerbs)) { $oResult = new RestResult(); $oResult->code = RestResult::UNKNOWN_OPERATION; $oResult->message = "The operation '$sVerb' is not supported by this application."; } else { $sMethod = self::$aVerbs[$sVerb]['method']; $oResult = $this->$sMethod($aParams); } return $oResult; } //----------- Define below your methods and RestResult classes ------------------- /** * @param mixed[] $aParams * @return \Combodo\iTop\Extension\XXXX\Service\ExtraRestResult */ private function GetMappings($aParams) { [...] } /** * @param mixed[] $aParams * @return \Combodo\iTop\Extension\XXXX\Service\ExtraRestResult */ private function TemplateDetails($aParams) { // Prepare an object of the RestResult sub-class need for this service $oResult = new ExtraRestResult(); // Read provided params $sSlug = RestUtils::GetMandatoryParam($aParams, 'slug'); // Get Data from iTop $bBooked = xxxx($sSlug); // Store result $oResult->aData['booked'] = $bBooked; return $oResult; } } class ExtraRestResult extends RestResult { /** * @var array */ public $aData; }
An example
class ExampleGetInlineImageRest implements iRestServiceProvider { public function ListOperations($sVersion) { $aOperations = array(); $aOperations[] = array( 'verb' => 'example/get_inlineimage', 'description' => 'Return InlineImages used within a given "class" belonging to an "org_id"', ); return $aOperations; } public function ExecOperation($sVersion, $sVerb, $aParams) { // Get the mandatory param $iOrgIDSource = RestUtils::GetMandatoryParam($aParams, 'org_id'); // Get an optional param - here it is useless $iClass = RestUtils::GetOptionalParam($aParams, 'class',''); $sInlineImageOQL = 'SELECT InlineImage WHERE item_org_id=' .$iOrgIDSource.' AND item_class="'.$iClass.'"'; // Search $oSearch = DBObjectSearch::FromOQL($sInlineImageOQL); $oSearch->AllowAllData() ; $oInlineImageSet = new CMDBObjectSet($oSearch); //list of fields to return $sFields = 'id,item_class,item_id,contents'; // return feedback foreach(explode(',', $sFields) as $sAttCode) { $sAttCode = trim($sAttCode); if (($sAttCode != 'id') && (!MetaModel::IsValidAttCode('InlineImage', $sAttCode))) { throw new Exception("Invalid attribute code '$sAttCode' for class InlineImage"); } $aShowFields['InlineImage'][] = $sAttCode; } //objects to return $oResult = new RestResultWithObjects(); while ($oInlineImage = $oInlineImageSet->Fetch()) { $oResult->AddObject(0, '', $oInlineImage, $aShowFields, false); } $oResult->message = "Found: ".$oInlineImageSet->Count(); return $oResult; } }
latest/customization/add-rest-services.txt · Last
modified: 2024/09/10 10:25 by 127.0.0.1