Guide

Storage OnLine: Utilizzare API S3 con JSP per l'upload dei file

Di seguito, come aggiungere la funzionalità di upload alla web application d'esempio descritta nella guida precedente.

Prerequisiti

Per prima cosa osserviamo che l'upload dei file con Servlet/JSP non è ben definito, nel senso che non ci sono delle chiamate standard per gestire questa funzionalità, per cui è necessario ricorrere a librerie esterne. La più famosa ed utilizzata è Apache Commons Upload, che è diventata uno standard di fatto. Una volta scaricata la distribuzione binaria (al momento la versione 1.3.1) e copiamo il file commons-fileupload-1.3.1.jar all'interno della cartella WEB-INF/lib della web application. Oltre a questa libreria sarà necessario scaricare e copiare in WEB-INF/lib anche Apache Commons IO, un'altra libreria del gruppo Apache Commons dedicata a classi di utilità dedicate l'Input/Output.

Richieste multipart

Per eseguire l'upload dei file tramite form, è necessario specificare, nel tag <form>, l'attributo enctype="multipart/form-data". In questo modo il browser invierà, nel corpo del POST, i file che abbiamo scelto. La gestione lato server di questa richiesta, però, è; più complicata rispetto ad una richiesta HTTP POST 'classica' (non multipart), ma con l'aiuto di Apache Commons Upload il processo ruisulta decisamente facilitato. In sostanza bisogna scorrere la lista dei parametri due volte:

  • la prima, per interpretare i parametri passati con la richiesta;
  • la seconda, per gestire l'elenco dei file caricati.

Di seguito il codice che implementa l'upload dei file sullo Storage OnLine:

<%@page import="java.util.ArrayList"%>
<%@page import="org.jets3t.service.utils.Mimetypes"%>
<%@page import="org.apache.commons.fileupload.FileItem"%>
<%@page import="java.util.List"%>
<%@page import="org.apache.commons.fileupload.servlet.ServletFileUpload"%>
<%@page import="org.apache.commons.fileupload.FileItemFactory"%>
<%@page import="org.apache.commons.fileupload.disk.DiskFileItemFactory"%>
<%@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">
<%
boolean isMultipart = ServletFileUpload.isMultipartContent(request);

String bucketName = null;
String prefix = null;
String parent = null;
List<FileItem> filesToUpload = new ArrayList<FileItem>();
S3Bucket bucket = null;

// se il form è stato inviato...
if (isMultipart) {
    // ... esegui il parsing dei parametri
    FileItemFactory factory = new DiskFileItemFactory();
    ServletFileUpload upload = new ServletFileUpload(factory);
    List<FileItem> items = upload.parseRequest(request);
    
    for(FileItem item : items) {
        // memorizza i parametri del form
        if (item.isFormField()) {
            if ("bucketName".equals(item.getFieldName())) {
                bucketName = item.getString();
            } else if ("prefix".equals(item.getFieldName())) {
                prefix = item.getString();
            } else if ("parent".equals(item.getFieldName())) {
                parent = item.getString();
            }
        }
        else {
            // file caricati
            filesToUpload.add(item);
        }
    }
    
    bucket = s3.getBucket(bucketName);
    
    // ora invia i file sullo storage
    for(FileItem item : filesToUpload) {
        // path assoluto all'interno del bucket
        String key = prefix + item.getName();
        
        // crea l'oggetto con i byte inviati
        S3Object s3Object = new S3Object(key, item.get());

        // determina Content-type
        String mimeType = item.getContentType();
        if (mimeType == null || mimeType.isEmpty()) {
            // se non è stato inviato dal browser prova a determinarlo dall'estensione
            mimeType = Mimetypes.getInstance().getMimetype(item.getName());
        }
        if (mimeType != null && !mimeType.isEmpty()) {
          s3Object.setContentType(mimeType);
        }
        
        // invia oggetto
        s3.putObject(bucket, s3Object);
    }
    
    pageContext.setAttribute("message", "File inviati correttamente");
    
}
else {
    // se non è un upload multipart, interpreta i parametri normalmente
    bucketName = request.getParameter("bucketName");
    prefix = request.getParameter("prefix");
    
    bucket = s3.getBucket(bucketName);
}

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

<c:choose>
  <c:when test="${!empty(ex)}">
    <div class="errorMessage">
      <p>Errore contattando lo storage: <c:out value="${ex.message}" /></p>
    </div>
  </c:when>
  <c:otherwise>
    
    <h2>jets3t test on Hostingsolutions.it Storage OnLine</h2>
    
    <c:if test="${!empty(message)}">
      <div class="message">
        <c:out value="${message}" />
      </div>
    </c:if>
    
    <c:url var="backUrl" value="listObjectsAdvanced.jsp">
      <c:param name="bucketName" value="${fn:escapeXml(bucketName)}" />
      <c:param name="prefix" value="${fn:escapeXml(prefix)}" />
      <c:param name="parent" value="${fn:escapeXml(parent)}" />
    </c:url>
    
    <p><a href="${backUrl}">Indietro</a></p>
    
    <h3>Carica un file</h3>
    
    <p>Bucket: <em><c:out value="${bucketName}" /></em></p>
    <p>Directory: <em><c:out value="${prefix}" /></em></p>
    
    <form action="uploadFile.jsp" method="post" enctype="multipart/form-data">
      <input type="hidden" name="bucketName" value="${fn:escapeXml(bucketName)}">
      <input type="hidden" name="prefix" value="${fn:escapeXml(prefix)}">
      <input type="hidden" name="parent" value="${fn:escapeXml(parent)}">
      <label for="file">Scegli un file da caricare:</label>
      <input type="file" name="file" id="file" />
      <button type="submit">Invia</button>
    </form>

  </c:otherwise>
</c:choose>

</body>
</html>
      

Osservare che:

  • il parsing dei parametri viene eseguito prima dell'upload dei file;
  • l'assegnamento del Content-type in base alle indicazioni del browser o in base all'estensione del file.

A differenza di php, non siamo così legati alle librerie di sistema, possiamo quindi sfruttare caratteristiche avanzate delle librerie. Commons Upload, ad esempio, permette di specificare diversi parametri di funzionamento, come, ad esempio, l'uso di file temporanei o (come nel nostro caso) il processamento interamente in memoria, le dimensioni massime e molto altro.

Una volta caricato il file, infine, la pagina ci mostra un link "Indietro" che ci riporta, grazie al passaggio dei parametri, al file browser posizionato sulla directory dove abbiamo eseguito l'upload, permettendoci di controllare che il caricamento sia avvenuto correttamente.

Risorse