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