1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19 package org.eclipse.jetty.servlet;
20
21 import static org.eclipse.jetty.http.GzipHttpContent.ETAG_GZIP_QUOTE;
22 import static org.eclipse.jetty.http.GzipHttpContent.removeGzipFromETag;
23
24 import java.io.FileNotFoundException;
25 import java.io.IOException;
26 import java.io.InputStream;
27 import java.io.OutputStream;
28 import java.net.MalformedURLException;
29 import java.net.URL;
30 import java.nio.ByteBuffer;
31 import java.util.ArrayList;
32 import java.util.Enumeration;
33 import java.util.List;
34 import java.util.StringTokenizer;
35
36 import javax.servlet.AsyncContext;
37 import javax.servlet.RequestDispatcher;
38 import javax.servlet.ServletContext;
39 import javax.servlet.ServletException;
40 import javax.servlet.UnavailableException;
41 import javax.servlet.http.HttpServlet;
42 import javax.servlet.http.HttpServletRequest;
43 import javax.servlet.http.HttpServletResponse;
44
45 import org.eclipse.jetty.http.DateParser;
46 import org.eclipse.jetty.http.GzipHttpContent;
47 import org.eclipse.jetty.http.HttpContent;
48 import org.eclipse.jetty.http.HttpField;
49 import org.eclipse.jetty.http.HttpFields;
50 import org.eclipse.jetty.http.HttpHeader;
51 import org.eclipse.jetty.http.HttpMethod;
52 import org.eclipse.jetty.http.MimeTypes;
53 import org.eclipse.jetty.http.PathMap.MappedEntry;
54 import org.eclipse.jetty.http.PreEncodedHttpField;
55 import org.eclipse.jetty.http.ResourceHttpContent;
56 import org.eclipse.jetty.io.WriterOutputStream;
57 import org.eclipse.jetty.server.HttpOutput;
58 import org.eclipse.jetty.server.InclusiveByteRange;
59 import org.eclipse.jetty.server.Request;
60 import org.eclipse.jetty.server.ResourceCache;
61 import org.eclipse.jetty.server.ResourceContentFactory;
62 import org.eclipse.jetty.server.Response;
63 import org.eclipse.jetty.server.handler.ContextHandler;
64 import org.eclipse.jetty.util.BufferUtil;
65 import org.eclipse.jetty.util.Callback;
66 import org.eclipse.jetty.util.IO;
67 import org.eclipse.jetty.util.MultiPartOutputStream;
68 import org.eclipse.jetty.util.QuotedStringTokenizer;
69 import org.eclipse.jetty.util.URIUtil;
70 import org.eclipse.jetty.util.log.Log;
71 import org.eclipse.jetty.util.log.Logger;
72 import org.eclipse.jetty.util.resource.Resource;
73 import org.eclipse.jetty.util.resource.ResourceCollection;
74 import org.eclipse.jetty.util.resource.ResourceFactory;
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
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147 public class DefaultServlet extends HttpServlet implements ResourceFactory
148 {
149 private static final Logger LOG = Log.getLogger(DefaultServlet.class);
150
151 private static final long serialVersionUID = 4930458713846881193L;
152
153 private static final PreEncodedHttpField ACCEPT_RANGES = new PreEncodedHttpField(HttpHeader.ACCEPT_RANGES, "bytes");
154
155 private ServletContext _servletContext;
156 private ContextHandler _contextHandler;
157
158 private boolean _acceptRanges=true;
159 private boolean _dirAllowed=true;
160 private boolean _welcomeServlets=false;
161 private boolean _welcomeExactServlets=false;
162 private boolean _redirectWelcome=false;
163 private boolean _gzip=false;
164 private boolean _pathInfoOnly=false;
165 private boolean _etags=false;
166
167 private Resource _resourceBase;
168 private ResourceCache _cache;
169 private HttpContent.Factory _contentFactory;
170
171 private MimeTypes _mimeTypes;
172 private String[] _welcomes;
173 private Resource _stylesheet;
174 private boolean _useFileMappedBuffer=false;
175 private HttpField _cacheControl;
176 private String _relativeResourceBase;
177 private ServletHandler _servletHandler;
178 private ServletHolder _defaultHolder;
179 private List<String> _gzipEquivalentFileExtensions;
180
181
182 @Override
183 public void init()
184 throws UnavailableException
185 {
186 _servletContext=getServletContext();
187 _contextHandler = initContextHandler(_servletContext);
188
189 _mimeTypes = _contextHandler.getMimeTypes();
190
191 _welcomes = _contextHandler.getWelcomeFiles();
192 if (_welcomes==null)
193 _welcomes=new String[] {"index.html","index.jsp"};
194
195 _acceptRanges=getInitBoolean("acceptRanges",_acceptRanges);
196 _dirAllowed=getInitBoolean("dirAllowed",_dirAllowed);
197 _redirectWelcome=getInitBoolean("redirectWelcome",_redirectWelcome);
198 _gzip=getInitBoolean("gzip",_gzip);
199 _pathInfoOnly=getInitBoolean("pathInfoOnly",_pathInfoOnly);
200
201 if ("exact".equals(getInitParameter("welcomeServlets")))
202 {
203 _welcomeExactServlets=true;
204 _welcomeServlets=false;
205 }
206 else
207 _welcomeServlets=getInitBoolean("welcomeServlets", _welcomeServlets);
208
209 _useFileMappedBuffer=getInitBoolean("useFileMappedBuffer",_useFileMappedBuffer);
210
211 _relativeResourceBase = getInitParameter("relativeResourceBase");
212
213 String rb=getInitParameter("resourceBase");
214 if (rb!=null)
215 {
216 if (_relativeResourceBase!=null)
217 throw new UnavailableException("resourceBase & relativeResourceBase");
218 try{_resourceBase=_contextHandler.newResource(rb);}
219 catch (Exception e)
220 {
221 LOG.warn(Log.EXCEPTION,e);
222 throw new UnavailableException(e.toString());
223 }
224 }
225
226 String css=getInitParameter("stylesheet");
227 try
228 {
229 if(css!=null)
230 {
231 _stylesheet = Resource.newResource(css);
232 if(!_stylesheet.exists())
233 {
234 LOG.warn("!" + css);
235 _stylesheet = null;
236 }
237 }
238 if(_stylesheet == null)
239 {
240 _stylesheet = Resource.newResource(this.getClass().getResource("/jetty-dir.css"));
241 }
242 }
243 catch(Exception e)
244 {
245 LOG.warn(e.toString());
246 LOG.debug(e);
247 }
248
249 String cc=getInitParameter("cacheControl");
250 if (cc!=null)
251 _cacheControl=new PreEncodedHttpField(HttpHeader.CACHE_CONTROL, cc);
252
253 String resourceCache = getInitParameter("resourceCache");
254 int max_cache_size=getInitInt("maxCacheSize", -2);
255 int max_cached_file_size=getInitInt("maxCachedFileSize", -2);
256 int max_cached_files=getInitInt("maxCachedFiles", -2);
257 if (resourceCache!=null)
258 {
259 if (max_cache_size!=-1 || max_cached_file_size!= -2 || max_cached_files!=-2)
260 LOG.debug("ignoring resource cache configuration, using resourceCache attribute");
261 if (_relativeResourceBase!=null || _resourceBase!=null)
262 throw new UnavailableException("resourceCache specified with resource bases");
263 _cache=(ResourceCache)_servletContext.getAttribute(resourceCache);
264
265 if (LOG.isDebugEnabled())
266 LOG.debug("Cache {}={}",resourceCache,_contentFactory);
267 }
268
269 _etags = getInitBoolean("etags",_etags);
270
271 try
272 {
273 if (_cache==null && (max_cached_files!=-2 || max_cache_size!=-2 || max_cached_file_size!=-2))
274 {
275 _cache = new ResourceCache(null,this,_mimeTypes,_useFileMappedBuffer,_etags,_gzip);
276 if (max_cache_size>=0)
277 _cache.setMaxCacheSize(max_cache_size);
278 if (max_cached_file_size>=-1)
279 _cache.setMaxCachedFileSize(max_cached_file_size);
280 if (max_cached_files>=-1)
281 _cache.setMaxCachedFiles(max_cached_files);
282 _servletContext.setAttribute(resourceCache==null?"resourceCache":resourceCache,_cache);
283 }
284 }
285 catch (Exception e)
286 {
287 LOG.warn(Log.EXCEPTION,e);
288 throw new UnavailableException(e.toString());
289 }
290
291 if (_cache!=null)
292 _contentFactory=_cache;
293 else
294 {
295 _contentFactory=new ResourceContentFactory(this,_mimeTypes,_gzip);
296 if (resourceCache!=null)
297 _servletContext.setAttribute(resourceCache,_contentFactory);
298 }
299
300 _gzipEquivalentFileExtensions = new ArrayList<String>();
301 String otherGzipExtensions = getInitParameter("otherGzipFileExtensions");
302 if (otherGzipExtensions != null)
303 {
304
305 StringTokenizer tok = new StringTokenizer(otherGzipExtensions,",",false);
306 while (tok.hasMoreTokens())
307 {
308 String s = tok.nextToken().trim();
309 _gzipEquivalentFileExtensions.add((s.charAt(0)=='.'?s:"."+s));
310 }
311 }
312 else
313 {
314
315 _gzipEquivalentFileExtensions.add(".svgz");
316 }
317
318 _servletHandler= _contextHandler.getChildHandlerByClass(ServletHandler.class);
319 for (ServletHolder h :_servletHandler.getServlets())
320 if (h.getServletInstance()==this)
321 _defaultHolder=h;
322
323 if (LOG.isDebugEnabled())
324 LOG.debug("resource base = "+_resourceBase);
325 }
326
327
328
329
330
331
332
333
334
335 protected ContextHandler initContextHandler(ServletContext servletContext)
336 {
337 ContextHandler.Context scontext=ContextHandler.getCurrentContext();
338 if (scontext==null)
339 {
340 if (servletContext instanceof ContextHandler.Context)
341 return ((ContextHandler.Context)servletContext).getContextHandler();
342 else
343 throw new IllegalArgumentException("The servletContext " + servletContext + " " +
344 servletContext.getClass().getName() + " is not " + ContextHandler.Context.class.getName());
345 }
346 else
347 return ContextHandler.getCurrentContext().getContextHandler();
348 }
349
350
351 @Override
352 public String getInitParameter(String name)
353 {
354 String value=getServletContext().getInitParameter("org.eclipse.jetty.servlet.Default."+name);
355 if (value==null)
356 value=super.getInitParameter(name);
357 return value;
358 }
359
360
361 private boolean getInitBoolean(String name, boolean dft)
362 {
363 String value=getInitParameter(name);
364 if (value==null || value.length()==0)
365 return dft;
366 return (value.startsWith("t")||
367 value.startsWith("T")||
368 value.startsWith("y")||
369 value.startsWith("Y")||
370 value.startsWith("1"));
371 }
372
373
374 private int getInitInt(String name, int dft)
375 {
376 String value=getInitParameter(name);
377 if (value==null)
378 value=getInitParameter(name);
379 if (value!=null && value.length()>0)
380 return Integer.parseInt(value);
381 return dft;
382 }
383
384
385
386
387
388
389
390
391
392 @Override
393 public Resource getResource(String pathInContext)
394 {
395 Resource r=null;
396 if (_relativeResourceBase!=null)
397 pathInContext=URIUtil.addPaths(_relativeResourceBase,pathInContext);
398
399 try
400 {
401 if (_resourceBase!=null)
402 {
403 r = _resourceBase.addPath(pathInContext);
404 if (!_contextHandler.checkAlias(pathInContext,r))
405 r=null;
406 }
407 else if (_servletContext instanceof ContextHandler.Context)
408 {
409 r = _contextHandler.getResource(pathInContext);
410 }
411 else
412 {
413 URL u = _servletContext.getResource(pathInContext);
414 r = _contextHandler.newResource(u);
415 }
416
417 if (LOG.isDebugEnabled())
418 LOG.debug("Resource "+pathInContext+"="+r);
419 }
420 catch (IOException e)
421 {
422 LOG.ignore(e);
423 }
424
425 if((r==null || !r.exists()) && pathInContext.endsWith("/jetty-dir.css"))
426 r=_stylesheet;
427
428 return r;
429 }
430
431
432 @Override
433 protected void doGet(HttpServletRequest request, HttpServletResponse response)
434 throws ServletException, IOException
435 {
436 String servletPath=null;
437 String pathInfo=null;
438 Enumeration<String> reqRanges = null;
439 boolean included =request.getAttribute(RequestDispatcher.INCLUDE_REQUEST_URI)!=null;
440 if (included)
441 {
442 servletPath=(String)request.getAttribute(RequestDispatcher.INCLUDE_SERVLET_PATH);
443 pathInfo=(String)request.getAttribute(RequestDispatcher.INCLUDE_PATH_INFO);
444 if (servletPath==null)
445 {
446 servletPath=request.getServletPath();
447 pathInfo=request.getPathInfo();
448 }
449 }
450 else
451 {
452 servletPath = _pathInfoOnly?"/":request.getServletPath();
453 pathInfo = request.getPathInfo();
454
455
456 reqRanges = request.getHeaders(HttpHeader.RANGE.asString());
457 if (!hasDefinedRange(reqRanges))
458 reqRanges = null;
459 }
460
461 String pathInContext=URIUtil.addPaths(servletPath,pathInfo);
462 boolean endsWithSlash=(pathInfo==null?request.getServletPath():pathInfo).endsWith(URIUtil.SLASH);
463 boolean gzippable=_gzip && !endsWithSlash && !included && reqRanges==null;
464
465 HttpContent content=null;
466 boolean release_content=true;
467 try
468 {
469
470 content=_contentFactory.getContent(pathInContext,response.getBufferSize());
471 if (LOG.isDebugEnabled())
472 LOG.info("content={}",content);
473
474
475 if (content==null || !content.getResource().exists())
476 {
477 if (included)
478 throw new FileNotFoundException("!" + pathInContext);
479 response.sendError(HttpServletResponse.SC_NOT_FOUND);
480 return;
481 }
482
483
484 if (content.getResource().isDirectory())
485 {
486 sendWelcome(content,pathInContext,endsWithSlash,included,request,response);
487 return;
488 }
489
490
491 if (endsWithSlash && pathInContext.length()>1)
492 {
493 String q=request.getQueryString();
494 pathInContext=pathInContext.substring(0,pathInContext.length()-1);
495 if (q!=null&&q.length()!=0)
496 pathInContext+="?"+q;
497 response.sendRedirect(response.encodeRedirectURL(URIUtil.addPaths(_servletContext.getContextPath(),pathInContext)));
498 return;
499 }
500
501
502 if (!included && !passConditionalHeaders(request,response,content))
503 return;
504
505
506 HttpContent gzip_content = gzippable?content.getGzipContent():null;
507 if (gzip_content!=null)
508 {
509
510 response.addHeader(HttpHeader.VARY.asString(),HttpHeader.ACCEPT_ENCODING.asString());
511
512
513 String accept=request.getHeader(HttpHeader.ACCEPT_ENCODING.asString());
514 if (accept!=null && accept.indexOf("gzip")>=0)
515 {
516 if (LOG.isDebugEnabled())
517 LOG.debug("gzip={}",gzip_content);
518 content=gzip_content;
519 }
520 }
521
522
523 if (isGzippedContent(pathInContext))
524 response.setHeader(HttpHeader.CONTENT_ENCODING.asString(),"gzip");
525
526
527 release_content=sendData(request,response,included,content,reqRanges);
528
529 }
530 catch(IllegalArgumentException e)
531 {
532 LOG.warn(Log.EXCEPTION,e);
533 if(!response.isCommitted())
534 response.sendError(500, e.getMessage());
535 }
536 finally
537 {
538 if (release_content)
539 {
540 if (content!=null)
541 content.release();
542 }
543 }
544
545 }
546
547 protected void sendWelcome(HttpContent content, String pathInContext, boolean endsWithSlash, boolean included, HttpServletRequest request, HttpServletResponse response)
548 throws ServletException, IOException
549 {
550
551 if (!endsWithSlash || (pathInContext.length()==1 && request.getAttribute("org.eclipse.jetty.server.nullPathInfo")!=null))
552 {
553 StringBuffer buf=request.getRequestURL();
554 synchronized(buf)
555 {
556 int param=buf.lastIndexOf(";");
557 if (param<0)
558 buf.append('/');
559 else
560 buf.insert(param,'/');
561 String q=request.getQueryString();
562 if (q!=null&&q.length()!=0)
563 {
564 buf.append('?');
565 buf.append(q);
566 }
567 response.setContentLength(0);
568 response.sendRedirect(response.encodeRedirectURL(buf.toString()));
569 }
570 return;
571 }
572
573
574 String welcome=getWelcomeFile(pathInContext);
575 if (welcome!=null)
576 {
577 if (LOG.isDebugEnabled())
578 LOG.debug("welcome={}",welcome);
579 if (_redirectWelcome)
580 {
581
582 response.setContentLength(0);
583 String q=request.getQueryString();
584 if (q!=null&&q.length()!=0)
585 response.sendRedirect(response.encodeRedirectURL(URIUtil.addPaths( _servletContext.getContextPath(),welcome)+"?"+q));
586 else
587 response.sendRedirect(response.encodeRedirectURL(URIUtil.addPaths( _servletContext.getContextPath(),welcome)));
588 }
589 else
590 {
591
592 RequestDispatcher dispatcher=request.getRequestDispatcher(welcome);
593 if (dispatcher!=null)
594 {
595 if (included)
596 dispatcher.include(request,response);
597 else
598 {
599 request.setAttribute("org.eclipse.jetty.server.welcome",welcome);
600 dispatcher.forward(request,response);
601 }
602 }
603 }
604 return;
605 }
606
607 if (included || passConditionalHeaders(request,response, content))
608 sendDirectory(request,response,content.getResource(),pathInContext);
609 }
610
611
612 protected boolean isGzippedContent(String path)
613 {
614 if (path == null) return false;
615
616 for (String suffix:_gzipEquivalentFileExtensions)
617 if (path.endsWith(suffix))
618 return true;
619 return false;
620 }
621
622
623 private boolean hasDefinedRange(Enumeration<String> reqRanges)
624 {
625 return (reqRanges!=null && reqRanges.hasMoreElements());
626 }
627
628
629 @Override
630 protected void doPost(HttpServletRequest request, HttpServletResponse response)
631 throws ServletException, IOException
632 {
633 doGet(request,response);
634 }
635
636
637
638
639
640 @Override
641 protected void doTrace(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException
642 {
643 resp.sendError(HttpServletResponse.SC_METHOD_NOT_ALLOWED);
644 }
645
646
647 @Override
648 protected void doOptions(HttpServletRequest req, HttpServletResponse resp)
649 throws ServletException, IOException
650 {
651 resp.setHeader("Allow", "GET,HEAD,POST,OPTIONS");
652 }
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667 private String getWelcomeFile(String pathInContext) throws MalformedURLException, IOException
668 {
669 if (_welcomes==null)
670 return null;
671
672 String welcome_servlet=null;
673 for (int i=0;i<_welcomes.length;i++)
674 {
675 String welcome_in_context=URIUtil.addPaths(pathInContext,_welcomes[i]);
676 Resource welcome=getResource(welcome_in_context);
677 if (welcome!=null && welcome.exists())
678 return _welcomes[i];
679
680 if ((_welcomeServlets || _welcomeExactServlets) && welcome_servlet==null)
681 {
682 MappedEntry<?> entry=_servletHandler.getHolderEntry(welcome_in_context);
683 if (entry!=null && entry.getValue()!=_defaultHolder &&
684 (_welcomeServlets || (_welcomeExactServlets && entry.getKey().equals(welcome_in_context))))
685 welcome_servlet=welcome_in_context;
686
687 }
688 }
689 return welcome_servlet;
690 }
691
692
693
694
695 protected boolean passConditionalHeaders(HttpServletRequest request,HttpServletResponse response, HttpContent content)
696 throws IOException
697 {
698 try
699 {
700 String ifm=null;
701 String ifnm=null;
702 String ifms=null;
703 long ifums=-1;
704
705 if (request instanceof Request)
706 {
707
708 HttpFields fields = ((Request)request).getHttpFields();
709 for (int i=fields.size();i-->0;)
710 {
711 HttpField field=fields.getField(i);
712 if (field.getHeader() != null)
713 {
714 switch (field.getHeader())
715 {
716 case IF_MATCH:
717 ifm=field.getValue();
718 break;
719 case IF_NONE_MATCH:
720 ifnm=field.getValue();
721 break;
722 case IF_MODIFIED_SINCE:
723 ifms=field.getValue();
724 break;
725 case IF_UNMODIFIED_SINCE:
726 ifums=DateParser.parseDate(field.getValue());
727 break;
728 default:
729 }
730 }
731 }
732 }
733 else
734 {
735 ifm=request.getHeader(HttpHeader.IF_MATCH.asString());
736 ifnm=request.getHeader(HttpHeader.IF_NONE_MATCH.asString());
737 ifms=request.getHeader(HttpHeader.IF_MODIFIED_SINCE.asString());
738 ifums=request.getDateHeader(HttpHeader.IF_UNMODIFIED_SINCE.asString());
739 }
740
741 if (!HttpMethod.HEAD.is(request.getMethod()))
742 {
743 if (_etags)
744 {
745 String etag=content.getETagValue();
746 if (ifm!=null)
747 {
748 boolean match=false;
749 if (etag!=null)
750 {
751 QuotedStringTokenizer quoted = new QuotedStringTokenizer(ifm,", ",false,true);
752 while (!match && quoted.hasMoreTokens())
753 {
754 String tag = quoted.nextToken();
755 if (etag.equals(tag) || tag.endsWith(ETAG_GZIP_QUOTE) && etag.equals(removeGzipFromETag(tag)))
756 match=true;
757 }
758 }
759
760 if (!match)
761 {
762 response.setStatus(HttpServletResponse.SC_PRECONDITION_FAILED);
763 return false;
764 }
765 }
766
767 if (ifnm!=null && etag!=null)
768 {
769
770 if (etag.equals(ifnm) || ifnm.endsWith(ETAG_GZIP_QUOTE) && ifnm.indexOf(',')<0 && etag.equals(removeGzipFromETag(etag)))
771 {
772 response.setStatus(HttpServletResponse.SC_NOT_MODIFIED);
773 response.setHeader(HttpHeader.ETAG.asString(),ifnm);
774 return false;
775 }
776
777
778 QuotedStringTokenizer quoted = new QuotedStringTokenizer(ifnm,", ",false,true);
779 while (quoted.hasMoreTokens())
780 {
781 String tag = quoted.nextToken();
782 if (etag.equals(tag) || tag.endsWith(ETAG_GZIP_QUOTE) && etag.equals(removeGzipFromETag(tag)))
783 {
784 response.setStatus(HttpServletResponse.SC_NOT_MODIFIED);
785 response.setHeader(HttpHeader.ETAG.asString(),tag);
786 return false;
787 }
788 }
789
790
791 return true;
792 }
793 }
794
795
796 if (ifms!=null)
797 {
798
799 String mdlm=content.getLastModifiedValue();
800 if (mdlm!=null && ifms.equals(mdlm))
801 {
802 response.setStatus(HttpServletResponse.SC_NOT_MODIFIED);
803 if (_etags)
804 response.setHeader(HttpHeader.ETAG.asString(),content.getETagValue());
805 response.flushBuffer();
806 return false;
807 }
808
809 long ifmsl=request.getDateHeader(HttpHeader.IF_MODIFIED_SINCE.asString());
810 if (ifmsl!=-1 && content.getResource().lastModified()/1000 <= ifmsl/1000)
811 {
812 response.setStatus(HttpServletResponse.SC_NOT_MODIFIED);
813 if (_etags)
814 response.setHeader(HttpHeader.ETAG.asString(),content.getETagValue());
815 response.flushBuffer();
816 return false;
817 }
818 }
819
820
821 if (ifums!=-1 && content.getResource().lastModified()/1000 > ifums/1000)
822 {
823 response.sendError(HttpServletResponse.SC_PRECONDITION_FAILED);
824 return false;
825 }
826
827 }
828 }
829 catch(IllegalArgumentException iae)
830 {
831 if(!response.isCommitted())
832 response.sendError(400, iae.getMessage());
833 throw iae;
834 }
835 return true;
836 }
837
838
839
840 protected void sendDirectory(HttpServletRequest request,
841 HttpServletResponse response,
842 Resource resource,
843 String pathInContext)
844 throws IOException
845 {
846 if (!_dirAllowed)
847 {
848 response.sendError(HttpServletResponse.SC_FORBIDDEN);
849 return;
850 }
851
852 byte[] data=null;
853 String base = URIUtil.addPaths(request.getRequestURI(),URIUtil.SLASH);
854
855
856 if (_resourceBase != null)
857 {
858
859 if (_resourceBase instanceof ResourceCollection)
860 resource=_resourceBase.addPath(pathInContext);
861 }
862
863 else if (_contextHandler.getBaseResource() instanceof ResourceCollection)
864 resource=_contextHandler.getBaseResource().addPath(pathInContext);
865
866 String dir = resource.getListHTML(base,pathInContext.length()>1);
867 if (dir==null)
868 {
869 response.sendError(HttpServletResponse.SC_FORBIDDEN,
870 "No directory");
871 return;
872 }
873
874 data=dir.getBytes("utf-8");
875 response.setContentType("text/html;charset=utf-8");
876 response.setContentLength(data.length);
877 response.getOutputStream().write(data);
878 }
879
880
881 protected boolean sendData(HttpServletRequest request,
882 HttpServletResponse response,
883 boolean include,
884 final HttpContent content,
885 Enumeration<String> reqRanges)
886 throws IOException
887 {
888 final long content_length = content.getContentLengthValue();
889
890
891 OutputStream out =null;
892 boolean written;
893 try
894 {
895 out = response.getOutputStream();
896
897
898 written = out instanceof HttpOutput
899 ? ((HttpOutput)out).isWritten()
900 : true;
901 }
902 catch(IllegalStateException e)
903 {
904 out = new WriterOutputStream(response.getWriter());
905 written=true;
906 }
907
908 if (LOG.isDebugEnabled())
909 LOG.debug(String.format("sendData content=%s out=%s async=%b",content,out,request.isAsyncSupported()));
910
911 if ( reqRanges == null || !reqRanges.hasMoreElements() || content_length<0)
912 {
913
914 if (include)
915 {
916
917 content.getResource().writeTo(out,0,content_length);
918 }
919
920 else if (written || !(out instanceof HttpOutput))
921 {
922
923 putHeaders(response,content,written?-1:0);
924 ByteBuffer buffer = content.getIndirectBuffer();
925 if (buffer!=null)
926 BufferUtil.writeTo(buffer,out);
927 else
928 content.getResource().writeTo(out,0,content_length);
929 }
930
931 else
932 {
933
934 putHeaders(response,content,0);
935
936
937 if (request.isAsyncSupported())
938 {
939 final AsyncContext context = request.startAsync();
940 context.setTimeout(0);
941
942 ((HttpOutput)out).sendContent(content,new Callback()
943 {
944 @Override
945 public void succeeded()
946 {
947 context.complete();
948 content.release();
949 }
950
951 @Override
952 public void failed(Throwable x)
953 {
954 if (x instanceof IOException)
955 LOG.debug(x);
956 else
957 LOG.warn(x);
958 context.complete();
959 content.release();
960 }
961
962 @Override
963 public String toString()
964 {
965 return String.format("DefaultServlet@%x$CB", DefaultServlet.this.hashCode());
966 }
967 });
968 return false;
969 }
970
971 ((HttpOutput)out).sendContent(content);
972 }
973 }
974 else
975 {
976
977 List<InclusiveByteRange> ranges =InclusiveByteRange.satisfiableRanges(reqRanges,content_length);
978
979
980 if (ranges==null || ranges.size()==0)
981 {
982 putHeaders(response,content,0);
983 response.setStatus(HttpServletResponse.SC_REQUESTED_RANGE_NOT_SATISFIABLE);
984 response.setHeader(HttpHeader.CONTENT_RANGE.asString(),
985 InclusiveByteRange.to416HeaderRangeString(content_length));
986 content.getResource().writeTo(out,0,content_length);
987 return true;
988 }
989
990
991
992 if ( ranges.size()== 1)
993 {
994 InclusiveByteRange singleSatisfiableRange = ranges.get(0);
995 long singleLength = singleSatisfiableRange.getSize(content_length);
996 putHeaders(response,content,singleLength);
997 response.setStatus(HttpServletResponse.SC_PARTIAL_CONTENT);
998 if (!response.containsHeader(HttpHeader.DATE.asString()))
999 response.addDateHeader(HttpHeader.DATE.asString(),System.currentTimeMillis());
1000 response.setHeader(HttpHeader.CONTENT_RANGE.asString(),
1001 singleSatisfiableRange.toHeaderRangeString(content_length));
1002 content.getResource().writeTo(out,singleSatisfiableRange.getFirst(content_length),singleLength);
1003 return true;
1004 }
1005
1006
1007
1008
1009
1010 putHeaders(response,content,-1);
1011 String mimetype=(content==null?null:content.getContentTypeValue());
1012 if (mimetype==null)
1013 LOG.warn("Unknown mimetype for "+request.getRequestURI());
1014 MultiPartOutputStream multi = new MultiPartOutputStream(out);
1015 response.setStatus(HttpServletResponse.SC_PARTIAL_CONTENT);
1016 if (!response.containsHeader(HttpHeader.DATE.asString()))
1017 response.addDateHeader(HttpHeader.DATE.asString(),System.currentTimeMillis());
1018
1019
1020
1021
1022 String ctp;
1023 if (request.getHeader(HttpHeader.REQUEST_RANGE.asString())!=null)
1024 ctp = "multipart/x-byteranges; boundary=";
1025 else
1026 ctp = "multipart/byteranges; boundary=";
1027 response.setContentType(ctp+multi.getBoundary());
1028
1029 InputStream in=content.getResource().getInputStream();
1030 long pos=0;
1031
1032
1033 int length=0;
1034 String[] header = new String[ranges.size()];
1035 for (int i=0;i<ranges.size();i++)
1036 {
1037 InclusiveByteRange ibr = ranges.get(i);
1038 header[i]=ibr.toHeaderRangeString(content_length);
1039 length+=
1040 ((i>0)?2:0)+
1041 2+multi.getBoundary().length()+2+
1042 (mimetype==null?0:HttpHeader.CONTENT_TYPE.asString().length()+2+mimetype.length())+2+
1043 HttpHeader.CONTENT_RANGE.asString().length()+2+header[i].length()+2+
1044 2+
1045 (ibr.getLast(content_length)-ibr.getFirst(content_length))+1;
1046 }
1047 length+=2+2+multi.getBoundary().length()+2+2;
1048 response.setContentLength(length);
1049
1050 for (int i=0;i<ranges.size();i++)
1051 {
1052 InclusiveByteRange ibr = ranges.get(i);
1053 multi.startPart(mimetype,new String[]{HttpHeader.CONTENT_RANGE+": "+header[i]});
1054
1055 long start=ibr.getFirst(content_length);
1056 long size=ibr.getSize(content_length);
1057 if (in!=null)
1058 {
1059
1060 if (start<pos)
1061 {
1062 in.close();
1063 in=content.getResource().getInputStream();
1064 pos=0;
1065 }
1066 if (pos<start)
1067 {
1068 in.skip(start-pos);
1069 pos=start;
1070 }
1071
1072 IO.copy(in,multi,size);
1073 pos+=size;
1074 }
1075 else
1076
1077 content.getResource().writeTo(multi,start,size);
1078 }
1079 if (in!=null)
1080 in.close();
1081 multi.close();
1082 }
1083 return true;
1084 }
1085
1086
1087 protected void putHeaders(HttpServletResponse response,HttpContent content, long contentLength)
1088 {
1089 if (response instanceof Response)
1090 {
1091 Response r = (Response)response;
1092 r.putHeaders(content,contentLength,_etags);
1093 HttpFields f = r.getHttpFields();
1094 if (_acceptRanges)
1095 f.put(ACCEPT_RANGES);
1096
1097 if (_cacheControl!=null)
1098 f.put(_cacheControl);
1099 }
1100 else
1101 {
1102 Response.putHeaders(response,content,contentLength,_etags);
1103 if (_acceptRanges)
1104 response.setHeader(ACCEPT_RANGES.getName(),ACCEPT_RANGES.getValue());
1105
1106 if (_cacheControl!=null)
1107 response.setHeader(_cacheControl.getName(),_cacheControl.getValue());
1108 }
1109 }
1110
1111
1112
1113
1114
1115 @Override
1116 public void destroy()
1117 {
1118 if (_cache!=null)
1119 _cache.flushCache();
1120 super.destroy();
1121 }
1122
1123 }