View Javadoc
1   /* 
2    * Licensed under the Apache License, Version 2.0 (the "License");
3    * you may not use this file except in compliance with the License.
4    * You may obtain a copy of the License at
5    *
6    * http://www.apache.org/licenses/LICENSE-2.0
7    *
8    * Unless required by applicable law or agreed to in writing, software
9    * distributed under the License is distributed on an "AS IS" BASIS,
10   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11   * See the License for the specific language governing permissions and
12   * limitations under the License.
13   *
14   */
15  
16  package org.esigate;
17  
18  import java.io.IOException;
19  import java.io.PrintWriter;
20  import java.net.SocketException;
21  import java.net.SocketTimeoutException;
22  
23  import org.apache.commons.io.output.StringBuilderWriter;
24  import org.apache.http.Header;
25  import org.apache.http.HttpEntity;
26  import org.apache.http.HttpStatus;
27  import org.apache.http.HttpVersion;
28  import org.apache.http.client.ClientProtocolException;
29  import org.apache.http.client.methods.CloseableHttpResponse;
30  import org.apache.http.conn.ConnectTimeoutException;
31  import org.apache.http.conn.ConnectionPoolTimeoutException;
32  import org.apache.http.conn.HttpHostConnectException;
33  import org.apache.http.entity.ByteArrayEntity;
34  import org.apache.http.entity.ContentType;
35  import org.apache.http.entity.StringEntity;
36  import org.apache.http.message.BasicHttpResponse;
37  import org.apache.http.message.BasicStatusLine;
38  import org.apache.http.util.EntityUtils;
39  import org.esigate.http.BasicCloseableHttpResponse;
40  import org.slf4j.Logger;
41  import org.slf4j.LoggerFactory;
42  
43  /**
44   * Exception thrown when an error occurred retrieving a resource.
45   * <p>
46   * This exception can include a HTTP response (with error page body) or a String body.
47   * 
48   * @author Francois-Xavier Bonnet
49   * @author Nicolas Richeton
50   */
51  public class HttpErrorPage extends Exception {
52      private static final long serialVersionUID = 1L;
53      private static final Logger LOG = LoggerFactory.getLogger(HttpErrorPage.class);
54      private final CloseableHttpResponse httpResponse;
55  
56      private static HttpEntity toMemoryEntity(String content) {
57          return new StringEntity(content, "UTF-8");
58      }
59  
60      private static HttpEntity toMemoryEntity(Exception exception) {
61          StringBuilderWriter out = new StringBuilderWriter(Parameters.DEFAULT_BUFFER_SIZE);
62          PrintWriter pw = new PrintWriter(out);
63          exception.printStackTrace(pw);
64          String content = out.toString();
65          try {
66              return toMemoryEntity(content);
67          } finally {
68              pw.close();
69          }
70      }
71  
72      private static HttpEntity toMemoryEntity(HttpEntity httpEntity) {
73          if (httpEntity == null) {
74              return null;
75          }
76          HttpEntity memoryEntity;
77          try {
78              byte[] content = EntityUtils.toByteArray(httpEntity);
79              ByteArrayEntity byteArrayEntity = new ByteArrayEntity(content, ContentType.get(httpEntity));
80              Header contentEncoding = httpEntity.getContentEncoding();
81              if (contentEncoding != null) {
82                  byteArrayEntity.setContentEncoding(contentEncoding);
83              }
84              memoryEntity = byteArrayEntity;
85          } catch (IOException e) {
86              StringBuilderWriter out = new StringBuilderWriter(Parameters.DEFAULT_BUFFER_SIZE);
87              PrintWriter pw = new PrintWriter(out);
88              e.printStackTrace(pw);
89              pw.close();
90              memoryEntity = new StringEntity(out.toString(), ContentType.getOrDefault(httpEntity));
91          }
92          return memoryEntity;
93      }
94  
95      /**
96       * Create an HTTP error page exception from an Http response.
97       * 
98       * @param httpResponse
99       *            backend response.
100      */
101     public HttpErrorPage(CloseableHttpResponse httpResponse) {
102         super(httpResponse.getStatusLine().getStatusCode() + " " + httpResponse.getStatusLine().getReasonPhrase());
103         this.httpResponse = httpResponse;
104         // Consume the entity and replace it with an in memory Entity
105         httpResponse.setEntity(toMemoryEntity(httpResponse.getEntity()));
106     }
107 
108     /**
109      * Create an HTTP response from a String content wich will be used as the response entity.
110      * 
111      * @param statusCode
112      * @param statusMessage
113      * @param content
114      */
115     public HttpErrorPage(int statusCode, String statusMessage, String content) {
116         super(statusCode + " " + statusMessage);
117         this.httpResponse = HttpErrorPage.generateHttpResponse(statusCode, statusMessage);
118         this.httpResponse.setEntity(toMemoryEntity(content));
119     }
120 
121     /**
122      * Create an HTTP response from an exception. This exception stack trace will be showed to the end user.
123      * 
124      * @param statusCode
125      * @param statusMessage
126      * @param exception
127      */
128     public HttpErrorPage(int statusCode, String statusMessage, Exception exception) {
129         super(statusCode + " " + statusMessage, exception);
130         this.httpResponse = HttpErrorPage.generateHttpResponse(statusCode, statusMessage);
131         this.httpResponse.setEntity(toMemoryEntity(exception));
132     }
133 
134     /**
135      * Get HTTP response enclosed in this exception.
136      * 
137      * @return HTTP response
138      */
139     public CloseableHttpResponse getHttpResponse() {
140         return this.httpResponse;
141     }
142 
143     public static CloseableHttpResponse generateHttpResponse(Exception exception) {
144         if (exception instanceof HttpHostConnectException) {
145             return generateHttpResponse(HttpStatus.SC_BAD_GATEWAY, "Connection refused");
146         } else if (exception instanceof ConnectionPoolTimeoutException) {
147             return generateHttpResponse(HttpStatus.SC_GATEWAY_TIMEOUT, "Connection pool timeout");
148         } else if (exception instanceof ConnectTimeoutException) {
149             return generateHttpResponse(HttpStatus.SC_GATEWAY_TIMEOUT, "Connect timeout");
150         } else if (exception instanceof SocketTimeoutException) {
151             return generateHttpResponse(HttpStatus.SC_GATEWAY_TIMEOUT, "Socket timeout");
152         } else if (exception instanceof SocketException) {
153             return generateHttpResponse(HttpStatus.SC_BAD_GATEWAY, "Socket Exception");
154         } else if (exception instanceof ClientProtocolException) {
155             String message = exception.getMessage();
156             if (message == null && exception.getCause() != null) {
157                 message = exception.getCause().getMessage();
158             }
159             return generateHttpResponse(HttpStatus.SC_BAD_GATEWAY, "Protocol error: " + message);
160         } else {
161             LOG.error("Error retrieving URL", exception);
162             return generateHttpResponse(HttpStatus.SC_BAD_GATEWAY, "Error retrieving URL");
163         }
164     }
165 
166     public static CloseableHttpResponse generateHttpResponse(int statusCode, String statusText) {
167         CloseableHttpResponse result =
168                 BasicCloseableHttpResponse.adapt(new BasicHttpResponse(new BasicStatusLine(HttpVersion.HTTP_1_1,
169                         statusCode, statusText)));
170         result.setEntity(toMemoryEntity(statusText));
171         return result;
172     }
173 
174 }