1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16 package org.esigate.servlet.impl;
17
18 import java.io.IOException;
19 import java.io.PrintWriter;
20 import java.io.Writer;
21 import java.util.Locale;
22
23 import javax.servlet.ServletOutputStream;
24 import javax.servlet.http.Cookie;
25 import javax.servlet.http.HttpServletResponse;
26 import javax.servlet.http.HttpServletResponseWrapper;
27
28 import org.apache.commons.io.output.ByteArrayOutputStream;
29 import org.apache.commons.io.output.StringBuilderWriter;
30 import org.apache.http.Header;
31 import org.apache.http.HttpHeaders;
32 import org.apache.http.HttpStatus;
33 import org.apache.http.HttpVersion;
34 import org.apache.http.client.methods.CloseableHttpResponse;
35 import org.apache.http.entity.ByteArrayEntity;
36 import org.apache.http.entity.ContentType;
37 import org.apache.http.entity.StringEntity;
38 import org.apache.http.message.BasicHttpResponse;
39 import org.apache.http.message.BasicStatusLine;
40 import org.esigate.Parameters;
41 import org.esigate.http.BasicCloseableHttpResponse;
42 import org.esigate.http.ContentTypeHelper;
43 import org.esigate.http.DateUtils;
44 import org.esigate.http.HttpResponseUtils;
45 import org.esigate.http.IncomingRequest;
46 import org.slf4j.Logger;
47 import org.slf4j.LoggerFactory;
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63 public class ResponseCapturingWrapper extends HttpServletResponseWrapper {
64 private static final Logger LOG = LoggerFactory.getLogger(ResponseCapturingWrapper.class);
65
66
67 private ServletOutputStream outputStream;
68 private PrintWriter writer;
69
70
71 private ServletOutputStream responseOutputStream;
72 private PrintWriter responseWriter;
73
74
75 private ByteArrayOutputStream internalOutputStream;
76 private StringBuilderWriter internalWriter;
77
78 private HttpServletResponse response;
79
80 private CloseableHttpResponse httpClientResponse = BasicCloseableHttpResponse.adapt(new BasicHttpResponse(
81 new BasicStatusLine(HttpVersion.HTTP_1_1, HttpStatus.SC_OK, "OK")));
82
83 private String contentType;
84 private String characterEncoding;
85 private int bufferSize;
86 private int bytesWritten = 0;
87 private boolean committed = false;
88 private ContentTypeHelper contentTypeHelper;
89 private final boolean proxy;
90 private boolean capture = true;
91 private final ResponseSender responseSender;
92 private final IncomingRequest incomingRequest;
93
94 public ResponseCapturingWrapper(HttpServletResponse response, ContentTypeHelper contentTypeHelper, boolean proxy,
95 int bufferSize, ResponseSender responseSender, IncomingRequest incomingRequest) {
96 super(response);
97 this.response = response;
98 this.bufferSize = bufferSize;
99 this.contentTypeHelper = contentTypeHelper;
100 this.proxy = proxy;
101 this.responseSender = responseSender;
102 this.incomingRequest = incomingRequest;
103 }
104
105 @Override
106 public void setStatus(int sc) {
107 httpClientResponse.setStatusLine(new BasicStatusLine(HttpVersion.HTTP_1_1, sc, ""));
108 }
109
110 @Override
111 public void setStatus(int sc, String sm) {
112 httpClientResponse.setStatusLine(new BasicStatusLine(HttpVersion.HTTP_1_1, sc, sm));
113 }
114
115 public int getStatus() {
116 return httpClientResponse.getStatusLine().getStatusCode();
117 }
118
119 @Override
120 public void sendError(int sc, String msg) {
121 httpClientResponse.setStatusLine(new BasicStatusLine(HttpVersion.HTTP_1_1, sc, msg));
122 }
123
124 @Override
125 public void sendError(int sc) {
126 httpClientResponse.setStatusLine(new BasicStatusLine(HttpVersion.HTTP_1_1, sc, ""));
127 }
128
129 @Override
130 public void sendRedirect(String location) {
131 httpClientResponse.setStatusLine(new BasicStatusLine(HttpVersion.HTTP_1_1, HttpStatus.SC_MOVED_TEMPORARILY,
132 "Temporary redirect"));
133 httpClientResponse.setHeader(HttpHeaders.LOCATION, location);
134 }
135
136 @Override
137 public boolean containsHeader(String name) {
138 return httpClientResponse.containsHeader(name);
139 }
140
141 @Override
142 public void setHeader(String name, String value) {
143 if (HttpHeaders.CONTENT_TYPE.equalsIgnoreCase(name)) {
144 setContentType(value);
145 } else {
146 httpClientResponse.setHeader(name, value);
147 }
148 }
149
150 @Override
151 public void addHeader(String name, String value) {
152 if (HttpHeaders.CONTENT_TYPE.equalsIgnoreCase(name)) {
153 setContentType(value);
154 } else {
155 httpClientResponse.addHeader(name, value);
156 }
157 }
158
159 @Override
160 public void setDateHeader(String name, long date) {
161 setHeader(name, DateUtils.formatDate(date));
162 }
163
164 @Override
165 public void addDateHeader(String name, long date) {
166 addHeader(name, DateUtils.formatDate(date));
167 }
168
169 @Override
170 public void setIntHeader(String name, int value) {
171 setHeader(name, Integer.toString(value));
172 }
173
174 @Override
175 public void addIntHeader(String name, int value) {
176 addHeader(name, Integer.toString(value));
177 }
178
179 @Override
180 public void setContentLength(int len) {
181 setHeader(HttpHeaders.CONTENT_LENGTH, Integer.toString(len));
182 }
183
184 @Override
185 public void setCharacterEncoding(String charset) {
186 this.characterEncoding = charset;
187 updateContentTypeHeader();
188 }
189
190 @Override
191 public String getCharacterEncoding() {
192 return this.characterEncoding;
193 }
194
195 @Override
196 public String getContentType() {
197 Header contentTypeHeader = httpClientResponse.getFirstHeader(HttpHeaders.CONTENT_TYPE);
198 if (contentTypeHeader != null) {
199 return contentTypeHeader.getValue();
200 } else {
201 return null;
202 }
203 }
204
205 @Override
206 public void setContentType(String type) {
207 ContentType parsedContentType = ContentType.parse(type);
208 this.contentType = parsedContentType.getMimeType();
209 if (parsedContentType.getCharset() != null) {
210 this.characterEncoding = parsedContentType.getCharset().name();
211 }
212 updateContentTypeHeader();
213 }
214
215 private void updateContentTypeHeader() {
216 if (contentType != null) {
217 if (characterEncoding == null) {
218 httpClientResponse.setHeader(HttpHeaders.CONTENT_TYPE, contentType);
219 } else {
220 httpClientResponse.setHeader(HttpHeaders.CONTENT_TYPE, contentType + ";charset=" + characterEncoding);
221 }
222 }
223 }
224
225 @Override
226 public void setLocale(Locale loc) {
227 response.setLocale(loc);
228 if (characterEncoding == null) {
229 characterEncoding = response.getCharacterEncoding();
230 updateContentTypeHeader();
231 }
232 }
233
234 @Override
235 public Locale getLocale() {
236 return response.getLocale();
237 }
238
239 @Override
240 public void setBufferSize(int size) {
241 this.bufferSize = size;
242 }
243
244 @Override
245 public int getBufferSize() {
246 return bufferSize;
247 }
248
249 @Override
250 public void addCookie(Cookie cookie) {
251 response.addCookie(cookie);
252 }
253
254 @Override
255 public String encodeURL(String url) {
256 return response.encodeURL(url);
257 }
258
259 @Override
260 public String encodeRedirectURL(String url) {
261 return response.encodeRedirectURL(url);
262 }
263
264 @SuppressWarnings("deprecation")
265 @Override
266 public String encodeUrl(String url) {
267 return response.encodeUrl(url);
268 }
269
270 @SuppressWarnings("deprecation")
271 @Override
272 public String encodeRedirectUrl(String url) {
273 return response.encodeRedirectUrl(url);
274 }
275
276 @Override
277 public void reset() {
278 if (isCommitted()) {
279 throw new IllegalStateException("Response is already committed");
280 }
281 httpClientResponse =
282 BasicCloseableHttpResponse.adapt(new BasicHttpResponse(new BasicStatusLine(HttpVersion.HTTP_1_1,
283 HttpStatus.SC_OK, "OK")));
284 }
285
286 @Override
287 public ServletOutputStream getOutputStream() {
288 LOG.debug("getOutputStream");
289 if (writer != null) {
290 throw new IllegalStateException("Writer already obtained");
291 }
292 if (outputStream == null) {
293 internalOutputStream = new ByteArrayOutputStream(Parameters.DEFAULT_BUFFER_SIZE);
294 outputStream = new ServletOutputStream() {
295
296 @Override
297 public void write(int b) throws IOException {
298 if (capture || bytesWritten < bufferSize) {
299 internalOutputStream.write(b);
300 } else {
301 responseOutputStream.write(b);
302 }
303 bytesWritten++;
304 if (bytesWritten == bufferSize) {
305 commit();
306 }
307 }
308
309 @Override
310 public void flush() throws IOException {
311 commit();
312 }
313
314 @Override
315 public void close() throws IOException {
316 commit();
317 }
318
319 private void commit() throws IOException {
320 if (!committed) {
321 capture = hasToCaptureOutput();
322 if (!capture) {
323 responseSender.sendHeaders(httpClientResponse, incomingRequest, response);
324 responseOutputStream = response.getOutputStream();
325 internalOutputStream.writeTo(responseOutputStream);
326 }
327 committed = true;
328 }
329
330 }
331
332 };
333 }
334 return outputStream;
335 }
336
337 @Override
338 public PrintWriter getWriter() {
339 LOG.debug("getWriter");
340 if (outputStream != null) {
341 throw new IllegalStateException("OutputStream already obtained");
342 }
343 if (writer == null) {
344 internalWriter = new StringBuilderWriter(Parameters.DEFAULT_BUFFER_SIZE);
345 writer = new PrintWriter(new Writer() {
346
347 @Override
348 public void write(char[] cbuf, int off, int len) throws IOException {
349 if (capture || bytesWritten < bufferSize) {
350 internalWriter.write(cbuf, off, len);
351 } else {
352 responseWriter.write(cbuf, off, len);
353 }
354 bytesWritten++;
355 if (bytesWritten == bufferSize) {
356 commit();
357 }
358 }
359
360 @Override
361 public void flush() throws IOException {
362 commit();
363 }
364
365 @Override
366 public void close() throws IOException {
367 commit();
368 }
369
370 private void commit() throws IOException {
371 if (!committed) {
372 capture = hasToCaptureOutput();
373 if (!capture) {
374 responseSender.sendHeaders(httpClientResponse, incomingRequest, response);
375 responseWriter = response.getWriter();
376 responseWriter.write(internalWriter.toString());
377 }
378 committed = true;
379 }
380
381 }
382
383 });
384 }
385 return writer;
386 }
387
388 @Override
389 public void flushBuffer() throws IOException {
390 if (outputStream != null) {
391 outputStream.flush();
392 }
393 if (writer != null) {
394 writer.flush();
395 }
396 }
397
398 @Override
399 public void resetBuffer() {
400 if (isCommitted()) {
401 throw new IllegalStateException("Response is already committed");
402 }
403 if (internalOutputStream != null) {
404 internalOutputStream.reset();
405 }
406 if (internalWriter != null) {
407 internalWriter = new StringBuilderWriter(Parameters.DEFAULT_BUFFER_SIZE);
408 }
409 bytesWritten = 0;
410 }
411
412 @Override
413 public boolean isCommitted() {
414 return committed;
415 }
416
417
418
419
420
421
422
423
424
425
426 private boolean hasToCaptureOutput() {
427 return !proxy || contentTypeHelper.isTextContentType(httpClientResponse)
428 || HttpResponseUtils.getFirstHeader(HttpHeaders.CONTENT_TYPE, httpClientResponse) == null;
429 }
430
431
432
433
434
435
436
437 public CloseableHttpResponse getCloseableHttpResponse() {
438 ContentType resultContentType = null;
439 if (this.contentType != null) {
440 resultContentType = ContentType.create(this.contentType, characterEncoding);
441 }
442 if (internalWriter != null) {
443 writer.flush();
444 httpClientResponse.setEntity(new StringEntity(internalWriter.toString(), resultContentType));
445 } else if (internalOutputStream != null) {
446 try {
447 outputStream.flush();
448 } catch (IOException e) {
449
450 }
451 httpClientResponse.setEntity(new ByteArrayEntity(internalOutputStream.toByteArray(), resultContentType));
452 }
453 if (!capture) {
454
455
456 if (responseWriter != null) {
457 responseWriter.close();
458 }
459 if (responseOutputStream != null) {
460 try {
461 responseOutputStream.close();
462 } catch (IOException e) {
463 LOG.warn("Could not close servlet output stream: " + e.getMessage());
464 }
465 }
466 }
467 return httpClientResponse;
468 }
469
470 }