编写下载服务器。 第四部分:有效地实现HEAD操作
HEAD是一個(gè)經(jīng)常被遺忘的HTTP方法(動(dòng)詞),其行為類似于GET,但不會(huì)返回正文。 您使用HEAD來(lái)檢查資源的存在(如果不存在,它應(yīng)該返回404),并確保您的緩存中沒(méi)有陳舊的版本。 在這種情況下,您期望304 Not Modified ,而200表示服務(wù)器具有最新版本。 例如,您可以使用HEAD有效地實(shí)施軟件更新。 在這種情況下, ETag是您的應(yīng)用程序版本(生成,標(biāo)記,提交哈希),并且您具有固定的/most_recent端點(diǎn)。 您的軟件會(huì)在ETag發(fā)送具有當(dāng)前版本的HEAD請(qǐng)求。 如果沒(méi)有更新,服務(wù)器將以304答復(fù)。如果為200,則可以詢問(wèn)用戶是否要升級(jí)而無(wú)需下載軟件。 最終請(qǐng)求GET /most_recent將始終下載您軟件的最新版本。 HTTP的力量!
在servlet中,默認(rèn)情況下, HEAD是在doHead() ,您應(yīng)該重寫它。 默認(rèn)實(shí)現(xiàn)只是將委派給GET但會(huì)丟棄主體。 這效率不高,尤其是當(dāng)您從外部(例如Amazon S3)加載資源時(shí)。 幸運(yùn)的是(?)Spring MVC默認(rèn)情況下不實(shí)現(xiàn)HEAD,因此您必須手動(dòng)執(zhí)行。 讓我們從HEAD的一些集成測(cè)試開(kāi)始:
def 'should return 200 OK on HEAD request, but without body'() {expect:mockMvc.perform(head('/download/' + FileExamples.TXT_FILE_UUID)).andExpect(status().isOk()).andExpect(content().bytes(new byte[0])) }def 'should return 304 on HEAD request if we have cached version'() {expect:mockMvc.perform(head('/download/' + FileExamples.TXT_FILE_UUID).header(IF_NONE_MATCH, FileExamples.TXT_FILE.getEtag())).andExpect(status().isNotModified()).andExpect(header().string(ETAG, FileExamples.TXT_FILE.getEtag())) }def 'should return Content-length header'() {expect:mockMvc.perform(head('/download/' + FileExamples.TXT_FILE_UUID)).andExpect(status().isOk()).andExpect(header().longValue(CONTENT_LENGTH, FileExamples.TXT_FILE.size)) }實(shí)際的實(shí)現(xiàn)非常簡(jiǎn)單,但是需要一些重構(gòu)以避免重復(fù)。 下載端點(diǎn)現(xiàn)在接受GET和HEAD:
@RequestMapping(method = {GET, HEAD}, value = "/{uuid}") public ResponseEntity<Resource> download(HttpMethod method,@PathVariable UUID uuid,@RequestHeader(IF_NONE_MATCH) Optional<String> requestEtagOpt,@RequestHeader(IF_MODIFIED_SINCE) Optional<Date> ifModifiedSinceOpt) {return storage.findFile(uuid).map(pointer -> new ExistingFile(method, pointer)).map(file -> file.handle(requestEtagOpt, ifModifiedSinceOpt)).orElseGet(() -> new ResponseEntity<>(NOT_FOUND)); }我創(chuàng)建了一個(gè)新的抽象ExistingFile ,它封裝了我們?cè)谄渖险{(diào)用的找到的FilePointer和HTTP動(dòng)詞。 ExistingFile.handle()具有通過(guò)HEAD提供文件或僅提供元數(shù)據(jù)所需的一切:
public class ExistingFile {private static final Logger log = LoggerFactory.getLogger(ExistingFile.class);private final HttpMethod method;private final FilePointer filePointer;public ExistingFile(HttpMethod method, FilePointer filePointer) {this.method = method;this.filePointer = filePointer;}public ResponseEntity<Resource> handle(Optional<String> requestEtagOpt, Optional<Date> ifModifiedSinceOpt) {if (requestEtagOpt.isPresent()) {final String requestEtag = requestEtagOpt.get();if (filePointer.matchesEtag(requestEtag)) {return notModified(filePointer);}}if (ifModifiedSinceOpt.isPresent()) {final Instant isModifiedSince = ifModifiedSinceOpt.get().toInstant();if (filePointer.modifiedAfter(isModifiedSince)) {return notModified(filePointer);}}return serveDownload(filePointer);}private ResponseEntity<Resource> serveDownload(FilePointer filePointer) {log.debug("Serving {} '{}'", method, filePointer);final InputStreamResource resource = resourceToReturn(filePointer);return response(filePointer, OK, resource);}private InputStreamResource resourceToReturn(FilePointer filePointer) {if (method == HttpMethod.GET)return buildResource(filePointer);elsereturn null;}private InputStreamResource buildResource(FilePointer filePointer) {final InputStream inputStream = filePointer.open();return new InputStreamResource(inputStream);}private ResponseEntity<Resource> notModified(FilePointer filePointer) {log.trace("Cached on client side {}, returning 304", filePointer);return response(filePointer, NOT_MODIFIED, null);}private ResponseEntity<Resource> response(FilePointer filePointer, HttpStatus status, Resource body) {return ResponseEntity.status(status).eTag(filePointer.getEtag()).lastModified(filePointer.getLastModified().toEpochMilli()).body(body);}}resourceToReturn()至關(guān)重要。 如果返回null ,Spring MVC將不包含任何主體作為響應(yīng)。 其他所有內(nèi)容均保持不變(響應(yīng)標(biāo)頭等)
編寫下載服務(wù)器
- 第一部分:始終流式傳輸,永遠(yuǎn)不要完全保留在內(nèi)存中
- 第二部分:標(biāo)頭:Last-Modified,ETag和If-None-Match
- 第三部分:標(biāo)頭:內(nèi)容長(zhǎng)度和范圍
- 第四部分:有效地實(shí)現(xiàn)HEAD操作
- 第五部分:油門下載速度
- 第六部分:描述您發(fā)送的內(nèi)容(內(nèi)容類型等)
- 這些文章中開(kāi)發(fā)的示例應(yīng)用程序可在GitHub上找到。
翻譯自: https://www.javacodegeeks.com/2015/07/writing-a-download-server-part-iv-implement-head-operation-efficiently.html
總結(jié)
以上是生活随笔為你收集整理的编写下载服务器。 第四部分:有效地实现HEAD操作的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: PayPal提现被退回的解决办法?(教程
- 下一篇: 龙芯3A5000LL与i7-10700的