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=true;
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 LOG.debug(x);
886 context.complete();
887 }
888 });
889 }
890
891 else
892 {
893 ((HttpOutput)out).sendContent(content);
894 }
895 }
896 }
897 else
898 {
899
900 List<InclusiveByteRange> ranges =InclusiveByteRange.satisfiableRanges(reqRanges,content_length);
901
902
903 if (ranges==null || ranges.size()==0)
904 {
905 writeHeaders(response, content, content_length);
906 response.setStatus(HttpServletResponse.SC_REQUESTED_RANGE_NOT_SATISFIABLE);
907 response.setHeader(HttpHeader.CONTENT_RANGE.asString(),
908 InclusiveByteRange.to416HeaderRangeString(content_length));
909 resource.writeTo(out,0,content_length);
910 return;
911 }
912
913
914
915 if ( ranges.size()== 1)
916 {
917 InclusiveByteRange singleSatisfiableRange = ranges.get(0);
918 long singleLength = singleSatisfiableRange.getSize(content_length);
919 writeHeaders(response,content,singleLength );
920 response.setStatus(HttpServletResponse.SC_PARTIAL_CONTENT);
921 response.setHeader(HttpHeader.CONTENT_RANGE.asString(),
922 singleSatisfiableRange.toHeaderRangeString(content_length));
923 resource.writeTo(out,singleSatisfiableRange.getFirst(content_length),singleLength);
924 return;
925 }
926
927
928
929
930
931 writeHeaders(response,content,-1);
932 String mimetype=(content==null?null:content.getContentType());
933 if (mimetype==null)
934 LOG.warn("Unknown mimetype for "+request.getRequestURI());
935 MultiPartOutputStream multi = new MultiPartOutputStream(out);
936 response.setStatus(HttpServletResponse.SC_PARTIAL_CONTENT);
937
938
939
940
941 String ctp;
942 if (request.getHeader(HttpHeader.REQUEST_RANGE.asString())!=null)
943 ctp = "multipart/x-byteranges; boundary=";
944 else
945 ctp = "multipart/byteranges; boundary=";
946 response.setContentType(ctp+multi.getBoundary());
947
948 InputStream in=resource.getInputStream();
949 long pos=0;
950
951
952 int length=0;
953 String[] header = new String[ranges.size()];
954 for (int i=0;i<ranges.size();i++)
955 {
956 InclusiveByteRange ibr = ranges.get(i);
957 header[i]=ibr.toHeaderRangeString(content_length);
958 length+=
959 ((i>0)?2:0)+
960 2+multi.getBoundary().length()+2+
961 (mimetype==null?0:HttpHeader.CONTENT_TYPE.asString().length()+2+mimetype.length())+2+
962 HttpHeader.CONTENT_RANGE.asString().length()+2+header[i].length()+2+
963 2+
964 (ibr.getLast(content_length)-ibr.getFirst(content_length))+1;
965 }
966 length+=2+2+multi.getBoundary().length()+2+2;
967 response.setContentLength(length);
968
969 for (int i=0;i<ranges.size();i++)
970 {
971 InclusiveByteRange ibr = ranges.get(i);
972 multi.startPart(mimetype,new String[]{HttpHeader.CONTENT_RANGE+": "+header[i]});
973
974 long start=ibr.getFirst(content_length);
975 long size=ibr.getSize(content_length);
976 if (in!=null)
977 {
978
979 if (start<pos)
980 {
981 in.close();
982 in=resource.getInputStream();
983 pos=0;
984 }
985 if (pos<start)
986 {
987 in.skip(start-pos);
988 pos=start;
989 }
990
991 IO.copy(in,multi,size);
992 pos+=size;
993 }
994 else
995
996 (resource).writeTo(multi,start,size);
997 }
998 if (in!=null)
999 in.close();
1000 multi.close();
1001 }
1002 return;
1003 }
1004
1005
1006 protected void writeHeaders(HttpServletResponse response,HttpContent content,long count)
1007 {
1008 if (content.getContentType()!=null && response.getContentType()==null)
1009 response.setContentType(content.getContentType().toString());
1010
1011 if (response instanceof Response)
1012 {
1013 Response r=(Response)response;
1014 HttpFields fields = r.getHttpFields();
1015
1016 if (content.getLastModified()!=null)
1017 fields.put(HttpHeader.LAST_MODIFIED,content.getLastModified());
1018 else if (content.getResource()!=null)
1019 {
1020 long lml=content.getResource().lastModified();
1021 if (lml!=-1)
1022 fields.putDateField(HttpHeader.LAST_MODIFIED,lml);
1023 }
1024
1025 if (count != -1)
1026 r.setLongContentLength(count);
1027
1028 writeOptionHeaders(fields);
1029
1030 if (_etags)
1031 fields.put(HttpHeader.ETAG,content.getETag());
1032 }
1033 else
1034 {
1035 long lml=content.getResource().lastModified();
1036 if (lml>=0)
1037 response.setDateHeader(HttpHeader.LAST_MODIFIED.asString(),lml);
1038
1039 if (count != -1)
1040 {
1041 if (count<Integer.MAX_VALUE)
1042 response.setContentLength((int)count);
1043 else
1044 response.setHeader(HttpHeader.CONTENT_LENGTH.asString(),Long.toString(count));
1045 }
1046
1047 writeOptionHeaders(response);
1048
1049 if (_etags)
1050 response.setHeader(HttpHeader.ETAG.asString(),content.getETag());
1051 }
1052 }
1053
1054
1055 protected void writeOptionHeaders(HttpFields fields)
1056 {
1057 if (_acceptRanges)
1058 fields.put(HttpHeader.ACCEPT_RANGES,"bytes");
1059
1060 if (_cacheControl!=null)
1061 fields.put(HttpHeader.CACHE_CONTROL,_cacheControl);
1062 }
1063
1064
1065 protected void writeOptionHeaders(HttpServletResponse response)
1066 {
1067 if (_acceptRanges)
1068 response.setHeader(HttpHeader.ACCEPT_RANGES.asString(),"bytes");
1069
1070 if (_cacheControl!=null)
1071 response.setHeader(HttpHeader.CACHE_CONTROL.asString(),_cacheControl);
1072 }
1073
1074
1075
1076
1077
1078 @Override
1079 public void destroy()
1080 {
1081 if (_cache!=null)
1082 _cache.flushCache();
1083 super.destroy();
1084 }
1085
1086 }