【问题标题】:static asset serving from absolute path in play framework 2.3.x从播放框架 2.3.x 中的绝对路径提供静态资产
【发布时间】:2015-03-03 08:50:36
【问题描述】:

我需要从不在类路径中的绝对路径提供图像文件。当我使用Assets.at(path, file) 时,它只在/assets 内部搜索。我已经将 url 映射到一个控制器函数上,如下所示:

public static Action<AnyContent> getImage(String imageId) {
    String path = PICTURE_UPLOAD_DIR; // here this path is absolute
    String file = imageId + ".png";
    return Assets.at(path, file);
}

我怎样才能做到这一点?

注意:使用Assets 提供图像的原因是因为自动标记功能可以轻松发送未修改的 http 304。似乎 play 没有独立于Assets 提供的自动添加标签功能

【问题讨论】:

    标签: java playframework playframework-2.3


    【解决方案1】:

    Assets.at() 仅适用于在构建时添加到类路径的资产。 见:https://www.playframework.com/documentation/2.4.x/Assets

    解决方案是将文件从磁盘读取为 byte[],然后在响应正文中返回 byte[]。

    将图像转换为字节[](此解决方案仅适用于小文件,大文件查看流):

    private static Promise<byte[]> toBytes(final File file) {
        return Promise.promise(new Function0<byte[]>() {
            @Override
            public byte[] apply() throws Throwable {
                byte[] buffer = new byte[1024];
                ByteArrayOutputStream os = new ByteArrayOutputStream();
                FileInputStream is = new FileInputStream(file);
    
                for (int readNum; (readNum = is.read(buffer)) != -1;) {
                    os.write(buffer, 0, readNum);
                }
                return os.toByteArray();
            }
        });
    }
    

    使用 toBytes() 提供图像的控制器:

    public static Promise<Result> img() {
        //path is sent as JSON in request body
        JsonNode path = request().body().asJson();
    
        Logger.debug("path -> " + path.get("path").asText());
        Path p = Paths.get(path.get("path").asText());
        File file = new File(path.get("path").asText());
    
        try {
            response().setHeader("Content-Type", Files.probeContentType(p));
        } catch (IOException e) {
            Logger.error("BUMMER!", e);
            return Promise.promise(new Function0<Result>() {
                @Override
                public Result apply() throws Throwable {
                    return badRequest();
                }
            });
        }
    
        return toBytes(file).map(new Function<byte[], Result>() {
            @Override
            public Result apply(byte[] bytes) throws Throwable {
                return ok(bytes);
            }       
        }).recover(new Function<Throwable, Result>() {
            @Override
            public Result apply(Throwable t) throws Throwable {
                return badRequest(t.getMessage());
            }
        });
    }
    

    路线:

    POST    /img    controllers.YourControllerName.img()
    


    如果需要 ETag 支持:

    (不添加 Date 或 Last-Modified 标头,因为如果使用 ETag 标头,则不需要它们):

    获取文件的 SHA1:

    private static Promise<String> toSHA1(final byte[] bytes) {       
        return Promise.promise(new Function0<String>() {
            @Override
            public String apply() throws Throwable {
                MessageDigest digest = MessageDigest.getInstance("SHA-1");
                byte[] digestResult = digest.digest(bytes);
                String hexResult = "";
                for (int i = 0; i < digestResult.length; i++) {
                    hexResult += Integer.toString(( bytes[i] & 0xff ) + 0x100, 16).substring(1);
                }
                return hexResult;
            }
        });
    }
    

    设置 ETag 标头:

    private static boolean setETagHeaders(String etag, String mime) {
        response().setHeader("Cache-Control", "no-cache");
        response().setHeader("ETag", "\"" + etag + "\"");
        boolean ifNoneMatch = false;
    
        if (request().hasHeader(IF_NONE_MATCH)) {
            String header = request().getHeader(IF_NONE_MATCH);
            //removing ""
            if (!etag.equals(header.substring(1, header.length() - 1))) {
                response().setHeader(CONTENT_TYPE, mime);
            } 
            ifNoneMatch = true;
        } else {
            response().setHeader(CONTENT_TYPE, mime);
        }
        return ifNoneMatch;
    }
    

    支持 ETag 的控制器:

    public static Promise<Result> img() {
        //path is sent as JSON in request body
        JsonNode path = request().body().asJson();
        Logger.debug("path -> " + path.get("path").asText());
        Path p = Paths.get(path.get("path").asText());
        File file = new File(path.get("path").asText());        
        final String mime;
    
        try {
            mime = Files.probeContentType(p);            
        } catch (IOException e) {
            Logger.error("BUMMER!", e);
            return Promise.promise(new Function0<Result>() {
                @Override
                public Result apply() throws Throwable {
                    return badRequest();
                }
            });
        }
        return toBytes(file).flatMap(new Function<byte[], Promise<Result>>() {
            @Override
            public Promise<Result> apply(final byte[] bytes) throws Throwable {
                return toSHA1(bytes).map(new Function<String, Result>() {
                    @Override
                    public Result apply(String sha1) throws Throwable {
                        if (setETagHeaders(sha1, mime)) {
                            return status(304);
                        }
                        return ok(bytes);
                    }
                });
            }
        }).recover(new Function<Throwable, Result>() {
            @Override
            public Result apply(Throwable t) throws Throwable {
                return badRequest(t.getMessage());
            }
        });
    }
    



    一些缺点(总有一个 BUT):

    1. 这是阻塞的。所以最好在另一个配置为阻塞 IO 的 Akka 线程池上执行。
    2. 如前所述,转换为 byte[] 仅适用于小文件,因为它使用内存进行缓冲。如果您只提供小文件(想想网站级图像),这应该不是问题。请参阅:http://docs.oracle.com/javase/tutorial/essential/io/file.html,了解使用 NIO2 读取文件的不同方式。

    【讨论】:

    • 我问这个问题的原因是能够使用不在assets 文件夹内的自动标记静态资产。但是,似乎需要为图像手动生成 etag。那么,你能添加一个解决方案来为静态图像生成 etag 吗?
    • 在这种情况下,请编辑您的问题。我稍后会更新答案。
    • 已更新。供将来参考:尽量避免改变问题。如果发生这种情况,您应该创建一个包含全新问题的单独帖子。
    • 还有一件事:根据我最初的回答,您可以自己轻松实现 ETag 支持。我不是要破坏你的蛋蛋,但不时需要一点努力。
    • 那么,您是否尝试过代码?都好?需要更多说明吗?
    【解决方案2】:

    我已经设法以更简单的方式解决了这个问题:

    public static Result image(String image) {
      String basePath = "/opt/myapp/images";
    
      Path path = Paths.get(basePath + File.separator + image);
      Logger.info("External image::" + path);
      File file = path.toFile();
      if(file.exists()) {
        return ok(file);
      } else {
        String fallbackImage = "/assets/images/myimage.jpg";
        return redirect(fallbackImage);
      }
    }
    

    路线示例:

    GET     /image/:file    controllers.ExternalImagesController.image(file: String)
    

    对于大图像文件,您可以使用流式传输。 官方文档可以在这方面为您提供帮助。

    【讨论】:

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