【问题标题】:How to return a PNG image from Jersey REST service method to the browser如何从 Jersey REST 服务方法返回 PNG 图像到浏览器
【发布时间】:2012-02-09 01:38:45
【问题描述】:

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

代码:

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

}

干杯

【问题讨论】:

  • 你想完成什么?您不能通过发送带有图像位置的 URI 来实现这一点吗?
  • 我希望用户在下订单之前预览照片上选定的图形。我现在看到这不能通过 AJAX 帖子完成,需要按照您所说的那样请求网页,指向已处理的图像。

标签: java image glassfish jersey javax.imageio


【解决方案1】:

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

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

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

添加异常处理等

【讨论】:

  • 谢谢!这是一种方法。
  • 最终得到一个 PHP 服务器应用程序,该应用程序使用 cURL 从这个 RESTful Java Web 服务获取图像并在 HTML 图像标记中指向它们。
  • @gorn 你应该在你的答案中写下你的解决方案作为编辑
  • @Perception 如果您可以通过提供您在开头段落中建议的代码来完成您的答案,那就太好了,如果有人(比如说我呵呵)被您的论点说服,这可能会很有用。
  • 要降低您的带宽,您可以将 CacheControl 添加到响应 CacheControll cc = new CacheControl(); cc.setMaxAge(number); Response(..).cacheControl(cc).build();
【解决方案2】:

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

  • 如果文件没有在本地修改,则返回“未修改”,Status.NOT_MODIFIED 被发送给调用者。使用Apache Commons Lang
  • 使用文件流对象而不是读取文件本身

代码如下:

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

请注意,区域设置切换似乎不是线程安全的。我认为,最好在全球范围内切换语言环境。不过我不确定副作用...

【讨论】:

  • 您可以使用 Jersey 的 Request.evaluatePreconditions(...) 删除许多上次修改的逻辑,因为它会处理解析和检查日期,如果您支持它们,还可以处理 etags。
【解决方案3】:

关于@Perception 的回答,在使用字节数组时确实非常消耗内存,但您也可以简单地写回输出流

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

【讨论】:

    【解决方案4】:

    如果您有多个图像资源方法,那么值得创建一个MessageBodyWriter 来输出BufferedImage

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

    如果为 Jersey 启用了自动发现,则此 MessageBodyWriter 将自动使用,否则需要由自定义应用程序子类返回。请参阅JAX-RS Entity Providers 了解更多信息。

    设置完成后,只需从资源方法返回一个BufferedImage,它将作为图像文件数据输出:

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

    这种方法的几个优点:

    • 它写入响应 OutputSteam 而不是中介 BufferedOutputStream
    • 它同时支持pngjpg输出(取决于资源方法允许的媒体类型)

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2015-10-24
      • 2016-02-03
      • 1970-01-01
      • 1970-01-01
      • 2012-09-03
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多