1 16 package org.mortbay.http.handler; 17 18 import java.io.IOException ; 19 import java.io.InputStream ; 20 import java.io.OutputStream ; 21 import java.util.Enumeration ; 22 import java.util.List ; 23 24 import org.apache.commons.logging.Log; 25 import org.mortbay.log.LogFactory; 26 import org.mortbay.http.HttpException; 27 import org.mortbay.http.HttpFields; 28 import org.mortbay.http.HttpRequest; 29 import org.mortbay.http.HttpResponse; 30 import org.mortbay.http.InclusiveByteRange; 31 import org.mortbay.http.MultiPartResponse; 32 import org.mortbay.http.ResourceCache; 33 import org.mortbay.util.CachedResource; 34 import org.mortbay.util.IO; 35 import org.mortbay.util.LogSupport; 36 import org.mortbay.util.Resource; 37 import org.mortbay.util.StringMap; 38 import org.mortbay.util.TypeUtil; 39 import org.mortbay.util.URI; 40 41 42 53 public class ResourceHandler extends AbstractHttpHandler 54 { 55 private static Log log = LogFactory.getLog(ResourceHandler.class); 56 57 58 private boolean _acceptRanges=true; 59 private boolean _redirectWelcomeFiles ; 60 private String [] _methods=null; 61 private String _allowed; 62 private boolean _dirAllowed=true; 63 private int _minGzipLength =-1; 64 private StringMap _methodMap = new StringMap(); 65 { 66 setAllowedMethods(new String [] 67 { 68 HttpRequest.__GET, 69 HttpRequest.__POST, 70 HttpRequest.__HEAD, 71 HttpRequest.__OPTIONS, 72 HttpRequest.__TRACE 73 }); 74 } 75 76 77 79 public ResourceHandler() 80 {} 81 82 83 84 public synchronized void start() 85 throws Exception 86 { 87 super.start(); 88 } 89 90 91 public void stop() 92 throws InterruptedException 93 { 94 super.stop(); 95 } 96 97 98 public String [] getAllowedMethods() 99 { 100 return _methods; 101 } 102 103 104 public void setAllowedMethods(String [] methods) 105 { 106 StringBuffer b = new StringBuffer (); 107 _methods=methods; 108 _methodMap.clear(); 109 for (int i=0;i<methods.length;i++) 110 { 111 _methodMap.put(methods[i],methods[i]); 112 if (i>0) 113 b.append(','); 114 b.append(methods[i]); 115 } 116 _allowed=b.toString(); 117 } 118 119 120 public boolean isMethodAllowed(String method) 121 { 122 return _methodMap.get(method)!=null; 123 } 124 125 126 public String getAllowedString() 127 { 128 return _allowed; 129 } 130 131 132 public boolean isDirAllowed() 133 { 134 return _dirAllowed; 135 } 136 137 138 public void setDirAllowed(boolean dirAllowed) 139 { 140 _dirAllowed = dirAllowed; 141 } 142 143 144 public boolean isAcceptRanges() 145 { 146 return _acceptRanges; 147 } 148 149 150 153 public boolean getRedirectWelcome() 154 { 155 return _redirectWelcomeFiles; 156 } 157 158 159 163 public void setRedirectWelcome(boolean redirectWelcome) 164 { 165 _redirectWelcomeFiles = redirectWelcome; 166 } 167 168 169 173 public void setAcceptRanges(boolean ar) 174 { 175 _acceptRanges=ar; 176 } 177 178 179 182 public int getMinGzipLength() 183 { 184 return _minGzipLength; 185 } 186 187 188 193 public void setMinGzipLength(int minGzipLength) 194 { 195 _minGzipLength = minGzipLength; 196 } 197 198 199 200 207 protected Resource getResource(String pathInContext) 208 throws IOException 209 { 210 return getHttpContext().getResource(pathInContext); 211 } 212 213 214 public void handle(String pathInContext, 215 String pathParams, 216 HttpRequest request, 217 HttpResponse response) 218 throws HttpException, IOException 219 { 220 Resource resource = getResource(pathInContext); 221 if (resource==null) 222 return; 223 224 if (!isMethodAllowed(request.getMethod())) 226 { 227 if(log.isDebugEnabled())log.debug("Method not allowed: "+request.getMethod()); 228 if (resource.exists()) 229 { 230 setAllowHeader(response); 231 response.sendError(HttpResponse.__405_Method_Not_Allowed); 232 } 233 return; 234 } 235 236 try 238 { 239 if(log.isDebugEnabled())log.debug("PATH="+pathInContext+" RESOURCE="+resource); 240 241 String method=request.getMethod(); 243 if (method.equals(HttpRequest.__GET) || 244 method.equals(HttpRequest.__POST) || 245 method.equals(HttpRequest.__HEAD)) 246 handleGet(request, response, pathInContext, pathParams, resource); 247 else if (method.equals(HttpRequest.__PUT)) 248 handlePut(request, response, pathInContext, resource); 249 else if (method.equals(HttpRequest.__DELETE)) 250 handleDelete(request, response, pathInContext, resource); 251 else if (method.equals(HttpRequest.__OPTIONS)) 252 handleOptions(response, pathInContext); 253 else if (method.equals(HttpRequest.__MOVE)) 254 handleMove(request, response, pathInContext, resource); 255 else if (method.equals(HttpRequest.__TRACE)) 256 handleTrace(request, response); 257 else 258 { 259 if(log.isDebugEnabled())log.debug("Unknown action:"+method); 260 try{ 262 if (resource.exists()) 263 response.sendError(HttpResponse.__501_Not_Implemented); 264 } 265 catch(Exception e) {LogSupport.ignore(log,e);} 266 } 267 } 268 catch(IllegalArgumentException e) 269 { 270 LogSupport.ignore(log,e); 271 } 272 finally 273 { 274 if (resource!=null && !(resource instanceof CachedResource)) 275 resource.release(); 276 } 277 } 278 279 280 public void handleGet(HttpRequest request, 281 HttpResponse response, 282 String pathInContext, 283 String pathParams, 284 Resource resource) 285 throws IOException 286 { 287 if(log.isDebugEnabled())log.debug("Looking for "+resource); 288 289 if (resource!=null && resource.exists()) 290 { 291 if (resource.isDirectory()) 293 { 294 if (!pathInContext.endsWith("/") && !pathInContext.equals("/")) 295 { 296 log.debug("Redirect to directory/"); 297 298 String q=request.getQuery(); 299 StringBuffer buf=request.getRequestURL(); 300 if (q!=null&&q.length()!=0) 301 { 302 buf.append('?'); 303 buf.append(q); 304 } 305 response.setField(HttpFields.__Location, URI.addPaths(buf.toString(),"/")); 306 response.setStatus(302); 307 request.setHandled(true); 308 return; 309 } 310 311 String welcome=getHttpContext().getWelcomeFile(resource); 313 if (welcome!=null) 314 { 315 String ipath=URI.addPaths(pathInContext,welcome); 317 if (_redirectWelcomeFiles) 318 { 319 ipath=URI.addPaths(getHttpContext().getContextPath(),ipath); 321 response.setContentLength(0); 322 response.sendRedirect(ipath); 323 } 324 else 325 { 326 URI uri=request.getURI(); 327 uri.setPath(URI.addPaths(uri.getPath(),welcome)); 328 getHttpContext().handle(ipath,pathParams,request,response); 329 } 330 return; 331 } 332 333 if (!passConditionalHeaders(request,response,resource)) 335 return; 336 sendDirectory(request,response,resource,pathInContext.length()>1); 338 } 339 else if (resource.exists()) 341 { 342 if (!passConditionalHeaders(request,response,resource)) 344 return; 345 sendData(request,response,pathInContext,resource,true); 346 } 347 else 348 log.warn("Unknown file type"); 350 } 351 } 352 353 354 355 357 private boolean passConditionalHeaders(HttpRequest request, 358 HttpResponse response, 359 Resource resource) 360 throws IOException 361 { 362 if (!request.getMethod().equals(HttpRequest.__HEAD)) 363 { 364 ResourceCache.ResourceMetaData metaData = 368 (ResourceCache.ResourceMetaData)resource.getAssociate(); 369 if (metaData!=null) 370 { 371 String ifms=request.getField(HttpFields.__IfModifiedSince); 372 String mdlm=metaData.getLastModified(); 373 if (ifms!=null && mdlm!=null && ifms.equals(mdlm)) 374 { 375 response.setStatus(HttpResponse.__304_Not_Modified); 376 request.setHandled(true); 377 return false; 378 } 379 } 380 381 382 long date=0; 383 385 if ((date=request.getDateField(HttpFields.__IfUnmodifiedSince))>0) 386 { 387 if (resource.lastModified()/1000 > date/1000) 388 { 389 response.sendError(HttpResponse.__412_Precondition_Failed); 390 return false; 391 } 392 } 393 394 if ((date=request.getDateField(HttpFields.__IfModifiedSince))>0) 395 { 396 397 if (resource.lastModified()/1000 <= date/1000) 398 { 399 response.setStatus(HttpResponse.__304_Not_Modified); 400 request.setHandled(true); 401 return false; 402 } 403 } 404 405 } 406 return true; 407 } 408 409 410 411 void handlePut(HttpRequest request, 412 HttpResponse response, 413 String pathInContext, 414 Resource resource) 415 throws IOException 416 { 417 if(log.isDebugEnabled())log.debug("PUT "+pathInContext+" in "+resource); 418 419 boolean exists=resource!=null && resource.exists(); 420 if (exists && 421 !passConditionalHeaders(request,response,resource)) 422 return; 423 424 if (pathInContext.endsWith("/")) 425 { 426 if (!exists) 427 { 428 if (!resource.getFile().mkdirs()) 429 response.sendError(HttpResponse.__403_Forbidden, "Directories could not be created"); 430 else 431 { 432 request.setHandled(true); 433 response.setStatus(HttpResponse.__201_Created); 434 response.commit(); 435 } 436 } 437 else 438 { 439 request.setHandled(true); 440 response.setStatus(HttpResponse.__200_OK); 441 response.commit(); 442 } 443 } 444 else 445 { 446 try 447 { 448 int toRead = request.getContentLength(); 449 InputStream in = request.getInputStream(); 450 OutputStream out = resource.getOutputStream(); 451 if (toRead>=0) 452 IO.copy(in,out,toRead); 453 else 454 IO.copy(in,out); 455 out.close(); 456 request.setHandled(true); 457 response.setStatus(exists 458 ?HttpResponse.__200_OK 459 :HttpResponse.__201_Created); 460 response.commit(); 461 } 462 catch (Exception ex) 463 { 464 log.warn(LogSupport.EXCEPTION,ex); 465 response.sendError(HttpResponse.__403_Forbidden, 466 ex.getMessage()); 467 } 468 } 469 } 470 471 472 void handleDelete(HttpRequest request, 473 HttpResponse response, 474 String pathInContext, 475 Resource resource) 476 throws IOException 477 { 478 if(log.isDebugEnabled())log.debug("DELETE "+pathInContext+" from "+resource); 479 480 if (!resource.exists() || 481 !passConditionalHeaders(request,response,resource)) 482 return; 483 484 try 485 { 486 if (resource.delete()) 488 response.setStatus(HttpResponse.__204_No_Content); 489 else 490 response.sendError(HttpResponse.__403_Forbidden); 491 492 request.setHandled(true); 494 } 495 catch (SecurityException sex) 496 { 497 log.warn(LogSupport.EXCEPTION,sex); 498 response.sendError(HttpResponse.__403_Forbidden, sex.getMessage()); 499 } 500 } 501 502 503 504 void handleMove(HttpRequest request, 505 HttpResponse response, 506 String pathInContext, 507 Resource resource) 508 throws IOException 509 { 510 if (!resource.exists() || !passConditionalHeaders(request,response,resource)) 511 return; 512 513 514 String newPath = URI.canonicalPath(request.getField("New-uri")); 515 if (newPath==null) 516 { 517 response.sendError(HttpResponse.__405_Method_Not_Allowed, 518 "Bad new uri"); 519 return; 520 } 521 522 String contextPath = getHttpContext().getContextPath(); 523 if (contextPath!=null && !newPath.startsWith(contextPath)) 524 { 525 response.sendError(HttpResponse.__405_Method_Not_Allowed, 526 "Not in context"); 527 return; 528 } 529 530 531 try 533 { 534 String newInfo=newPath; 536 if (contextPath!=null) 537 newInfo=newInfo.substring(contextPath.length()); 538 Resource newFile = getHttpContext().getBaseResource().addPath(newInfo); 539 540 if(log.isDebugEnabled())log.debug("Moving "+resource+" to "+newFile); 541 resource.renameTo(newFile); 542 543 response.setStatus(HttpResponse.__204_No_Content); 544 request.setHandled(true); 545 } 546 catch (Exception ex) 547 { 548 log.warn(LogSupport.EXCEPTION,ex); 549 setAllowHeader(response); 550 response.sendError(HttpResponse.__405_Method_Not_Allowed, 551 "Error:"+ex); 552 return; 553 } 554 } 555 556 557 void handleOptions(HttpResponse response, String pathInContext) 558 throws IOException 559 { 560 if ("*".equals(pathInContext)) 561 return; 562 setAllowHeader(response); 563 response.commit(); 564 } 565 566 567 void setAllowHeader(HttpResponse response) 568 { 569 response.setField(HttpFields.__Allow, getAllowedString()); 570 } 571 572 573 public void writeHeaders(HttpResponse response,Resource resource, long count) 574 throws IOException 575 { 576 ResourceCache.ResourceMetaData metaData = 577 (ResourceCache.ResourceMetaData)resource.getAssociate(); 578 579 response.setContentType(metaData.getMimeType()); 580 if (count != -1) 581 { 582 if (count==resource.length()) 583 response.setField(HttpFields.__ContentLength,metaData.getLength()); 584 else 585 response.setContentLength((int)count); 586 } 587 588 response.setField(HttpFields.__LastModified,metaData.getLastModified()); 589 590 if (_acceptRanges && response.getHttpRequest().getDotVersion()>0) 591 response.setField(HttpFields.__AcceptRanges,"bytes"); 592 } 593 594 595 public void sendData(HttpRequest request, 596 HttpResponse response, 597 String pathInContext, 598 Resource resource, 599 boolean writeHeaders) 600 throws IOException 601 { 602 long resLength=resource.length(); 603 604 Enumeration reqRanges = 606 request.getDotVersion()>0 607 ?request.getFieldValues(HttpFields.__Range) 608 :null; 609 610 if (!writeHeaders || reqRanges == null || !reqRanges.hasMoreElements()) 611 { 612 Resource data=resource; 614 if (_minGzipLength>0) 615 { 616 String accept=request.getField(HttpFields.__AcceptEncoding); 617 if (accept!=null && resLength>_minGzipLength && 618 !pathInContext.endsWith(".gz")) 619 { 620 Resource gz = getHttpContext().getResource(pathInContext+".gz"); 621 if (gz.exists() && accept.indexOf("gzip")>=0) 622 { 623 if(log.isDebugEnabled())log.debug("gzip="+gz); 624 response.setField(HttpFields.__ContentEncoding,"gzip"); 625 data=gz; 626 resLength=data.length(); 627 } 628 } 629 } 630 writeHeaders(response,resource,resLength); 631 632 request.setHandled(true); 633 OutputStream out = response.getOutputStream(); 634 data.writeTo(out,0,resLength); 635 return; 636 } 637 638 List ranges =InclusiveByteRange.satisfiableRanges(reqRanges,resLength); 640 if(log.isDebugEnabled())log.debug("ranges: " + reqRanges + " == " + ranges); 641 642 if (ranges==null || ranges.size()==0) 644 { 645 log.debug("no satisfiable ranges"); 646 writeHeaders(response, resource, resLength); 647 response.setStatus(HttpResponse.__416_Requested_Range_Not_Satisfiable); 648 response.setReason((String )HttpResponse.__statusMsg 649 .get(TypeUtil.newInteger(HttpResponse.__416_Requested_Range_Not_Satisfiable))); 650 651 response.setField(HttpFields.__ContentRange, 652 InclusiveByteRange.to416HeaderRangeString(resLength)); 653 654 OutputStream out = response.getOutputStream(); 655 resource.writeTo(out,0,resLength); 656 request.setHandled(true); 657 return; 658 } 659 660 661 if ( ranges.size()== 1) 664 { 665 InclusiveByteRange singleSatisfiableRange = 666 (InclusiveByteRange)ranges.get(0); 667 if(log.isDebugEnabled())log.debug("single satisfiable range: " + singleSatisfiableRange); 668 long singleLength = singleSatisfiableRange.getSize(resLength); 669 writeHeaders(response,resource,singleLength); 670 response.setStatus(HttpResponse.__206_Partial_Content); 671 response.setReason((String )HttpResponse.__statusMsg 672 .get(TypeUtil.newInteger(HttpResponse.__206_Partial_Content))); 673 response.setField(HttpFields.__ContentRange, 674 singleSatisfiableRange.toHeaderRangeString(resLength)); 675 OutputStream out = response.getOutputStream(); 676 resource.writeTo(out, 677 singleSatisfiableRange.getFirst(resLength), 678 singleLength); 679 request.setHandled(true); 680 return; 681 } 682 683 684 ResourceCache.ResourceMetaData metaData = 689 (ResourceCache.ResourceMetaData)resource.getAssociate(); 690 String encoding = metaData.getMimeType(); 691 MultiPartResponse multi = new MultiPartResponse(response); 692 response.setStatus(HttpResponse.__206_Partial_Content); 693 response.setReason((String )HttpResponse.__statusMsg 694 .get(TypeUtil.newInteger(HttpResponse.__206_Partial_Content))); 695 696 String ctp; 700 if (request.containsField(HttpFields.__RequestRange)) 701 ctp = "multipart/x-byteranges; boundary="; 702 else 703 ctp = "multipart/byteranges; boundary="; 704 response.setContentType(ctp+multi.getBoundary()); 705 706 InputStream in=(resource instanceof CachedResource) 707 ?null:resource.getInputStream(); 708 OutputStream out = response.getOutputStream(); 709 long pos=0; 710 711 for (int i=0;i<ranges.size();i++) 712 { 713 InclusiveByteRange ibr = (InclusiveByteRange) ranges.get(i); 714 String header=HttpFields.__ContentRange+": "+ 715 ibr.toHeaderRangeString(resLength); 716 if(log.isDebugEnabled())log.debug("multi range: "+encoding+" "+header); 717 multi.startPart(encoding,new String []{header}); 718 719 long start=ibr.getFirst(resLength); 720 long size=ibr.getSize(resLength); 721 if (in!=null) 722 { 723 if (start<pos) 725 { 726 in.close(); 727 in=resource.getInputStream(); 728 pos=0; 729 } 730 if (pos<start) 731 { 732 in.skip(start-pos); 733 pos=start; 734 } 735 IO.copy(in,out,size); 736 pos+=size; 737 } 738 else 739 resource.writeTo(out,start,size); 741 742 } 743 if (in!=null) 744 in.close(); 745 multi.close(); 746 747 request.setHandled(true); 748 749 return; 750 } 751 752 753 754 void sendDirectory(HttpRequest request, 755 HttpResponse response, 756 Resource resource, 757 boolean parent) 758 throws IOException 759 { 760 if (!_dirAllowed) 761 { 762 response.sendError(HttpResponse.__403_Forbidden); 763 return; 764 } 765 766 request.setHandled(true); 767 768 if(log.isDebugEnabled())log.debug("sendDirectory: "+resource); 769 byte[] data=null; 770 if (resource instanceof CachedResource) 771 data=((CachedResource)resource).getCachedData(); 772 773 if (data==null) 774 { 775 String base = URI.addPaths(request.getPath(),"/"); 776 String dir = resource.getListHTML(URI.encodePath(base),parent); 777 if (dir==null) 778 { 779 response.sendError(HttpResponse.__403_Forbidden, 780 "No directory"); 781 return; 782 } 783 data=dir.getBytes("UTF8"); 784 if (resource instanceof CachedResource) 785 ((CachedResource)resource).setCachedData(data); 786 } 787 788 response.setContentType("text/html; charset=UTF8"); 789 response.setContentLength(data.length); 790 791 if (request.getMethod().equals(HttpRequest.__HEAD)) 792 { 793 response.commit(); 794 return; 795 } 796 797 response.getOutputStream().write(data,0,data.length); 798 response.commit(); 799 } 800 } 801 802 803 804 | Popular Tags |