View Javadoc

1   //
2   //  ========================================================================
3   //  Copyright (c) 1995-2016 Mort Bay Consulting Pty. Ltd.
4   //  ------------------------------------------------------------------------
5   //  All rights reserved. This program and the accompanying materials
6   //  are made available under the terms of the Eclipse Public License v1.0
7   //  and Apache License v2.0 which accompanies this distribution.
8   //
9   //      The Eclipse Public License is available at
10  //      http://www.eclipse.org/legal/epl-v10.html
11  //
12  //      The Apache License v2.0 is available at
13  //      http://www.opensource.org/licenses/apache2.0.php
14  //
15  //  You may elect to redistribute this code under either of these licenses.
16  //  ========================================================================
17  //
18  
19  package org.eclipse.jetty.server.handler;
20  
21  import java.io.IOException;
22  import java.io.PrintWriter;
23  import java.io.StringWriter;
24  import java.io.Writer;
25  import java.nio.ByteBuffer;
26  
27  import javax.servlet.RequestDispatcher;
28  import javax.servlet.ServletException;
29  import javax.servlet.http.HttpServletRequest;
30  import javax.servlet.http.HttpServletResponse;
31  
32  import org.eclipse.jetty.http.HttpField;
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.server.Dispatcher;
39  import org.eclipse.jetty.server.Request;
40  import org.eclipse.jetty.server.Response;
41  import org.eclipse.jetty.server.Server;
42  import org.eclipse.jetty.util.BufferUtil;
43  import org.eclipse.jetty.util.ByteArrayISO8859Writer;
44  import org.eclipse.jetty.util.StringUtil;
45  import org.eclipse.jetty.util.log.Log;
46  import org.eclipse.jetty.util.log.Logger;
47  
48  /* ------------------------------------------------------------ */
49  /** Handler for Error pages
50   * An ErrorHandler is registered with {@link ContextHandler#setErrorHandler(ErrorHandler)} or
51   * {@link org.eclipse.jetty.server.Server#addBean(Object)}.
52   * It is called by the HttpResponse.sendError method to write a error page via {@link #handle(String, Request, HttpServletRequest, HttpServletResponse)}
53   * or via {@link #badMessageError(int, String, HttpFields)} for bad requests for which a dispatch cannot be done.
54   *
55   */
56  public class ErrorHandler extends AbstractHandler
57  {    
58      private static final Logger LOG = Log.getLogger(ErrorHandler.class);
59      public final static String ERROR_PAGE="org.eclipse.jetty.server.error_page";
60      
61      boolean _showStacks=true;
62      boolean _showMessageInTitle=true;
63      String _cacheControl="must-revalidate,no-cache,no-store";
64  
65      /* ------------------------------------------------------------ */
66      /*
67       * @see org.eclipse.jetty.server.server.Handler#handle(javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse, int)
68       */
69      @Override
70      public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException
71      {
72          String method = request.getMethod();
73          if (!HttpMethod.GET.is(method) && !HttpMethod.POST.is(method) && !HttpMethod.HEAD.is(method))
74          {
75              baseRequest.setHandled(true);
76              return;
77          }
78          
79          if (this instanceof ErrorPageMapper)
80          {
81              String error_page=((ErrorPageMapper)this).getErrorPage(request);
82              if (error_page!=null && request.getServletContext()!=null)
83              {
84                  String old_error_page=(String)request.getAttribute(ERROR_PAGE);
85                  if (old_error_page==null || !old_error_page.equals(error_page))
86                  {
87                      request.setAttribute(ERROR_PAGE, error_page);
88  
89                      Dispatcher dispatcher = (Dispatcher) request.getServletContext().getRequestDispatcher(error_page);
90                      try
91                      {
92                          if(dispatcher!=null)
93                          {
94                              dispatcher.error(request, response);
95                              return;
96                          }
97                          LOG.warn("No error page "+error_page);
98                      }
99                      catch (ServletException e)
100                     {
101                         LOG.warn(Log.EXCEPTION, e);
102                         return;
103                     }
104                 }
105             } else {
106                 if (LOG.isDebugEnabled())
107                 {
108                     LOG.debug("No Error Page mapping for request({} {}) (using default)",request.getMethod(),request.getRequestURI());
109                 }
110             }
111         }
112         
113         baseRequest.setHandled(true);
114 
115         // Issue #124 - Don't produce text/html if the request doesn't accept it
116         HttpField accept = baseRequest.getHttpFields().getField(HttpHeader.ACCEPT);
117         if (accept == null || accept.contains("text/html") || accept.contains("*/*"))
118         {
119             response.setContentType(MimeTypes.Type.TEXT_HTML_8859_1.asString());
120             if (_cacheControl != null)
121                 response.setHeader(HttpHeader.CACHE_CONTROL.asString(), _cacheControl);
122             ByteArrayISO8859Writer writer = new ByteArrayISO8859Writer(4096);
123             String reason = (response instanceof Response) ? ((Response) response).getReason() : null;
124             handleErrorPage(request, writer, response.getStatus(), reason);
125             writer.flush();
126             response.setContentLength(writer.size());
127             writer.writeTo(response.getOutputStream());
128             writer.destroy();
129         }
130     }
131 
132     /* ------------------------------------------------------------ */
133     protected void handleErrorPage(HttpServletRequest request, Writer writer, int code, String message)
134         throws IOException
135     {
136         writeErrorPage(request, writer, code, message, _showStacks);
137     }
138 
139     /* ------------------------------------------------------------ */
140     protected void writeErrorPage(HttpServletRequest request, Writer writer, int code, String message, boolean showStacks)
141         throws IOException
142     {
143         if (message == null)
144             message=HttpStatus.getMessage(code);
145 
146         writer.write("<html>\n<head>\n");
147         writeErrorPageHead(request,writer,code,message);
148         writer.write("</head>\n<body>");
149         writeErrorPageBody(request,writer,code,message,showStacks);
150         writer.write("\n</body>\n</html>\n");
151     }
152 
153     /* ------------------------------------------------------------ */
154     protected void writeErrorPageHead(HttpServletRequest request, Writer writer, int code, String message)
155         throws IOException
156         {
157         writer.write("<meta http-equiv=\"Content-Type\" content=\"text/html;charset=utf-8\"/>\n");
158         writer.write("<title>Error ");
159         writer.write(Integer.toString(code));
160 
161         if (_showMessageInTitle)
162         {
163             writer.write(' ');
164             write(writer,message);
165         }
166         writer.write("</title>\n");
167     }
168 
169     /* ------------------------------------------------------------ */
170     protected void writeErrorPageBody(HttpServletRequest request, Writer writer, int code, String message, boolean showStacks)
171         throws IOException
172     {
173         String uri= request.getRequestURI();
174 
175         writeErrorPageMessage(request,writer,code,message,uri);
176         if (showStacks)
177             writeErrorPageStacks(request,writer);
178 
179         Request.getBaseRequest(request).getHttpChannel().getHttpConfiguration()
180             .writePoweredBy(writer,"<hr>","<hr/>\n");
181     }
182 
183     /* ------------------------------------------------------------ */
184     protected void writeErrorPageMessage(HttpServletRequest request, Writer writer, int code, String message,String uri)
185     throws IOException
186     {
187         writer.write("<h2>HTTP ERROR ");
188         writer.write(Integer.toString(code));
189         writer.write("</h2>\n<p>Problem accessing ");
190         write(writer,uri);
191         writer.write(". Reason:\n<pre>    ");
192         write(writer,message);
193         writer.write("</pre></p>");
194     }
195 
196     /* ------------------------------------------------------------ */
197     protected void writeErrorPageStacks(HttpServletRequest request, Writer writer)
198         throws IOException
199     {
200         Throwable th = (Throwable)request.getAttribute(RequestDispatcher.ERROR_EXCEPTION);
201         while(th!=null)
202         {
203             writer.write("<h3>Caused by:</h3><pre>");
204             StringWriter sw = new StringWriter();
205             PrintWriter pw = new PrintWriter(sw);
206             th.printStackTrace(pw);
207             pw.flush();
208             write(writer,sw.getBuffer().toString());
209             writer.write("</pre>\n");
210 
211             th =th.getCause();
212         }
213     }
214 
215     /* ------------------------------------------------------------ */
216     /** Bad Message Error body
217      * <p>Generate a error response body to be sent for a bad message.
218      * In this case there is something wrong with the request, so either
219      * a request cannot be built, or it is not safe to build a request.
220      * This method allows for a simple error page body to be returned 
221      * and some response headers to be set.
222      * @param status The error code that will be sent
223      * @param reason The reason for the error code (may be null)
224      * @param fields The header fields that will be sent with the response.
225      * @return The content as a ByteBuffer, or null for no body.
226      */
227     public ByteBuffer badMessageError(int status, String reason, HttpFields fields)
228     {
229         if (reason==null)
230             reason=HttpStatus.getMessage(status);
231         fields.put(HttpHeader.CONTENT_TYPE,MimeTypes.Type.TEXT_HTML_8859_1.asString());
232         return BufferUtil.toBuffer("<h1>Bad Message " + status + "</h1><pre>reason: " + reason + "</pre>");
233     }    
234     
235     /* ------------------------------------------------------------ */
236     /** Get the cacheControl.
237      * @return the cacheControl header to set on error responses.
238      */
239     public String getCacheControl()
240     {
241         return _cacheControl;
242     }
243 
244     /* ------------------------------------------------------------ */
245     /** Set the cacheControl.
246      * @param cacheControl the cacheControl header to set on error responses.
247      */
248     public void setCacheControl(String cacheControl)
249     {
250         _cacheControl = cacheControl;
251     }
252 
253     /* ------------------------------------------------------------ */
254     /**
255      * @return True if stack traces are shown in the error pages
256      */
257     public boolean isShowStacks()
258     {
259         return _showStacks;
260     }
261 
262     /* ------------------------------------------------------------ */
263     /**
264      * @param showStacks True if stack traces are shown in the error pages
265      */
266     public void setShowStacks(boolean showStacks)
267     {
268         _showStacks = showStacks;
269     }
270 
271     /* ------------------------------------------------------------ */
272     /**
273      * @param showMessageInTitle if true, the error message appears in page title
274      */
275     public void setShowMessageInTitle(boolean showMessageInTitle)
276     {
277         _showMessageInTitle = showMessageInTitle;
278     }
279 
280 
281     /* ------------------------------------------------------------ */
282     public boolean getShowMessageInTitle()
283     {
284         return _showMessageInTitle;
285     }
286 
287     /* ------------------------------------------------------------ */
288     protected void write(Writer writer,String string)
289         throws IOException
290     {
291         if (string==null)
292             return;
293 
294         writer.write(StringUtil.sanitizeXmlString(string));
295     }
296 
297     /* ------------------------------------------------------------ */
298     public interface ErrorPageMapper
299     {
300         String getErrorPage(HttpServletRequest request);
301     }
302 
303     /* ------------------------------------------------------------ */
304     public static ErrorHandler getErrorHandler(Server server, ContextHandler context)
305     {
306         ErrorHandler error_handler=null;
307         if (context!=null)
308             error_handler=context.getErrorHandler();
309         if (error_handler==null && server!=null)
310             error_handler = server.getBean(ErrorHandler.class);
311         return error_handler;
312     }
313 }