วิธีที่แนะนำในการบันทึกไฟล์ที่อัปโหลดในแอปพลิเคชัน servlet


121

ฉันอ่านที่นี่ว่าไม่ควรบันทึกไฟล์ในเซิร์ฟเวอร์อยู่ดีเนื่องจากไม่ใช่แบบพกพาธุรกรรมและต้องใช้พารามิเตอร์ภายนอก อย่างไรก็ตามเนื่องจากฉันต้องการโซลูชัน tmp สำหรับ tomcat (7) และฉันมี (ญาติ) ควบคุมเครื่องเซิร์ฟเวอร์ที่ฉันต้องการทราบ:

  • สถานที่ที่ดีที่สุดในการบันทึกไฟล์คืออะไร? ฉันควรบันทึกไว้ใน/WEB-INF/uploads(แนะนำให้ต่อต้านที่นี่ ) หรือบางแห่งภายใต้$CATALINA_BASE(ดูที่นี่ ) หรือ ... ? บทช่วยสอน JavaEE 6 รับเส้นทางจากผู้ใช้ (: wtf :) หมายเหตุ: ไม่ควรดาวน์โหลดไฟล์ด้วยวิธีใด ๆ

  • ฉันควรตั้งค่าพารามิเตอร์ config ตามรายละเอียดที่นี่หรือไม่ ฉันขอขอบคุณรหัสบางอย่าง (ฉันควรให้เส้นทางสัมพัทธ์ - อย่างน้อยก็เป็น Tomcat แบบพกพา) - Part.write()ดูมีแนวโน้ม - แต่ดูเหมือนจะต้องการเส้นทางที่แน่นอน

  • ฉันสนใจในการอธิบายข้อเสียของแนวทางนี้กับฐานข้อมูล / ที่เก็บ JCR

น่าเสียดายที่FileServlet โดย @BalusC มุ่งเน้นไปที่การดาวน์โหลดไฟล์ในขณะที่คำตอบของเขาเกี่ยวกับการอัปโหลดไฟล์จะข้ามส่วนที่จะบันทึกไฟล์

โซลูชันที่แปลงสภาพได้อย่างง่ายดายเพื่อใช้ DB หรือการใช้งาน JCR (เช่นjackrabbit ) จะดีกว่า


สำหรับวิธีสุดท้ายของฉันโปรดดูคำตอบด้านล่าง
Mr_and_Mrs_D

คำตอบ:


165

จัดเก็บไว้ที่ใดก็ได้ในตำแหน่งที่สามารถเข้าถึงได้ยกเว้นโฟลเดอร์โครงการของ IDE หรือที่เรียกว่าโฟลเดอร์การปรับใช้ของเซิร์ฟเวอร์ด้วยเหตุผลที่ระบุไว้ในคำตอบสำหรับรูปภาพที่อัปโหลดจะมีให้หลังจากรีเฟรชหน้าเท่านั้น :

  1. การเปลี่ยนแปลงในโฟลเดอร์โครงการของ IDE จะไม่ปรากฏในโฟลเดอร์งานของเซิร์ฟเวอร์ในทันที มีงานเบื้องหลังใน IDE ซึ่งดูแลว่าโฟลเดอร์งานของเซิร์ฟเวอร์ได้รับการซิงค์กับการอัปเดตล่าสุด (ซึ่งอยู่ในข้อกำหนด IDE เรียกว่า "การเผยแพร่") นี่คือสาเหตุหลักของปัญหาที่คุณพบ

  2. ในรหัสโลกแห่งความเป็นจริงมีบางสถานการณ์ที่การจัดเก็บไฟล์ที่อัปโหลดไว้ในโฟลเดอร์ปรับใช้ของเว็บแอปจะใช้งานไม่ได้เลย เซิร์ฟเวอร์บางตัวทำ (โดยดีฟอลต์หรือตามคอนฟิกูเรชัน) ไม่ขยายไฟล์ WAR ที่ปรับใช้ไปยังระบบไฟล์โลคัลดิสก์ แต่จะอยู่ในหน่วยความจำทั้งหมดแทน คุณไม่สามารถสร้างไฟล์ใหม่ในหน่วยความจำได้โดยไม่ต้องแก้ไขไฟล์ WAR ที่ปรับใช้โดยพื้นฐานแล้วปรับใช้ใหม่

  3. แม้ว่าเซิร์ฟเวอร์จะขยายไฟล์ WAR ที่ปรับใช้ไปยังระบบไฟล์ดิสก์ภายในเครื่องไฟล์ที่สร้างขึ้นใหม่ทั้งหมดจะสูญหายไปในการปรับใช้ใหม่หรือแม้แต่การรีสตาร์ทแบบธรรมดาเพียงเพราะไฟล์ใหม่เหล่านั้นไม่ได้เป็นส่วนหนึ่งของไฟล์ WAR ดั้งเดิม

จริงๆมันไม่สำคัญกับผมหรือคนอื่นที่ว่าในระบบไฟล์ดิสก์ภายในก็จะถูกบันทึกไว้ตราบใดที่คุณไม่ไม่เคยใช้getRealPath()วิธีการ โดยใช้วิธีการที่อยู่ในใด ๆกรณีที่น่ากลัว

เส้นทางไปยังสถานที่จัดเก็บสามารถกำหนดได้หลายวิธี ที่คุณต้องทำทุกอย่างด้วยตัวเอง บางทีนี่อาจเป็นสาเหตุของความสับสนของคุณเนื่องจากคุณคาดว่าเซิร์ฟเวอร์จะดำเนินการทั้งหมดโดยอัตโนมัติ โปรดทราบว่า@MultipartConfig(location)ไม่ได้ระบุปลายทางที่อัปโหลดสุดท้าย แต่สถานที่เก็บชั่วคราวสำหรับขนาดกรณีไฟล์เกินเกณฑ์เก็บความทรงจำ

ดังนั้นเส้นทางไปยังตำแหน่งจัดเก็บสุดท้ายสามารถกำหนดได้ด้วยวิธีใดวิธีหนึ่งดังต่อไปนี้:

  • hardcoded:

      File uploads = new File("/path/to/uploads");
  • ตัวแปรสภาพแวดล้อมผ่านSET UPLOAD_LOCATION=/path/to/uploads:

      File uploads = new File(System.getenv("UPLOAD_LOCATION"));
  • อาร์กิวเมนต์ VM ระหว่างการเริ่มต้นเซิร์ฟเวอร์ผ่าน-Dupload.location="/path/to/uploads":

      File uploads = new File(System.getProperty("upload.location"));
  • *.propertiesรายการไฟล์เป็นupload.location=/path/to/uploads:

      File uploads = new File(properties.getProperty("upload.location"));
  • web.xml <context-param>ด้วยชื่อupload.locationและมูลค่า/path/to/uploads:

      File uploads = new File(getServletContext().getInitParameter("upload.location"));
  • ถ้ามีให้ใช้ตำแหน่งที่เซิร์ฟเวอร์จัดเตรียมไว้เช่นในJBoss AS / WildFly :

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

ไม่ว่าจะด้วยวิธีใดคุณสามารถอ้างอิงและบันทึกไฟล์ได้อย่างง่ายดายดังนี้:

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

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

หรือเมื่อคุณต้องการสร้างชื่อไฟล์ที่ไม่ซ้ำกันโดยอัตโนมัติเพื่อป้องกันไม่ให้ผู้ใช้เขียนทับไฟล์ที่มีอยู่ด้วยชื่อเดียวกันโดยบังเอิญ:

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

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

วิธีการขอรับpartใน JSP / Servlet มีคำตอบในHow to upload files to server โดยใช้ JSP / Servlet? และวิธีการรับpartใน JSF มีคำตอบในHow to upload file โดยใช้ JSF 2.2 <h: inputFile>? ไฟล์ที่บันทึกไว้อยู่ที่ไหน

หมายเหตุ: ไม่ได้ใช้งานในขณะที่มันตีความเส้นทางเทียบกับที่ตั้งของการจัดเก็บชั่วคราวที่กำหนดไว้ในPart#write()@MultipartConfig(location)

ดูสิ่งนี้ด้วย:


@MultipartConfig(location)ระบุชั่วคราวที่ตั้ง storge ซึ่งเซิร์ฟเวอร์ควรใช้เมื่อมีขนาดไฟล์เกินเกณฑ์สำหรับการจัดเก็บหน่วยความจำไม่ได้เป็นสถานที่เก็บถาวรที่คุณต้องการในท้ายที่สุดว่ามันจะถูกเก็บไว้ ค่านี้ดีฟอลต์เป็นพา ธ ตามที่ระบุโดยjava.io.tmpdirคุณสมบัติระบบ ดูคำตอบที่เกี่ยวข้องกับความพยายาม JSF ที่ล้มเหลว: stackoverflow.com/questions/18478154/…
BalusC

1
ขอบคุณ - หวังว่าฉันจะไม่ฟังดูงี่เง่า แต่คำพูดนี้จากPart.write>> สิ่งนี้ช่วยให้การใช้งานบางอย่างสามารถใช้งานได้ตัวอย่างเช่นการเปลี่ยนชื่อไฟล์หากเป็นไปได้แทนที่จะคัดลอกข้อมูลพื้นฐานทั้งหมดจึงได้รับประโยชน์ด้านประสิทธิภาพที่สำคัญร่วมกับบางส่วน วิธี "ตัด" (เทียบกับสำเนา) ที่ไม่รู้จักจากการบอกว่า apache lib บางตัวจะช่วยให้ฉันไม่ต้องยุ่งยากในการเขียนไบต์ด้วยตัวเองและสร้างไฟล์ที่มีอยู่แล้ว (ดูที่นี่ )
Mr_and_Mrs_D

ใช่ถ้าคุณใช้ Servlet 3.0 อยู่แล้วคุณสามารถใช้ประโยชน์จากPart#write()ไฟล์. ฉันอัปเดตคำตอบด้วย
BalusC

ขอบคุณมากที่คอยอัพเดทโพสต์ - Tomcat มีคุณสมบัติเช่นนี้"jboss.server.data.dir"หรือไม่?
Mr_and_Mrs_D

1
ไม่มันไม่มี
BalusC

7

ฉันโพสต์วิธีสุดท้ายในการทำตามคำตอบที่ยอมรับ:

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

ที่ไหน:

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

และ /WEB-INF/app.properties:

upload.location=C:/_/

HTH และหากคุณพบข้อบกพร่องแจ้งให้เราทราบ


1
จะเกิดอะไรขึ้นถ้าฉันต้องการโซลูชันอิสระ SO ที่ใช้ได้ทั้งในกรณี (win / ux) ฉันต้องตั้งค่าเส้นทางการอัพโหลดสถานที่อื่นหรือมีคำใบ้อื่น ๆ หรือไม่?
pikimota
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.