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