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 Resource base = _baseResource;
307 if (base==null)
308 {
309 if (_context==null)
310 return null;
311 base=_context.getBaseResource();
312 if (base==null)
313 return null;
314 }
315
316 try
317 {
318 path=URIUtil.canonicalPath(path);
319 return base.addPath(path);
320 }
321 catch(Exception e)
322 {
323 LOG.ignore(e);
324 }
325
326 return null;
327 }
328
329
330 protected Resource getResource(HttpServletRequest request) throws MalformedURLException
331 {
332 String servletPath;
333 String pathInfo;
334 Boolean included = request.getAttribute(RequestDispatcher.INCLUDE_REQUEST_URI) != null;
335 if (included != null && included.booleanValue())
336 {
337 servletPath = (String)request.getAttribute(RequestDispatcher.INCLUDE_SERVLET_PATH);
338 pathInfo = (String)request.getAttribute(RequestDispatcher.INCLUDE_PATH_INFO);
339
340 if (servletPath == null && pathInfo == null)
341 {
342 servletPath = request.getServletPath();
343 pathInfo = request.getPathInfo();
344 }
345 }
346 else
347 {
348 servletPath = request.getServletPath();
349 pathInfo = request.getPathInfo();
350 }
351
352 String pathInContext=URIUtil.addPaths(servletPath,pathInfo);
353 return getResource(pathInContext);
354 }
355
356
357
358 public String[] getWelcomeFiles()
359 {
360 return _welcomeFiles;
361 }
362
363
364 public void setWelcomeFiles(String[] welcomeFiles)
365 {
366 _welcomeFiles=welcomeFiles;
367 }
368
369
370 protected Resource getWelcome(Resource directory) throws MalformedURLException, IOException
371 {
372 for (int i=0;i<_welcomeFiles.length;i++)
373 {
374 Resource welcome=directory.addPath(_welcomeFiles[i]);
375 if (welcome.exists() && !welcome.isDirectory())
376 return welcome;
377 }
378
379 return null;
380 }
381
382
383
384
385
386 @Override
387 public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
388 {
389 if (baseRequest.isHandled())
390 return;
391
392 boolean skipContentBody = false;
393
394 if(!HttpMethod.GET.is(request.getMethod()))
395 {
396 if(!HttpMethod.HEAD.is(request.getMethod()))
397 {
398
399 super.handle(target, baseRequest, request, response);
400 return;
401 }
402 skipContentBody = true;
403 }
404
405 Resource resource = getResource(request);
406
407 if (resource==null || !resource.exists())
408 {
409
410 if (target.endsWith("/jetty-dir.css"))
411 {
412 resource = getStylesheet();
413 if (resource==null)
414 return;
415 response.setContentType("text/css");
416 }
417 else
418 {
419
420 super.handle(target, baseRequest, request, response);
421 return;
422 }
423 }
424
425
426 baseRequest.setHandled(true);
427
428
429 if (resource.isDirectory())
430 {
431 if (!request.getPathInfo().endsWith(URIUtil.SLASH))
432 {
433 response.sendRedirect(response.encodeRedirectURL(URIUtil.addPaths(request.getRequestURI(),URIUtil.SLASH)));
434 return;
435 }
436
437 Resource welcome=getWelcome(resource);
438 if (welcome!=null && welcome.exists())
439 resource=welcome;
440 else
441 {
442 doDirectory(request,response,resource);
443 baseRequest.setHandled(true);
444 return;
445 }
446 }
447
448
449 long last_modified=resource.lastModified();
450 String etag=null;
451 if (_etags)
452 {
453
454 String ifnm = request.getHeader(HttpHeader.IF_NONE_MATCH.asString());
455 etag=resource.getWeakETag();
456 if (ifnm!=null && resource!=null && ifnm.equals(etag))
457 {
458 response.setStatus(HttpStatus.NOT_MODIFIED_304);
459 baseRequest.getResponse().getHttpFields().put(HttpHeader.ETAG,etag);
460 return;
461 }
462 }
463
464
465 if (last_modified>0)
466 {
467 long if_modified=request.getDateHeader(HttpHeader.IF_MODIFIED_SINCE.asString());
468 if (if_modified>0 && last_modified/1000<=if_modified/1000)
469 {
470 response.setStatus(HttpStatus.NOT_MODIFIED_304);
471 return;
472 }
473 }
474
475
476 String mime=_mimeTypes.getMimeByExtension(resource.toString());
477 if (mime==null)
478 mime=_mimeTypes.getMimeByExtension(request.getPathInfo());
479 doResponseHeaders(response,resource,mime);
480 if (_etags)
481 baseRequest.getResponse().getHttpFields().put(HttpHeader.ETAG,etag);
482
483 if(skipContentBody)
484 return;
485
486
487
488 OutputStream out =null;
489 try {out = response.getOutputStream();}
490 catch(IllegalStateException e) {out = new WriterOutputStream(response.getWriter());}
491
492
493 if (!(out instanceof HttpOutput))
494
495 resource.writeTo(out,0,resource.length());
496 else
497 {
498
499 int min_async_size=_minAsyncContentLength==0?response.getBufferSize():_minAsyncContentLength;
500
501 if (request.isAsyncSupported() &&
502 min_async_size>0 &&
503 resource.length()>=min_async_size)
504 {
505 final AsyncContext async = request.startAsync();
506 Callback callback = new Callback()
507 {
508 @Override
509 public void succeeded()
510 {
511 async.complete();
512 }
513
514 @Override
515 public void failed(Throwable x)
516 {
517 LOG.warn(x.toString());
518 LOG.debug(x);
519 async.complete();
520 }
521 };
522
523
524 if (_minMemoryMappedContentLength>0 &&
525 resource.length()>_minMemoryMappedContentLength &&
526 resource instanceof FileResource)
527 {
528 ByteBuffer buffer = BufferUtil.toMappedBuffer(resource.getFile());
529 ((HttpOutput)out).sendContent(buffer,callback);
530 }
531 else
532 {
533
534 ReadableByteChannel channel= resource.getReadableByteChannel();
535 if (channel!=null)
536 ((HttpOutput)out).sendContent(channel,callback);
537 else
538 ((HttpOutput)out).sendContent(resource.getInputStream(),callback);
539 }
540 }
541 else
542 {
543
544 if (_minMemoryMappedContentLength>0 &&
545 resource.length()>_minMemoryMappedContentLength &&
546 resource instanceof FileResource)
547 {
548 ByteBuffer buffer = BufferUtil.toMappedBuffer(resource.getFile());
549 ((HttpOutput)out).sendContent(buffer);
550 }
551 else
552 {
553 ReadableByteChannel channel= resource.getReadableByteChannel();
554 if (channel!=null)
555 ((HttpOutput)out).sendContent(channel);
556 else
557 ((HttpOutput)out).sendContent(resource.getInputStream());
558 }
559 }
560 }
561 }
562
563
564 protected void doDirectory(HttpServletRequest request,HttpServletResponse response, Resource resource)
565 throws IOException
566 {
567 if (_directory)
568 {
569 String listing = resource.getListHTML(request.getRequestURI(),request.getPathInfo().lastIndexOf("/") > 0);
570 response.setContentType("text/html; charset=UTF-8");
571 response.getWriter().println(listing);
572 }
573 else
574 response.sendError(HttpStatus.FORBIDDEN_403);
575 }
576
577
578
579
580
581
582
583
584
585 protected void doResponseHeaders(HttpServletResponse response, Resource resource, String mimeType)
586 {
587 if (mimeType!=null)
588 response.setContentType(mimeType);
589
590 long length=resource.length();
591
592 if (response instanceof Response)
593 {
594 HttpFields fields = ((Response)response).getHttpFields();
595
596 if (length>0)
597 ((Response)response).setLongContentLength(length);
598
599 if (_cacheControl!=null)
600 fields.put(HttpHeader.CACHE_CONTROL,_cacheControl);
601 }
602 else
603 {
604 if (length>Integer.MAX_VALUE)
605 response.setHeader(HttpHeader.CONTENT_LENGTH.asString(),Long.toString(length));
606 else if (length>0)
607 response.setContentLength((int)length);
608
609 if (_cacheControl!=null)
610 response.setHeader(HttpHeader.CACHE_CONTROL.asString(),_cacheControl);
611 }
612 }
613 }