简体   繁体   English

从Play框架2.3.x中的绝对路径提供静态资产

[英]static asset serving from absolute path in play framework 2.3.x

I need to serve image files from an absolute path that is not on the classpath. 我需要从不在classpath上的绝对路径提供图像文件。 When I use Assets.at(path, file) , it only searches inside /assets . 当我使用Assets.at(path, file) ,它仅在/assets内部搜索。 I have mapped the url onto a controller function like the following: 我已经将网址映射到如下所示的控制器函数上:

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

How can I make this work? 我该如何进行这项工作?

NOTE: The reason to make images served using Assets is because of the auto etagging feature that make easy to send http 304 not modified. 注意:之所以要使用素材Assets投放图片,是因为具有自动标记功能,可以轻松发送未修改的http 304。 It seems that there is no auto etagging feature that play provides independently from Assets 似乎没有独立于素材Assets提供的自动标记功能

Assets.at() works only for assets added to the classpath at build-time. Assets.at()仅适用于在构建时添加到类路径的资产。 See: https://www.playframework.com/documentation/2.4.x/Assets 请参阅: https//www.playframework.com/documentation/2.4.x/资产

The solution would be to read the files from the disk as byte[] then return the byte[] in the response body. 解决方案是从磁盘读取文件为byte [],然后在响应正文中返回byte []。

Converting the image to byte[] (this solution is for small files only, for large files look into streams): 将图像转换为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();
        }
    });
}

The controller that uses toBytes() to serve the image: 使用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());
        }
    });
}

The route: 路线:

POST    /img    controllers.YourControllerName.img()


If ETag support is needed: 如果需要ETag支持:

(not adding Date or Last-Modified headers as they are not needed if ETag header is used instead): (不添加Date或Last-Modified头,因为如果使用ETag头则不需要它们):

Get SHA1 for the file: 获取文件的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;
        }
    });
}

Setting the ETag headers: 设置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;
}

Controller with ETag support: 支持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());
        }
    });
}



A few drawbacks(there's always a BUT): 一些缺点(总是有一个缺点):

  1. This is blocking. 这是封锁。 So it's better to execute it on another Akka thread-pool configured for blocking IO. 因此最好在配置为阻止IO的另一个Akka线程池上执行它。
  2. As mentioned, the conversion to byte[] is for small files only, as it uses the memory for buffering. 如前所述,到byte []的转换仅适用于小型文件,因为它使用内存进行缓冲。 This should not be a problem in the case where you only serve small files(think web site grade images). 在仅提供小文件的情况下(考虑网站级图片),这应该不是问题。 See: http://docs.oracle.com/javase/tutorial/essential/io/file.html for different ways to read files using NIO2. 有关使用NIO2读取文件的不同方法,请参见: http ://docs.oracle.com/javase/tutorial/essential/io/file.html。

I've managed to solve this problem in a simpler way: 我设法以一种更简单的方式解决了这个问题:

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

Route example: 路线示例:

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

For large image files, you can use streaming. 对于大图像文件,可以使用流式传输。 Official docs can help you on that way. 官方文档可以通过这种方式为您提供帮助。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM