Metodo consigliato per salvare i file caricati in un'applicazione servlet


121

Ho letto qui che non si dovrebbe comunque salvare il file nel server in quanto non è portabile, transazionale e richiede parametri esterni. Tuttavia, dato che ho bisogno di una soluzione tmp per tomcat (7) e che ho il controllo (relativo) sulla macchina server, voglio sapere:

  • Qual è il posto migliore per salvare il file? Devo salvarlo in /WEB-INF/uploads(sconsigliato qui ) o da qualche parte sotto $CATALINA_BASE(vedi qui ) o ...? Il tutorial JavaEE 6 ottiene il percorso dall'utente (: wtf :). NB: il file non deve essere scaricabile in alcun modo.

  • Devo impostare un parametro di configurazione come descritto qui ? Apprezzerei del codice (preferirei dargli un percorso relativo, quindi è almeno portatile Tomcat) - Part.write()sembra promettente - ma a quanto pare ha bisogno di un percorso assoluto

  • Sarei interessato a un'esposizione degli svantaggi di questo approccio rispetto a un database / repository JCR

Sfortunatamente il FileServlet di @BalusC si concentra sul download dei file, mentre la sua risposta sul caricamento dei file salta la parte su dove salvare il file.

Sarebbe preferibile una soluzione facilmente convertibile per utilizzare un DB o un'implementazione JCR (come jackrabbit ).


Per il mio ultimo modo di farlo, vedi la risposta di seguito
Mr_and_Mrs_D

Risposte:


165

Memorizzalo ovunque in una posizione accessibile tranne che nella cartella del progetto dell'IDE, ovvero la cartella di distribuzione del server, per i motivi menzionati nella risposta all'immagine caricata disponibile solo dopo aver aggiornato la pagina :

  1. Le modifiche nella cartella del progetto dell'IDE non si riflettono immediatamente nella cartella di lavoro del server. C'è una specie di lavoro in background nell'IDE che si prende cura che la cartella di lavoro del server venga sincronizzata con gli ultimi aggiornamenti (questo è in termini IDE chiamato "pubblicazione"). Questa è la causa principale del problema che stai riscontrando.

  2. Nel codice del mondo reale ci sono circostanze in cui l'archiviazione dei file caricati nella cartella di distribuzione della webapp non funzionerà affatto. Alcuni server (per impostazione predefinita o per configurazione) non espandono il file WAR distribuito nel file system del disco locale, ma invece completamente nella memoria. Non è possibile creare nuovi file nella memoria senza modificare sostanzialmente il file WAR distribuito e ridistribuirlo.

  3. Anche quando il server espande il file WAR distribuito nel file system del disco locale, tutti i file appena creati andranno persi durante una ridistribuzione o anche un semplice riavvio, semplicemente perché quei nuovi file non fanno parte del file WAR originale.

Non ha molta importanza per me o chiunque altro dove esattamente sul file system del disco locale sarà salvato, fino a quando non non mai utilizzare getRealPath()il metodo . Usare quel metodo è in ogni caso allarmante.

Il percorso verso la posizione di archiviazione può a sua volta essere definito in molti modi. Bisogna fare tutto da soli . Forse è qui che si crea la tua confusione perché in qualche modo ti aspettavi che il server facesse tutto automaticamente. Si prega di notare che @MultipartConfig(location)non non specificare la destinazione finale di upload, ma la posizione di archiviazione temporanea per la dimensione del file caso supera la soglia memoria.

Pertanto, il percorso per la posizione di archiviazione finale può essere definito in uno dei seguenti modi:

  • hardcoded:

      File uploads = new File("/path/to/uploads");
  • Variabile d'ambiente tramite SET UPLOAD_LOCATION=/path/to/uploads:

      File uploads = new File(System.getenv("UPLOAD_LOCATION"));
  • Argomento della macchina virtuale durante l'avvio del server tramite -Dupload.location="/path/to/uploads":

      File uploads = new File(System.getProperty("upload.location"));
  • *.propertiesvoce del file come upload.location=/path/to/uploads:

      File uploads = new File(properties.getProperty("upload.location"));
  • web.xml <context-param>con nome upload.locatione valore /path/to/uploads:

      File uploads = new File(getServletContext().getInitParameter("upload.location"));
  • Se presente, utilizza la posizione fornita dal server, ad esempio in JBoss AS / WildFly :

      File uploads = new File(System.getProperty("jboss.server.data.dir"), "uploads");

In ogni caso, puoi facilmente fare riferimento e salvare il file come segue:

File file = new File(uploads, "somefilename.ext");

try (InputStream input = part.getInputStream()) {
    Files.copy(input, file.toPath());
}

Oppure, quando si desidera generare automaticamente un nome file univoco per impedire agli utenti di sovrascrivere file esistenti con lo stesso nome casualmente:

File file = File.createTempFile("somefilename-", ".ext", uploads);

try (InputStream input = part.getInputStream()) {
    Files.copy(input, file.toPath(), StandardCopyOption.REPLACE_EXISTING);
}

Come ottenere partin JSP / Servlet si risponde in Come caricare i file sul server utilizzando JSP / Servlet? e come ottenere partin JSF si risponde in Come caricare file utilizzando JSF 2.2 <h: inputFile>? Dov'è il file salvato?

Nota: non utilizzare Part#write()in quanto interpreta il percorso relativo alla posizione di archiviazione temporanea definita in @MultipartConfig(location).

Guarda anche:


I @MultipartConfig(location)specifica la temporanea posizione storge che il server dovrebbe utilizzare quando la dimensione del file supera la soglia per la conservazione della memoria, non la posizione di memorizzazione permanente dove faresti in ultima analisi, come è da memorizzare. Il valore predefinito è il percorso identificato dalla java.io.tmpdirproprietà di sistema. Si veda anche questa risposta relativa ad un tentativo fallito JSF: stackoverflow.com/questions/18478154/...
BalusC

1
Grazie - spero di non sembrare idiota ma questa citazione da Part.write>> Ciò consente a una particolare implementazione di utilizzare, ad esempio, la ridenominazione dei file, ove possibile, piuttosto che copiare tutti i dati sottostanti, ottenendo così un significativo vantaggio in termini di prestazioni insieme ad alcuni metodo sconosciuto "cut" (vs copy) da dire che alcune librerie di apache mi eviterebbero il fastidio di scrivere i byte da solo - e di ricreare un file già lì (vedi anche qui )
Mr_and_Mrs_D

Sì, se sei già su Servlet 3.0, potresti utilizzare Part#write(). Ho aggiornato la risposta con esso.
BalusC

Grazie mille per mantenere aggiornato il post: esiste una proprietà per Tomcat come "jboss.server.data.dir"?
Mr_and_Mrs_D

1
No, non ha.
BalusC

7

Inserisco il mio modo finale di farlo in base alla risposta accettata:

@SuppressWarnings("serial")
@WebServlet("/")
@MultipartConfig
public final class DataCollectionServlet extends Controller {

    private static final String UPLOAD_LOCATION_PROPERTY_KEY="upload.location";
    private String uploadsDirName;

    @Override
    public void init() throws ServletException {
        super.init();
        uploadsDirName = property(UPLOAD_LOCATION_PROPERTY_KEY);
    }

    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp)
            throws ServletException, IOException {
        // ...
    }

    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp)
            throws ServletException, IOException {
        Collection<Part> parts = req.getParts();
        for (Part part : parts) {
            File save = new File(uploadsDirName, getFilename(part) + "_"
                + System.currentTimeMillis());
            final String absolutePath = save.getAbsolutePath();
            log.debug(absolutePath);
            part.write(absolutePath);
            sc.getRequestDispatcher(DATA_COLLECTION_JSP).forward(req, resp);
        }
    }

    // helpers
    private static String getFilename(Part part) {
        // courtesy of BalusC : http://stackoverflow.com/a/2424824/281545
        for (String cd : part.getHeader("content-disposition").split(";")) {
            if (cd.trim().startsWith("filename")) {
                String filename = cd.substring(cd.indexOf('=') + 1).trim()
                        .replace("\"", "");
                return filename.substring(filename.lastIndexOf('/') + 1)
                        .substring(filename.lastIndexOf('\\') + 1); // MSIE fix.
            }
        }
        return null;
    }
}

dove :

@SuppressWarnings("serial")
class Controller extends HttpServlet {

    static final String DATA_COLLECTION_JSP="/WEB-INF/jsp/data_collection.jsp";
    static ServletContext sc;
    Logger log;
    // private
    // "/WEB-INF/app.properties" also works...
    private static final String PROPERTIES_PATH = "WEB-INF/app.properties";
    private Properties properties;

    @Override
    public void init() throws ServletException {
        super.init();
        // synchronize !
        if (sc == null) sc = getServletContext();
        log = LoggerFactory.getLogger(this.getClass());
        try {
            loadProperties();
        } catch (IOException e) {
            throw new RuntimeException("Can't load properties file", e);
        }
    }

    private void loadProperties() throws IOException {
        try(InputStream is= sc.getResourceAsStream(PROPERTIES_PATH)) {
                if (is == null)
                    throw new RuntimeException("Can't locate properties file");
                properties = new Properties();
                properties.load(is);
        }
    }

    String property(final String key) {
        return properties.getProperty(key);
    }
}

e le /WEB-INF/app.properties:

upload.location=C:/_/

HTH e se trovi un bug fammelo sapere


1
E se volessi una soluzione SO indipendente, che funzioni in entrambi i casi (win / ux)? Devo impostare un percorso di upload.location diverso o c'è qualche altro suggerimento?
pikimota
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.