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