Giới thiệu
Để duyệt và chọn tệp để tải lên, bạn cần có <input type="file">
trường HTML trong biểu mẫu. Như đã nêu trong đặc tả HTML, bạn phải sử dụng POST
phương thức và enctype
thuộc tính của biểu mẫu phải được đặt thành "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>
Sau khi gửi biểu mẫu như vậy, dữ liệu biểu mẫu nhiều phần nhị phân có sẵn trong phần yêu cầu ở định dạng khác với khi enctype
không được đặt.
Trước Servlet 3.0, API Servlet thực sự không hỗ trợ multipart/form-data
. Nó chỉ hỗ trợ các kiểu mã mặc định của application/x-www-form-urlencoded
. Các request.getParameter()
và các phu nhân sẽ trở lại tất cả null
khi sử dụng dữ liệu mẫu nhiều phần dữ liệu. Đây là nơi mà FileUpload nổi tiếng của Apache được đưa vào hình ảnh.
Đừng tự phân tích nó!
Về lý thuyết, bạn có thể phân tích cơ thể yêu cầu dựa trên ServletRequest#getInputStream()
. Tuy nhiên, đây là một công việc chính xác và tẻ nhạt đòi hỏi kiến thức chính xác về RFC2388 . Bạn không nên cố gắng tự làm điều này hoặc sao chép một số mã không có thư viện tại nhà được tìm thấy ở nơi khác trên Internet. Nhiều nguồn trực tuyến đã thất bại nặng nề trong việc này, chẳng hạn như roseindia.net. Xem thêm tải lên tập tin pdf . Bạn nên sử dụng một thư viện thực sự được sử dụng (và được kiểm tra ngầm!) Bởi hàng triệu người dùng trong nhiều năm. Một thư viện như vậy đã chứng minh sự mạnh mẽ của nó.
Khi bạn đã sử dụng Servlet 3.0 trở lên, hãy sử dụng API gốc
Nếu bạn đang sử dụng ít nhất Servlet 3.0 (Tomcat 7, Jetty 9, JBoss AS 6, GlassFish 3, v.v.), thì bạn chỉ có thể sử dụng API tiêu chuẩn được cung cấp HttpServletRequest#getPart()
để thu thập các mục dữ liệu biểu mẫu nhiều phần riêng lẻ (hầu hết các triển khai Servlet 3.0 thực sự sử dụng Apache Commons FileUpload dưới vỏ bọc cho việc này!). Ngoài ra, các trường mẫu thông thường có sẵn theo cách getParameter()
thông thường.
Đầu tiên chú thích servlet của bạn với @MultipartConfig
để cho phép nó nhận ra và hỗ trợ multipart/form-data
các yêu cầu và do đó bắt getPart()
đầu hoạt động:
@WebServlet("/upload")
@MultipartConfig
public class UploadServlet extends HttpServlet {
// ...
}
Sau đó, thực hiện doPost()
như sau:
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)
}
Lưu ý Path#getFileName()
. Đây là một sửa chữa MSIE để có được tên tệp. Trình duyệt này gửi không chính xác đường dẫn tệp đầy đủ dọc theo tên thay vì chỉ tên tệp.
Trong trường hợp bạn có <input type="file" name="file" multiple="true" />
tải lên nhiều tệp, hãy thu thập chúng như dưới đây (không may là không có phương pháp nào như 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)
}
}
Khi bạn chưa có trên Servlet 3.1, hãy lấy tên tệp đã gửi theo cách thủ công
Lưu ý rằng Part#getSubmittedFileName()
đã được giới thiệu trong Servlet 3.1 (Tomcat 8, Jetty 9, WildFly 8, GlassFish 4, v.v.). Nếu bạn chưa có trên Servlet 3.1, thì bạn cần một phương thức tiện ích bổ sung để có được tên tệp đã gửi.
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);
Lưu ý sửa lỗi MSIE để lấy tên tệp. Trình duyệt này gửi không chính xác đường dẫn tệp đầy đủ dọc theo tên thay vì chỉ tên tệp.
Khi bạn chưa có trên Servlet 3.0, hãy sử dụng Apache Commons FileUpload
Nếu bạn chưa có trên Servlet 3.0 (chưa đến lúc phải nâng cấp?), Thói quen phổ biến là sử dụng Apache Commons FileUpload để phân tích các yêu cầu dữ liệu dạng nhiều phần. Nó có Hướng dẫn sử dụng và Câu hỏi thường gặp tuyệt vời (cẩn thận thực hiện cả hai). Ngoài ra còn có O'Reilly (" cos ") MultipartRequest
, nhưng nó có một số lỗi (nhỏ) và không được duy trì tích cực trong nhiều năm. Tôi không khuyên bạn nên sử dụng nó. Apache Commons FileUpload vẫn được duy trì tích cực và hiện đang rất trưởng thành.
Để sử dụng Apache Commons FileUpload, bạn cần có ít nhất các tệp sau trong ứng dụng web của mình /WEB-INF/lib
:
Nỗ lực ban đầu của bạn thất bại rất có thể vì bạn đã quên IO chung.
Đây là một ví dụ khởi đầu về cách doPost()
bạn UploadServlet
có thể trông như thế nào khi sử dụng 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);
}
// ...
}
Nó rất quan trọng là bạn không gọi getParameter()
, getParameterMap()
, getParameterValues()
, getInputStream()
, getReader()
, vv trên cùng một yêu cầu trước đó. Mặt khác, thùng chứa servlet sẽ đọc và phân tích phần thân yêu cầu và do đó Apache Commons FileUpload sẽ nhận được phần thân yêu cầu trống. Xem thêm ao ServletFileUpload # parseRequest (request) trả về một danh sách trống .
Lưu ý FilenameUtils#getName()
. Đây là một sửa chữa MSIE để có được tên tệp. Trình duyệt này gửi không chính xác đường dẫn tệp đầy đủ dọc theo tên thay vì chỉ tên tệp.
Ngoài ra, bạn cũng có thể gói tất cả những thứ này trong Filter
đó phân tích tất cả một cách tự động và đưa nội dung trở lại vào parametermap của yêu cầu để bạn có thể tiếp tục sử dụng cách request.getParameter()
thông thường và truy xuất tệp đã tải lên bằng cách request.getAttribute()
. Bạn có thể tìm thấy một ví dụ trong bài viết blog này .
Giải pháp cho lỗi GlassFish3 getParameter()
vẫn quay trở lạinull
Lưu ý rằng các phiên bản Glassfish cũ hơn 3.1.2 có lỗi trong đó getParameter()
vẫn trả về null
. Nếu bạn đang nhắm mục tiêu một thùng chứa như vậy và không thể nâng cấp nó, thì bạn cần trích xuất giá trị từ getPart()
sự trợ giúp của phương pháp tiện ích này:
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">
Lưu tệp đã tải lên (không sử dụng getRealPath()
cũng không part.write()
!)
Đi đến các câu trả lời sau để biết chi tiết về cách lưu chính xác InputStream
( fileContent
biến như được hiển thị trong đoạn mã trên) vào đĩa hoặc cơ sở dữ liệu:
Phục vụ tập tin tải lên
Đi đến các câu trả lời sau để biết chi tiết về việc phục vụ đúng cách tệp đã lưu từ đĩa hoặc cơ sở dữ liệu trở lại máy khách:
Xác định mẫu
Hướng tới các câu trả lời sau đây về cách tải lên bằng Ajax (và jQuery). Xin lưu ý rằng mã servlet để thu thập dữ liệu biểu mẫu không cần phải thay đổi cho việc này! Chỉ có cách thay đổi cách bạn trả lời, nhưng điều này khá tầm thường (tức là thay vì chuyển tiếp tới JSP, chỉ cần in một số JSON hoặc XML hoặc thậm chí văn bản đơn giản tùy thuộc vào bất kỳ kịch bản nào chịu trách nhiệm cho lệnh gọi Ajax đang mong đợi).
Hy vọng tất cả điều này sẽ giúp :)