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