回覆列表
  • 1 # 使用者4038238679905

    之前看過apach及nginx對於靜態資源(含js,圖片,css等)部分的快取,用於加速並減輕後臺實際web伺服器的壓力。 靜態資源快取是WEB伺服器最佳化的一種手段,基本原理如下: 1.客戶端瀏覽器請求伺服器一個服務(該服務含有圖片,js等靜態資源),通常會對於每一個網頁中的獨立圖片或js檔案傳送一個http請求 2.WEB伺服器對於每個資源HTTP請求進行解析,並生成一個資源修改時間的唯一值(可以是etag或last_modified引數),放入伺服器端map,key為資源url,value為資源修改時間。最後將此資源修改時間的唯一值包含在http頭上返回,因為是首次請求,所以會將所有內容放在http body中一併返回給客戶瀏覽器端 3.客戶瀏覽器接收服伺服器響應,並將伺服器返回的資源修改時間作為key放入瀏覽器客戶端,value為http body中的實際資源內容 4.客戶瀏覽器再次請求靜態資源時,會將資源修改時間一併傳送給伺服器 5.服務端會從最新的map中取出該資源url對應的修改時間,如果值晚於客戶端請求的資源修改時間,這時會返回最新的已經修改過的資源給客戶端。否則返回304 not modifed 這裡記錄資源修改時間的方式有etag及last_modified。最先有的是last_modified,它的工作方式就是上述介紹的,但缺點是隻能精確到秒級別。也就是說當你在一秒中修改資源兩次,而客戶端拿到的是第一次修改,那之後就算客戶端第二次再次請求也不會拿到最新的資源。 而etag的出現正是為了解決last_modified的秒級問題,於http 1.1被提出。 今天測試了下,在沒有nginx等前端反向代理伺服器時,tomcat竟然預設對靜態資源做了快取。 tomcat預設運用etag及last_modifed。etag與if_no_match(客戶端瀏覽器上傳時在http head中應該放的屬性名)一起使用,last_modified與If-Modified-Since一起使用。 客戶端首次請求時,得到請求響應資料如下: GET http://localhost:8080/webTest/jsp/index.jsp [HTTP/1.1 200 OK 1ms] GET http://localhost:8080/webTest/js/hello.js [HTTP/1.1 200 OK 1ms] GET http://localhost:8080/webTest/img/a.jpg [HTTP/1.1 200 OK 2ms] 我們看一下Hello.js這個請求響應具體資訊: server Apache-Coyote/1.1 (表明伺服器是tomcat) Last-Modified: Sun, 11 May 2014 10:54:33 GMT Etag: W/"175-1399805673000" Date: Sun, 11 May 2014 10:59:23 GMT Content-Type: application/javascript;charset=UTF-8 Content-Length: 175 Accept-Ranges: bytes 從上面可以看到tomcat即返回了last_modified也返回了etag。 客戶端再次請求時,請求資料如下: If-None-Match: W/"175-1399805673000" If-Modified-Since: Sun, 11 May 2014 10:54:33 GMT 響應如下: GET http://localhost:8080/webTest/jsp/index.jsp [HTTP/1.1 200 OK 1ms] GET http://localhost:8080/webTest/js/hello.js [HTTP/1.1 304 Not Modified 1ms] GET http://localhost:8080/webTest/img/a.jpg [HTTP/1.1 304 Not Modified 1ms] 從中我們可以看到tomcat對於靜態資料作了快取。 接著我們分析tomcat對於這部分靜態快取的判斷處理,這部分邏輯是寫在DefaultServlet類中, 我們可以在doGet方法中進入ServiceContext方法中找到以下原始碼: // Check if the conditions specified in the optional If headers are // satisfied. if (cacheEntry.context == null) { // Checking If headers boolean included = (request.getAttribute(Globals.INCLUDE_CONTEXT_PATH_ATTR) != null); if (!included && !checkIfHeaders(request, response, cacheEntry.attributes)) { //這句判斷是否需要返回整個資源請求 return; } } 上面原始碼的 if (!included && !checkIfHeaders(request, response, cacheEntry.attributes)) 用於判斷是否需要返回整個資源,如果indcluded與checkIfHeaders方法返回的都是false,這時就直接返回,說明資源未修改,或者是快取不支援的請求方式。 我們接著檢視checkIfHeaders方法: /** * Check if the conditions specified in the optional If headers are * satisfied. * * @param request The servlet request we are processing * @param response The servlet response we are creating * @param resourceAttributes The resource information * @return boolean true if the resource meets all the specified conditions, * and false if any of the conditions is not satisfied, in which case * request processing is stopped */ protected boolean checkIfHeaders(HttpServletRequest request, HttpServletResponse response, ResourceAttributes resourceAttributes) throws IOException { return checkIfMatch(request, response, resourceAttributes) && checkIfModifiedSince(request, response, resourceAttributes) && checkIfNoneMatch(request, response, resourceAttributes) && checkIfUnmodifiedSince(request, response, resourceAttributes); } 可以看到tomcat只有當這四個屬性全部返回true(也就是說全部認為資源已經改變)才會返回true,這樣最終會將整個資源(最新修改過的)返回客戶端。 在這裡,我們從上面實際過程當中看到,瀏覽器第二次請求資源時在http請求header中放了 If-None-Match: W/"175-1399805673000" If-Modified-Since: Sun, 11 May 2014 10:54:33 GMT 這兩個屬性。 因此我們檢視 && checkIfModifiedSince(request, response, resourceAttributes) && checkIfNoneMatch(request, response, resourceAttributes) 這兩個方法 checkIfModifiedSince原始碼如下: /** * Check if the if-modified-since condition is satisfied. * * @param request The servlet request we are processing * @param response The servlet response we are creating * @param resourceInfo File object * @return boolean true if the resource meets the specified condition, * and false if the condition is not satisfied, in which case request * processing is stopped */ protected boolean checkIfModifiedSince(HttpServletRequest request, HttpServletResponse response, ResourceAttributes resourceAttributes) { try { long headerValue = request.getDateHeader("If-Modified-Since"); long lastModified = resourceAttributes.getLastModified(); if (headerValue != -1) { // If an If-None-Match header has been specified, if modified since // is ignored. if ((request.getHeader("If-None-Match") == null) && (lastModified < headerValue + 1000)) { // The entity has not been modified since the date // specified by the client. This is not an error case. response.setStatus(HttpServletResponse.SC_NOT_MODIFIED); response.setHeader("ETag", resourceAttributes.getETag()); return false; } } } catch (IllegalArgumentException illegalArgument) { return true; } return true; } 原始碼中可以看到: if ((request.getHeader("If-None-Match") == null) && (lastModified < headerValue + 1000)) { 這句話表明只有在客戶端瀏覽器傳送的請求頭中不包含If-None-Match,IfModifiedSince才會生效。 我們接著看checkIfNoneMatch,原始碼如下: /** * Check if the if-none-match condition is satisfied. * * @param request The servlet request we are processing * @param response The servlet response we are creating * @param resourceInfo File object * @return boolean true if the resource meets the specified condition, * and false if the condition is not satisfied, in which case request * processing is stopped */ protected boolean checkIfNoneMatch(HttpServletRequest request, HttpServletResponse response, ResourceAttributes resourceAttributes) throws IOException { String eTag = resourceAttributes.getETag(); String headerValue = request.getHeader("If-None-Match"); if (headerValue != null) { boolean conditionSatisfied = false; if (!headerValue.equals("*")) { StringTokenizer commaTokenizer = new StringTokenizer(headerValue, ","); while (!conditionSatisfied && commaTokenizer.hasMoreTokens()) { String currentToken = commaTokenizer.nextToken(); if (currentToken.trim().equals(eTag)) conditionSatisfied = true; } } else { conditionSatisfied = true; } if (conditionSatisfied) { // For GET and HEAD, we should respond with // 304 Not Modified. // For every other method, 412 Precondition Failed is sent // back. if ( ("GET".equals(request.getMethod())) || ("HEAD".equals(request.getMethod())) ) { response.setStatus(HttpServletResponse.SC_NOT_MODIFIED); response.setHeader("ETag", eTag); return false; } response.sendError(HttpServletResponse.SC_PRECONDITION_FAILED); return false; } } return true; } 這裡: String eTag = resourceAttributes.getETag(); String headerValue = request.getHeader("If-None-Match"); 這兩句比較簡單,就是分別從伺服器快取和http請求頭中中取出etag。 接著判斷這兩個etag如果相等,則conditionSatisfied為true,會執行到以下語句: if (conditionSatisfied) { // For GET and HEAD, we should respond with // 304 Not Modified. // For every other method, 412 Precondition Failed is sent // back. if ( ("GET".equals(request.getMethod())) || ("HEAD".equals(request.getMethod())) ) { response.setStatus(HttpServletResponse.SC_NOT_MODIFIED); response.setHeader("ETag", eTag); return false; } response.sendError(HttpServletResponse.SC_PRECONDITION_FAILED); return false; } 這段語句中可以發現,如果資源未改變的情況下,並且請求方式為GET或者HEAD時,會返回304狀態碼。否則返回一個412狀態碼,同樣不會返回資源內容。 如果上述最終 if ((request.getHeader("If-None-Match") == null) && (lastModified < headerValue + 1000)) 條件不成立,即資源更新了或者是第一次請求,這裡會讀取當前請求資原始檔,並最終放入http響應中。

  • 中秋節和大豐收的關聯?
  • 男孩青春期發育半年能長高15釐米嗎?