Como retornar uma imagem PNG do método de serviço Jersey REST para o navegador

Eu tenho um servidor web rodando com resources Jersey REST e gostaria de saber como obter uma referência image / png para a tag img do navegador; depois de enviar um Formulário ou obter uma resposta do Ajax. O código de image processing para adicionar charts está funcionando, basta devolvê-lo de alguma forma.

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. .. ? } 

Felicidades

Não estou convencido de que seja uma boa idéia retornar os dados da imagem em um serviço REST. Ele amarra a memory do seu servidor de aplicativos e a largura de banda de E / S. É muito melhor delegar essa tarefa a um servidor da Web adequado que seja otimizado para esse tipo de transferência. Você pode fazer isso enviando um redirecionamento para o recurso de imagem (como uma resposta HTTP 302 com o URI da imagem). Isso pressupõe, claro, que suas imagens são organizadas como conteúdo da web.

Dito isto, se você decidir que realmente precisa transferir dados de imagem de um serviço da Web, poderá fazê-lo com o seguinte (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(); } 

Adicionar no tratamento de exceção, etc etc.

Eu construí um método geral para isso com os seguintes resources:

  • retornando “não modificado” se o arquivo não tiver sido modificado localmente, um Status.NOT_MODIFIED será enviado ao chamador. Usa o Apache Commons Lang
  • usando um object de stream de arquivo em vez de ler o arquivo em si

Aqui o 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 * asctime() 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 }; 

Observe que a alternância Locale não parece ser thread-safe. Eu acho que é melhor mudar o local globalmente. Eu não tenho certeza sobre os efeitos colaterais embora …

em relação à resposta de @Perception, é verdade que consome muita memory quando se trabalha com matrizes de bytes, mas você também pode simplesmente escrever de volta no stream de saída.

 @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 } }; } } 

Se você tiver vários methods de recurso de imagem, vale a pena criar um MessageBodyWriter para gerar o BufferedImage:

 @Produces({ "image/png", "image/jpg" }) @Provider public class BufferedImageBodyWriter implements MessageBodyWriter { @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 mm, OutputStream out) throws IOException, WebApplicationException { ImageIO.write(image, mt.getSubtype(), out); } } 

Este MessageBodyWriter será usado automaticamente se a detecção automática estiver habilitada para Jersey, caso contrário, ele precisará ser retornado por uma subclass de Aplicativo personalizada. Veja Provedores de Entidade JAX-RS para mais informações.

Uma vez que isto esteja configurado, simplesmente retorne um BufferedImage de um método de recurso e ele será enviado como dados do arquivo de imagem:

 @Path("/whatever") @Produces({"image/png", "image/jpg"}) public Response getFullImage(...) { BufferedImage image = ...; return Response.ok(image).build(); } 

Algumas vantagens para esta abordagem:

  • Ele grava na resposta OutputSteam em vez de um intermediário BufferedOutputStream
  • Suporta a saída png e jpg (dependendo dos tipos de mídia permitidos pelo método resource)