Cara yang disarankan untuk menyimpan file yang diunggah dalam aplikasi servlet

121

Saya membaca di sini bahwa seseorang tidak boleh menyimpan file di server karena tidak portabel, transaksional dan memerlukan parameter eksternal. Namun, mengingat bahwa saya memerlukan solusi tmp untuk tomcat (7) dan saya memiliki kontrol (relatif) atas mesin server yang ingin saya ketahui:

  • Di mana tempat terbaik untuk menyimpan file? Haruskah saya menyimpannya di /WEB-INF/uploads(disarankan agar tidak di sini ) atau di suatu tempat di bawah $CATALINA_BASE(lihat di sini ) atau ...? Tutorial JavaEE 6 mendapatkan jalur dari pengguna (: wtf :). NB: File tidak boleh diunduh dengan cara apa pun.

  • Haruskah saya menyiapkan parameter konfigurasi seperti yang dijelaskan di sini ? Saya akan menghargai beberapa kode (saya lebih suka memberikannya jalur relatif - jadi setidaknya ini portabel Tomcat) - Part.write()terlihat menjanjikan - tetapi tampaknya membutuhkan jalur absolut

  • Saya akan tertarik pada eksposisi kerugian dari pendekatan ini vs database / repositori JCR

Sayangnya FileServlet oleh @BalusC berkonsentrasi pada mengunduh file, sementara jawabannya pada mengunggah file melompati bagian tempat menyimpan file.

Solusi yang mudah dikonversi untuk menggunakan implementasi DB atau JCR (seperti jackrabbit ) akan lebih disukai.

Mr_and_Mrs_D
sumber
Untuk cara terakhir saya melakukannya lihat jawaban di bawah
Mr_and_Mrs_D

Jawaban:

165

Simpan di mana saja di lokasi yang dapat diakses kecuali folder proyek IDE alias folder penerapan server, karena alasan yang disebutkan dalam jawaban untuk Gambar yang diunggah hanya tersedia setelah menyegarkan halaman :

  1. Perubahan dalam folder proyek IDE tidak langsung terlihat di folder kerja server. Ada semacam pekerjaan latar belakang di IDE yang mengatur agar folder kerja server disinkronkan dengan pembaruan terakhir (ini dalam istilah IDE disebut "penerbitan"). Ini adalah penyebab utama masalah yang Anda lihat.

  2. Dalam kode dunia nyata ada keadaan di mana menyimpan file yang diunggah di folder penerapan aplikasi web tidak akan berfungsi sama sekali. Beberapa server (baik secara default atau dengan konfigurasi) tidak memperluas file WAR yang diterapkan ke dalam sistem file disk lokal, melainkan sepenuhnya di memori. Anda tidak dapat membuat file baru di memori tanpa mengedit file WAR yang diterapkan dan menerapkannya kembali.

  3. Bahkan saat server memperluas file WAR yang diterapkan ke sistem file disk lokal, semua file yang baru dibuat akan hilang saat penerapan ulang atau bahkan restart sederhana, hanya karena file baru tersebut bukan bagian dari file WAR asli.

Itu benar-benar tidak peduli dengan saya atau orang lain di mana tepatnya pada sistem file disk lokal itu akan disimpan, selama Anda tidak tidak pernah menggunakan getRealPath()metode . Menggunakan metode itu bagaimanapun juga mengkhawatirkan.

Jalan menuju lokasi penyimpanan pada gilirannya dapat ditentukan dengan berbagai cara. Anda harus melakukan semuanya sendiri . Mungkin di sinilah kebingungan Anda disebabkan karena Anda entah bagaimana berharap server melakukan itu semua secara otomatis. Harap dicatat bahwa @MultipartConfig(location)tidak tidak menentukan tujuan meng-upload akhir, tapi lokasi penyimpanan sementara untuk ukuran berkas kasus melebihi ambang batas penyimpanan memori.

Jadi, jalur ke lokasi penyimpanan akhir dapat ditentukan dengan salah satu cara berikut:

  • Hardcode:

      File uploads = new File("/path/to/uploads");
  • Variabel lingkungan melalui SET UPLOAD_LOCATION=/path/to/uploads:

      File uploads = new File(System.getenv("UPLOAD_LOCATION"));
  • Argumen VM selama startup server melalui -Dupload.location="/path/to/uploads":

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

      File uploads = new File(properties.getProperty("upload.location"));
  • web.xml <context-param>dengan nama upload.locationdan nilai /path/to/uploads:

      File uploads = new File(getServletContext().getInitParameter("upload.location"));
  • Jika ada, gunakan lokasi yang disediakan server, misalnya di JBoss AS / WildFly :

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

Apa pun itu, Anda dapat dengan mudah merujuk dan menyimpan file sebagai berikut:

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

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

Atau, jika Anda ingin membuat nama file unik secara otomatis untuk mencegah pengguna menimpa file yang ada dengan nama yang sama secara tidak sengaja:

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

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

Bagaimana cara mendapatkan partdi JSP / Servlet dijawab di Bagaimana cara mengunggah file ke server menggunakan JSP / Servlet? dan cara mendapatkan partdi JSF dijawab di Cara mengupload file menggunakan JSF 2.2 <h: inputFile>? Di mana File yang disimpan?

Catatan: jangan tidak menggunakan Part#write()karena menafsirkan path relatif ke lokasi penyimpanan sementara didefinisikan dalam @MultipartConfig(location).

Lihat juga:

BalusC
sumber
The @MultipartConfig(location)Menentukan sementara lokasi storge yang server harus menggunakan ketika ukuran file melebihi ambang batas untuk penyimpanan memori, bukan lokasi penyimpanan permanen di mana Anda akan lebih akhirnya seperti itu untuk disimpan. Nilai ini secara default ke jalur seperti yang diidentifikasi oleh java.io.tmpdirproperti sistem. Lihat juga jawaban terkait ini untuk upaya JSF yang gagal: stackoverflow.com/questions/18478154/…
BalusC
1
Terima kasih - harap saya tidak terdengar bodoh tetapi kutipan ini dari Part.write>> Ini memungkinkan implementasi tertentu untuk digunakan, misalnya, penggantian nama file, jika memungkinkan, daripada menyalin semua data yang mendasarinya, sehingga mendapatkan manfaat kinerja yang signifikan sehubungan dengan beberapa tidak diketahui metode "potong" (vs salin) dari mengatakan beberapa lib apache akan menyelamatkan saya dari kerumitan menulis byte sendiri - dan membuat ulang file yang sudah ada (lihat juga di sini )
Mr_and_Mrs_D
Ya, jika Anda sudah menggunakan Servlet 3.0, Anda dapat memanfaatkan Part#write(). Saya memperbarui jawabannya dengan itu.
BalusC
Terima kasih banyak telah memperbarui posting ini - adakah properti seperti itu untuk Tomcat "jboss.server.data.dir"?
Mr_and_Mrs_D
1
Tidak, tidak perlu.
BalusC
7

Saya memposting cara terakhir saya melakukannya berdasarkan jawaban yang diterima:

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

dimana:

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

dan properti /WEB-INF/app.:

upload.location=C:/_/

HTH dan jika Anda menemukan bug beri tahu saya

Mr_and_Mrs_D
sumber
1
Bagaimana jika saya menginginkan solusi SO independen, yang berfungsi di kedua kasus (win / ux)? Apakah saya harus menyetel jalur upload.location yang berbeda atau ada petunjuk lain?
pikimota