Mercurial Hosting > nabble
comparison src/fschmidt/util/servlet/JtpContextServlet.java @ 68:00520880ad02
add fschmidt source
| author | Franklin Schmidt <fschmidt@gmail.com> |
|---|---|
| date | Sun, 05 Oct 2025 17:24:15 -0600 |
| parents | |
| children |
comparison
equal
deleted
inserted
replaced
| 67:9d0fefce6985 | 68:00520880ad02 |
|---|---|
| 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 } |
