Méthode recommandée pour enregistrer les fichiers téléchargés dans une application de servlet

121

J'ai lu ici qu'il ne faut de toute façon pas enregistrer le fichier sur le serveur car il n'est pas portable, transactionnel et nécessite des paramètres externes. Cependant, étant donné que j'ai besoin d'une solution tmp pour tomcat (7) et que j'ai un contrôle (relatif) sur la machine serveur, je veux savoir:

  • Quel est le meilleur endroit pour enregistrer le fichier? Dois-je l'enregistrer dans /WEB-INF/uploads(déconseillé ici ) ou quelque part sous $CATALINA_BASE(voir ici ) ou ...? Le didacticiel JavaEE 6 obtient le chemin d'accès de l'utilisateur (: wtf :). NB: Le fichier ne doit en aucun cas être téléchargeable.

  • Dois-je configurer un paramètre de configuration comme détaillé ici ? J'apprécierais un peu de code (je préfère lui donner un chemin relatif - donc c'est au moins Tomcat portable) - Part.write()semble prometteur - mais a apparemment besoin d'un chemin absolu

  • Je serais intéressé par une exposition des inconvénients de cette approche par rapport à une base de données / référentiel JCR

Malheureusement , le FileServlet par @BalusC se concentre sur le téléchargement de fichiers, tandis que sa réponse sur les fichiers de la partie téléchargement des saute sur l' endroit où enregistrer le fichier.

Une solution facilement convertible pour utiliser une implémentation DB ou JCR (comme jackrabbit ) serait préférable.

Mr_and_Mrs_D
la source
Pour ma dernière façon de le faire, voir la réponse ci
Mr_and_Mrs_D

Réponses:

165

Stockez-le n'importe où dans un emplacement accessible à l' exception du dossier de projet de l'EDI, alias le dossier de déploiement du serveur, pour les raisons mentionnées dans la réponse à l' image téléchargée uniquement disponible après l'actualisation de la page :

  1. Les modifications dans le dossier de projet de l'EDI ne sont pas immédiatement reflétées dans le dossier de travail du serveur. Il y a une sorte de travail d'arrière-plan dans l'EDI qui veille à ce que le dossier de travail du serveur soit synchronisé avec les dernières mises à jour (c'est en termes IDE appelé «publication»). C'est la principale cause du problème que vous rencontrez.

  2. Dans le code du monde réel, il existe des circonstances dans lesquelles le stockage des fichiers téléchargés dans le dossier de déploiement de l'application Web ne fonctionnera pas du tout. Certains serveurs ne développent pas (par défaut ou par configuration) le fichier WAR déployé dans le système de fichiers du disque local, mais au contraire entièrement dans la mémoire. Vous ne pouvez pas créer de nouveaux fichiers dans la mémoire sans modifier fondamentalement le fichier WAR déployé et le redéployer.

  3. Même lorsque le serveur étend le fichier WAR déployé dans le système de fichiers du disque local, tous les fichiers nouvellement créés seront perdus lors d'un redéploiement ou même d'un simple redémarrage, simplement parce que ces nouveaux fichiers ne font pas partie du fichier WAR d'origine.

Il n'a pas d' importance pour moi ou quelqu'un d' autre où exactement sur le système de fichiers du disque local , il sera sauvegardé, aussi longtemps que vous ne ne jamais utiliser la getRealPath()méthode . L'utilisation de cette méthode est en tout cas alarmante.

Le chemin vers l'emplacement de stockage peut à son tour être défini de plusieurs manières. Vous devez tout faire par vous-même . C'est peut-être là que votre confusion est causée parce que vous vous attendiez d'une manière ou d'une autre à ce que le serveur fasse tout cela automatiquement. Veuillez noter que @MultipartConfig(location)cela ne spécifie pas la destination finale du téléchargement, mais que l'emplacement de stockage temporaire pour la taille du fichier de cas dépasse le seuil de stockage de la mémoire.

Ainsi, le chemin d'accès à l'emplacement de stockage final peut être défini de l'une des manières suivantes:

  • Codé en dur:

      File uploads = new File("/path/to/uploads");
  • Variable d'environnement via SET UPLOAD_LOCATION=/path/to/uploads:

      File uploads = new File(System.getenv("UPLOAD_LOCATION"));
  • Argument VM lors du démarrage du serveur via -Dupload.location="/path/to/uploads":

      File uploads = new File(System.getProperty("upload.location"));
  • *.propertiesentrée de fichier comme upload.location=/path/to/uploads:

      File uploads = new File(properties.getProperty("upload.location"));
  • web.xml <context-param>avec nom upload.locationet valeur /path/to/uploads:

      File uploads = new File(getServletContext().getInitParameter("upload.location"));
  • Le cas échéant, utilisez l'emplacement fourni par le serveur, par exemple dans JBoss AS / WildFly :

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

Dans tous les cas, vous pouvez facilement référencer et enregistrer le fichier comme suit:

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

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

Ou, lorsque vous souhaitez générer automatiquement un nom de fichier unique pour empêcher les utilisateurs d'écraser les fichiers existants avec le même nom par hasard:

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

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

Comment obtenir partdans JSP / Servlet est répondu dans Comment télécharger des fichiers sur le serveur à l'aide de JSP / Servlet? et comment obtenir partdans JSF est répondu dans Comment télécharger un fichier en utilisant JSF 2.2 <h: inputFile>? Où est le fichier enregistré?

Remarque: ne pas utiliser Part#write()car il interprète le chemin relatif à l'emplacement de stockage temporaire défini dans @MultipartConfig(location).

Voir également:

BalusC
la source
Le @MultipartConfig(location)spécifie l' emplacement de stockage temporaire que le serveur doit utiliser lorsque la taille du fichier dépasse le seuil de stockage de la mémoire, et non l'emplacement de stockage permanent où vous souhaitez finalement qu'il soit stocké. Cette valeur correspond par défaut au chemin identifié par la java.io.tmpdirpropriété système. Voir aussi cette réponse associée à une tentative JSF ayant échoué: stackoverflow.com/questions/18478154/…
BalusC
1
Merci - j'espère que je n'ai pas l'air idiot, mais cette citation de Part.write>> Cela permet à une implémentation particulière d'utiliser, par exemple, le changement de nom de fichier, lorsque cela est possible, plutôt que de copier toutes les données sous-jacentes, obtenant ainsi un avantage significatif en termes de performances en conjonction avec certains La méthode inconnue "cut" (vs copy) de disons que certaines lib apache me sauveraient les tracas d'écrire les octets moi-même - et de recréer un fichier déjà là (voir aussi ici )
Mr_and_Mrs_D
Oui, si vous êtes déjà sur Servlet 3.0, vous pouvez utiliser Part#write(). J'ai mis à jour la réponse avec.
BalusC
Merci beaucoup d'avoir mis à jour le message - existe-t-il une propriété pour Tomcat "jboss.server.data.dir"?
Mr_and_Mrs_D
1
Non, ce n'est pas le cas.
BalusC
7

Je poste ma dernière façon de le faire en fonction de la réponse acceptée:

@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;
    }
}

où :

@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);
    }
}

et le /WEB-INF/app.properties:

upload.location=C:/_/

HTH et si vous trouvez un bug, faites le moi savoir

Mr_and_Mrs_D
la source
1
Que faire si je veux une solution indépendante du SO, qui fonctionne dans les deux cas (win / ux)? Dois-je définir un chemin upload.location différent ou il y a un autre indice?
pikimota