1
2
3
4
5
6
7
8
9
10
11
12
13
14
15 package org.mortbay.jetty.servlet;
16
17 import java.io.IOException;
18 import java.io.InputStream;
19 import java.io.OutputStream;
20 import java.net.MalformedURLException;
21 import java.net.URL;
22 import java.util.Enumeration;
23 import java.util.List;
24
25 import javax.servlet.RequestDispatcher;
26 import javax.servlet.ServletContext;
27 import javax.servlet.ServletException;
28 import javax.servlet.UnavailableException;
29 import javax.servlet.http.HttpServlet;
30 import javax.servlet.http.HttpServletRequest;
31 import javax.servlet.http.HttpServletResponse;
32
33 import org.mortbay.io.Buffer;
34 import org.mortbay.io.ByteArrayBuffer;
35 import org.mortbay.io.WriterOutputStream;
36 import org.mortbay.jetty.Connector;
37 import org.mortbay.jetty.HttpConnection;
38 import org.mortbay.jetty.HttpContent;
39 import org.mortbay.jetty.HttpFields;
40 import org.mortbay.jetty.HttpHeaderValues;
41 import org.mortbay.jetty.HttpHeaders;
42 import org.mortbay.jetty.HttpMethods;
43 import org.mortbay.jetty.InclusiveByteRange;
44 import org.mortbay.jetty.MimeTypes;
45 import org.mortbay.jetty.ResourceCache;
46 import org.mortbay.jetty.Response;
47 import org.mortbay.jetty.handler.ContextHandler;
48 import org.mortbay.jetty.nio.NIOConnector;
49 import org.mortbay.log.Log;
50 import org.mortbay.resource.Resource;
51 import org.mortbay.resource.ResourceFactory;
52 import org.mortbay.util.IO;
53 import org.mortbay.util.MultiPartOutputStream;
54 import org.mortbay.util.TypeUtil;
55 import org.mortbay.util.URIUtil;
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113 public class DefaultServlet extends HttpServlet implements ResourceFactory
114 {
115 private ServletContext _servletContext;
116 private ContextHandler _contextHandler;
117
118 private boolean _acceptRanges=true;
119 private boolean _dirAllowed=true;
120 private boolean _redirectWelcome=false;
121 private boolean _gzip=true;
122
123 private Resource _resourceBase;
124 private NIOResourceCache _nioCache;
125 private ResourceCache _bioCache;
126
127 private MimeTypes _mimeTypes;
128 private String[] _welcomes;
129 private boolean _useFileMappedBuffer=false;
130 private ByteArrayBuffer _cacheControl;
131 private String _relativeResourceBase;
132
133
134
135 public void init()
136 throws UnavailableException
137 {
138 _servletContext=getServletContext();
139 ContextHandler.SContext scontext=ContextHandler.getCurrentContext();
140 if (scontext==null)
141 _contextHandler=((ContextHandler.SContext)_servletContext).getContextHandler();
142 else
143 _contextHandler = ContextHandler.getCurrentContext().getContextHandler();
144
145 _mimeTypes = _contextHandler.getMimeTypes();
146
147 _welcomes = _contextHandler.getWelcomeFiles();
148 if (_welcomes==null)
149 _welcomes=new String[] {"index.jsp","index.html"};
150
151 _acceptRanges=getInitBoolean("acceptRanges",_acceptRanges);
152 _dirAllowed=getInitBoolean("dirAllowed",_dirAllowed);
153 _redirectWelcome=getInitBoolean("redirectWelcome",_redirectWelcome);
154 _gzip=getInitBoolean("gzip",_gzip);
155
156 String aliases=_servletContext.getInitParameter("aliases");
157 if (aliases!=null)
158 _contextHandler.setAliases(Boolean.parseBoolean(aliases));
159
160 _useFileMappedBuffer=getInitBoolean("useFileMappedBuffer",_useFileMappedBuffer);
161
162 _relativeResourceBase = getInitParameter("relativeResourceBase");
163
164 String rb=getInitParameter("resourceBase");
165 if (rb!=null)
166 {
167 if (_relativeResourceBase!=null)
168 throw new UnavailableException("resourceBase & relativeResourceBase");
169 try{_resourceBase=_contextHandler.newResource(rb);}
170 catch (Exception e)
171 {
172 Log.warn(Log.EXCEPTION,e);
173 throw new UnavailableException(e.toString());
174 }
175 }
176
177 String t=getInitParameter("cacheControl");
178 if (t!=null)
179 _cacheControl=new ByteArrayBuffer(t);
180
181 try
182 {
183 String cache_type =getInitParameter("cacheType");
184 int max_cache_size=getInitInt("maxCacheSize", -2);
185 int max_cached_file_size=getInitInt("maxCachedFileSize", -2);
186 int max_cached_files=getInitInt("maxCachedFiles", -2);
187
188 if (cache_type==null || "nio".equals(cache_type)|| "both".equals(cache_type))
189 {
190 if (max_cache_size==-2 || max_cache_size>0)
191 {
192 _nioCache=new NIOResourceCache(_mimeTypes);
193 _nioCache.setUseFileMappedBuffer(_useFileMappedBuffer);
194 if (max_cache_size>0)
195 _nioCache.setMaxCacheSize(max_cache_size);
196 if (max_cached_file_size>=-1)
197 _nioCache.setMaxCachedFileSize(max_cached_file_size);
198 if (max_cached_files>=-1)
199 _nioCache.setMaxCachedFiles(max_cached_files);
200 _nioCache.start();
201 }
202 }
203 if ("bio".equals(cache_type)|| "both".equals(cache_type))
204 {
205 if (max_cache_size==-2 || max_cache_size>0)
206 {
207 _bioCache=new ResourceCache(_mimeTypes);
208 if (max_cache_size>0)
209 _bioCache.setMaxCacheSize(max_cache_size);
210 if (max_cached_file_size>=-1)
211 _bioCache.setMaxCachedFileSize(max_cached_file_size);
212 if (max_cached_files>=-1)
213 _bioCache.setMaxCachedFiles(max_cached_files);
214 _bioCache.start();
215 }
216 }
217 if (_nioCache==null)
218 _bioCache=null;
219
220 }
221 catch (Exception e)
222 {
223 Log.warn(Log.EXCEPTION,e);
224 throw new UnavailableException(e.toString());
225 }
226
227 if (Log.isDebugEnabled()) Log.debug("resource base = "+_resourceBase);
228 }
229
230
231 public String getInitParameter(String name)
232 {
233 String value=getServletContext().getInitParameter("org.mortbay.jetty.servlet.Default."+name);
234 if (value==null)
235 value=super.getInitParameter(name);
236 return value;
237 }
238
239
240 private boolean getInitBoolean(String name, boolean dft)
241 {
242 String value=getInitParameter(name);
243 if (value==null || value.length()==0)
244 return dft;
245 return (value.startsWith("t")||
246 value.startsWith("T")||
247 value.startsWith("y")||
248 value.startsWith("Y")||
249 value.startsWith("1"));
250 }
251
252
253 private int getInitInt(String name, int dft)
254 {
255 String value=getInitParameter(name);
256 if (value==null)
257 value=getInitParameter(name);
258 if (value!=null && value.length()>0)
259 return Integer.parseInt(value);
260 return dft;
261 }
262
263
264
265
266
267
268
269
270
271 public Resource getResource(String pathInContext)
272 {
273 Resource r=null;
274 if (_relativeResourceBase!=null)
275 pathInContext=URIUtil.addPaths(_relativeResourceBase,pathInContext);
276
277 try
278 {
279 if (_resourceBase!=null)
280 r = _resourceBase.addPath(pathInContext);
281 else
282 {
283 URL u = _servletContext.getResource(pathInContext);
284 r = _contextHandler.newResource(u);
285 }
286
287 if (Log.isDebugEnabled()) Log.debug("RESOURCE="+r);
288 }
289 catch (IOException e)
290 {
291 Log.ignore(e);
292 }
293 return r;
294 }
295
296
297 protected void doGet(HttpServletRequest request, HttpServletResponse response)
298 throws ServletException, IOException
299 {
300 String servletPath=null;
301 String pathInfo=null;
302 Enumeration reqRanges = null;
303 Boolean included =(Boolean)request.getAttribute(Dispatcher.__INCLUDE_JETTY);
304 if (included!=null && included.booleanValue())
305 {
306 servletPath=(String)request.getAttribute(Dispatcher.__INCLUDE_SERVLET_PATH);
307 pathInfo=(String)request.getAttribute(Dispatcher.__INCLUDE_PATH_INFO);
308 if (servletPath==null)
309 {
310 servletPath=request.getServletPath();
311 pathInfo=request.getPathInfo();
312 }
313 }
314 else
315 {
316 included=Boolean.FALSE;
317 servletPath=request.getServletPath();
318 pathInfo=request.getPathInfo();
319
320
321 reqRanges = request.getHeaders(HttpHeaders.RANGE);
322 if (reqRanges!=null && !reqRanges.hasMoreElements())
323 reqRanges=null;
324 }
325
326 String pathInContext=URIUtil.addPaths(servletPath,pathInfo);
327 boolean endsWithSlash=pathInContext.endsWith(URIUtil.SLASH);
328
329
330 String pathInContextGz=null;
331 boolean gzip=false;
332 if (!included.booleanValue() && _gzip && reqRanges==null && !endsWithSlash )
333 {
334 String accept=request.getHeader(HttpHeaders.ACCEPT_ENCODING);
335 if (accept!=null && accept.indexOf("gzip")>=0)
336 gzip=true;
337 }
338
339
340 Resource resource=null;
341 HttpContent content=null;
342
343 Connector connector = HttpConnection.getCurrentConnection().getConnector();
344 ResourceCache cache=(connector instanceof NIOConnector) ?_nioCache:_bioCache;
345 try
346 {
347
348 if (gzip)
349 {
350 pathInContextGz=pathInContext+".gz";
351
352 if (cache==null)
353 {
354 resource=getResource(pathInContextGz);
355 }
356 else
357 {
358 content=cache.lookup(pathInContextGz,this);
359
360 if (content!=null)
361 resource=content.getResource();
362 else
363 resource=getResource(pathInContextGz);
364 }
365
366 if (resource==null || !resource.exists()|| resource.isDirectory())
367 {
368 if (cache!=null && content==null)
369 {
370 String real_path=_servletContext.getRealPath(pathInContextGz);
371 if (real_path!=null)
372 cache.miss(pathInContextGz,_contextHandler.newResource(real_path));
373 }
374 gzip=false;
375 pathInContextGz=null;
376 }
377 }
378
379
380 if (!gzip)
381 {
382 if (cache==null)
383 resource=getResource(pathInContext);
384 else
385 {
386 content=cache.lookup(pathInContext,this);
387
388 if (content!=null)
389 resource=content.getResource();
390 else
391 resource=getResource(pathInContext);
392 }
393 }
394
395 if (Log.isDebugEnabled())
396 Log.debug("resource="+resource+(content!=null?" content":""));
397
398
399 if (resource==null || !resource.exists())
400 response.sendError(HttpServletResponse.SC_NOT_FOUND);
401 else if (!resource.isDirectory())
402 {
403
404 if (content==null)
405 content=new UnCachedContent(resource);
406
407 if (included.booleanValue() || passConditionalHeaders(request,response, resource,content))
408 {
409 if (gzip)
410 {
411 response.setHeader(HttpHeaders.CONTENT_ENCODING,"gzip");
412 String mt=_servletContext.getMimeType(pathInContext);
413 if (mt!=null)
414 response.setContentType(mt);
415 }
416 sendData(request,response,included.booleanValue(),resource,content,reqRanges);
417 }
418 }
419 else
420 {
421 String welcome=null;
422
423 if (!endsWithSlash || (pathInContext.length()==1 && request.getAttribute("org.mortbay.jetty.nullPathInfo")!=null))
424 {
425 StringBuffer buf=request.getRequestURL();
426 synchronized(buf)
427 {
428 int param=buf.lastIndexOf(";");
429 if (param<0)
430 buf.append('/');
431 else
432 buf.insert(param,'/');
433 String q=request.getQueryString();
434 if (q!=null&&q.length()!=0)
435 {
436 buf.append('?');
437 buf.append(q);
438 }
439 response.setContentLength(0);
440 response.sendRedirect(response.encodeRedirectURL(buf.toString()));
441 }
442 }
443
444 else if (null!=(welcome=getWelcomeFile(resource)))
445 {
446 String ipath=URIUtil.addPaths(pathInContext,welcome);
447 if (_redirectWelcome)
448 {
449
450 response.setContentLength(0);
451 String q=request.getQueryString();
452 if (q!=null&&q.length()!=0)
453 response.sendRedirect(URIUtil.addPaths( _servletContext.getContextPath(),ipath)+"?"+q);
454 else
455 response.sendRedirect(URIUtil.addPaths( _servletContext.getContextPath(),ipath));
456 }
457 else
458 {
459
460 RequestDispatcher dispatcher=request.getRequestDispatcher(ipath);
461 if (dispatcher!=null)
462 {
463 if (included.booleanValue())
464 dispatcher.include(request,response);
465 else
466 {
467 request.setAttribute("org.mortbay.jetty.welcome",ipath);
468 dispatcher.forward(request,response);
469 }
470 }
471 }
472 }
473 else
474 {
475 content=new UnCachedContent(resource);
476 if (included.booleanValue() || passConditionalHeaders(request,response, resource,content))
477 sendDirectory(request,response,resource,pathInContext.length()>1);
478 }
479 }
480 }
481 catch(IllegalArgumentException e)
482 {
483 Log.warn(Log.EXCEPTION,e);
484 }
485 finally
486 {
487 if (content!=null)
488 content.release();
489 else if (resource!=null)
490 resource.release();
491 }
492
493 }
494
495
496 protected void doPost(HttpServletRequest request, HttpServletResponse response)
497 throws ServletException, IOException
498 {
499 doGet(request,response);
500 }
501
502
503
504
505
506 protected void doTrace(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException
507 {
508 resp.sendError(HttpServletResponse.SC_METHOD_NOT_ALLOWED);
509 }
510
511
512
513
514
515
516
517
518
519
520
521
522
523 private String getWelcomeFile(Resource resource) throws MalformedURLException, IOException
524 {
525 if (!resource.isDirectory() || _welcomes==null)
526 return null;
527
528 for (int i=0;i<_welcomes.length;i++)
529 {
530 Resource welcome=resource.addPath(_welcomes[i]);
531 if (welcome.exists())
532 return _welcomes[i];
533 }
534
535 return null;
536 }
537
538
539
540
541 protected boolean passConditionalHeaders(HttpServletRequest request,HttpServletResponse response, Resource resource, HttpContent content)
542 throws IOException
543 {
544 if (!request.getMethod().equals(HttpMethods.HEAD) )
545 {
546 String ifms=request.getHeader(HttpHeaders.IF_MODIFIED_SINCE);
547 if (ifms!=null)
548 {
549 if (content!=null)
550 {
551 Buffer mdlm=content.getLastModified();
552 if (mdlm!=null)
553 {
554 if (ifms.equals(mdlm.toString()))
555 {
556 response.reset();
557 response.setStatus(HttpServletResponse.SC_NOT_MODIFIED);
558 response.flushBuffer();
559 return false;
560 }
561 }
562 }
563
564 long ifmsl=request.getDateHeader(HttpHeaders.IF_MODIFIED_SINCE);
565 if (ifmsl!=-1)
566 {
567 if (resource.lastModified()/1000 <= ifmsl/1000)
568 {
569 response.reset();
570 response.setStatus(HttpServletResponse.SC_NOT_MODIFIED);
571 response.flushBuffer();
572 return false;
573 }
574 }
575 }
576
577
578 long date=request.getDateHeader(HttpHeaders.IF_UNMODIFIED_SINCE);
579
580 if (date!=-1)
581 {
582 if (resource.lastModified()/1000 > date/1000)
583 {
584 response.sendError(HttpServletResponse.SC_PRECONDITION_FAILED);
585 return false;
586 }
587 }
588
589 }
590 return true;
591 }
592
593
594
595 protected void sendDirectory(HttpServletRequest request,
596 HttpServletResponse response,
597 Resource resource,
598 boolean parent)
599 throws IOException
600 {
601 if (!_dirAllowed)
602 {
603 response.sendError(HttpServletResponse.SC_FORBIDDEN);
604 return;
605 }
606
607 byte[] data=null;
608 String base = URIUtil.addPaths(request.getRequestURI(),URIUtil.SLASH);
609 String dir = resource.getListHTML(base,parent);
610 if (dir==null)
611 {
612 response.sendError(HttpServletResponse.SC_FORBIDDEN,
613 "No directory");
614 return;
615 }
616
617 data=dir.getBytes("UTF-8");
618 response.setContentType("text/html; charset=UTF-8");
619 response.setContentLength(data.length);
620 response.getOutputStream().write(data);
621 }
622
623
624 protected void sendData(HttpServletRequest request,
625 HttpServletResponse response,
626 boolean include,
627 Resource resource,
628 HttpContent content,
629 Enumeration reqRanges)
630 throws IOException
631 {
632 long content_length=resource.length();
633
634
635 OutputStream out =null;
636 try{out = response.getOutputStream();}
637 catch(IllegalStateException e) {out = new WriterOutputStream(response.getWriter());}
638
639 if ( reqRanges == null || !reqRanges.hasMoreElements())
640 {
641
642 if (include)
643 {
644 resource.writeTo(out,0,content_length);
645 }
646 else
647 {
648
649 if (out instanceof HttpConnection.Output)
650 {
651 if (_cacheControl!=null)
652 {
653 if (response instanceof Response)
654 ((Response)response).getHttpFields().put(HttpHeaders.CACHE_CONTROL_BUFFER,_cacheControl);
655 else
656 response.setHeader(HttpHeaders.CACHE_CONTROL,_cacheControl.toString());
657 }
658 ((HttpConnection.Output)out).sendContent(content);
659 }
660 else
661 {
662
663
664 writeHeaders(response,content,content_length);
665 resource.writeTo(out,0,content_length);
666 }
667 }
668 }
669 else
670 {
671
672 List ranges =InclusiveByteRange.satisfiableRanges(reqRanges,content_length);
673
674
675 if (ranges==null || ranges.size()==0)
676 {
677 writeHeaders(response, content, content_length);
678 response.setStatus(HttpServletResponse.SC_REQUESTED_RANGE_NOT_SATISFIABLE);
679 response.setHeader(HttpHeaders.CONTENT_RANGE,
680 InclusiveByteRange.to416HeaderRangeString(content_length));
681 resource.writeTo(out,0,content_length);
682 return;
683 }
684
685
686
687
688 if ( ranges.size()== 1)
689 {
690 InclusiveByteRange singleSatisfiableRange =
691 (InclusiveByteRange)ranges.get(0);
692 long singleLength = singleSatisfiableRange.getSize(content_length);
693 writeHeaders(response,content,singleLength );
694 response.setStatus(HttpServletResponse.SC_PARTIAL_CONTENT);
695 response.setHeader(HttpHeaders.CONTENT_RANGE,
696 singleSatisfiableRange.toHeaderRangeString(content_length));
697 resource.writeTo(out,singleSatisfiableRange.getFirst(content_length),singleLength);
698 return;
699 }
700
701
702
703
704
705
706 writeHeaders(response,content,-1);
707 String mimetype=content.getContentType().toString();
708 MultiPartOutputStream multi = new MultiPartOutputStream(out);
709 response.setStatus(HttpServletResponse.SC_PARTIAL_CONTENT);
710
711
712
713
714 String ctp;
715 if (request.getHeader(HttpHeaders.REQUEST_RANGE)!=null)
716 ctp = "multipart/x-byteranges; boundary=";
717 else
718 ctp = "multipart/byteranges; boundary=";
719 response.setContentType(ctp+multi.getBoundary());
720
721 InputStream in=resource.getInputStream();
722 long pos=0;
723
724 for (int i=0;i<ranges.size();i++)
725 {
726 InclusiveByteRange ibr = (InclusiveByteRange) ranges.get(i);
727 String header=HttpHeaders.CONTENT_RANGE+": "+
728 ibr.toHeaderRangeString(content_length);
729 multi.startPart(mimetype,new String[]{header});
730
731 long start=ibr.getFirst(content_length);
732 long size=ibr.getSize(content_length);
733 if (in!=null)
734 {
735
736 if (start<pos)
737 {
738 in.close();
739 in=resource.getInputStream();
740 pos=0;
741 }
742 if (pos<start)
743 {
744 in.skip(start-pos);
745 pos=start;
746 }
747 IO.copy(in,multi,size);
748 pos+=size;
749 }
750 else
751
752 (resource).writeTo(multi,start,size);
753
754 }
755 if (in!=null)
756 in.close();
757 multi.close();
758 }
759 return;
760 }
761
762
763 protected void writeHeaders(HttpServletResponse response,HttpContent content,long count)
764 throws IOException
765 {
766 if (content.getContentType()!=null)
767 response.setContentType(content.getContentType().toString());
768
769 if (response instanceof Response)
770 {
771 Response r=(Response)response;
772 HttpFields fields = r.getHttpFields();
773
774 if (content.getLastModified()!=null)
775 fields.put(HttpHeaders.LAST_MODIFIED_BUFFER,content.getLastModified(),content.getResource().lastModified());
776 else if (content.getResource()!=null)
777 {
778 long lml=content.getResource().lastModified();
779 if (lml!=-1)
780 fields.putDateField(HttpHeaders.LAST_MODIFIED_BUFFER,lml);
781 }
782
783 if (count != -1)
784 r.setLongContentLength(count);
785
786 if (_acceptRanges)
787 fields.put(HttpHeaders.ACCEPT_RANGES_BUFFER,HttpHeaderValues.BYTES_BUFFER);
788
789 if (_cacheControl!=null)
790 fields.put(HttpHeaders.CACHE_CONTROL_BUFFER,_cacheControl);
791
792 }
793 else
794 {
795 long lml=content.getResource().lastModified();
796 if (lml>=0)
797 response.setDateHeader(HttpHeaders.LAST_MODIFIED,lml);
798
799 if (count != -1)
800 {
801 if (count<Integer.MAX_VALUE)
802 response.setContentLength((int)count);
803 else
804 response.setHeader(HttpHeaders.CONTENT_LENGTH,TypeUtil.toString(count));
805 }
806
807 if (_acceptRanges)
808 response.setHeader(HttpHeaders.ACCEPT_RANGES,"bytes");
809
810 if (_cacheControl!=null)
811 response.setHeader(HttpHeaders.CACHE_CONTROL,_cacheControl.toString());
812 }
813 }
814
815
816
817
818
819 public void destroy()
820 {
821 try
822 {
823 if (_nioCache!=null)
824 _nioCache.stop();
825 if (_bioCache!=null)
826 _bioCache.stop();
827 }
828 catch(Exception e)
829 {
830 Log.warn(Log.EXCEPTION,e);
831 }
832 finally
833 {
834 super.destroy();
835 }
836 }
837
838
839
840
841 private class UnCachedContent implements HttpContent
842 {
843 Resource _resource;
844
845 UnCachedContent(Resource resource)
846 {
847 _resource=resource;
848 }
849
850
851 public Buffer getContentType()
852 {
853 return _mimeTypes.getMimeByExtension(_resource.toString());
854 }
855
856
857 public Buffer getLastModified()
858 {
859 return null;
860 }
861
862
863 public Buffer getBuffer()
864 {
865 return null;
866 }
867
868
869 public long getContentLength()
870 {
871 return _resource.length();
872 }
873
874
875 public InputStream getInputStream() throws IOException
876 {
877 return _resource.getInputStream();
878 }
879
880
881 public Resource getResource()
882 {
883 return _resource;
884 }
885
886
887 public void release()
888 {
889 _resource.release();
890 _resource=null;
891 }
892
893 }
894 }