java - Cómo devolver una imagen PNG desde el método de servicio REST de Jersey al navegador
return image java (4)
Tengo un servidor web ejecutándose con los recursos de Jersey REST y me pregunto cómo obtener una referencia de imagen / png para la etiqueta de navegación de los navegadores; después de enviar un Formulario u obtener una respuesta Ajax. El código de procesamiento de imágenes para agregar gráficos está funcionando, solo necesita devolverlo de alguna manera.
Código:
@POST
@Path("{fullsize}")
@Consumes(MediaType.MULTIPART_FORM_DATA)
@Produces("image/png")
// Would need to replace void
public void getFullImage(@FormDataParam("photo") InputStream imageIS,
@FormDataParam("submit") String extra) {
BufferedImage image = ImageIO.read(imageIS);
// .... image processing
//.... image processing
return ImageIO. .. ?
}
Aclamaciones
Construí un método general para eso con las siguientes características:
- devolviendo "no modificado" si el archivo no se ha modificado localmente, se envía un Status.NOT_MODIFIED a la persona que llama. Utiliza Apache Commons Lang
- utilizando un objeto de secuencia de archivos en lugar de leer el archivo
Aquí el código:
import org.apache.commons.lang3.time.DateUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
private static final Logger logger = LoggerFactory.getLogger(Utils.class);
@GET
@Path("16x16")
@Produces("image/png")
public Response get16x16PNG(@HeaderParam("If-Modified-Since") String modified) {
File repositoryFile = new File("c:/temp/myfile.png");
return returnFile(repositoryFile, modified);
}
/**
*
* Sends the file if modified and "not modified" if not modified
* future work may put each file with a unique id in a separate folder in tomcat
* * use that static URL for each file
* * if file is modified, URL of file changes
* * -> client always fetches correct file
*
* method header for calling method public Response getXY(@HeaderParam("If-Modified-Since") String modified) {
*
* @param file to send
* @param modified - HeaderField "If-Modified-Since" - may be "null"
* @return Response to be sent to the client
*/
public static Response returnFile(File file, String modified) {
if (!file.exists()) {
return Response.status(Status.NOT_FOUND).build();
}
// do we really need to send the file or can send "not modified"?
if (modified != null) {
Date modifiedDate = null;
// we have to switch the locale to ENGLISH as parseDate parses in the default locale
Locale old = Locale.getDefault();
Locale.setDefault(Locale.ENGLISH);
try {
modifiedDate = DateUtils.parseDate(modified, org.apache.http.impl.cookie.DateUtils.DEFAULT_PATTERNS);
} catch (ParseException e) {
logger.error(e.getMessage(), e);
}
Locale.setDefault(old);
if (modifiedDate != null) {
// modifiedDate does not carry milliseconds, but fileDate does
// therefore we have to do a range-based comparison
// 1000 milliseconds = 1 second
if (file.lastModified()-modifiedDate.getTime() < DateUtils.MILLIS_PER_SECOND) {
return Response.status(Status.NOT_MODIFIED).build();
}
}
}
// we really need to send the file
try {
Date fileDate = new Date(file.lastModified());
return Response.ok(new FileInputStream(file)).lastModified(fileDate).build();
} catch (FileNotFoundException e) {
return Response.status(Status.NOT_FOUND).build();
}
}
/*** copied from org.apache.http.impl.cookie.DateUtils, Apache 2.0 License ***/
/**
* Date format pattern used to parse HTTP date headers in RFC 1123 format.
*/
public static final String PATTERN_RFC1123 = "EEE, dd MMM yyyy HH:mm:ss zzz";
/**
* Date format pattern used to parse HTTP date headers in RFC 1036 format.
*/
public static final String PATTERN_RFC1036 = "EEEE, dd-MMM-yy HH:mm:ss zzz";
/**
* Date format pattern used to parse HTTP date headers in ANSI C
* <code>asctime()</code> format.
*/
public static final String PATTERN_ASCTIME = "EEE MMM d HH:mm:ss yyyy";
public static final String[] DEFAULT_PATTERNS = new String[] {
PATTERN_RFC1036,
PATTERN_RFC1123,
PATTERN_ASCTIME
};
Tenga en cuenta que el cambio de configuración regional no parece ser seguro para subprocesos. Creo que es mejor cambiar la configuración regional globalmente. Aunque no estoy seguro acerca de los efectos secundarios ...
En cuanto a la respuesta de @Perception, es cierto que consume mucha memoria cuando se trabaja con matrices de bytes, pero también se puede escribir en la salida de salida
@Path("/picture")
public class ProfilePicture {
@GET
@Path("/thumbnail")
@Produces("image/png")
public StreamingOutput getThumbNail() {
return new StreamingOutput() {
@Override
public void write(OutputStream os) throws IOException, WebApplicationException {
//... read your stream and write into os
}
};
}
}
No estoy convencido de que sea una buena idea devolver datos de imágenes en un servicio REST. Ata la memoria del servidor de aplicaciones y el ancho de banda de IO. Es mucho mejor delegar esa tarea en un servidor web adecuado que esté optimizado para este tipo de transferencia. Puede lograr esto enviando un redireccionamiento al recurso de imagen (como una respuesta HTTP 302 con el URI de la imagen). Esto supone, por supuesto, que sus imágenes están organizadas como contenido web.
Habiendo dicho eso, si decide que realmente necesita transferir datos de imagen de un servicio web, puede hacerlo con el siguiente (pseudo) código:
@Path("/whatever")
@Produces("image/png")
public Response getFullImage(...) {
BufferedImage image = ...;
ByteArrayOutputStream baos = new ByteArrayOutputStream();
ImageIO.write(image, "png", baos);
byte[] imageData = baos.toByteArray();
// uncomment line below to send non-streamed
// return Response.ok(imageData).build();
// uncomment line below to send streamed
// return Response.ok(new ByteArrayInputStream(imageData)).build();
}
Agregar en manejo de excepciones, etc. etc.
Si tiene varios métodos de recursos de imágenes, vale la pena crear un MessageBodyWriter para generar la imagen Buffered:
@Produces({ "image/png", "image/jpg" })
@Provider
public class BufferedImageBodyWriter implements MessageBodyWriter<BufferedImage> {
@Override
public boolean isWriteable(Class<?> type, Type type1, Annotation[] antns, MediaType mt) {
return type == BufferedImage.class;
}
@Override
public long getSize(BufferedImage t, Class<?> type, Type type1, Annotation[] antns, MediaType mt) {
return -1; // not used in JAX-RS 2
}
@Override
public void writeTo(BufferedImage image, Class<?> type, Type type1, Annotation[] antns, MediaType mt, MultivaluedMap<String, Object> mm, OutputStream out) throws IOException, WebApplicationException {
ImageIO.write(image, mt.getSubtype(), out);
}
}
Este MessageBodyWriter se usará automáticamente si el descubrimiento automático está habilitado para Jersey, de lo contrario debe ser devuelto por una subclase de la Aplicación personalizada. Ver proveedores de entidades JAX-RS para más información.
Una vez que esto esté configurado, simplemente devuelva una Imagen Buffered de un método de recursos y se mostrará como datos de archivos de imagen:
@Path("/whatever")
@Produces({"image/png", "image/jpg"})
public Response getFullImage(...) {
BufferedImage image = ...;
return Response.ok(image).build();
}
Un par de ventajas para este enfoque:
- Escribe en la respuesta OutputSteam en lugar de en un intermediario BufferedOutputStream
- Admite salidas png y jpg (según los tipos de medios permitidos por el método de recursos)