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