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