旅游网站建设水平评价,wordpress migrate.min.js是什么,柳北网站制作,浏览器打不开网页怎么办就HTTP而言#xff0c;客户端下载的只是一堆字节。 但是#xff0c;客户真的很想知道如何解释这些字节。 它是图像吗#xff1f; 或者也许是ZIP文件#xff1f; 本系列的最后一部分描述了如何向客户端提示她下载的内容。 设置 内容类型描述了要返回的资源的MIME类型 。 此… 就HTTP而言客户端下载的只是一堆字节。 但是客户真的很想知道如何解释这些字节。 它是图像吗 或者也许是ZIP文件 本系列的最后一部分描述了如何向客户端提示她下载的内容。 设置 内容类型描述了要返回的资源的MIME类型 。 此标头指示Web浏览器如何处理从下载服务器流出的字节流。 如果没有此标头浏览器将无法得知其实际接收到的内容只会像显示文本文件一样显示内容。 不用说二进制PDF请参见上面的屏幕截图像文本文件一样显示的图像或视频看起来并不好。 最难的部分是以某种方式实际获得媒体类型。 幸运的是Java本身有一个用于根据资源的扩展名和/或内容来猜测媒体类型的工具 import com.google.common.net.MediaType;
import java.io.*;
import java.time.Instant;public class FileSystemPointer implements FilePointer {private final MediaType mediaTypeOrNull;public FileSystemPointer(File target) {final String contentType java.nio.file.Files.probeContentType(target.toPath());this.mediaTypeOrNull contentType ! null ?MediaType.parse(contentType) :null;} 请注意使用OptionalT作为类字段不是惯用的因为它不是可Serializable并且我们避免了潜在的问题。 知道媒体类型后我们必须在响应中返回它。 注意这一小段代码使用了JDK 8和Guava中的Optional 以及Spring框架和Guava中的MediaType类。 多么糟糕的类型系统 private ResponseEntityResource response(FilePointer filePointer, HttpStatus status, Resource body) {final ResponseEntity.BodyBuilder responseBuilder ResponseEntity.status(status).eTag(filePointer.getEtag()).contentLength(filePointer.getSize()).lastModified(filePointer.getLastModified().toEpochMilli());filePointer.getMediaType().map(this::toMediaType).ifPresent(responseBuilder::contentType);return responseBuilder.body(body);
}private MediaType toMediaType(com.google.common.net.MediaType input) {return input.charset().transform(c - new MediaType(input.type(), input.subtype(), c)).or(new MediaType(input.type(), input.subtype()));
}Override
public OptionalMediaType getMediaType() {return Optional.ofNullable(mediaTypeOrNull);
}保留原始文件名和扩展名 当您直接在Web浏览器中打开文档时虽然Content-type效果很好但是可以想象您的用户将该文档存储在磁盘上。 浏览器是决定显示还是存储下载的文件不在本文的讨论范围之内但是我们应该为两者做好准备。 如果浏览器只是将文件存储在磁盘上则必须使用某种名称进行保存。 默认情况下Firefox将使用URL的最后一部分在本例中该部分恰好是资源的UUID。 不太用户友好。 铬是好一点-知道根据MIME类型Content-type报头将试探性地加入适当的扩展名例如.zip中的情况下 application/zip 。 但是文件名仍然是随机的UUID而用户上传的文件可能是cats.zip 。 因此如果您的目标是浏览器而不是自动化客户端则最好使用真实名称作为URL的最后一部分。 我们仍然希望使用UUID在内部区分资源避免冲突并且不公开我们的内部存储结构。 但是在外部我们可以重定向到用户友好的URL但为了安全起见保留UUID。 首先我们需要一个额外的端点 RequestMapping(method {GET, HEAD}, value /{uuid})
public ResponseEntityResource redirect(HttpMethod method,PathVariable UUID uuid,RequestHeader(IF_NONE_MATCH) OptionalString requestEtagOpt,RequestHeader(IF_MODIFIED_SINCE) OptionalDate ifModifiedSinceOpt) {return findExistingFile(method, uuid).map(file - file.redirect(requestEtagOpt, ifModifiedSinceOpt)).orElseGet(() - new ResponseEntity(NOT_FOUND));
}RequestMapping(method {GET, HEAD}, value /{uuid}/{filename})
public ResponseEntityResource download(HttpMethod method,PathVariable UUID uuid,RequestHeader(IF_NONE_MATCH) OptionalString requestEtagOpt,RequestHeader(IF_MODIFIED_SINCE) OptionalDate ifModifiedSinceOpt) {return findExistingFile(method, uuid).map(file - file.handle(requestEtagOpt, ifModifiedSinceOpt)).orElseGet(() - new ResponseEntity(NOT_FOUND));
}private OptionalExistingFile findExistingFile(HttpMethod method, PathVariable UUID uuid) {return storage.findFile(uuid).map(pointer - new ExistingFile(method, pointer, uuid));
} 如果仔细观察甚至没有使用{filename} 它只是浏览器的提示。 如果需要额外的安全性可以将提供的文件名与映射到给定UUID文件名进行比较。 这里真正重要的是仅要求提供UUID重定向我们 $ curl -v localhost:8080/download/4a8883b6-ead6-4b9e-8979-85f9846cab4bGET /download/4a8883b6-ead6-4b9e-8979-85f9846cab4b HTTP/1.1
...HTTP/1.1 301 Moved PermanentlyLocation: /download/4a8883b6-ead6-4b9e-8979-85f9846cab4b/cats.zip 而且您需要进行一次额外的网络行程来获取实际文件 GET /download/4a8883b6-ead6-4b9e-8979-85f9846cab4b/cats.zip HTTP/1.1
...HTTP/1.1 200 OKETag: be20c3b1...fb1a4Last-Modified: Thu, 21 Aug 2014 22:44:37 GMTContent-Type: application/zip;charsetUTF-8Content-Length: 489455 该实现很简单但是为了避免重复对其进行了一些重构 public ResponseEntityResource redirect(OptionalString requestEtagOpt, OptionalDate ifModifiedSinceOpt) {if (cached(requestEtagOpt, ifModifiedSinceOpt))return notModified(filePointer);return redirectDownload(filePointer);
}public ResponseEntityResource handle(OptionalString requestEtagOpt, OptionalDate ifModifiedSinceOpt) {if (cached(requestEtagOpt, ifModifiedSinceOpt))return notModified(filePointer);return serveDownload(filePointer);
}private boolean cached(OptionalString requestEtagOpt, OptionalDate ifModifiedSinceOpt) {final boolean matchingEtag requestEtagOpt.map(filePointer::matchesEtag).orElse(false);final boolean notModifiedSince ifModifiedSinceOpt.map(Date::toInstant).map(filePointer::modifiedAfter).orElse(false);return matchingEtag || notModifiedSince;
}private ResponseEntityResource redirectDownload(FilePointer filePointer) {try {log.trace(Redirecting {} {}, method, filePointer);return ResponseEntity.status(MOVED_PERMANENTLY).location(new URI(/download/ uuid / filePointer.getOriginalName())).body(null);} catch (URISyntaxException e) {throw new IllegalArgumentException(e);}
}private ResponseEntityResource serveDownload(FilePointer filePointer) {log.debug(Serving {} {}, method, filePointer);final InputStreamResource resource resourceToReturn(filePointer);return response(filePointer, OK, resource);
} 您甚至可以进一步使用高阶函数来避免重复 public ResponseEntityResource redirect(OptionalString requestEtagOpt, OptionalDate ifModifiedSinceOpt) {return serveWithCaching(requestEtagOpt, ifModifiedSinceOpt, this::redirectDownload);
}public ResponseEntityResource handle(OptionalString requestEtagOpt, OptionalDate ifModifiedSinceOpt) {return serveWithCaching(requestEtagOpt, ifModifiedSinceOpt, this::serveDownload);
}private ResponseEntityResource serveWithCaching(OptionalString requestEtagOpt, OptionalDate ifModifiedSinceOpt, FunctionFilePointer, ResponseEntityResource notCachedResponse) {if (cached(requestEtagOpt, ifModifiedSinceOpt))return notModified(filePointer);return notCachedResponse.apply(filePointer);
} 显然额外的重定向是每次下载都必须支付的额外费用因此这是一个折衷方案。 您可以考虑基于User-agent启发式如果是浏览器则为重定向如果是自动客户端则为服务器以避免非人工客户端的重定向。 这样就结束了我们有关文件下载的系列文章。 HTTP / 2的出现必将带来更多的改进和技术例如确定优先级。 编写下载服务器 第一部分始终流式传输永远不要完全保留在内存中 第二部分标头Last-ModifiedETag和If-None-Match 第三部分标头内容长度和范围 第四部分有效地实现HEAD操作 第五部分油门下载速度 第六部分描述您发送的内容内容类型等 这些文章中开发的示例应用程序可在GitHub上找到。 翻译自: https://www.javacodegeeks.com/2015/07/writing-a-download-server-part-vi-describe-what-you-send-content-type-et-al.html