Wprowadzenie
Aby przeglądać i wybrać plik do przesłania, potrzebujesz <input type="file">
pola HTML w formularzu. Jak podano w specyfikacji HTML , musisz użyć POST
metody, a enctype
atrybut formularza musi być ustawiony na "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>
Po przesłaniu takiego formularza binarne dane wieloczęściowego formularza są dostępne w treści żądania w innym formacie niż wtedy, gdy enctype
nie jest ustawiony.
Przed serwletem 3.0 interfejs API serwletów nie obsługiwał natywnie multipart/form-data
. Obsługuje tylko domyślny typ formularza application/x-www-form-urlencoded
. Wszyscy request.getParameter()
i małżonkowie wróciliby, null
gdy użyli danych wieloczęściowych. To tutaj pojawił się dobrze znany plik Apache Commons FileUpload .
Nie parsuj go ręcznie!
Teoretycznie możesz samodzielnie przeanalizować treść żądania ServletRequest#getInputStream()
. Jest to jednak dokładna i żmudna praca, która wymaga dokładnej znajomości RFC2388 . Nie powinieneś próbować tego robić samemu lub kopiować własnego kodu bez biblioteki znalezionego gdzie indziej w Internecie. Wiele źródeł internetowych zawiodło w tym mocno, takich jak roseindia.net. Zobacz także przesyłanie pliku pdf . Powinieneś raczej używać prawdziwej biblioteki, która jest używana (i domyślnie testowana!) Przez miliony użytkowników od lat. Taka biblioteka dowiodła swojej solidności.
Jeśli korzystasz już z Servlet 3.0 lub nowszego, użyj natywnego API
Jeśli używasz co najmniej Servlet 3.0 (Tomcat 7, Jetty 9, JBoss AS 6, GlassFish 3 itp.), Możesz po prostu użyć standardowego interfejsu API podanego HttpServletRequest#getPart()
do zbierania poszczególnych elementów danych formularza wieloczęściowego (większość implementacji Servlet 3.0 faktycznie używa Apache Plik Commons Załaduj pod przykryciem!). Ponadto zwykłe pola formularza są dostępne getParameter()
w zwykły sposób.
Najpierw dodaj adnotację do serwletu @MultipartConfig
, aby umożliwić mu rozpoznawanie i obsługę multipart/form-data
żądań, a tym samym getPart()
rozpoczęcie pracy:
@WebServlet("/upload")
@MultipartConfig
public class UploadServlet extends HttpServlet {
// ...
}
Następnie zaimplementuj go doPost()
w następujący sposób:
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)
}
Uwaga Path#getFileName()
. Jest to poprawka MSIE dotycząca uzyskiwania nazwy pliku. Ta przeglądarka niepoprawnie wysyła pełną ścieżkę pliku wzdłuż nazwy zamiast samej nazwy pliku.
Jeśli masz możliwość <input type="file" name="file" multiple="true" />
przesyłania wielu plików, zbierz je jak poniżej (niestety nie ma takiej metody jak 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)
}
}
Jeśli nie korzystasz jeszcze z serwletu 3.1, ręcznie pobierz przesłaną nazwę pliku
Uwaga: Part#getSubmittedFileName()
została wprowadzona w Servlet 3.1 (Tomcat 8, Jetty 9, WildFly 8, GlassFish 4 itp.). Jeśli nie korzystasz jeszcze z serwletu 3.1, potrzebujesz dodatkowej metody narzędzia, aby uzyskać nazwę przesłanego pliku.
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);
Zwróć uwagę na poprawkę MSIE dotyczącą uzyskiwania nazwy pliku. Ta przeglądarka niepoprawnie wysyła pełną ścieżkę pliku wzdłuż nazwy zamiast samej nazwy pliku.
Jeśli nie korzystasz jeszcze z Servlet 3.0, użyj Apache Commons FileUpload
Jeśli nie korzystasz jeszcze z Servlet 3.0 (czy nie czas na aktualizację?), Powszechną praktyką jest używanie Apache Commons FileUpload do analizowania żądań danych formularza wieloczęściowego. Ma doskonałą instrukcję obsługi i często zadawane pytania (dokładnie przejrzyj oba). Jest też O'Reilly („ cos ”) MultipartRequest
, ale ma kilka (drobnych) błędów i nie jest już aktywnie utrzymywany przez lata. Nie polecałbym tego używać. Plik Apache Commons FileUpload jest nadal aktywnie utrzymywany i obecnie bardzo dojrzały.
Aby korzystać z Apache Commons FileUpload, musisz mieć co najmniej następujące pliki w swojej aplikacji internetowej /WEB-INF/lib
:
Twoja pierwsza próba nie powiodła się najprawdopodobniej, ponieważ zapomniałeś o wspólnym We / Wy.
Oto przykładowy przykład tego, jak może wyglądać doPost()
twój UploadServlet
podczas używania 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);
}
// ...
}
To bardzo ważne, aby nie dzwonić getParameter()
, getParameterMap()
, getParameterValues()
, getInputStream()
, getReader()
, itp na ten sam wniosek wcześniej. W przeciwnym razie kontener serwletu odczyta i przeanalizuje treść żądania, a zatem Apache Commons FileUpload otrzyma pustą treść żądania. Zobacz także ao ServletFileUpload # parseRequest (request) zwraca pustą listę .
Uwaga FilenameUtils#getName()
. Jest to poprawka MSIE dotycząca uzyskiwania nazwy pliku. Ta przeglądarka niepoprawnie wysyła pełną ścieżkę pliku wzdłuż nazwy zamiast samej nazwy pliku.
Alternatywnie możesz również zawinąć to wszystko w Filter
parsowanie wszystkiego w sposób automatyczny i umieścić rzeczy z powrotem w parametryzacji żądania, abyś mógł nadal używać request.getParameter()
zwykłego sposobu i pobierać przesłany plik request.getAttribute()
. Możesz znaleźć przykład w tym artykule na blogu .
Obejście problemu z błędem GlassFish3, który getParameter()
wciąż powracanull
Należy pamiętać, że starsze niż GlassFish wersje 3.1.2 miałem błąd przy czym getParameter()
powraca jeszcze null
. Jeśli celujesz w taki kontener i nie możesz go zaktualizować, musisz wyodrębnić wartość getPart()
za pomocą tej metody narzędzia:
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">
Zapisywanie przesłanego pliku (nie używaj getRealPath()
ani part.write()
!)
Przejdź do poniższych odpowiedzi, aby uzyskać szczegółowe informacje na temat prawidłowego zapisywania uzyskanej InputStream
( fileContent
zmiennej, jak pokazano w powyższych fragmentach kodu) na dysk lub bazę danych:
Udostępnianie przesłanego pliku
Przejdź do następujących odpowiedzi, aby uzyskać szczegółowe informacje na temat prawidłowego udostępniania zapisanego pliku z dysku lub bazy danych z powrotem do klienta:
Ajaxifying the form
Przejdź do poniższych odpowiedzi na temat przesyłania za pomocą Ajax (i jQuery). Należy pamiętać, że kod serwletu do gromadzenia danych formularza nie musi być w tym celu zmieniany! Można zmienić tylko sposób, w jaki reagujesz, ale jest to raczej trywialne (tj. Zamiast przekierowywać do JSP, po prostu wydrukuj trochę JSON lub XML, a nawet zwykły tekst, w zależności od tego, czego oczekuje skrypt odpowiedzialny za wywołanie Ajax).
Mam nadzieję, że to wszystko pomoże :)