| 68 | 1 /* | 
|  | 2 Copyright (c) 2008  Franklin Schmidt <fschmidt@gmail.com> | 
|  | 3 | 
|  | 4 Permission is hereby granted, free of charge, to any person obtaining a copy | 
|  | 5 of this software and associated documentation files (the "Software"), to deal | 
|  | 6 in the Software without restriction, including without limitation the rights | 
|  | 7 to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | 
|  | 8 copies of the Software, and to permit persons to whom the Software is | 
|  | 9 furnished to do so, subject to the following conditions: | 
|  | 10 | 
|  | 11 The above copyright notice and this permission notice shall be included in | 
|  | 12 all copies or substantial portions of the Software. | 
|  | 13 | 
|  | 14 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | 
|  | 15 IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | 
|  | 16 FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | 
|  | 17 AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | 
|  | 18 LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | 
|  | 19 OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN | 
|  | 20 THE SOFTWARE. | 
|  | 21 */ | 
|  | 22 | 
|  | 23 package fschmidt.util.servlet; | 
|  | 24 | 
|  | 25 import fschmidt.util.java.ProcUtils; | 
|  | 26 import fschmidt.util.java.SimpleClassLoader; | 
|  | 27 import org.slf4j.Logger; | 
|  | 28 import org.slf4j.LoggerFactory; | 
|  | 29 | 
|  | 30 import javax.servlet.ServletContext; | 
|  | 31 import javax.servlet.ServletException; | 
|  | 32 import javax.servlet.ServletOutputStream; | 
|  | 33 import javax.servlet.http.HttpServlet; | 
|  | 34 import javax.servlet.http.HttpServletRequest; | 
|  | 35 import javax.servlet.http.HttpServletResponse; | 
|  | 36 import javax.servlet.http.HttpServletResponseWrapper; | 
|  | 37 import java.io.File; | 
|  | 38 import java.io.IOException; | 
|  | 39 import java.io.PrintWriter; | 
|  | 40 import java.net.URL; | 
|  | 41 import java.util.Arrays; | 
|  | 42 import java.util.Collection; | 
|  | 43 import java.util.Collections; | 
|  | 44 import java.util.HashMap; | 
|  | 45 import java.util.HashSet; | 
|  | 46 import java.util.LinkedHashMap; | 
|  | 47 import java.util.LinkedList; | 
|  | 48 import java.util.Map; | 
|  | 49 import java.util.Set; | 
|  | 50 | 
|  | 51 | 
|  | 52 public final class JtpContextServlet extends HttpServlet implements JtpContext { | 
|  | 53 	private static final Logger logger = LoggerFactory.getLogger(JtpContextServlet.class); | 
|  | 54 | 
|  | 55 	private static final Set<String> allowedMethods = new HashSet<String>(Arrays.asList( | 
|  | 56 		"GET", "POST", "HEAD" | 
|  | 57 	)); | 
|  | 58 	private String base; | 
|  | 59 	private boolean reload = false; | 
|  | 60 	private boolean recompile = false; | 
|  | 61 	private SimpleClassLoader.Filter filter = null; | 
|  | 62 	private ClassLoader cl = null; | 
|  | 63 	private Map<String,HttpServlet> map = new HashMap<String,HttpServlet>(); | 
|  | 64 	private long clTime; | 
|  | 65 	private Object lock = new Object(); | 
|  | 66 	private HttpCache httpCache; | 
|  | 67 	private boolean isCaching; | 
|  | 68 	private String characterEncoding; | 
|  | 69 	private Map<String, String> customHeaders = new HashMap<String, String>(); | 
|  | 70 	private UrlMapper urlMapper = new UrlMapper() { | 
|  | 71 		public UrlMapping getUrlMapping(HttpServletRequest request) { | 
|  | 72 			return null; | 
|  | 73 		} | 
|  | 74 	}; | 
|  | 75 	private Set<String> errorCache = null; | 
|  | 76 	private Collection<String> ipList = null; | 
|  | 77 	private static final String authKeyAttr = "authKey"; | 
|  | 78 	private static final String[] noModifyingEvents = new String[]{"_"}; | 
|  | 79 | 
|  | 80 	public void setUrlMapper(UrlMapper urlMapper) { | 
|  | 81 		this.urlMapper = urlMapper; | 
|  | 82 	} | 
|  | 83 | 
|  | 84 	public HttpCache getHttpCache() { | 
|  | 85 		return httpCache; | 
|  | 86 	} | 
|  | 87 | 
|  | 88 	public void setHttpCache(HttpCache httpCache) { | 
|  | 89 		this.httpCache = httpCache; | 
|  | 90 	} | 
|  | 91 | 
|  | 92 	public void addCustomHeader(String key, String value) { | 
|  | 93 		this.customHeaders.put(key, value); | 
|  | 94 	} | 
|  | 95 | 
|  | 96 	public void unloadServlets() { | 
|  | 97 		if( !reload ) | 
|  | 98 			throw new UnsupportedOperationException("'reload' must be set"); | 
|  | 99 		synchronized(lock) { | 
|  | 100 			cl = new SimpleClassLoader(filter); | 
|  | 101 			map = new HashMap<String,HttpServlet>(); | 
|  | 102 			clTime = System.currentTimeMillis(); | 
|  | 103 		} | 
|  | 104 	} | 
|  | 105 | 
|  | 106 	public void setBase(String base) { | 
|  | 107 		if( base==null ) | 
|  | 108 			throw new NullPointerException(); | 
|  | 109 		this.base = base; | 
|  | 110 	} | 
|  | 111 | 
|  | 112 	public void init() | 
|  | 113 		throws ServletException | 
|  | 114 	{ | 
|  | 115 		ServletContext context = getServletContext(); | 
|  | 116 		String newBase = getInitParameter("base"); | 
|  | 117 		if( newBase != null ) | 
|  | 118 			setBase(newBase); | 
|  | 119 		recompile = Boolean.valueOf(getInitParameter("recompile")); | 
|  | 120 		reload = recompile || Boolean.valueOf(getInitParameter("reload")); | 
|  | 121 		if( reload ) { | 
|  | 122 			filter = new SimpleClassLoader.Filter(){ | 
|  | 123 				final String s = base + "."; | 
|  | 124 				public boolean load(String className)  { | 
|  | 125 					return className.startsWith(s); | 
|  | 126 				} | 
|  | 127 			}; | 
|  | 128 			unloadServlets(); | 
|  | 129 		} | 
|  | 130 		context.setAttribute(JtpContext.attrName,this); | 
|  | 131 		String servletS = getInitParameter("servlet"); | 
|  | 132 		if( servletS != null ) { | 
|  | 133 			throw new RuntimeException("the 'servlet' init parameter is no longer supported"); | 
|  | 134 		} | 
|  | 135 		isCaching = "true".equalsIgnoreCase(getInitParameter("cache")); | 
|  | 136 		if( isCaching ) { | 
|  | 137 			if( httpCache==null ) { | 
|  | 138 				logger.error("can't set init parameter 'cache' to true without httpCache"); | 
|  | 139 				System.exit(-1); | 
|  | 140 			} | 
|  | 141 			logger.info("cache"); | 
|  | 142 		} | 
|  | 143 		characterEncoding = getInitParameter("characterEncoding"); | 
|  | 144 		{ | 
|  | 145 			String s = getInitParameter("timeLimit"); | 
|  | 146 			if( s != null ) | 
|  | 147 				timeLimit = Long.parseLong(s); | 
|  | 148 		} | 
|  | 149 		{ | 
|  | 150 			String s = getInitParameter("errorCacheSize"); | 
|  | 151 			if( s != null ) { | 
|  | 152 				final int errorCacheSize = Integer.parseInt(s); | 
|  | 153 				errorCache = Collections.synchronizedSet(Collections.newSetFromMap(new LinkedHashMap<String,Boolean>(){ | 
|  | 154 					protected boolean removeEldestEntry(Map.Entry eldest) { | 
|  | 155 						return size() > errorCacheSize; | 
|  | 156 					} | 
|  | 157 				})); | 
|  | 158 			} | 
|  | 159 		} | 
|  | 160 		{ | 
|  | 161 			String s = getInitParameter("ipListSize"); | 
|  | 162 			if( s != null ) { | 
|  | 163 				final int ipListSize = Integer.parseInt(s); | 
|  | 164 				ipList = Collections.synchronizedList(new LinkedList<String>() { | 
|  | 165 					public boolean add(String s) { | 
|  | 166 						if( contains(s) ) | 
|  | 167 							return false; | 
|  | 168 						super.add(s); | 
|  | 169 						if( size() > ipListSize ) | 
|  | 170 							removeFirst(); | 
|  | 171 						return true; | 
|  | 172 					} | 
|  | 173 				}); | 
|  | 174 			} | 
|  | 175 		} | 
|  | 176 	} | 
|  | 177 | 
|  | 178 	private boolean isInErrorCache(String s) { | 
|  | 179 		return errorCache==null || !errorCache.add(s); | 
|  | 180 	} | 
|  | 181 | 
|  | 182 	private boolean isInIpList(String ip) { | 
|  | 183 		return ipList!=null && !ipList.add(ip); | 
|  | 184 	} | 
|  | 185 | 
|  | 186 	public static interface DestroyListener { | 
|  | 187 		public void destroyed(); | 
|  | 188 	} | 
|  | 189 | 
|  | 190 	private DestroyListener destroyListener = null; | 
|  | 191 | 
|  | 192 	public void addDestroyListener(DestroyListener dl) { | 
|  | 193 		synchronized(lock) { | 
|  | 194 			if( destroyListener!=null ) | 
|  | 195 				throw new RuntimeException("only one DestroyListener allowed"); | 
|  | 196 			destroyListener = dl; | 
|  | 197 		} | 
|  | 198 	} | 
|  | 199 | 
|  | 200 	public void destroy() { | 
|  | 201 		synchronized(lock) { | 
|  | 202 			if( destroyListener != null ) | 
|  | 203 				destroyListener.destroyed(); | 
|  | 204 		} | 
|  | 205 	} | 
|  | 206 | 
|  | 207 	public static final class RequestAndResponse { | 
|  | 208 		public final HttpServletRequest request; | 
|  | 209 		public final HttpServletResponse response; | 
|  | 210 | 
|  | 211 		public RequestAndResponse(HttpServletRequest request,HttpServletResponse response) { | 
|  | 212 			this.request = request; | 
|  | 213 			this.response = response; | 
|  | 214 		} | 
|  | 215 	} | 
|  | 216 | 
|  | 217 	public static interface CustomWrappers { | 
|  | 218 		public RequestAndResponse wrap(HttpServletRequest request, HttpServletResponse response); | 
|  | 219 	} | 
|  | 220 | 
|  | 221 	private CustomWrappers customWrappers; | 
|  | 222 | 
|  | 223 	public void setCustomWrappers(CustomWrappers customWrappers) { | 
|  | 224 		this.customWrappers = customWrappers; | 
|  | 225 	} | 
|  | 226 | 
|  | 227 	private static String hideNull(String s) { | 
|  | 228 		return s==null ? "" : s; | 
|  | 229 	} | 
|  | 230 | 
|  | 231 	private String getServletPath(HttpServletRequest request) { | 
|  | 232 		return request.getServletPath() + hideNull(request.getPathInfo()); | 
|  | 233 	} | 
|  | 234 | 
|  | 235 	protected void service(HttpServletRequest request,HttpServletResponse response) | 
|  | 236 		throws ServletException, IOException | 
|  | 237 	{ | 
|  | 238 		final TimeLimit tl = startTimeLimit(request); | 
|  | 239 		response = new HttpServletResponseWrapper(response) { | 
|  | 240 			PrintWriter writer = null; | 
|  | 241 			ServletOutputStream out = null; | 
|  | 242 | 
|  | 243 			public PrintWriter getWriter() | 
|  | 244 				throws java.io.IOException | 
|  | 245 			{ | 
|  | 246 				if( writer==null ) { | 
|  | 247 					writer = new PrintWriter(super.getWriter()) { | 
|  | 248 						public void write(String s,int off,int len) { | 
|  | 249 							long t = System.currentTimeMillis(); | 
|  | 250 							super.write(s,off,len); | 
|  | 251 							tl.ioTime += System.currentTimeMillis() - t; | 
|  | 252 						} | 
|  | 253 						public void write(char[] buf,int off,int len) { | 
|  | 254 							long t = System.currentTimeMillis(); | 
|  | 255 							super.write(buf,off,len); | 
|  | 256 							tl.ioTime += System.currentTimeMillis() - t; | 
|  | 257 						} | 
|  | 258 						public void write(int c) { | 
|  | 259 							long t = System.currentTimeMillis(); | 
|  | 260 							super.write(c); | 
|  | 261 							tl.ioTime += System.currentTimeMillis() - t; | 
|  | 262 						} | 
|  | 263 						public void flush() { | 
|  | 264 							long t = System.currentTimeMillis(); | 
|  | 265 							super.flush(); | 
|  | 266 							tl.ioTime += System.currentTimeMillis() - t; | 
|  | 267 						} | 
|  | 268 						public void println() { | 
|  | 269 							long t = System.currentTimeMillis(); | 
|  | 270 							super.println(); | 
|  | 271 							tl.ioTime += System.currentTimeMillis() - t; | 
|  | 272 						} | 
|  | 273 					}; | 
|  | 274 				} | 
|  | 275 				return writer; | 
|  | 276 			} | 
|  | 277 | 
|  | 278 			public ServletOutputStream getOutputStream() | 
|  | 279 				throws java.io.IOException | 
|  | 280 			{ | 
|  | 281 				if( out==null ) { | 
|  | 282 					final ServletOutputStream sos = super.getOutputStream(); | 
|  | 283 					out = new ServletOutputStream() { | 
|  | 284 						public void write(byte[] b,int off,int len) throws IOException { | 
|  | 285 							long t = System.currentTimeMillis(); | 
|  | 286 							sos.write(b,off,len); | 
|  | 287 							tl.ioTime += System.currentTimeMillis() - t; | 
|  | 288 						} | 
|  | 289 						public void write(byte[] b) throws IOException { | 
|  | 290 							long t = System.currentTimeMillis(); | 
|  | 291 							sos.write(b); | 
|  | 292 							tl.ioTime += System.currentTimeMillis() - t; | 
|  | 293 						} | 
|  | 294 						public void write(int c) throws IOException { | 
|  | 295 							long t = System.currentTimeMillis(); | 
|  | 296 							sos.write(c); | 
|  | 297 							tl.ioTime += System.currentTimeMillis() - t; | 
|  | 298 						} | 
|  | 299 						public void flush() throws IOException { | 
|  | 300 							long t = System.currentTimeMillis(); | 
|  | 301 							sos.flush(); | 
|  | 302 							tl.ioTime += System.currentTimeMillis() - t; | 
|  | 303 						} | 
|  | 304 					}; | 
|  | 305 				} | 
|  | 306 				return out; | 
|  | 307 			} | 
|  | 308 | 
|  | 309 			public void sendError(int sc) throws IOException { | 
|  | 310 				long t = System.currentTimeMillis(); | 
|  | 311 				super.sendError(sc); | 
|  | 312 				tl.ioTime += System.currentTimeMillis() - t; | 
|  | 313 			} | 
|  | 314 | 
|  | 315 			public void sendRedirect(String location) throws IOException { | 
|  | 316 				if( containsHeader("Expires") ) | 
|  | 317 					setHeader("Expires",null); | 
|  | 318 				if( containsHeader("Last-Modified") ) | 
|  | 319 					setHeader("Last-Modified",null); | 
|  | 320 				if( containsHeader("Etag") ) | 
|  | 321 					setHeader("Etag",null); | 
|  | 322 				if( containsHeader("Cache-Control") ) | 
|  | 323 					setHeader("Cache-Control",null); | 
|  | 324 				if( containsHeader("Content-Type") ) | 
|  | 325 					setHeader("Content-Type",null); | 
|  | 326 				if( containsHeader("Content-Length") ) | 
|  | 327 //					setHeader("Content-Length",null); | 
|  | 328 					setContentLength(0); | 
|  | 329 				super.sendRedirect(location); | 
|  | 330 			} | 
|  | 331 		}; | 
|  | 332 		service2(request,response); | 
|  | 333 		checkTimeLimit(request); | 
|  | 334 	} | 
|  | 335 | 
|  | 336 	private void service2(HttpServletRequest request, HttpServletResponse response) | 
|  | 337 		throws ServletException, IOException | 
|  | 338 	{ | 
|  | 339 		if( !allowedMethods.contains(request.getMethod()) ) { | 
|  | 340 			response.sendError(HttpServletResponse.SC_METHOD_NOT_ALLOWED); | 
|  | 341 			return; | 
|  | 342 		} | 
|  | 343 //		String contextPath = request.getContextPath(); | 
|  | 344 //		String contextUrl = ServletUtils.getContextURL(request); | 
|  | 345 | 
|  | 346 		// First we set the character encoding because any manipulation | 
|  | 347 		// of request parameters will break without this. | 
|  | 348 		response.setHeader("Content-Type","text/html; charset=utf-8");  // default, servlet can override | 
|  | 349 		if( characterEncoding != null ) { | 
|  | 350 			response.setCharacterEncoding(characterEncoding); | 
|  | 351 			request.setCharacterEncoding(characterEncoding); | 
|  | 352 		} | 
|  | 353 | 
|  | 354 		HttpServlet servlet; | 
|  | 355 		String path = getServletPath(request); | 
|  | 356 | 
|  | 357 		UrlMapping urlMapping = urlMapper.getUrlMapping(request); | 
|  | 358 		if( urlMapping != null ) { | 
|  | 359 			try { | 
|  | 360 				servlet = getServletFromClass(urlMapping.servletClass.getName()); | 
|  | 361 			} catch(ClassNotFoundException e) { | 
|  | 362 				throw new RuntimeException(e); | 
|  | 363 			} | 
|  | 364 			final Map params = urlMapping.parameterMap; | 
|  | 365 			request = new BetterRequestWrapper(request) { | 
|  | 366 				public Map getParameterMap() { | 
|  | 367 					return params; | 
|  | 368 				} | 
|  | 369 			}; | 
|  | 370 		} else { | 
|  | 371 			try { | 
|  | 372 				servlet = getServlet(path); | 
|  | 373 			} catch(ClassNotFoundException e) { | 
|  | 374 				response.sendError(HttpServletResponse.SC_NOT_FOUND); | 
|  | 375 				String agent = request.getHeader("user-agent"); | 
|  | 376 				String referer = request.getHeader("referer"); | 
|  | 377 				String remote = request.getRemoteAddr(); | 
|  | 378 				String msg = request.getRequestURL()+" referer="+referer+" user-agent="+agent+" remote="+remote; | 
|  | 379 				if( referer==null ) { | 
|  | 380 					logger.info(msg,e); | 
|  | 381 				} else { | 
|  | 382 					logger.warn(msg,e); | 
|  | 383 				} | 
|  | 384 				return; | 
|  | 385 			} | 
|  | 386 		} | 
|  | 387 | 
|  | 388 		// Custom headers | 
|  | 389 		addCustomHeaders(response); | 
|  | 390 | 
|  | 391 		AuthorizingServlet auth = servlet instanceof AuthorizingServlet ? (AuthorizingServlet)servlet : null; | 
|  | 392 		if( isCaching ) { | 
|  | 393 			String etagS = request.getHeader("If-None-Match"); | 
|  | 394 			if( etagS != null ) { | 
|  | 395 				String prevEtag = null; | 
|  | 396 				for( String etag : etagS.split("\\s*,\\s*") ) { | 
|  | 397 					if( etag.equals(prevEtag) ) | 
|  | 398 						continue; | 
|  | 399 					prevEtag = etag; | 
|  | 400 					if( etag.length()>=2 && etag.charAt(0)=='"' && etag.charAt(etag.length()-1)=='"' ) | 
|  | 401 						etag = etag.substring(1,etag.length()-1); | 
|  | 402 					String authKey = null; | 
|  | 403 					if( etag.length()>=2 && etag.charAt(0)=='[' ) { | 
|  | 404 						int i = etag.indexOf(']'); | 
|  | 405 						if( i > 0 ) { | 
|  | 406 							if( auth != null ) | 
|  | 407 								authKey = etag.substring(1,i); | 
|  | 408 							etag = etag.substring(i+1); | 
|  | 409 						} | 
|  | 410 					} | 
|  | 411 					String[] events = etag.split("~"); | 
|  | 412 					long lastModified = getLastModified(events); | 
|  | 413 					try { | 
|  | 414 						if( lastModified <= request.getDateHeader("If-Modified-Since") ) { | 
|  | 415 							if( authKey==null || authorize(auth,authKey,request,response) ) | 
|  | 416 								response.sendError(HttpServletResponse.SC_NOT_MODIFIED); | 
|  | 417 							return; | 
|  | 418 						} | 
|  | 419 					} catch(RuntimeException e) { | 
|  | 420 						handleException(request,e); | 
|  | 421 					} | 
|  | 422 				} | 
|  | 423 			} | 
|  | 424 		} | 
|  | 425 		String authKey = auth==null ? null : getAuthorizationKey(auth,request); | 
|  | 426 		if( authKey != null ) { | 
|  | 427 			if( !authorize(auth,authKey,request,response) ) | 
|  | 428 				return; | 
|  | 429 			request.setAttribute(authKeyAttr,authKey); | 
|  | 430 		} | 
|  | 431 | 
|  | 432 		if( servlet instanceof CanonicalUrl ) { | 
|  | 433 			CanonicalUrl srv = (CanonicalUrl)servlet; | 
|  | 434 			StringBuffer currentUrl = request.getRequestURL(); | 
|  | 435 			int i = currentUrl.indexOf(";"); | 
|  | 436 			if( i != -1 ) | 
|  | 437 				currentUrl.setLength(i); | 
|  | 438 			String query = request.getQueryString(); | 
|  | 439 			if( query != null ) | 
|  | 440 				currentUrl.append( '?' ).append( query ); | 
|  | 441 			try { | 
|  | 442 				String canonicalUrl = srv.getCanonicalUrl(request); | 
|  | 443 				if( canonicalUrl != null && !currentUrl.toString().equals(canonicalUrl) ) { | 
|  | 444 					response.setHeader("Location",canonicalUrl); | 
|  | 445 					response.sendError( HttpServletResponse.SC_MOVED_PERMANENTLY ); | 
|  | 446 					return; | 
|  | 447 				} | 
|  | 448 			} catch(RuntimeException e) { | 
|  | 449 				logger.warn("couldn't get canonical url",e); | 
|  | 450 			} | 
|  | 451 		} | 
|  | 452 | 
|  | 453 		request.setAttribute("servlet",servlet); | 
|  | 454 | 
|  | 455 		try { | 
|  | 456 			if (customWrappers != null) { | 
|  | 457 				RequestAndResponse rr = customWrappers.wrap(request, response); | 
|  | 458 				request = rr.request; | 
|  | 459 				response = rr.response; | 
|  | 460 			} | 
|  | 461 			servlet.service(request,response); | 
|  | 462 		} catch(RuntimeException e) { | 
|  | 463 			handleException(request,e); | 
|  | 464 		} catch(ServletException e) { | 
|  | 465 			handleException(request,e); | 
|  | 466 		} | 
|  | 467 	} | 
|  | 468 | 
|  | 469 	public void setEtag( HttpServletRequest request, HttpServletResponse response, String... modifyingEvents ) { | 
|  | 470 		if( modifyingEvents.length == 0 ) | 
|  | 471 			modifyingEvents = noModifyingEvents; | 
|  | 472 		StringBuilder buf = new StringBuilder(); | 
|  | 473 		String authKey = (String)request.getAttribute(authKeyAttr); | 
|  | 474 		if( authKey != null ) | 
|  | 475 			buf.append( '[' ).append( authKey).append( ']' ); | 
|  | 476 		buf.append( modifyingEvents[0] ); | 
|  | 477 		for( int i=1; i<modifyingEvents.length; i++ ) { | 
|  | 478 			buf.append( '~' ).append( modifyingEvents[i] ); | 
|  | 479 		} | 
|  | 480 		response.setHeader("Etag",buf.toString()); | 
|  | 481 		long lastModified = getLastModified(modifyingEvents); | 
|  | 482 		response.setDateHeader("Last-Modified",lastModified); | 
|  | 483 		response.setHeader("Cache-Control","max-age=0"); | 
|  | 484 	} | 
|  | 485 | 
|  | 486 	private boolean authorize(AuthorizingServlet auth,String authKey,HttpServletRequest request,HttpServletResponse response) | 
|  | 487 		throws IOException, ServletException | 
|  | 488 	{ | 
|  | 489 		try { | 
|  | 490 			if (customWrappers != null) { | 
|  | 491 				RequestAndResponse rr = customWrappers.wrap(request, response); | 
|  | 492 				request = rr.request; | 
|  | 493 				response = rr.response; | 
|  | 494 			} | 
|  | 495 			return auth.authorize(authKey,request,response); | 
|  | 496 		} catch(RuntimeException e) { | 
|  | 497 			handleException(request,e); | 
|  | 498 		} catch(ServletException e) { | 
|  | 499 			handleException(request,e); | 
|  | 500 		} | 
|  | 501 		throw new RuntimeException("never"); | 
|  | 502 	} | 
|  | 503 | 
|  | 504 	private String getAuthorizationKey(AuthorizingServlet auth,HttpServletRequest request) | 
|  | 505 		throws ServletException | 
|  | 506 	{ | 
|  | 507 		try { | 
|  | 508 			return auth.getAuthorizationKey(request); | 
|  | 509 		} catch(RuntimeException e) { | 
|  | 510 			handleException(request,e); | 
|  | 511 		} catch(ServletException e) { | 
|  | 512 			handleException(request,e); | 
|  | 513 		} | 
|  | 514 		return null;  // never gets here | 
|  | 515 	} | 
|  | 516 | 
|  | 517 	private long getLastModified(String[] modifyingEvents) { | 
|  | 518 		long[] lastModifieds = httpCache.lastModifieds(modifyingEvents); | 
|  | 519 		long lastModified = lastModifieds[0]; | 
|  | 520 		for( int i=1; i<lastModifieds.length; i++ ) { | 
|  | 521 			long lm = lastModifieds[i]; | 
|  | 522 			if( lastModified < lm ) | 
|  | 523 				lastModified = lm; | 
|  | 524 		} | 
|  | 525 		return lastModified; | 
|  | 526 	} | 
|  | 527 | 
|  | 528 	/** Adds all custom headers to the response object. */ | 
|  | 529 	private void addCustomHeaders(HttpServletResponse response) { | 
|  | 530 		Set<Map.Entry<String, String>> entries = this.customHeaders.entrySet(); | 
|  | 531 		for (Map.Entry<String, String> entry : entries) { | 
|  | 532 			response.setHeader(entry.getKey(), entry.getValue()); | 
|  | 533 		} | 
|  | 534 	} | 
|  | 535 | 
|  | 536 	private void handleException(HttpServletRequest request,RuntimeException e) | 
|  | 537 		throws ServletException | 
|  | 538 	{ | 
|  | 539 		JtpRuntimeException rte; | 
|  | 540 		try { | 
|  | 541 			String agent = request.getHeader("user-agent"); | 
|  | 542 			if( agent == null ) | 
|  | 543 				throw new JtpServletException(request,"null agent",e); | 
|  | 544 			if (!isValidAgent(agent)) | 
|  | 545 				throw new JtpServletException(request, "bad agent " + agent, e); | 
|  | 546 			String remote = request.getRemoteAddr(); | 
|  | 547 			String referer = request.getHeader("referer"); | 
|  | 548 			StringBuilder buf = new StringBuilder() | 
|  | 549 				.append( "method=" ).append( request.getMethod() ) | 
|  | 550 				.append( " user-agent=" ).append( agent ) | 
|  | 551 				.append( " referer=" ).append( referer ) | 
|  | 552 				.append( " remote=" ).append( remote ) | 
|  | 553 			; | 
|  | 554 			String etag = request.getHeader("If-None-Match"); | 
|  | 555 			if( etag != null ) | 
|  | 556 				buf.append( " etag=[" ).append( etag ).append( "]" ); | 
|  | 557 			if( referer==null || isInIpList(remote) ) | 
|  | 558 				throw new JtpServletException(request,buf.toString(),e); | 
|  | 559 			rte = new JtpRuntimeException(request,buf.toString(),e); | 
|  | 560 		} catch(RuntimeException e2) { | 
|  | 561 			logger.error("failed to handle",e); | 
|  | 562 			throw e2; | 
|  | 563 		} | 
|  | 564 		throw rte; | 
|  | 565 	} | 
|  | 566 | 
|  | 567 	private static void handleException(HttpServletRequest request,ServletException e) | 
|  | 568 		throws ServletException | 
|  | 569 	{ | 
|  | 570 		String agent = request.getHeader("user-agent"); | 
|  | 571 		throw new JtpServletException(request,"user-agent="+agent+" method="+request.getMethod()+" referer="+request.getHeader("referer"),e); | 
|  | 572 	} | 
|  | 573 | 
|  | 574 	private static class JtpRuntimeException extends RuntimeException { | 
|  | 575 		private JtpRuntimeException(HttpServletRequest request,String msg,RuntimeException e) { | 
|  | 576 			super("url="+getCurrentURL(request)+"  "+msg,e); | 
|  | 577 		} | 
|  | 578 	} | 
|  | 579 | 
|  | 580 	private static class JtpServletException extends ServletException { | 
|  | 581 		private JtpServletException(HttpServletRequest request,String msg,Exception e) { | 
|  | 582 			super("url="+getCurrentURL(request)+"  "+msg,e); | 
|  | 583 		} | 
|  | 584 	} | 
|  | 585 | 
|  | 586 	// work-around jetty bug | 
|  | 587 	private static String getCurrentURL(HttpServletRequest request) { | 
|  | 588 		try { | 
|  | 589 			return ServletUtils.getCurrentURL(request); | 
|  | 590 		} catch(RuntimeException e) { | 
|  | 591 			logger.warn("jetty screwed up",e); | 
|  | 592 			return "[failed]"; | 
|  | 593 		} | 
|  | 594 	} | 
|  | 595 | 
|  | 596 	private static boolean isValidAgent(String agent) { | 
|  | 597 		if (agent == null) | 
|  | 598 			return false; | 
|  | 599 		for (String badAgent : badAgents) { | 
|  | 600 			if (agent.indexOf(badAgent) >= 0) | 
|  | 601 				return false; | 
|  | 602 		} | 
|  | 603 		return true; | 
|  | 604 	} | 
|  | 605 | 
|  | 606 	private static final String[] badAgents = new String[]{ | 
|  | 607 		"MJ12bot", | 
|  | 608 		"WISEnutbot", | 
|  | 609 		"Win98",  // not worth handling these | 
|  | 610 		"Windows 98", | 
|  | 611 		"Windows 95", | 
|  | 612 		"RixBot", | 
|  | 613 		"User-Agent",  // from corrupt header | 
|  | 614 		"Firefox/0",  // ancient version of Firefox | 
|  | 615 		"Firefox/2.",  // ancient version of Firefox | 
|  | 616 		"Firefox/3.",  // ancient version of Firefox | 
|  | 617 		"Opera 7.",  // ancient version of Opera | 
|  | 618 		"Opera/7.", | 
|  | 619 		"Opera 8.", | 
|  | 620 		"Opera/8.", | 
|  | 621 		"Opera/9.", | 
|  | 622 		"TwitterFeed 3", | 
|  | 623 		"NAVER Blog Rssbot", | 
|  | 624 		"AOL 9.0", | 
|  | 625 		"rssreader@newstin.com", | 
|  | 626 		"PHPCrawl", | 
|  | 627 		"MSIE 2.", | 
|  | 628 		"MSIE 4.", | 
|  | 629 		"MSIE 5.", | 
|  | 630 		"MSIE 6.", | 
|  | 631 		"MSIE 7.0", | 
|  | 632 		"Mozilla/0.", | 
|  | 633 		"Mozilla/2.0", | 
|  | 634 		"Mozilla/3.0", | 
|  | 635 		"Mozilla/4.6", | 
|  | 636 		"Mozilla/4.7", | 
|  | 637 		"RSSIncludeBot/1.0", // cause exceptions in xml feeds | 
|  | 638 		"Powermarks", | 
|  | 639 		"GenwiFeeder", | 
|  | 640 		"Akregator", | 
|  | 641 		"ia_archiver", | 
|  | 642 		"Atomic_Email_Hunter", | 
|  | 643 		"Yahoo! Slurp", | 
|  | 644 		"Python-urllib", | 
|  | 645 		"BlackBerry", | 
|  | 646 		"SimplePie", // Feeds parser | 
|  | 647 		"www.webintegration.at", // crazy bot | 
|  | 648 		"www.run4dom.com", // crazy bot | 
|  | 649 		"zia-httpmirror", | 
|  | 650 		"POE-Component-Client-HTTP", | 
|  | 651 		"anonymous", | 
|  | 652 		"Sosospider", | 
|  | 653 		"Java/1.6", | 
|  | 654 		"Shareaza", | 
|  | 655 		"Jakarta Commons-HttpClient", | 
|  | 656 		"Apache-HttpClient", | 
|  | 657 		"Baiduspider", | 
|  | 658 		"bingbot", | 
|  | 659 		"MLBot", // www.metadatalabs.com/mlbot | 
|  | 660 		"www.vbseo.com", | 
|  | 661 		"yacybot", // yacy.net/bot.html | 
|  | 662 		"SearchBot" | 
|  | 663 	}; | 
|  | 664 | 
|  | 665 	private static boolean isBot(String agent) { | 
|  | 666 		if (agent == null) | 
|  | 667 			return false; | 
|  | 668 		for (String bot : bots) { | 
|  | 669 			if (agent.indexOf(bot) >= 0) | 
|  | 670 				return true; | 
|  | 671 		} | 
|  | 672 		return false; | 
|  | 673 	} | 
|  | 674 | 
|  | 675 	private static final String[] bots = new String[]{ | 
|  | 676 		"Googlebot" | 
|  | 677 	}; | 
|  | 678 | 
|  | 679 	private HttpServlet getServlet(String path) | 
|  | 680 		throws ServletException, ClassNotFoundException, IOException | 
|  | 681 	{ | 
|  | 682 		int i = path.lastIndexOf('.'); | 
|  | 683 		if( i == -1 ) | 
|  | 684 			throw new ClassNotFoundException(path); | 
|  | 685 		return getServletFromClass( | 
|  | 686 			base + path.substring(0,i).replace('/','.') | 
|  | 687 		); | 
|  | 688 	} | 
|  | 689 | 
|  | 690 	private HttpServlet getServletFromClass(String cls) | 
|  | 691 		throws ClassNotFoundException | 
|  | 692 	{ | 
|  | 693 		synchronized(lock) { | 
|  | 694 			if( reload && hasChanged(cls) ) { | 
|  | 695 				unloadServlets(); | 
|  | 696 			} | 
|  | 697 			HttpServlet srv = map.get(cls); | 
|  | 698 			if( srv==null ) { | 
|  | 699 				try { | 
|  | 700 					Class clas = reload ? cl.loadClass(cls) : Class.forName(cls); | 
|  | 701 					srv = (HttpServlet)clas.newInstance(); | 
|  | 702 				} catch(IllegalAccessException e) { | 
|  | 703 					throw new RuntimeException(e); | 
|  | 704 				} catch(InstantiationException e) { | 
|  | 705 					throw new RuntimeException(e); | 
|  | 706 				} | 
|  | 707 				try { | 
|  | 708 					srv.init(this); | 
|  | 709 				} catch(ServletException e) { | 
|  | 710 					throw new RuntimeException(e); | 
|  | 711 				} | 
|  | 712 				map.put(cls,srv); | 
|  | 713 			} | 
|  | 714 			return srv; | 
|  | 715 		} | 
|  | 716 	} | 
|  | 717 | 
|  | 718 	private boolean hasChanged(String cls) { | 
|  | 719 		try { | 
|  | 720 			URL url = cl.getResource( SimpleClassLoader.classToResource(cls) ); | 
|  | 721 			if( url==null ) | 
|  | 722 				return true; | 
|  | 723 			File file = new File(url.getPath()); | 
|  | 724 			if( recompile ) { | 
|  | 725 				String path = file.toString(); | 
|  | 726 				if( !path.endsWith(".class") ) | 
|  | 727 					throw new RuntimeException(); | 
|  | 728 				File dir = file.getParentFile(); | 
|  | 729 				String base = path.substring(0,path.length()-6); | 
|  | 730 				File source = new File( base + ".jtp" ); | 
|  | 731 				if( source.lastModified() > clTime ) { | 
|  | 732 					Process proc = Runtime.getRuntime().exec(new String[]{ | 
|  | 733 						"java", "fschmidt.tools.Jtp", source.getName() | 
|  | 734 					},null,dir); | 
|  | 735 					ProcUtils.checkProc(proc); | 
|  | 736 				} | 
|  | 737 				source = new File( base + ".java" ); | 
|  | 738 				if( source.lastModified() > clTime ) { | 
|  | 739 					Process proc = Runtime.getRuntime().exec(new String[]{ | 
|  | 740 						"javac", "-g", source.getName() | 
|  | 741 					},null,dir); | 
|  | 742 					ProcUtils.checkProc(proc); | 
|  | 743 				} | 
|  | 744 			} | 
|  | 745 			return file.lastModified() > clTime; | 
|  | 746 		} catch(IOException e) { | 
|  | 747 			throw new RuntimeException(e); | 
|  | 748 		} | 
|  | 749 	} | 
|  | 750 | 
|  | 751 | 
|  | 752 	private long timeLimit = 0; | 
|  | 753 	private static final String timeLimitAttr = "time-limit"; | 
|  | 754 | 
|  | 755 	private static class TimeLimit { | 
|  | 756 		long timeLimit; | 
|  | 757 		final long startTime = System.currentTimeMillis(); | 
|  | 758 		long ioTime = 0L; | 
|  | 759 | 
|  | 760 		TimeLimit(long timeLimit) { | 
|  | 761 			this.timeLimit = timeLimit; | 
|  | 762 		} | 
|  | 763 	} | 
|  | 764 | 
|  | 765 	public long getTimeLimit() { | 
|  | 766 		return timeLimit; | 
|  | 767 	} | 
|  | 768 | 
|  | 769 	public void setTimeLimit(long timeLimit) { | 
|  | 770 		this.timeLimit = timeLimit; | 
|  | 771 	} | 
|  | 772 | 
|  | 773 	private TimeLimit startTimeLimit(HttpServletRequest request) { | 
|  | 774 		TimeLimit tl = new TimeLimit(timeLimit); | 
|  | 775 		request.setAttribute( timeLimitAttr, tl ); | 
|  | 776 		return tl; | 
|  | 777 	} | 
|  | 778 | 
|  | 779 	public void setTimeLimit(HttpServletRequest request,long timeLimit) { | 
|  | 780 		TimeLimit tl = (TimeLimit)request.getAttribute(timeLimitAttr); | 
|  | 781 		tl.timeLimit = timeLimit; | 
|  | 782 	} | 
|  | 783 | 
|  | 784 	private void checkTimeLimit(HttpServletRequest request) { | 
|  | 785 		TimeLimit tl = (TimeLimit)request.getAttribute(timeLimitAttr); | 
|  | 786 		if( tl.timeLimit == 0L ) | 
|  | 787 			return; | 
|  | 788         long time = System.currentTimeMillis() - tl.startTime - tl.ioTime; | 
|  | 789         if( time > tl.timeLimit ) { | 
|  | 790 			float free = Runtime.getRuntime().freeMemory(); | 
|  | 791 			float total = Runtime.getRuntime().totalMemory(); | 
|  | 792 			float used = (total - free) * 100f; | 
|  | 793 			logger.error(ServletUtils.getCurrentURL(request,100) + " took " + time + " ms | " + String.format("%.1f",used/total) + '%'); | 
|  | 794 /* | 
|  | 795 			Scheduler scheduler = TheScheduler.get(); | 
|  | 796 			if( scheduler instanceof ProfilingScheduler ) { | 
|  | 797 				ProfilingScheduler profilingScheduler = (ProfilingScheduler)scheduler; | 
|  | 798 				if( profilingScheduler.getMode()==ProfilingScheduler.Mode.FOREGROUND ) { | 
|  | 799 					profilingScheduler.captureCPUSnapshot(); | 
|  | 800 				} | 
|  | 801 			} | 
|  | 802 */ | 
|  | 803 		} | 
|  | 804 	} | 
|  | 805 | 
|  | 806 } |