บทนำ
ในการเรียกดูและเลือกไฟล์สำหรับการอัปโหลดคุณต้องมี<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)
หวังว่าทั้งหมดนี้จะช่วย :)