1 /* 2 * Licensed to the Apache Software Foundation (ASF) under one or more 3 * contributor license agreements. See the NOTICE file distributed with 4 * this work for additional information regarding copyright ownership. 5 * The ASF licenses this file to You under the Apache License, Version 2.0 6 * (the "License"); you may not use this file except in compliance with 7 * the License. You may obtain a copy of the License at 8 * 9 * http://www.apache.org/licenses/LICENSE-2.0 10 * 11 * Unless required by applicable law or agreed to in writing, software 12 * distributed under the License is distributed on an "AS IS" BASIS, 13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 * See the License for the specific language governing permissions and 15 * limitations under the License. 16 * 17 */ 18 19 // For unit tests @see TestCookieManager 20 21 package org.apache.jmeter.protocol.http.control; 22 23 import java.io.Serializable; 24 import java.net.HttpURLConnection; 25 import java.net.URL; 26 import java.net.URLConnection; 27 import java.util.HashMap; 28 import java.util.Map; 29 30 import org.apache.commons.httpclient.Header; 31 import org.apache.commons.httpclient.HttpMethod; 32 import org.apache.commons.httpclient.URIException; 33 import org.apache.jmeter.config.ConfigTestElement; 34 import org.apache.jmeter.engine.event.LoopIterationEvent; 35 import org.apache.jmeter.protocol.http.util.HTTPConstantsInterface; 36 import org.apache.jmeter.samplers.SampleResult; 37 import org.apache.jmeter.testelement.TestListener; 38 import org.apache.jmeter.testelement.property.BooleanProperty; 39 import org.apache.jorphan.logging.LoggingManager; 40 import org.apache.log.Logger; 41 42 /** 43 * Handles HTTP Caching 44 */ 45 public class CacheManager extends ConfigTestElement implements TestListener, Serializable { 46 47 private static final long serialVersionUID = 233L; 48 49 private static final Logger log = LoggingManager.getLoggerForClass(); 50 51 public static final String CLEAR = "clearEachIteration"; // $NON-NLS-1$ 52 53 private transient ThreadLocal threadCache; 54 55 public CacheManager() { 56 setProperty(new BooleanProperty(CLEAR, false)); 57 clearCache(); 58 } 59 60 /* 61 * Holder for storing cache details. 62 * Perhaps add original response later? 63 */ 64 private static class CacheEntry{ 65 private final String lastModified; 66 private final String etag; 67 public CacheEntry(String lastModified, String etag){ 68 this.lastModified = lastModified; 69 this.etag = etag; 70 } 71 public String getLastModified() { 72 return lastModified; 73 } 74 public String getEtag() { 75 return etag; 76 } 77 public String toString(){ 78 return lastModified+" "+etag; 79 } 80 } 81 82 /** 83 * Save the Last-Modified and Etag headers if the result is cacheable. 84 * 85 * @param conn connection 86 * @param res result 87 */ 88 public void saveDetails(URLConnection conn, SampleResult res){ 89 if (isCacheable(res)){ 90 String lastModified = conn.getHeaderField(HTTPConstantsInterface.LAST_MODIFIED); 91 String etag = conn.getHeaderField(HTTPConstantsInterface.ETAG); 92 String url = conn.getURL().toString(); 93 setCache(lastModified, etag, url); 94 } 95 } 96 97 /** 98 * Save the Last-Modified and Etag headers if the result is cacheable. 99 * 100 * @param method 101 * @param res result 102 */ 103 public void saveDetails(HttpMethod method, SampleResult res) throws URIException{ 104 if (isCacheable(res)){ 105 String lastModified = getHeader(method ,HTTPConstantsInterface.LAST_MODIFIED); 106 String etag = getHeader(method ,HTTPConstantsInterface.ETAG); 107 String url = method.getURI().toString(); 108 setCache(lastModified, etag, url); 109 } 110 } 111 112 // helper method to save the cache entry 113 private void setCache(String lastModified, String etag, String url) { 114 if (log.isDebugEnabled()){ 115 log.debug("SET(both) "+url + " " + lastModified + " " + etag); 116 } 117 getCache().put(url, new CacheEntry(lastModified, etag)); 118 } 119 120 // Helper method to deal with missing headers 121 private String getHeader(HttpMethod method, String name){ 122 Header hdr = method.getResponseHeader(name); 123 return hdr != null ? hdr.getValue() : null; 124 } 125 126 /* 127 * Is the sample result OK to cache? 128 * i.e is it in the 2xx range? 129 */ 130 private boolean isCacheable(SampleResult res){ 131 final String responseCode = res.getResponseCode(); 132 return "200".compareTo(responseCode) <= 0 // $NON-NLS-1$ 133 && "299".compareTo(responseCode) >= 0; // $NON-NLS-1$ 134 } 135 136 /** 137 * Check the cache, and if there is a match, set the headers:<br/> 138 * If-Modified-Since<br/> 139 * If-None-Match<br/> 140 * @param url URL to look up in cache 141 * @param method where to set the headers 142 */ 143 public void setHeaders(URL url, HttpMethod method) { 144 CacheEntry entry = (CacheEntry) getCache().get(url.toString()); 145 if (log.isDebugEnabled()){ 146 log.debug(method.getName()+"(OAHC) "+url.toString()+" "+entry); 147 } 148 if (entry != null){ 149 final String lastModified = entry.getLastModified(); 150 if (lastModified != null){ 151 method.setRequestHeader(HTTPConstantsInterface.IF_MODIFIED_SINCE, lastModified); 152 } 153 final String etag = entry.getEtag(); 154 if (etag != null){ 155 method.setRequestHeader(HTTPConstantsInterface.IF_NONE_MATCH, etag); 156 } 157 } 158 } 159 160 /** 161 * Check the cache, and if there is a match, set the headers:<br/> 162 * If-Modified-Since<br/> 163 * If-None-Match<br/> 164 * @param url URL to look up in cache 165 * @param conn where to set the headers 166 */ 167 public void setHeaders(HttpURLConnection conn, URL url) { 168 CacheEntry entry = (CacheEntry) getCache().get(url.toString()); 169 if (log.isDebugEnabled()){ 170 log.debug(conn.getRequestMethod()+"(Java) "+url.toString()+" "+entry); 171 } 172 if (entry != null){ 173 final String lastModified = entry.getLastModified(); 174 if (lastModified != null){ 175 conn.addRequestProperty(HTTPConstantsInterface.IF_MODIFIED_SINCE, lastModified); 176 } 177 final String etag = entry.getEtag(); 178 if (etag != null){ 179 conn.addRequestProperty(HTTPConstantsInterface.IF_NONE_MATCH, etag); 180 } 181 } 182 } 183 184 private Map getCache(){ 185 return (Map) threadCache.get(); 186 } 187 188 public boolean getClearEachIteration() { 189 return getPropertyAsBoolean(CLEAR); 190 } 191 192 public void setClearEachIteration(boolean clear) { 193 setProperty(new BooleanProperty(CLEAR, clear)); 194 } 195 196 public void clear(){ 197 super.clear(); 198 clearCache(); 199 } 200 201 private void clearCache() { 202 log.debug("Clear cache"); 203 threadCache = new ThreadLocal(){ 204 protected Object initialValue(){ 205 return new HashMap(); 206 } 207 }; 208 } 209 210 public void testStarted() { 211 } 212 213 public void testEnded() { 214 } 215 216 public void testStarted(String host) { 217 } 218 219 public void testEnded(String host) { 220 } 221 222 public void testIterationStart(LoopIterationEvent event) { 223 if (getClearEachIteration()) { 224 clearCache(); 225 } 226 } 227 }