Empfohlene Methode zum Speichern hochgeladener Dateien in einer Servlet-Anwendung

121

Ich habe hier gelesen , dass man die Datei sowieso nicht auf dem Server speichern sollte, da sie nicht portabel und transaktionsfähig ist und externe Parameter erfordert. Da ich jedoch eine tmp-Lösung für Tomcat (7) benötige und die (relative) Kontrolle über den Server habe, möchte ich Folgendes wissen:

  • Was ist der beste Ort, um die Datei zu speichern? Soll ich spare es in /WEB-INF/uploads( von denen abgeraten wird hier ) oder irgendwo unter $CATALINA_BASE(siehe hier ) , oder ...? Das JavaEE 6-Tutorial erhält den Pfad vom Benutzer (: wtf :). NB: Die Datei sollte auf keinen Fall heruntergeladen werden können.

  • Sollte ich einen Konfigurationsparameter wie hier beschrieben einrichten ? Ich würde mich über Code freuen (ich würde ihm lieber einen relativen Pfad geben - also ist er zumindest für Tomcat portabel) -Part.write() sieht vielversprechend aus - benötigt aber anscheinend einen absoluten Pfad

  • Ich würde mich für eine Darstellung der Nachteile dieses Ansatzes gegenüber einem Datenbank- / JCR-Repository interessieren

Leider konzentriert sich das FileServlet von @BalusC auf das Herunterladen von Dateien, während seine Antwort auf das Hochladen von Dateien den Teil überspringt, in dem die Datei gespeichert werden soll.

Eine Lösung, die leicht konvertierbar ist, um eine DB- oder JCR-Implementierung (wie Jackrabbit ) zu verwenden, wäre vorzuziehen.

Mr_and_Mrs_D
quelle
Für meine letzte Vorgehensweise siehe Antwort unten
Mr_and_Mrs_D

Antworten:

165

Speichern Sie es an einem beliebigen Ort mit Ausnahme des Projektordners der IDE, auch bekannt als Bereitstellungsordner des Servers, aus Gründen, die in der Antwort auf das hochgeladene Image angegeben sind und erst nach dem Aktualisieren der Seite verfügbar sind :

  1. Änderungen im Projektordner der IDE werden nicht sofort im Arbeitsordner des Servers angezeigt. Es gibt eine Art Hintergrundjob in der IDE, der dafür sorgt, dass der Arbeitsordner des Servers mit den letzten Aktualisierungen synchronisiert wird (dies wird in IDE-Begriffen als "Veröffentlichung" bezeichnet). Dies ist die Hauptursache für das Problem, das Sie sehen.

  2. Im realen Code gibt es Umstände, unter denen das Speichern hochgeladener Dateien im Bereitstellungsordner der Webanwendung überhaupt nicht funktioniert. Einige Server erweitern (entweder standardmäßig oder per Konfiguration) die bereitgestellte WAR-Datei nicht in das lokale Festplattendateisystem, sondern vollständig im Speicher. Sie können keine neuen Dateien im Speicher erstellen, ohne die bereitgestellte WAR-Datei zu bearbeiten und erneut bereitzustellen.

  3. Selbst wenn der Server die bereitgestellte WAR-Datei in das lokale Festplattendateisystem erweitert, gehen alle neu erstellten Dateien bei einer erneuten Bereitstellung oder sogar einem einfachen Neustart verloren, einfach weil diese neuen Dateien nicht Teil der ursprünglichen WAR-Datei sind.

Es ist wirklich egal , mir oder jemand anderes , wo genau auf dem Dateisystem der lokalen Festplatte der sie gespeichert wird, solange man sie nicht immer verwenden getRealPath()Methode . Die Verwendung dieser Methode ist auf jeden Fall alarmierend.

Der Pfad zum Speicherort kann wiederum auf viele Arten definiert werden. Du musst alles alleine machen . Vielleicht wird hier Ihre Verwirrung verursacht, weil Sie irgendwie erwartet haben, dass der Server das alles automatisch macht. Bitte beachten Sie, dass @MultipartConfig(location)sie nicht das letzte Upload - Ziel angeben, aber der temporäre Speicherort für die Fall , Dateigröße überschreitet Schwellenspeicher.

Der Pfad zum endgültigen Speicherort kann also auf eine der folgenden Arten definiert werden:

  • Hardcodiert:

      File uploads = new File("/path/to/uploads");
  • Umgebungsvariable über SET UPLOAD_LOCATION=/path/to/uploads:

      File uploads = new File(System.getenv("UPLOAD_LOCATION"));
  • VM-Argument beim Serverstart über -Dupload.location="/path/to/uploads":

      File uploads = new File(System.getProperty("upload.location"));
  • *.propertiesDateieintrag als upload.location=/path/to/uploads:

      File uploads = new File(properties.getProperty("upload.location"));
  • web.xml <context-param>mit Name upload.locationund Wert /path/to/uploads:

      File uploads = new File(getServletContext().getInitParameter("upload.location"));
  • Verwenden Sie gegebenenfalls den vom Server bereitgestellten Speicherort, z. B. in JBoss AS / WildFly :

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

In beiden Fällen können Sie die Datei wie folgt referenzieren und speichern:

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

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

Oder wenn Sie einen eindeutigen Dateinamen automatisch generieren möchten, um zu verhindern, dass Benutzer vorhandene Dateien mit zufällig demselben Namen überschreiben:

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

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

Wie man partin JSP / Servlet erhält, wird in Wie lade ich Dateien mit JSP / Servlet auf den Server hoch? und wie man partin JSF erhält, wird in Wie lade ich eine Datei mit JSF 2.2 <h: inputFile> hoch? Wo ist die gespeicherte Datei?

Hinweis: Verwenden Sie diese Option nicht , Part#write()da sie den Pfad relativ zum in definierten temporären Speicherort interpretiert @MultipartConfig(location).

Siehe auch:

BalusC
quelle
Das @MultipartConfig(location)gibt den temporären Speicherort an, den der Server verwenden soll, wenn die Dateigröße den Schwellenwert für die Speicherung überschreitet, nicht den permanenten Speicherort, an dem er letztendlich gespeichert werden soll. Dieser Wert ist standardmäßig der Pfad, der durch die java.io.tmpdirSystemeigenschaft angegeben wird. Siehe auch diese verwandte Antwort auf einen fehlgeschlagenen JSF-Versuch: stackoverflow.com/questions/18478154/…
BalusC
1
Vielen Dank - ich hoffe, ich klinge nicht idiotisch, aber dieses Zitat aus Part.write>> Dies ermöglicht einer bestimmten Implementierung, wenn möglich, beispielsweise das Umbenennen von Dateien zu verwenden, anstatt alle zugrunde liegenden Daten zu kopieren, wodurch in Verbindung mit einigen ein erheblicher Leistungsvorteil erzielt wird Unbekannte "cut" (vs copy) -Methode von sagen, eine Apache-Bibliothek würde mir den Aufwand ersparen, die Bytes selbst zu schreiben - und eine bereits vorhandene Datei neu zu erstellen (siehe auch hier )
Mr_and_Mrs_D
Ja, wenn Sie bereits Servlet 3.0 verwenden, können Sie davon Gebrauch machen Part#write(). Ich habe die Antwort damit aktualisiert.
BalusC
Vielen Dank, dass Sie den Beitrag auf dem neuesten Stand gehalten haben. Gibt es eine Eigenschaft für Tomcat wie "jboss.server.data.dir"?
Mr_and_Mrs_D
1
Nein, das hat es nicht.
BalusC
7

Ich poste meine endgültige Vorgehensweise basierend auf der akzeptierten Antwort:

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

wo :

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

und die /WEB-INF/app.properties:

upload.location=C:/_/

HTH und wenn Sie einen Fehler finden, lassen Sie es mich wissen

Mr_and_Mrs_D
quelle
1
Was ist, wenn ich eine SO unabhängige Lösung möchte, die in beiden Fällen (win / ux) funktioniert? Muss ich einen anderen Pfad für upload.location festlegen oder gibt es einen anderen Hinweis?
Pikimota