[英]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()
(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): 一些缺点(总是有一个缺点):
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.