บทนำ
ในการเรียกดูและเลือกไฟล์สำหรับการอัปโหลดคุณต้องมี<input type="file">ฟิลด์HTML ในแบบฟอร์ม ตามที่ระบุไว้ในข้อกำหนด HTMLคุณจะต้องใช้POSTวิธีการและแอตทริบิวต์ของรูปแบบจะต้องมีการตั้งค่าให้enctype"multipart/form-data"
<form action="upload" method="post" enctype="multipart/form-data">
<input type="text" name="description" />
<input type="file" name="file" />
<input type="submit" />
</form>
หลังจากส่งแบบฟอร์มดังกล่าวแล้วข้อมูลรูปแบบไบนารีหลายส่วนจะมีอยู่ในเนื้อหาคำขอในรูปแบบที่แตกต่างจากเมื่อenctypeไม่ได้ตั้งค่า
ก่อน Servlet 3.0 Servlet API multipart/form-dataไม่ได้รับการสนับสนุนโดยกำเนิด สนับสนุนเฉพาะรูปแบบเริ่มต้นของapplication/x-www-form-urlencodedประเภท request.getParameter()และภริยาจะกลับมาทั้งหมดnullเมื่อใช้ข้อมูลในแบบฟอร์ม multipart นี่คือจุดที่Apache Commons FileUpload อัพโหลดเข้ามาในรูปภาพ
อย่าแยกวิเคราะห์ด้วยตนเอง!
ในทางทฤษฎีคุณสามารถแยกวิเคราะห์คำขอตามตัวคุณเองServletRequest#getInputStream()ได้ แต่นี้คือการทำงานที่แม่นยำและน่าเบื่อที่ต้องมีความรู้ที่แม่นยำของRFC2388 คุณไม่ควรพยายามทำสิ่งนี้ด้วยตัวเองหรือทำสำเนาบางรหัสที่ไม่มีห้องสมุดที่ผลิตเองในที่อื่นบนอินเทอร์เน็ต แหล่งข้อมูลออนไลน์จำนวนมากล้มเหลวอย่างหนักในเรื่องนี้เช่น roseindia.net ดูเพิ่มเติมอัปโหลดไฟล์ PDF คุณควรใช้ไลบรารี่จริงที่ผู้ใช้หลายล้านคนใช้ (และผ่านการทดสอบโดยปริยาย!) ห้องสมุดดังกล่าวได้พิสูจน์ให้เห็นถึงความแข็งแกร่ง
เมื่อคุณใช้ Servlet 3.0 หรือใหม่กว่าอยู่แล้วให้ใช้ native API
หากคุณใช้ Servlet 3.0 เป็นอย่างน้อย (Tomcat 7, Jetty 9, JBoss AS 6, GlassFish 3 เป็นต้น) คุณสามารถใช้ API มาตรฐานที่มีให้HttpServletRequest#getPart()เพื่อรวบรวมรายการข้อมูลแบบหลายส่วน (การใช้งาน Servlet 3.0 ส่วนใหญ่ใช้ Apache จริง คอมมอนส์ไฟล์อัปโหลดภายใต้หน้าปกสำหรับสิ่งนี้!) นอกจากนี้ยังมีเขตข้อมูลฟอร์มgetParameter()ปกติด้วยวิธีปกติ
ขั้นแรกให้คำอธิบายประกอบเซิร์ฟเล็ตของคุณด้วย@MultipartConfigเพื่อให้สามารถรับรู้และสนับสนุนmultipart/form-dataคำขอและgetPart()ไปทำงาน:
@WebServlet("/upload")
@MultipartConfig
public class UploadServlet extends HttpServlet {
// ...
}
จากนั้นดำเนินการdoPost()ดังต่อไปนี้:
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
String description = request.getParameter("description"); // Retrieves <input type="text" name="description">
Part filePart = request.getPart("file"); // Retrieves <input type="file" name="file">
String fileName = Paths.get(filePart.getSubmittedFileName()).getFileName().toString(); // MSIE fix.
InputStream fileContent = filePart.getInputStream();
// ... (do your job here)
}
Path#getFileName()หมายเหตุ นี่คือการแก้ไข MSIE ที่จะได้รับชื่อไฟล์ เบราว์เซอร์นี้ส่งเส้นทางไฟล์แบบเต็มอย่างไม่ถูกต้องไปตามชื่อแทนที่จะเป็นเพียงชื่อไฟล์
ในกรณีที่คุณมีการ<input type="file" name="file" multiple="true" />อัปโหลดหลายไฟล์ให้รวบรวมไว้ด้านล่าง (น่าเสียดายที่ไม่มีวิธีการดังกล่าวrequest.getParts("file")):
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
// ...
List<Part> fileParts = request.getParts().stream().filter(part -> "file".equals(part.getName()) && part.getSize() > 0).collect(Collectors.toList()); // Retrieves <input type="file" name="file" multiple="true">
for (Part filePart : fileParts) {
String fileName = Paths.get(filePart.getSubmittedFileName()).getFileName().toString(); // MSIE fix.
InputStream fileContent = filePart.getInputStream();
// ... (do your job here)
}
}
เมื่อคุณไม่ได้อยู่ใน Servlet 3.1 ให้รับชื่อไฟล์ที่ส่งด้วยตนเอง
โปรดทราบว่าPart#getSubmittedFileName()เปิดตัวใน Servlet 3.1 (Tomcat 8, Jetty 9, WildFly 8, GlassFish 4, ฯลฯ ) หากคุณยังไม่ได้ใช้ Servlet 3.1 คุณต้องมีวิธีการอรรถประโยชน์เพิ่มเติมเพื่อรับชื่อไฟล์ที่ส่ง
private static String getSubmittedFileName(Part part) {
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;
}
String fileName = getSubmittedFileName(filePart);
หมายเหตุการแก้ไข MSIE เป็นการขอรับชื่อแฟ้ม เบราว์เซอร์นี้ส่งเส้นทางไฟล์แบบเต็มอย่างไม่ถูกต้องไปตามชื่อแทนที่จะเป็นเพียงชื่อไฟล์
เมื่อคุณไม่ได้ใช้ Servlet 3.0 ให้ใช้ Apache Commons FileUpload
หากคุณยังไม่ได้ใช้ Servlet 3.0 (ยังไม่ถึงเวลาที่จะอัพเกรดหรือไม่) วิธีปฏิบัติทั่วไปคือการใช้Apache Commons FileUploadเพื่อแยกการร้องขอข้อมูลแบบฟอร์มหลายส่วน มันมีคู่มือผู้ใช้ที่ดีเยี่ยมและคำถามที่พบบ่อย (ผ่านทั้งสองอย่างระมัดระวัง) นอกจากนี้ยังมี O'Reilly (" cos ") MultipartRequestแต่มีข้อผิดพลาด (เล็กน้อย) และไม่ได้รับการบำรุงรักษาอีกต่อไปเป็นเวลาหลายปี ฉันไม่แนะนำให้ใช้ Apache Commons FileUpload ยังคงได้รับการบำรุงรักษาอย่างแข็งขันและในปัจจุบันมีความสมบูรณ์มาก
ในการใช้ Apache Commons FileUpload คุณต้องมีไฟล์ต่อไปนี้อย่างน้อยใน webapp ของคุณ/WEB-INF/lib:
ความพยายามครั้งแรกของคุณล้มเหลวเนื่องจากคุณลืม IO ทั่วไป
นี่คือตัวอย่างของการแจ้งกำหนดการลักษณะdoPost()ของคุณUploadServletเมื่อใช้ Apache Commons FileUpload:
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
try {
List<FileItem> items = new ServletFileUpload(new DiskFileItemFactory()).parseRequest(request);
for (FileItem item : items) {
if (item.isFormField()) {
// Process regular form field (input type="text|radio|checkbox|etc", select, etc).
String fieldName = item.getFieldName();
String fieldValue = item.getString();
// ... (do your job here)
} else {
// Process form file field (input type="file").
String fieldName = item.getFieldName();
String fileName = FilenameUtils.getName(item.getName());
InputStream fileContent = item.getInputStream();
// ... (do your job here)
}
}
} catch (FileUploadException e) {
throw new ServletException("Cannot parse multipart request.", e);
}
// ...
}
มันเป็นสิ่งสำคัญมากที่คุณไม่ได้โทรgetParameter(), getParameterMap(), getParameterValues(), getInputStream(), getReader()ฯลฯ ตามคำขอเดียวกันก่อน มิฉะนั้นภาชนะ servlet จะอ่านและแยกเนื้อหาคำขอและ Apache Commons FileUpload จะได้รับร่างกายคำขอว่าง ดูเพิ่มเติมอ่าวServletFileUpload # parseRequest (ขอ) ส่งกลับรายการที่ว่างเปล่า
FilenameUtils#getName()หมายเหตุ นี่คือการแก้ไข MSIE ที่จะได้รับชื่อไฟล์ เบราว์เซอร์นี้ส่งเส้นทางไฟล์แบบเต็มอย่างไม่ถูกต้องไปตามชื่อแทนที่จะเป็นเพียงชื่อไฟล์
หรือคุณยังสามารถห่อนี้ทั้งหมดในFilterที่แยกมันทั้งหมดโดยอัตโนมัติและนำกลับสิ่งที่อยู่ใน parametermap ของการร้องขอเพื่อให้คุณสามารถดำเนินการโดยใช้วิธีการปกติและดึงไฟล์ที่อัปโหลดโดยrequest.getParameter() คุณสามารถค้นหาตัวอย่างในบทความบล็อกนี้request.getAttribute()
วิธีแก้ปัญหาสำหรับข้อผิดพลาด GlassFish3 getParameter()ยังคงกลับมาnull
หมายเหตุว่ารุ่น Glassfish เก่ากว่า 3.1.2 มีข้อผิดพลาดในประเด็นผลตอบแทนยังคงgetParameter() nullหากคุณกำหนดเป้าหมายไปที่คอนเทนเนอร์ดังกล่าวและไม่สามารถอัปเกรดได้คุณจะต้องแยกค่าจากgetPart()ด้วยความช่วยเหลือของวิธีการยูทิลิตี้นี้:
private static String getValue(Part part) throws IOException {
BufferedReader reader = new BufferedReader(new InputStreamReader(part.getInputStream(), "UTF-8"));
StringBuilder value = new StringBuilder();
char[] buffer = new char[1024];
for (int length = 0; (length = reader.read(buffer)) > 0;) {
value.append(buffer, 0, length);
}
return value.toString();
}
String description = getValue(request.getPart("description")); // Retrieves <input type="text" name="description">
กำลังบันทึกไฟล์ที่อัปโหลด (อย่าใช้getRealPath()หรือpart.write()!)
มุ่งไปที่คำตอบต่อไปนี้เพื่อดูรายละเอียดเกี่ยวกับการบันทึกที่ได้รับInputStream( fileContentตัวแปรตามที่แสดงในตัวอย่างโค้ดข้างต้น) ไปยังดิสก์หรือฐานข้อมูล:
กำลังแสดงไฟล์ที่อัพโหลด
ไปที่คำตอบต่อไปนี้เพื่อรับทราบรายละเอียดเกี่ยวกับการให้บริการไฟล์ที่ถูกบันทึกอย่างถูกต้องจากดิสก์หรือฐานข้อมูลกลับไปยังไคลเอนต์:
Ajaxifying แบบฟอร์ม
ตรงไปที่คำตอบต่อไปนี้วิธีการอัพโหลดโดยใช้ Ajax (และ jQuery) โปรดทราบว่ารหัส servlet เพื่อรวบรวมข้อมูลแบบฟอร์มไม่จำเป็นต้องเปลี่ยนแปลงสำหรับสิ่งนี้! เฉพาะวิธีที่คุณตอบกลับอาจเปลี่ยนแปลงได้ แต่สิ่งนี้ค่อนข้างเล็กน้อย (เช่นแทนที่จะส่งต่อไปยัง JSP เพียงพิมพ์ JSON หรือ XML หรือข้อความธรรมดาขึ้นอยู่กับสิ่งที่สคริปต์ที่รับผิดชอบในการเรียก Ajax)
หวังว่าทั้งหมดนี้จะช่วย :)