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