Introdução
Para procurar e selecionar um arquivo para upload, você precisa de um <input type="file">
campo HTML no formulário. Conforme declarado na especificação HTML, você deve usar o POST
método e o enctype
atributo do formulário deve ser definido como "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>
Depois de enviar esse formulário, os dados binários do formulário com várias partes ficam disponíveis no corpo da solicitação em um formato diferente do que quando enctype
não está definido.
Antes do Servlet 3.0, a API do Servlet não suportava nativamente multipart/form-data
. Ele suporta apenas o tipo de formulário padrão de application/x-www-form-urlencoded
. Todos os request.getParameter()
consorts e retornariam null
ao usar dados de formulário com várias partes. Foi aqui que o conhecido Apache Commons FileUpload entrou em cena.
Não o analise manualmente!
Em teoria, você pode analisar o corpo da solicitação com base em ServletRequest#getInputStream()
. No entanto, este é um trabalho preciso e tedioso que requer conhecimento preciso do RFC2388 . Você não deve tentar fazer isso sozinho ou copipar algum código caseiro sem biblioteca encontrado em outro lugar na Internet. Muitas fontes online falharam bastante nisso, como roseindia.net. Veja também o upload do arquivo pdf . Você deveria usar uma biblioteca real que é usada (e implicitamente testada!) Por milhões de usuários por anos. Essa biblioteca provou sua robustez.
Quando você já estiver no Servlet 3.0 ou mais recente, use a API nativa
Se você estiver usando pelo menos o Servlet 3.0 (Tomcat 7, Jetty 9, JBoss AS 6, GlassFish 3, etc.), poderá usar a API padrão fornecida HttpServletRequest#getPart()
para coletar os itens de dados de formulário de várias partes individuais (a maioria das implementações do Servlet 3.0 realmente usa Apache Commons FileUpload sob as capas para isso!). Além disso, os campos normais do formulário estão disponíveis getParameter()
da maneira usual.
Primeiro anote seu servlet @MultipartConfig
para permitir que ele reconheça e multipart/form-data
dê suporte a solicitações e, assim, comece getPart()
a trabalhar:
@WebServlet("/upload")
@MultipartConfig
public class UploadServlet extends HttpServlet {
// ...
}
Em seguida, implemente da doPost()
seguinte maneira:
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)
}
Observe o Path#getFileName()
. Esta é uma correção do MSIE para obter o nome do arquivo. Esse navegador envia incorretamente o caminho completo do arquivo ao longo do nome, em vez de apenas o nome do arquivo.
Caso você tenha um <input type="file" name="file" multiple="true" />
upload para vários arquivos, colete-os como abaixo (infelizmente não existe um método como esse 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)
}
}
Quando você ainda não estiver no Servlet 3.1, obtenha manualmente o nome do arquivo enviado
Observe que Part#getSubmittedFileName()
foi introduzido no Servlet 3.1 (Tomcat 8, Jetty 9, WildFly 8, GlassFish 4, etc.). Se você ainda não estiver no Servlet 3.1, precisará de um método utilitário adicional para obter o nome do arquivo enviado.
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);
Observe a correção do MSIE para obter o nome do arquivo. Esse navegador envia incorretamente o caminho completo do arquivo ao longo do nome, em vez de apenas o nome do arquivo.
Quando você ainda não estiver no Servlet 3.0, use o Apache Commons FileUpload
Se você ainda não está no Servlet 3.0 (não é hora de atualizar?), A prática comum é usar o Apache Commons FileUpload para analisar as solicitações de dados de formulário de várias partes. Possui um excelente Guia do Usuário e Perguntas frequentes (passe cuidadosamente por ambos). Há também o O'Reilly (" cos ") MultipartRequest
, mas possui alguns bugs (menores) e não é mais mantido ativamente por anos. Eu não recomendaria usá-lo. O Apache Commons FileUpload ainda é mantido ativamente e atualmente está muito maduro.
Para usar o Apache Commons FileUpload, você precisa ter pelo menos os seguintes arquivos nos aplicativos da web /WEB-INF/lib
:
Sua tentativa inicial falhou muito provavelmente porque você esqueceu o IO comum.
Aqui está um exemplo de como o doPost()
seu UploadServlet
pode parecer ao usar o 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);
}
// ...
}
É muito importante que você não chamar getParameter()
, getParameterMap()
, getParameterValues()
, getInputStream()
, getReader()
, etc no mesmo pedido de antemão. Caso contrário, o contêiner do servlet lerá e analisará o corpo da solicitação e, portanto, o Apache Commons FileUpload receberá um corpo de solicitação vazio. Consulte também ao ServletFileUpload # parseRequest (request) retorna uma lista vazia .
Observe o FilenameUtils#getName()
. Esta é uma correção do MSIE para obter o nome do arquivo. Esse navegador envia incorretamente o caminho completo do arquivo ao longo do nome, em vez de apenas o nome do arquivo.
Como alternativa, você também pode agrupar tudo em um Filter
que analisa tudo automaticamente e colocar as coisas de volta no mapa de parâmetros da solicitação, para que você possa continuar usando request.getParameter()
a maneira usual e recuperar o arquivo enviado por request.getAttribute()
. Você pode encontrar um exemplo neste artigo do blog .
Solução alternativa para o erro do GlassFish3 de getParameter()
retornarnull
Observe que as versões do Glassfish anteriores à 3.1.2 tinham um erro no qual o getParameter()
still ainda retorna null
. Se você estiver direcionando esse contêiner e não puder atualizá-lo, precisará extrair o valor getPart()
com a ajuda deste método utilitário:
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">
Salvando o arquivo enviado (não use getRealPath()
nem part.write()
!)
Vá para as seguintes respostas para obter detalhes sobre como salvar corretamente o obtido InputStream
(a fileContent
variável mostrada nos trechos de código acima) em disco ou banco de dados:
Servindo arquivo carregado
Vá para as seguintes respostas para obter detalhes sobre como enviar corretamente o arquivo salvo do disco ou banco de dados para o cliente:
Ajaxificando o formulário
Vá para as respostas a seguir como fazer upload usando o Ajax (e jQuery). Observe que o código do servlet para coletar os dados do formulário não precisa ser alterado para isso! Somente a maneira como você responde pode ser alterada, mas isso é bastante trivial (ou seja, em vez de encaminhar para JSP, basta imprimir algum JSON ou XML ou mesmo texto sem formatação, dependendo do que o script responsável pela chamada do Ajax estiver esperando).
Espero que tudo isso ajude :)