Windows Azure non è solo per chi sviluppa soluzioni utilizzando Visual Studio ed i framework .NET. Infatti, è possibile creare ed erogare dei cloud service anche utilizzando Java, Ruby o PHP. In questo post vedremo quanto è semplice usare PHP per muovere i primi passi su Windows Azure.
Per seguire questo tutorial è necessario predisporre Eclipse per lavorare su Windows Azure. Maggiori dettagli sui prerequisiti e sulla preparazione dell’ambiente di sviluppo sono disponibili nel post Anatomia e sviluppo di una soluzione cloud per Windows Azure. Inoltre, sebbene faccia riferimento all’utilizzo di Visual Studio, è consigliabile dare una lettura ai due post della serie Hello Web Role! per familiarizzare con i web role e gli storage service.
Un po’ di teoria
Windows Azure supporta il modulo FastCGI di Internet Information Server 7.0 consentendo la realizzazione di web role che eseguono applicazioni scritte in linguaggi interpretati o in codice nativo. Per creare un web role che esegue una applicazione FastCGI è necessario innanzitutto verificare che l’impostazione del flag enableNativeCodeExecution sul file ServiceDefinition.csdef sia true (è il valore di default sugli ultimi SDK):
<?xml version="1.0" encoding="utf-8"?>
<ServiceDefinition name="Simple"
xmlns="http://schemas.microsoft.com/ServiceHosting/2008/10/ServiceDefinition">
<WebRole name="WebRole" enableNativeCodeExecution="true">
<ConfigurationSettings>
</ConfigurationSettings>
<InputEndpoints>
<InputEndpoint name="HttpIn" protocol="http" port="80" />
</InputEndpoints>
</WebRole>
</ServiceDefinition>
Inoltre è necessario includere il file opzionale web.roleConfig sulla radice del progetto. In questo file va specificato il percorso assoluto all’interprete o all’applicazione in codice nativo. Per specificare il percorso assoluto viene utilizzata la variabile d’ambiente %RoleRoot%. Questa ritorna il percorso assoluto alla radice dell’albero di directory in cui è in esecuzione il web role (dove sono presenti i file web.config e web.roleConfig). Un esempio di file web.roleConfig è:
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<system.webServer>
<fastCgi>
<application fullPath="%RoleRoot%\approot\php\php.exe"/>
</fastCgi>
</system.webServer>
</configuration>
Un handler specifica quale risorsa di IIS gestisce la risposta per un certo tipo di richiesta. Configurando opportunamente l’handler è possibile indicare che sarà l’interprete PHP a gestire le richieste al server che fanno riferimento a file con estensione php. Tutte le richieste che collimano con il filtro path sulla definizione dell’handler verranno girate a php.exe (vedi MSDN). La configurazione degli handler viene effettuata nell’elemento handlers dell’elemento system.webServer sul file web.config, come segue:
<system.webServer>
…
<handlers>
…
<add name="PHP via FastCGI" path="*.php" verb="*"
modules="FastCgiModule"
scriptProcessor="%RoleRoot%\approot\php\php-cgi.exe"
resourceType="Unspecified" />
</handlers>
</system.webServer>
Affinchè il role funzioni correttamente è necessario includere l’interprete nel progetto. Questo sarà compatibile con Windows Azure solo se è possibile effettuarne il deployment attraverso un semplice xcopy. La posizione dell’interprete deve combaciare con quella specificata con l’attributo fullPath dell’elemento application nel file webRole.config.
Sviluppare il web role con Eclipse
Muoverei primi passi verso lo sviluppo di un web role in PHP con Eclipse è estremamente facile. Vedremo infatti che la procedura guidata per la creazione del progetto permette di selezionare le feature da utilizzare e genera dei file php con degli esempi d’uso molto utili. Apriamo Eclipse, lanciamo la procedura guidata per la creazione di un nuovo progetto Windows Azure PHP Project e inseriamo il nome:
Al terzo passo è possibile iniziare la configurazione del web role, a partire dal nome. Il checkbox Enable Windows Azure App Fabric support for this project inserisce nel progetto le classi per l’utilizzo dell’Access Control Service e il Service Bus. Visto il livello introduttivo del tutorial non approfondiremo l’argomento in proposito, ma vi consiglio di dargli un’occhiata. Il tab Data Storage Options permette di abilitare l’utilizzo dei servizi di storage e configurare le credenziali di accesso. Nel nostro caso useremo il development storage. Opzionalmente è possibile configurare anche le credenziali per l’accesso ad un database SQL Azure. Prima del deployment sarà possibile cambiare le impostazioni editando il file ServiceConfiguration.cscfg.
Il tab PHP Runtime permette di scegliere tra l’interprete PHP 5.3.4 compreso nel kit di sviluppo o una versione specifica installata sulla macchina.
Su Diagnostics è possibile attivare Windows Azure Diagnostics e specificare lo storage in cui inserire le informazioni diagnostiche, il livello di logging e l’intervallo di trasferimentio dei dati. Su Windows Azure Drives è possibile configurare l’utilizzo di eventuali Drives.
Cliccando su Finish viene creato il progetto. Le impostazioni di cui abbiamo parlato in precedenza dovrebbero essere già corrette. Dopo aver avviato il Compute Emulator e lo Storage Emulator, clicchiamo sul file index.php e selezioniamo Run/Run As/PHP Windows Azure Page. Dopo alcuni istanti viene aperta la pagina contenente il tipico output della funzione phpinfo() di PHP:
In cima alla pagina possiamo notare due link che permettono di effettuare il test delle operazioni sui blob e sulle table. Come dicevo all’inizio le pagine relative sono generate automaticamente e permettono di comprendere come effettuare delle operazioni sugli storage service. Non esiste un esempio riguardo le code, ma sulla documentazione del Windows Azure SDK for PHP è possibile trovare tutte le informazioni necessarie.
Dando un’occhiata al file BlobSample.php vediamo che innanzi tutto vengono inclusi i riferimenti alle librerie dell’SDK:
/**
* Add PHP Azure SDK to the PHP include_path. This SDK is avilable in Application Root directory.
* This can be done by adding following set_include_path statement in
* every PHP file refering PHP Azure SDK.
*
* Alternatively user can update the include_path in PHP.ini file.
*/
set_include_path(get_include_path() . PATH_SEPARATOR . $_SERVER["RoleRoot"] . "\\approot\\");
/**
* Refer PHP Azure SDK library files for Azure Storage Services Operations
*/
require_once 'Microsoft/WindowsAzure/Storage.php';
require_once 'Microsoft/WindowsAzure/Storage/Blob.php';
All’interno del file Storage.php è definita la classe Microsoft_WindowsAzure_Storage, mentre in Blob.php è definita la classe Microsoft_WindowsAzure_Storage_Blob, che estende la prima.
Proseguendo con la lettura del file BlobSample.php, vediamo che viene creato lo storage client:
$blobStorageClient = createBlobStorageClient();
if(!isBlobServiceAccessible($blobStorageClient))
{
return;
}
La funzione createBlobStorageClient() è definita più in basso come segue:
function createBlobStorageClient()
{
if (azure_getconfig('UseDevelopmentStorage') == 'false')
{
$host = Microsoft_WindowsAzure_Storage::URL_CLOUD_BLOB;
$accountName = azure_getconfig('StorageAccountName');
$accountKey = azure_getconfig('StorageAccountKey');
$usePathStyleUri = false;
$retryPolicy = Microsoft_WindowsAzure_RetryPolicy_RetryPolicyAbstract::retryN(10, 250);
$blobStorageClient = new Microsoft_WindowsAzure_Storage_Blob(
$host,
$accountName,
$accountKey,
$usePathStyleUri,
$retryPolicy
);
}
else
{
$blobStorageClient = new Microsoft_WindowsAzure_Storage_Blob();
}
return $blobStorageClient;
}
come possiamo vedere, nel caso in cui non venga utilizzato il development storage, vengono recuperate le credenziali e creato il client facendo riferimento a queste ultime. L’oggetto Microsoft_WindowsAzure_RetryPolicy_RetryPolicyAbstract permette di specificare il numero di tentativi e la distanza tra questi (vedi il file Microsoft/WindowsAzure/RetryPolicy/RetryPolicyAbstract.php). Come è possibile vedere in Storage.php, non specificando le credenziali, il client viene creato per il development storage.
Il passo successivo è quello di recuperare la lista di container usando il metodo listContainers dello storage client. La funzione listContainers($blobStorageClient), definita come segue, stampa i nomi dei container trovati.
function listContainers($blobStorageClient)
{
echo "<strong>List of Containers:</strong>";
$containers = $blobStorageClient->listContainers();
foreach ($containers as $container)
{
echo "- $container->Name </br>";
}
echo "</br>";
}
Viene creato un nuovo container con un nome unico prefissato dalla string “testcontainer” (ad es. testcontainer4d641216454b0) utilizzando il metodo createContainer dello storage client.
$containerName = uniqid("testcontainer");
$result = $blobStorageClient->createContainer($containerName);
echo "New container with name <strong>'" . $result->Name . "'</strong> is created.";
Dopo aver ristampato la lista dei container, per poter verificare che il container sia stato effettivamente creato, vengono creati due blob con nome differente dall’immagine WindowsAzure.jpg presente nella root del progetto usando il metodo putBlob:
$azureLogoFile = ".\\WindowsAzure.jpg";
$result = $blobStorageClient->putBlob($containerName, 'WindowsAzure.jpg', $azureLogoFile);
echo "New blob with name '<b>" . $result->Name . "'</b> is created. <br/><br/>";
Dopo aver stampato l’URL per il primo blob creato, viene stampata la lista di tutti i blob contenuti nel container usando la funzione listBlobs, costruita intorno al metodo listBlobs dello storage client:
function listBlobs($blobStorageClient, $containerName)
{
echo "<b>List of blobs in container '" . $containerName . "': </b><br/>";
$blobs = $blobStorageClient->listBlobs($containerName);
foreach ($blobs as $blob)
{
echo " - $blob->Name " . "<br/>";
}
echo "<br/>";
}
A questo punto viene dimostrata la funzionalità di impostazione e lettura dei metadati sui blob con i metodi getBlobMetadata e setBlobMetadata. Vengono impostati i valori ‘createdby’ = ‘PHPAzure’ e ‘FileType’ = ‘jpg’.
/**
* Set metadata on blob "WindowsAzure.jpg" in $containerName container
*/
$blobStorageClient->setBlobMetadata($containerName, 'WindowsAzure.jpg',
array('createdby' => 'PHPAzure', 'FileType' => 'jpg'));
echo "Set metadata for '<b>WindowsAzure.jpg</b>' blob!<br/><br/>";
/**
* Read metadata on blob "WindowsAzure.jpg" in $containerName container
*/
$metaData = $blobStorageClient->getBlobMetadata($containerName, 'WindowsAzure.jpg');
echo "metadata for '<b>WindowsAzure.jpg</b>' blob:<br/>";
var_dump($metaData);
echo "<br/><br/>";
Il metodo getBlob dello storage client permette di effettuare il download del blob su un file temporaneo in locale:
$temp_file_name = tempnam(sys_get_temp_dir(), 'WindowsAzure');
$blobStorageClient->getBlob($containerName, 'WindowsAzure.jpg', $temp_file_name);
echo "Downloaded WindowsAzure.jpg blob to <b>" . $temp_file_name . "</b><br/><br/>";
Per dimostrare le funzionalità relativa all’impostazione del livello di accesso ai container viene impostato quello finora utilizzato come pubblico. Successivamente viene creato un nuovo container e impostato come privato. In esso viene creato un blob e generata una shared signature con validità di 5 minuti:
/**
* By default, blob storage containers on Windows Azure are protected from public viewing. If
* any user on the Internet should have access to a blob container, its ACL can be set to public.
* Note that this applies to a complete container and not to a single blob!
*/
$blobStorageClient->setContainerAcl($containerName, Microsoft_WindowsAzure_Storage_Blob::ACL_PUBLIC);
echo "Made container '$containerName' <b>public!</b> <br/><br/>";
/**
* Create shared signature for specified 'WindowsAzure.jpg' blob in private container
*/
/**
* First create a private container with uniq name and add 'WindowsAzure.jpg' blob file
*/
$privateContainerName = uniqid("privatecontainer");
$result = $blobStorageClient->createContainer($privateContainerName);
$blobStorageClient->setContainerAcl($privateContainerName, Microsoft_WindowsAzure_Storage_Blob::ACL_PRIVATE);
$result = $blobStorageClient->putBlob($privateContainerName, 'WindowsAzure.jpg', $azureLogoFile);
$startTime = time();
$endTime = $startTime + 5*60; // Timestamp after 5 minutes
$sharedSignatureURL = $blobStorageClient->generateSharedAccessUrl(
$privateContainerName,
"WindowsAzure.jpg",
'b',
'r',
isoDate($startTime),
isoDate($endTime)
);
echo "<b>SharedSignature for '$privateContainerName/WindowsAzure.jpg' with 5 minutes validity:</b><br/>" .
'<a href="' . $sharedSignatureURL . '">' . $sharedSignatureURL . '</a><br/><br/>';
Infine viene cancellato il container pubblico col metodo deleteBlob e verificata l’effettiva cancellazione con il metodo containerExists:
/**
* Delete "WindowsAzure.jpg" blob from the $containerName container
*/
$blobStorageClient->deleteBlob($containerName, 'WindowsAzure2.jpg');
echo "Deleted blob <b>'WindowsAzure2.jpg'</b> from container <b>'$containerName'</b><br/><br/>";
/**
* Check if container exists
*/
if ($blobStorageClient->containerExists($containerName))
{
echo "Container <b>'$containerName'</b> exists<br/><br/>";
}
if ($blobStorageClient->containerExists($privateContainerName))
{
echo "Container <b>'$privateContainerName'</b> exists<br/><br/>";
}
Prima di concludere questo post vorrei indicarvi un utilizzo del table storage molto interessante per una applicazione PHP eseguita su Windows Azure. Quando l’applicazione è eseguita su almeno due istanze (a garanzia dello SLA), è necessario che lo stato delle sessioni sia condiviso tra le macchine virtuali. A tale scopo l’SDK per PHP mette a disposizione la classe Microsoft_WindowsAzure_SessionHandler che permette di utilizzare il Table Storage di Windows Azure come session handler. Maggiori dettagli sono disponibili sulla documentazione del Windows Azure SDK for PHP. Alla prossima!