Contenido
- Arquitectura del proceso
- Video
- El Servlet
- Variables del Servlet
- Método doPost()
- Método insertar_actualizarBestia()
- Método subirFoto()
- Qué ocurre paso a paso
- Formulario HTML necesario
- Mostrar la imagen guardada
- Métodos utilizados
- Buenas prácticas
- Tabla de métodos/clases usados
- Validaciones en la subida de imágenes (métodos independientes)
En esta práctica aprenderás a:
- Enviar un archivo desde un formulario HTML hacia un Servlet.
- Procesar peticiones
multipart/form-data. - Guardar una imagen en el servidor.
- Obtener el nombre del archivo subido.
- Persistir la referencia en base de datos.
- Entender el flujo completo cliente → servidor → disco → BD → vista.
Arquitectura del proceso
El flujo que vamos a implementar es el siguiente:
[Formulario HTML]
↓
POST multipart/form-data
↓
[Servlet controlador]
↓
Procesa campos + fichero
↓
Guarda imagen en disco
↓
Inserta datos en BD (nombre del archivo)
↓
Redirección a galería
↓
[Vista HTML muestra la imagen]
Video
El Servlet
Anotaciones necesarias
@WebServlet("/BestiasFoto")
@MultipartConfig
public class BestiasFoto extends HttpServlet {
@WebServlet
Define la URL que ejecuta el servlet:
http://servidor/app/BestiasFoto
@MultipartConfig
Activa el soporte de subida de archivos.
Sin esta anotación:
request.getPart("foto")no funcionará- El servidor no podrá procesar
multipart/form-data
Variables del Servlet
private String pathFiles = "C:\\entornos\\clasesMaster\\BestiarioJEE\\bestiario\\src\\main\\webapp\\img\\";
private File uploads = new File(pathFiles);
pathFiles→ carpeta donde se guardarán las imágenesuploads→ objetoFileque representa esa carpeta
En esta práctica guardamos las imágenes dentro del proyecto. En producción normalmente se usa una carpeta externa.
Método doPost()
protected void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
if(request.getParameter("opcion").equals("1")) {
insertar_actualizarBestia(request,response);
} else if(request.getParameter("opcion").equals("2")) {
listarBestias(Integer.parseInt(request.getParameter("id")),request,response);
}
}
Este método actúa como enrutador.
| opcion | Acción |
|---|---|
| 1 | Inserta o actualiza una bestia + sube imagen |
| 2 | Listado de bestias |
Método insertar_actualizarBestia()
Este método:
- Recoge los datos del formulario
- Sube la imagen
- Inserta en BD
- Redirige a la galería
public void insertar_actualizarBestia(HttpServletRequest request, HttpServletResponse response)
throws IOException, ServletException {
int id = Integer.parseInt(request.getParameter("id"));
String nombre = request.getParameter("nombre");
int tam = Integer.parseInt(request.getParameter("tam"));
int alineamiento = Integer.parseInt(request.getParameter("alineamiento"));
String descripcion = request.getParameter("descripcion");
String ruta = this.subirFoto(request, response);
if(id != 0) {
// actualizar
} else {
Bestia b = new Bestia(nombre, tam, alineamiento, descripcion, ruta);
try {
b.insertar();
} catch (SQLException e) {
e.printStackTrace();
}
response.sendRedirect("galeria.html");
}
}
Explicación
getParameter()→ obtiene datos normales del formulariosubirFoto()→ guarda la imagen y devuelve el nombre del archivosendRedirect()→ evita reenviar el formulario al refrescar
Método subirFoto()
Este método recibe el archivo, lo guarda en el servidor y devuelve el nombre.
private String subirFoto(HttpServletRequest request, HttpServletResponse response)
throws IOException, ServletException {
Part part = request.getPart("foto");
Path path = Paths.get(part.getSubmittedFileName());
String fileName = path.getFileName().toString();
InputStream input = part.getInputStream();
String ruta ="";
String nombreArchivo ="";
if(input != null) {
File file = new File(uploads, fileName);
ruta = file.getAbsolutePath();
nombreArchivo = file.getName();
System.out.println("La ruta de la foto es: " + ruta);
try {
Files.copy(input, file.toPath());
} catch (Exception e) {
PrintWriter respuesta = response.getWriter();
respuesta.print("Error al copiar la foto");
System.out.println("Error al copiar la foto");
}
}
return nombreArchivo;
}
Qué ocurre paso a paso
1. Obtener el archivo enviado
Part part = request.getPart("foto");
Part representa el archivo enviado desde el formulario.
2. Obtener el nombre del archivo
Path path = Paths.get(part.getSubmittedFileName());
String fileName = path.getFileName().toString();
Se usa Path para eliminar posibles rutas y quedarse solo con el nombre.
3. Obtener los bytes del archivo
InputStream input = part.getInputStream();
Un InputStream permite leer el contenido del archivo.
4. Crear el archivo destino
File file = new File(uploads, fileName);
Esto crea el archivo dentro de la carpeta img.
5. Copiar el archivo
Files.copy(input, file.toPath());
Copia los bytes desde el InputStream al fichero del servidor.
6. Devolver el nombre del archivo
returnnombreArchivo;
En la base de datos solo guardamos el nombre, no la ruta completa.
Formulario HTML necesario
<form action="BestiasFoto" method="post" enctype="multipart/form-data">
<input type="hidden" name="opcion" value="1">
<input type="hidden" name="id" value="0">
<input type="text" name="nombre" placeholder="Nombre">
<input type="number" name="tam" placeholder="Tamaño">
<input type="number" name="alineamiento" placeholder="Alineamiento">
<textarea name="descripcion"></textarea>
<input type="file" name="foto" accept="image/*">
<button type="submit">Guardar</button>
</form>
Mostrar la imagen guardada
Si en BD se guarda dragon.jpg:
<img src="img/dragon.jpg">
Métodos utilizados
HttpServletRequest
| Método | Función |
|---|---|
| getParameter() | Obtiene valores del formulario |
| getPart() | Obtiene el archivo subido |
Part
| Método | Función |
|---|---|
| getSubmittedFileName() | Nombre original del archivo |
| getInputStream() | Bytes del archivo |
| getSize() | Tamaño del archivo |
| getContentType() | Tipo MIME |
Files / Path
| Método | Función |
|---|---|
| Files.copy() | Copia el archivo al servidor |
| Paths.get() | Crea un Path |
| getFileName() | Obtiene el nombre del archivo |
Buenas prácticas
- Guardar solo el nombre del archivo en BD
- Validar que el archivo no esté vacío
- Validar tipo MIME (image/png, image/jpeg…)
- Evitar nombres duplicados
- Crear la carpeta si no existe
Tabla de métodos/clases usados
Servlet / Request / Response
| Elemento | Método | ¿Para qué sirve? |
|---|---|---|
HttpServletRequest | getParameter("x") | Lee un campo normal del formulario (texto, hidden, number…) |
HttpServletRequest | getPart("foto") | Recupera un archivo (o parte) del multipart con ese name |
HttpServletResponse | sendRedirect("url") | Redirige al navegador a otra página tras el POST |
HttpServletResponse | getWriter() | Permite escribir una respuesta de texto al cliente |
Multipart / Part
| Elemento | Método | ¿Para qué sirve? |
|---|---|---|
Part | getSubmittedFileName() | Devuelve el nombre original del fichero enviado |
Part | getInputStream() | Devuelve un stream con los bytes del archivo |
Part | getSize() | Tamaño del archivo en bytes (útil para validar) |
Part | getContentType() | Tipo MIME (ej. image/png) |
Files / Path
| Elemento | Método | ¿Para qué sirve? |
|---|---|---|
Paths | get(String) | Crea un Path a partir de una ruta o nombre |
Path | getFileName() | Devuelve el último segmento (nombre) |
Files | copy(InputStream, Path, ...) | Copia datos de un stream a un fichero |
File | getAbsolutePath() | Ruta absoluta en disco |
File | getName() | Nombre del archivo sin ruta |
Validaciones en la subida de imágenes (métodos independientes)
- Validar el nombre del archivo
- Evitar archivos duplicados
- Validar tipo MIME
- Validar extensión
- Validar tamaño
- Validar que el archivo sea realmente una imagen
- Generar nombre seguro (UUID)
Cada método tiene una responsabilidad única.
1. Validar nombre de archivo
Evitar nombres peligrosos, rutas maliciosas o caracteres inválidos.
Método
private String validarNombreArchivo(String originalName) throws ServletException {
if (originalName == null || originalName.isBlank()) {
throw new ServletException("El archivo no tiene nombre válido");
}
// eliminar rutas potenciales
String nombre = Paths.get(originalName).getFileName().toString();
// permitir solo letras, números, punto, guion y guion bajo
if (!nombre.matches("[a-zA-Z0-9._-]+")) {
throw new ServletException("Nombre de archivo inválido");
}
return nombre;
}
Qué hace
- Evita nombres vacíos
- Elimina rutas (
C:\algo\malicioso\file.jpg) - Bloquea caracteres peligrosos
- Reduce riesgo de path traversal
2. Validar tipo MIME
Evitar que suban archivos ejecutables disfrazados de imagen.
Método
private void validarTipoMime(Part part) throws ServletException {
String mime = part.getContentType();
if (mime == null || !mime.startsWith("image/")) {
throw new ServletException("Solo se permiten archivos de imagen");
}
}
Qué hace
- Comprueba que el archivo es
image/* - Bloquea
.exe,.jsp,.php, etc.
3. Validar extensión
Permitir solo formatos conocidos.
Método
private void validarExtension(String fileName) throws ServletException {
String ext = "";
int punto = fileName.lastIndexOf('.');
if (punto >= 0) {
ext = fileName.substring(punto + 1).toLowerCase();
}
switch (ext) {
case "jpg":
case "jpeg":
case "png":
case "gif":
case "webp":
return;
default:
throw new ServletException("Extensión no permitida");
}
}
Qué hace
- Extrae la extensión
- Permite solo imágenes conocidas
- Evita subir scripts renombrados
4. Validar tamaño del archivo
Evitar archivos demasiado grandes.
Método
private void validarTamano(Part part, long maxBytes) throws ServletException {
if (part.getSize() == 0) {
throw new ServletException("El archivo está vacío");
}
if (part.getSize() > maxBytes) {
throw new ServletException("El archivo supera el tamaño permitido");
}
}
Uso típico
validarTamano(part, 2*1024*1024); // 2 MB
5. Validar que realmente sea imagen
Evitar archivos falsos con extensión .jpg pero que no son imagen.
Método
private void validarContenidoImagen(InputStream input) throws ServletException, IOException {
BufferedImage img = ImageIO.read(input);
if (img == null) {
throw new ServletException("El archivo no es una imagen válida");
}
}
Qué hace
- Intenta decodificar la imagen
- Si falla → no es imagen real
6. Evitar archivos duplicados
No sobrescribir archivos existentes.
Método
private File evitarDuplicado(File carpeta, String fileName) {
File file = new File(carpeta, fileName);
if (!file.exists()) {
return file;
}
String nombre = fileName;
String base = nombre;
String ext = "";
int punto = nombre.lastIndexOf('.');
if (punto >= 0) {
base = nombre.substring(0, punto);
ext = nombre.substring(punto);
}
int contador = 1;
File nuevo;
do {
nuevo = new File(carpeta, base + "_" + contador + ext);
contador++;
} while (nuevo.exists());
return nuevo;
}
Qué hace
Si ya existe:
dragon.jpg
Genera:
dragon_1.jpg
dragon_2.jpg
dragon_3.jpg
7. Generar nombre seguro (UUID)
Esto penalizaria el SEO si se trata de archivos que los buscadores pudiesen indexar
Evitar colisiones y nombres maliciosos.
Método
private String generarNombreSeguro(String original) {
String ext = "";
int punto = original.lastIndexOf('.');
if (punto >= 0) {
ext = original.substring(punto);
}
return java.util.UUID.randomUUID().toString() + ext;
}
Qué hace
Convierte:
dragon.png
en algo como:
f47ac10b-58cc-4372-a567-0e02b2c3d479.png
8. Crear carpeta si no existe
private void asegurarCarpeta(File carpeta) throws ServletException {
if (!carpeta.exists()) {
if (!carpeta.mkdirs()) {
throw new ServletException("No se pudo crear la carpeta de subida");
}
}
}
9. Ejemplo integrando todas las validaciones
Este sería el flujo dentro de subirFoto():
Part part = request.getPart("foto");
validarTipoMime(part);
validarTamano(part, 2 * 1024 * 1024);
String nombreOriginal = validarNombreArchivo(part.getSubmittedFileName());
validarExtension(nombreOriginal);
InputStream input = part.getInputStream();
validarContenidoImagen(input);
// resetear stream tras validación
input = part.getInputStream();
asegurarCarpeta(uploads);
String nombreSeguro = generarNombreSeguro(nombreOriginal);
File destino = evitarDuplicado(uploads, nombreSeguro);
Files.copy(input, destino.toPath());
return destino.getName();
Subir un archivo parece trivial, pero en realidad es una intersección entre E/S, seguridad, validación, arquitectura y persistencia. Cuando el alumno domina esto, deja de “subir fotos” y empieza a construir backend serio.




