1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19 package org.eclipse.jetty.server.handler;
20
21 import java.io.IOException;
22 import java.io.OutputStream;
23 import java.net.MalformedURLException;
24 import java.nio.ByteBuffer;
25 import java.nio.channels.ReadableByteChannel;
26
27 import javax.servlet.AsyncContext;
28 import javax.servlet.RequestDispatcher;
29 import javax.servlet.ServletException;
30 import javax.servlet.http.HttpServletRequest;
31 import javax.servlet.http.HttpServletResponse;
32
33 import org.eclipse.jetty.http.HttpFields;
34 import org.eclipse.jetty.http.HttpHeader;
35 import org.eclipse.jetty.http.HttpMethod;
36 import org.eclipse.jetty.http.HttpStatus;
37 import org.eclipse.jetty.http.MimeTypes;
38 import org.eclipse.jetty.io.WriterOutputStream;
39 import org.eclipse.jetty.server.HttpOutput;
40 import org.eclipse.jetty.server.Request;
41 import org.eclipse.jetty.server.Response;
42 import org.eclipse.jetty.server.handler.ContextHandler.Context;
43 import org.eclipse.jetty.util.BufferUtil;
44 import org.eclipse.jetty.util.Callback;
45 import org.eclipse.jetty.util.URIUtil;
46 import org.eclipse.jetty.util.log.Log;
47 import org.eclipse.jetty.util.log.Logger;
48 import org.eclipse.jetty.util.resource.PathResource;
49 import org.eclipse.jetty.util.resource.Resource;
50
51
52
53
54
55
56
57
58
59
60
61 public class ResourceHandler extends HandlerWrapper
62 {
63 private static final Logger LOG = Log.getLogger(ResourceHandler.class);
64
65 ContextHandler _context;
66 Resource _baseResource;
67 Resource _defaultStylesheet;
68 Resource _stylesheet;
69 String[] _welcomeFiles={"index.html"};
70 MimeTypes _mimeTypes = new MimeTypes();
71 String _cacheControl;
72 boolean _directory;
73 boolean _etags;
74 int _minMemoryMappedContentLength=1024;
75 int _minAsyncContentLength=0;
76
77
78 public ResourceHandler()
79 {
80
81 }
82
83
84 public MimeTypes getMimeTypes()
85 {
86 return _mimeTypes;
87 }
88
89
90 public void setMimeTypes(MimeTypes mimeTypes)
91 {
92 _mimeTypes = mimeTypes;
93 }
94
95
96
97
98
99 public boolean isDirectoriesListed()
100 {
101 return _directory;
102 }
103
104
105
106
107
108 public void setDirectoriesListed(boolean directory)
109 {
110 _directory = directory;
111 }
112
113
114
115
116
117
118
119 public int getMinMemoryMappedContentLength()
120 {
121 return _minMemoryMappedContentLength;
122 }
123
124
125
126
127
128
129
130 public void setMinMemoryMappedContentLength(int minMemoryMappedFileSize)
131 {
132 _minMemoryMappedContentLength = minMemoryMappedFileSize;
133 }
134
135
136
137
138
139
140
141 public int getMinAsyncContentLength()
142 {
143 return _minAsyncContentLength;
144 }
145
146
147
148
149
150
151
152 public void setMinAsyncContentLength(int minAsyncContentLength)
153 {
154 _minAsyncContentLength = minAsyncContentLength;
155 }
156
157
158
159
160
161 public boolean isEtags()
162 {
163 return _etags;
164 }
165
166
167
168
169
170 public void setEtags(boolean etags)
171 {
172 _etags = etags;
173 }
174
175
176 @Override
177 public void doStart()
178 throws Exception
179 {
180 Context scontext = ContextHandler.getCurrentContext();
181 _context = (scontext==null?null:scontext.getContextHandler());
182
183 super.doStart();
184 }
185
186
187
188
189
190 public Resource getBaseResource()
191 {
192 if (_baseResource==null)
193 return null;
194 return _baseResource;
195 }
196
197
198
199
200
201 public String getResourceBase()
202 {
203 if (_baseResource==null)
204 return null;
205 return _baseResource.toString();
206 }
207
208
209
210
211
212
213 public void setBaseResource(Resource base)
214 {
215 _baseResource=base;
216 }
217
218
219
220
221
222 public void setResourceBase(String resourceBase)
223 {
224 try
225 {
226 setBaseResource(Resource.newResource(resourceBase));
227 }
228 catch (Exception e)
229 {
230 LOG.warn(e.toString());
231 LOG.debug(e);
232 throw new IllegalArgumentException(resourceBase);
233 }
234 }
235
236
237
238
239
240 public Resource getStylesheet()
241 {
242 if(_stylesheet != null)
243 {
244 return _stylesheet;
245 }
246 else
247 {
248 if(_defaultStylesheet == null)
249 {
250 _defaultStylesheet = Resource.newResource(this.getClass().getResource("/jetty-dir.css"));
251 }
252 return _defaultStylesheet;
253 }
254 }
255
256
257
258
259
260 public void setStylesheet(String stylesheet)
261 {
262 try
263 {
264 _stylesheet = Resource.newResource(stylesheet);
265 if(!_stylesheet.exists())
266 {
267 LOG.warn("unable to find custom stylesheet: " + stylesheet);
268 _stylesheet = null;
269 }
270 }
271 catch(Exception e)
272 {
273 LOG.warn(e.toString());
274 LOG.debug(e);
275 throw new IllegalArgumentException(stylesheet);
276 }
277 }
278
279
280
281
282
283 public String getCacheControl()
284 {
285 return _cacheControl;
286 }
287
288
289
290
291
292 public void setCacheControl(String cacheControl)
293 {
294 _cacheControl=cacheControl;
295 }
296
297
298
299
300 public Resource getResource(String path) throws MalformedURLException
301 {
302 if (path==null || !path.startsWith("/"))
303 throw new MalformedURLException(path);
304
305 if (LOG.isDebugEnabled())
306 LOG.debug("{} getResource({})",_context==null?_baseResource:_context,_baseResource,path);
307
308 Resource base = _baseResource;
309 if (base==null)
310 {
311 if (_context==null)
312 return null;
313 return _context.getResource(path);
314 }
315
316 try
317 {
318 path=URIUtil.canonicalPath(path);
319 Resource r = base.addPath(path);
320 if (r!=null && r.isAlias() && (_context==null || !_context.checkAlias(path, r)))
321 {
322 if (LOG.isDebugEnabled())
323 LOG.debug("resource={} alias={}",r,r.getAlias());
324 return null;
325 }
326 return r;
327 }
328 catch(Exception e)
329 {
330 LOG.ignore(e);
331 }
332
333 return null;
334 }
335
336
337 protected Resource getResource(HttpServletRequest request) throws MalformedURLException
338 {
339 String servletPath;
340 String pathInfo;
341 Boolean included = request.getAttribute(RequestDispatcher.INCLUDE_REQUEST_URI) != null;
342 if (included != null && included.booleanValue())
343 {
344 servletPath = (String)request.getAttribute(RequestDispatcher.INCLUDE_SERVLET_PATH);
345 pathInfo = (String)request.getAttribute(RequestDispatcher.INCLUDE_PATH_INFO);
346
347 if (servletPath == null && pathInfo == null)
348 {
349 servletPath = request.getServletPath();
350 pathInfo = request.getPathInfo();
351 }
352 }
353 else
354 {
355 servletPath = request.getServletPath();
356 pathInfo = request.getPathInfo();
357 }
358
359 String pathInContext=URIUtil.addPaths(servletPath,pathInfo);
360 return getResource(pathInContext);
361 }
362
363
364
365 public String[] getWelcomeFiles()
366 {
367 return _welcomeFiles;
368 }
369
370
371 public void setWelcomeFiles(String[] welcomeFiles)
372 {
373 _welcomeFiles=welcomeFiles;
374 }
375
376
377 protected Resource getWelcome(Resource directory) throws MalformedURLException, IOException
378 {
379 for (int i=0;i<_welcomeFiles.length;i++)
380 {
381 Resource welcome=directory.addPath(_welcomeFiles[i]);
382 if (welcome.exists() && !welcome.isDirectory())
383 return welcome;
384 }
385
386 return null;
387 }
388
389
390
391
392
393 @Override
394 public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
395 {
396 if (baseRequest.isHandled())
397 return;
398
399 boolean skipContentBody = false;
400
401 if(!HttpMethod.GET.is(request.getMethod()))
402 {
403 if(!HttpMethod.HEAD.is(request.getMethod()))
404 {
405
406 super.handle(target, baseRequest, request, response);
407 return;
408 }
409 skipContentBody = true;
410 }
411
412 Resource resource = getResource(request);
413
414 if (LOG.isDebugEnabled())
415 {
416 if (resource==null)
417 LOG.debug("resource=null");
418 else
419 LOG.debug("resource={} alias={} exists={}",resource,resource.getAlias(),resource.exists());
420 }
421
422
423
424 if (resource==null || !resource.exists())
425 {
426
427 if (target.endsWith("/jetty-dir.css"))
428 {
429 resource = getStylesheet();
430 if (resource==null)
431 return;
432 response.setContentType("text/css");
433 }
434 else
435 {
436
437 super.handle(target, baseRequest, request, response);
438 return;
439 }
440 }
441
442
443 baseRequest.setHandled(true);
444
445
446 if (resource.isDirectory())
447 {
448 String pathInfo = request.getPathInfo();
449 boolean endsWithSlash=(pathInfo==null?request.getServletPath():pathInfo).endsWith(URIUtil.SLASH);
450 if (!endsWithSlash)
451 {
452 response.sendRedirect(response.encodeRedirectURL(URIUtil.addPaths(request.getRequestURI(),URIUtil.SLASH)));
453 return;
454 }
455
456 Resource welcome=getWelcome(resource);
457 if (welcome!=null && welcome.exists())
458 resource=welcome;
459 else
460 {
461 doDirectory(request,response,resource);
462 baseRequest.setHandled(true);
463 return;
464 }
465 }
466
467
468 long last_modified=resource.lastModified();
469 String etag=null;
470 if (_etags)
471 {
472
473 String ifnm = request.getHeader(HttpHeader.IF_NONE_MATCH.asString());
474 etag=resource.getWeakETag();
475 if (ifnm!=null && resource!=null && ifnm.equals(etag))
476 {
477 response.setStatus(HttpStatus.NOT_MODIFIED_304);
478 baseRequest.getResponse().getHttpFields().put(HttpHeader.ETAG,etag);
479 return;
480 }
481 }
482
483
484 if (last_modified>0)
485 {
486 long if_modified=request.getDateHeader(HttpHeader.IF_MODIFIED_SINCE.asString());
487 if (if_modified>0 && last_modified/1000<=if_modified/1000)
488 {
489 response.setStatus(HttpStatus.NOT_MODIFIED_304);
490 return;
491 }
492 }
493
494
495 String mime=_mimeTypes.getMimeByExtension(resource.toString());
496 if (mime==null)
497 mime=_mimeTypes.getMimeByExtension(request.getPathInfo());
498 doResponseHeaders(response,resource,mime);
499 if (_etags)
500 baseRequest.getResponse().getHttpFields().put(HttpHeader.ETAG,etag);
501
502 if(skipContentBody)
503 return;
504
505
506
507 OutputStream out =null;
508 try {out = response.getOutputStream();}
509 catch(IllegalStateException e) {out = new WriterOutputStream(response.getWriter());}
510
511
512 if (!(out instanceof HttpOutput))
513
514 resource.writeTo(out,0,resource.length());
515 else
516 {
517
518 int min_async_size=_minAsyncContentLength==0?response.getBufferSize():_minAsyncContentLength;
519
520 if (request.isAsyncSupported() &&
521 min_async_size>0 &&
522 resource.length()>=min_async_size)
523 {
524 final AsyncContext async = request.startAsync();
525 async.setTimeout(0);
526 Callback callback = new Callback()
527 {
528 @Override
529 public void succeeded()
530 {
531 async.complete();
532 }
533
534 @Override
535 public void failed(Throwable x)
536 {
537 LOG.warn(x.toString());
538 LOG.debug(x);
539 async.complete();
540 }
541 };
542
543
544 if (_minMemoryMappedContentLength>0 &&
545 resource.length()>_minMemoryMappedContentLength &&
546 resource.length()<Integer.MAX_VALUE &&
547 resource instanceof PathResource)
548 {
549 ByteBuffer buffer = BufferUtil.toMappedBuffer(resource.getFile());
550 ((HttpOutput)out).sendContent(buffer,callback);
551 }
552 else
553 {
554
555 ReadableByteChannel channel= resource.getReadableByteChannel();
556 if (channel!=null)
557 ((HttpOutput)out).sendContent(channel,callback);
558 else
559 ((HttpOutput)out).sendContent(resource.getInputStream(),callback);
560 }
561 }
562 else
563 {
564
565 if (_minMemoryMappedContentLength>0 &&
566 resource.length()>_minMemoryMappedContentLength &&
567 resource instanceof PathResource)
568 {
569 ByteBuffer buffer = BufferUtil.toMappedBuffer(resource.getFile());
570 ((HttpOutput)out).sendContent(buffer);
571 }
572 else
573 {
574 ReadableByteChannel channel= resource.getReadableByteChannel();
575 if (channel!=null)
576 ((HttpOutput)out).sendContent(channel);
577 else
578 ((HttpOutput)out).sendContent(resource.getInputStream());
579 }
580 }
581 }
582 }
583
584
585 protected void doDirectory(HttpServletRequest request,HttpServletResponse response, Resource resource)
586 throws IOException
587 {
588 if (_directory)
589 {
590 String listing = resource.getListHTML(request.getRequestURI(),request.getPathInfo().lastIndexOf("/") > 0);
591 response.setContentType("text/html;charset=utf-8");
592 response.getWriter().println(listing);
593 }
594 else
595 response.sendError(HttpStatus.FORBIDDEN_403);
596 }
597
598
599
600
601
602
603
604
605
606 protected void doResponseHeaders(HttpServletResponse response, Resource resource, String mimeType)
607 {
608 if (mimeType!=null)
609 response.setContentType(mimeType);
610
611 long length=resource.length();
612
613 if (response instanceof Response)
614 {
615 HttpFields fields = ((Response)response).getHttpFields();
616
617 if (length>0)
618 ((Response)response).setLongContentLength(length);
619
620 if (_cacheControl!=null)
621 fields.put(HttpHeader.CACHE_CONTROL,_cacheControl);
622 }
623 else
624 {
625 if (length>Integer.MAX_VALUE)
626 response.setHeader(HttpHeader.CONTENT_LENGTH.asString(),Long.toString(length));
627 else if (length>0)
628 response.setContentLength((int)length);
629
630 if (_cacheControl!=null)
631 response.setHeader(HttpHeader.CACHE_CONTROL.asString(),_cacheControl);
632 }
633 }
634 }