Java 如何将PNG图像从Jersey REST服务方法返回到浏览器

Java 如何将PNG图像从Jersey REST服务方法返回到浏览器,java,image,glassfish,jersey,javax.imageio,Java,Image,Glassfish,Jersey,Javax.imageio,我有一个运行Jersey REST资源的web服务器,我想知道如何获取浏览器img标记的图像/png参考;提交表单或获得Ajax响应后。用于添加图形的图像处理代码正在运行,只需以某种方式返回它 代码: 干杯我不认为在REST服务中返回图像数据是个好主意。它会占用应用服务器的内存和IO带宽。最好将该任务委托给适当的web服务器,该服务器针对此类传输进行了优化。您可以通过向映像资源发送重定向(作为带有映像URI的HTTP 302响应)来实现这一点。当然,这假设您的图像被安排为web内容 话虽如此,如

我有一个运行Jersey REST资源的web服务器,我想知道如何获取浏览器img标记的图像/png参考;提交表单或获得Ajax响应后。用于添加图形的图像处理代码正在运行,只需以某种方式返回它

代码:


干杯

我不认为在REST服务中返回图像数据是个好主意。它会占用应用服务器的内存和IO带宽。最好将该任务委托给适当的web服务器,该服务器针对此类传输进行了优化。您可以通过向映像资源发送重定向(作为带有映像URI的HTTP 302响应)来实现这一点。当然,这假设您的图像被安排为web内容

话虽如此,如果您决定确实需要从web服务传输图像数据,可以使用以下(伪)代码:


插件异常处理等。

我为此构建了一个通用方法,具有以下功能:

  • 返回“未修改”如果文件未在本地修改,则会向调用者发送Status.not_modified。使用
  • 使用文件流对象而不是读取文件本身
代码如下:

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

请注意,区域设置切换似乎不是线程安全的。我认为,最好在全球范围内切换区域设置。我不确定副作用是什么…

关于@Perception的回答,在使用字节数组时确实会消耗大量内存,但您也可以简单地写回outputstream

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

如果您有许多图像资源方法,那么创建MessageBodyWriter以输出BuffereImage是非常值得的:

@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);
  } 
}
这种方法有几个优点:

  • 它写入响应OutputSteam,而不是中间缓冲输出流
  • 它支持png和jpg输出(取决于资源方法允许的媒体类型)

您想实现什么目标?您不能通过发送带有图像位置的URI来实现这一点吗?我希望用户在下订单之前预览照片上选定的图形。我现在明白了,这不能用AJAX来完成,将需要请求网页,正如你所说的指向处理过的图像。谢谢!这是一种方法。最终得到一个PHP服务器应用程序,该应用程序使用cURL从这个RESTful Java web服务获取图像,并在HTML图像标记中指向它们。@gorn您应该在回答中以edit@Perception如果您能够按照您在应用程序中的建议提供代码来完成您的答案,那就太好了在开始的段落中,如果有人(说我呵呵)被你的论点说服了,这可能会很有用。要降低你的带宽开关,你可以在响应中添加CacheControl
CacheControll cc=new CacheControl();抄送setMaxAge(编号);响应(..).cacheControl(cc.build()。您可以使用Jersey的Request.evaluatePreconditions(…)删除许多上次修改的逻辑,因为它将处理解析和检查日期以及ETag(如果您支持的话)。
@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
      }
    };
  }
}
@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);
  } 
}
@Path("/whatever")
@Produces({"image/png", "image/jpg"})
public Response getFullImage(...) {
  BufferedImage image = ...;
  return Response.ok(image).build();
}