Guide

Storage OnLine: Utilizzare API S3 con JSP per l'elenco dei bucket

Di seguito verrà approfondito l'utilizzo della classe RestS3Service, già accennato nella web application d'esempio descritta in questa guida introduttiva per API S3 con JSP.

Le pagine JSP finora realizzate implementano queste funzionalità:

  • index.jsp: elenco dei bucket associati al nostro account;
  • listObjects.jsp: elenco degli oggetti contenuti nel bucket.

Pagina iniziale: index.jsp

Riportiamo di seguito il sorgente della pagina:

<%@page import="org.jets3t.service.model.S3Bucket"%>
<%@ include file="WEB-INF/includes/common.jsp"%>
<html>
<head>
  <link rel="stylesheet" type="text/css" href="style.css" />
</head>
<body>
<h2>jets3t test on Hostingsolutions.it Storage OnLine</h2>

<h3>Elenco bucket</h3>

<c:catch var="ex">
<%
S3Bucket[] buckets = s3.listAllBuckets();
pageContext.setAttribute("buckets", buckets);
%>
</c:catch>

<c:if test="${!empty(ex)}">
  <div class="errorMessage">
    <p>Errore contattando lo storage: <c:out value="${ex.message}" /></p>
  </div>
</c:if>

<ul>
  <c:forEach items="${buckets}" var="bucket">
    <li>
      <c:url var="url" value="listObjectsAdvanced.jsp">
        <c:param name="bucketName">${bucket.name}</c:param>
      </c:url>
      <a href="${url}">${fn:escapeXml(bucket.name)}</a>
    </li>
  </c:forEach>
</ul>

</body>
</html>
      

Per ottenere l'elenco dei bucket è necessario invocare il metodo S3Service.listAllBuckets(), che restituisce un array di oggetti di tipo S3Bucket, memorizzato nella variabile buckets.

L'istruzione pageContext.setAttribute("buckets", buckets) rende disponibile la variabile alla pagina, per poterla utilizzare nelle espressioni del tipo ${buckets}, ad esempio nei tag JSTL.

Da notare come entrambe le istruzioni siano racchiuse in nei tag <c:catch> ... </c:catch>: in caso di problemi, la variabile ex (di tipo java.lang.Exception) sarà disponibile alla pagina per mostrare un messaggio d'errore (vedi il blocco <c:if> ).

Infine, utilizziamo una lista <ul> per mostrare l'elenco dei bucket. Osservare che:

  • l'utilizzo dei tag <c:url> e <c:param> (sempre delle JSTL) facilita molto la creazione di URL con parametri;
  • il link sul bucket manda alla pagina di dettaglio listObjects.jsp;
  • la funzione fn:escapeXml() effettua un escape dei caratteri prima di scriverli nell'HTML: questa pratica, oltre a evitare problemi con bucket dai nomi con caratteri speciali (< > &), rafforza anche la sicurezza e dovrebbe essere utilizzata sempre perchè aiuta a debellare tentativi di Cross-site scripting (XSS). La stessa funzionalità è fornita dal tag <c:out>.

Elenco degli oggetti: listObjects.jsp

Questa pagina si occupa di mostrare tutti i contenuti del bucket. Eccone il sorgente:

<%@page import="org.jets3t.service.model.S3Object"%>
<%@page import="org.jets3t.service.model.S3Bucket"%>
<%@ include file="WEB-INF/includes/common.jsp"%>
<html>
<head>
  <link rel="stylesheet" type="text/css" href="style.css" />
</head>
<body>
<c:catch var="ex">
<%
String bucketName = request.getParameter("bucketName");
String prefix = request.getParameter("prefix");

S3Bucket bucket = s3.getBucket(bucketName);

S3Object[] objects = s3.listObjects(bucketName, prefix, null);

pageContext.setAttribute("bucket", bucket);
pageContext.setAttribute("objects", objects);
%>
</c:catch>

<c:if test="${!empty(ex)}">
  <div class="errorMessage">
    <p>Errore contattando lo storage: <c:out value="${ex.message}" /></p>
  </div>
</c:if>

<h2>jets3t test on Hostingsolutions.it Storage OnLine</h2>

<h3>Elenco oggetti del bucket: <em>${fn:escapeXml(bucket.name)}</em></h3>

<p><a href="index.jsp">Elenco bucket</a></p>

<form method="post">
  <input name="prefix" value="${fn:escapeXml(param.prefix)}" />
  <button type="submit">Filtra</button>
</form>

<table>
  <thead>
    <tr>
      <th>Name</th>
      <th>Size</th>
    </tr>
  </thead>
  <c:forEach items="${objects}" var="object">
    <tr>
      <td>
        ${fn:escapeXml(object.name)}
      </td>
      <td>
        ${object.contentLength}
      </td>
    </tr>
  </c:forEach>
</table>

</body>
</html>
      

Da notare:

  • l'utilizzo del metodo S3Service.listObjects() per ottenere un elenco degli oggetti presenti nello storage;
  • l'utilizzo di un parametro prefix, eventualmente nullo, per limitare l'elenco ai soli oggetti che hanno il nome che inizia per tale parametro;
  • un semplice <form> che permette all'utente di specificare il filtro da utilizzare.

Elenco avanzato: realizzazione di un file browser (listObjectsAdvanced.jsp)

Come già accennato nella guida per API S3 con php per salvare, modificare e cancellare i dati dallo storage, le interfacce API S3 e i sistemi di storage online che le implementano, non hanno il concetto di cartella, ma è possibile simulare una struttura ad albero come quella delle cartelle, filtrando i nomi per prefisso. Ad esempio, fissato il carattere separatore per i nomi delle cartelle (ad es. la barra / come sui sistemi Unix/Linux) e avendo questi oggetti nello storage:

  • images/computer.jpg;
  • images/sfondo_01.jpg;
  • sounds/beep.wav;
  • sounds/samples/crash.wav;
  • sounds/samples/drum.wav;

si può immaginare una struttura del genere:

+-- /images
|      +- computer.jpg
|      +- sfondo_01.jpg
|
+-- /sounds
       +- beep.wav
       |
       +- /samples
            +- crash.wav
            +- drum.wav
      

Questa astrazione non sarebbe di facile implementazione facendo uso del solo prefisso; tuttavia gli sviluppatori di JetS3t Tolkit hanno pensato ad una soluzione più sofisticata, che prevede l'utilizzo di un altro metodo, ovvero S3Service.listObjectsChunked().

<%@page import="org.jets3t.service.StorageObjectsChunk"%>
<%@page import="org.jets3t.service.model.S3Object"%>
<%@page import="org.jets3t.service.model.S3Bucket"%>
<%@page import="org.jets3t.service.impl.rest.httpclient.RestS3Service"%>
<%@ include file="WEB-INF/includes/common.jsp"%>
<html>
<body>
<c:catch var="ex">
<%
String bucketName = request.getParameter("bucketName");
String prefix = request.getParameter("prefix");

S3Bucket bucket = s3.getBucket(bucketName);

//S3Object[] objects = s3.listObjects(bucketName, prefix, "/");
StorageObjectsChunk chunk = s3.listObjectsChunked(bucketName, prefix, "/", 1000, null);

pageContext.setAttribute("bucket", bucket);
pageContext.setAttribute("chunk", chunk);
%>
</c:catch>

<c:if test="${!empty(ex)}">
  <div class="errorMessage">
    <p>Errore contattando lo storage: <c:out value="${ex.message}" /></p>
  </div>
</c:if>

<h2>jets3t test on Hostingsolutions.it Storage OnLine</h2>

<h3>Elenco oggetti del bucket: <em>${fn:escapeXml(bucket.name)}</em></h3>

<p><a href="index.jsp">Elenco bucket</a></p>

<table border="1">
  <thead>
    <tr>
      <th>Name</th>
      <th>Size</th>
    </tr>
  </thead>
  <%--
  Se siamo in una sotto-cartella, mostra il link ".."
  --%>
  <c:if test="${param.parent != null or not empty(chunk.commonPrefixes)}">
    <tr>
      <td colspan="2">
        <c:url var="url" value="listObjectsAdvanced.jsp">
          <c:param name="bucketName">${fn:escapeXml(param.bucketName)}</c:param>
          <%-- Usiamo il parametro parent come prefisso --%>
          <c:param name="prefix">${fn:escapeXml(param.parent)}</c:param>
        </c:url>
        <a href="${url}">..</a>
      </td>
    </tr>
  </c:if>
  <%--
  Elenca le directory:
  --%>
  <c:forEach items="${chunk.commonPrefixes}" var="prefix">
    <tr>
      <td colspan="2">
        <c:url var="url" value="listObjectsAdvanced.jsp">
          <c:param name="bucketName">${fn:escapeXml(param.bucketName)}</c:param>
          <c:param name="prefix">${fn:escapeXml(prefix)}</c:param>
          <c:param name="parent">${fn:escapeXml(param.prefix)}</c:param>
        </c:url>
        <a href="${url}">${fn:escapeXml(fn:substringAfter(prefix, param.prefix))}</a>
      </td>
    </tr>
  </c:forEach>
  <%--
  Elenca gli oggetti della directory corrente:
  --%>
  <c:forEach items="${chunk.objects}" var="object">
    <c:if test="${object.name ne param.prefix}">
      <tr>
        <td>
          ${fn:escapeXml(fn:substringAfter(object.name, param.prefix))}
        </td>
        <td>
          ${object.contentLength}
        </td>
      </tr>
    </c:if>
  </c:forEach>
</table>

</body>
</html>
      

Come evidente, la pagina si è notevolmente complicata:

  • al posto dell'array di oggetti S3Object abbiamo ora un oggetto di tipo StorageObjectsChunk, memorizzato nella variabile chunk;
  • la proprietà chunk.commonPrefixes contiene i nomi delle sotto-cartelle di quella attuale;
  • utilizziamo un parametro parent quando entriamo in una sotto-cartella, in questo modo possiamo fornire un 'aggancio' per tornare su di un livello;
  • da notare l'utilizzo di un'altra funzione di JSTL sulle stringhe, oltre alla già citata fn:escapeXml(), ovvero fn:substringAfter(), molto comoda per mostrare il nome degli oggetti senza del prefisso.

Risorse