Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to return a PNG image from Jersey REST service method to the browser

I have a web server running with Jersey REST resources up and I wonder how to get an image/png reference for the browsers img tag; after submitting a Form or getting an Ajax response. The image processing code for adding graphics is working, just need to return it somehow.

Code:

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

Cheers

like image 912
gorn Avatar asked Feb 09 '12 01:02

gorn


2 Answers

I'm not convinced its a good idea to return image data in a REST service. It ties up your application server's memory and IO bandwidth. Much better to delegate that task to a proper web server that is optimized for this kind of transfer. You can accomplish this by sending a redirect to the image resource (as a HTTP 302 response with the URI of the image). This assumes of course that your images are arranged as web content.

Having said that, if you decide you really need to transfer image data from a web service you can do so with the following (pseudo) code:

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

Add in exception handling, etc etc.

like image 182
Perception Avatar answered Sep 29 '22 01:09

Perception


I built a general method for that with following features:

  • returning "not modified" if the file hasn't been modified locally, a Status.NOT_MODIFIED is sent to the caller. Uses Apache Commons Lang
  • using a file stream object instead of reading the file itself

Here the code:

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

Note that the Locale switching does not seem to be thread-safe. I think, it's better to switch the locale globally. I am not sure about the side-effects though...

like image 42
koppor Avatar answered Sep 29 '22 00:09

koppor