1 /*
2 * Licensed to the Apache Software Foundation (ASF) under one or more
3 * contributor license agreements. See the NOTICE file distributed with
4 * this work for additional information regarding copyright ownership.
5 * The ASF licenses this file to You under the Apache License, Version 2.0
6 * (the "License"); you may not use this file except in compliance with
7 * the License. You may obtain a copy of the License at
8 *
9 * http://www.apache.org/licenses/LICENSE-2.0
10 *
11 * Unless required by applicable law or agreed to in writing, software
12 * distributed under the License is distributed on an "AS IS" BASIS,
13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 * See the License for the specific language governing permissions and
15 * limitations under the License.
16 */
17 package org.apache.coyote;
18
19 import java.io.IOException;
20 import java.io.StringReader;
21 import java.io.UnsupportedEncodingException;
22 import java.nio.ByteBuffer;
23 import java.nio.charset.Charset;
24 import java.util.Locale;
25 import java.util.Map;
26 import java.util.concurrent.atomic.AtomicBoolean;
27 import java.util.concurrent.atomic.AtomicInteger;
28 import java.util.function.Supplier;
29
30 import javax.servlet.WriteListener;
31
32 import org.apache.juli.logging.Log;
33 import org.apache.juli.logging.LogFactory;
34 import org.apache.tomcat.util.buf.B2CConverter;
35 import org.apache.tomcat.util.buf.MessageBytes;
36 import org.apache.tomcat.util.http.MimeHeaders;
37 import org.apache.tomcat.util.http.parser.MediaType;
38 import org.apache.tomcat.util.res.StringManager;
39
40 /**
41 * Response object.
42 *
43 * @author James Duncan Davidson [duncan@eng.sun.com]
44 * @author Jason Hunter [jch@eng.sun.com]
45 * @author James Todd [gonzo@eng.sun.com]
46 * @author Harish Prabandham
47 * @author Hans Bergsten [hans@gefionsoftware.com]
48 * @author Remy Maucherat
49 */
50 public final class Response {
51
52 private static final StringManager sm = StringManager.getManager(Response.class);
53
54 private static final Log log = LogFactory.getLog(Response.class);
55
56 // ----------------------------------------------------- Class Variables
57
58 /**
59 * Default locale as mandated by the spec.
60 */
61 private static final Locale DEFAULT_LOCALE = Locale.getDefault();
62
63
64 // ----------------------------------------------------- Instance Variables
65
66 /**
67 * Status code.
68 */
69 int status = 200;
70
71
72 /**
73 * Status message.
74 */
75 String message = null;
76
77
78 /**
79 * Response headers.
80 */
81 final MimeHeaders headers = new MimeHeaders();
82
83
84 private Supplier<Map<String,String>> trailerFieldsSupplier = null;
85
86 /**
87 * Associated output buffer.
88 */
89 OutputBuffer outputBuffer;
90
91
92 /**
93 * Notes.
94 */
95 final Object notes[] = new Object[Constants.MAX_NOTES];
96
97
98 /**
99 * Committed flag.
100 */
101 volatile boolean committed = false;
102
103
104 /**
105 * Action hook.
106 */
107 volatile ActionHook hook;
108
109
110 /**
111 * HTTP specific fields.
112 */
113 String contentType = null;
114 String contentLanguage = null;
115 Charset charset = null;
116 // Retain the original name used to set the charset so exactly that name is
117 // used in the ContentType header. Some (arguably non-specification
118 // compliant) user agents are very particular
119 String characterEncoding = null;
120 long contentLength = -1;
121 private Locale locale = DEFAULT_LOCALE;
122
123 // General informations
124 private long contentWritten = 0;
125 private long commitTime = -1;
126
127 /**
128 * Holds request error exception.
129 */
130 Exception errorException = null;
131
132 /**
133 * With the introduction of async processing and the possibility of
134 * non-container threads calling sendError() tracking the current error
135 * state and ensuring that the correct error page is called becomes more
136 * complicated. This state attribute helps by tracking the current error
137 * state and informing callers that attempt to change state if the change
138 * was successful or if another thread got there first.
139 *
140 * <pre>
141 * The state machine is very simple:
142 *
143 * 0 - NONE
144 * 1 - NOT_REPORTED
145 * 2 - REPORTED
146 *
147 *
148 * -->---->-- >NONE
149 * | | |
150 * | | | setError()
151 * ^ ^ |
152 * | | \|/
153 * | |-<-NOT_REPORTED
154 * | |
155 * ^ | report()
156 * | |
157 * | \|/
158 * |----<----REPORTED
159 * </pre>
160 */
161 private final AtomicInteger errorState = new AtomicInteger(0);
162
163 Request req;
164
165
166 // ------------------------------------------------------------- Properties
167
168 public Request getRequest() {
169 return req;
170 }
171
172 public void setRequest( Request req ) {
173 this.req=req;
174 }
175
176
177 public void setOutputBuffer(OutputBuffer outputBuffer) {
178 this.outputBuffer = outputBuffer;
179 }
180
181
182 public MimeHeaders getMimeHeaders() {
183 return headers;
184 }
185
186
187 protected void setHook(ActionHook hook) {
188 this.hook = hook;
189 }
190
191
192 // -------------------- Per-Response "notes" --------------------
193
194 public final void setNote(int pos, Object value) {
195 notes[pos] = value;
196 }
197
198
199 public final Object getNote(int pos) {
200 return notes[pos];
201 }
202
203
204 // -------------------- Actions --------------------
205
206 public void action(ActionCode actionCode, Object param) {
207 if (hook != null) {
208 if (param == null) {
209 hook.action(actionCode, this);
210 } else {
211 hook.action(actionCode, param);
212 }
213 }
214 }
215
216
217 // -------------------- State --------------------
218
219 public int getStatus() {
220 return status;
221 }
222
223
224 /**
225 * Set the response status.
226 *
227 * @param status The status value to set
228 */
229 public void setStatus(int status) {
230 this.status = status;
231 }
232
233
234 /**
235 * Get the status message.
236 *
237 * @return The message associated with the current status
238 */
239 public String getMessage() {
240 return message;
241 }
242
243
244 /**
245 * Set the status message.
246 *
247 * @param message The status message to set
248 */
249 public void setMessage(String message) {
250 this.message = message;
251 }
252
253
254 public boolean isCommitted() {
255 return committed;
256 }
257
258
259 public void setCommitted(boolean v) {
260 if (v && !this.committed) {
261 this.commitTime = System.currentTimeMillis();
262 }
263 this.committed = v;
264 }
265
266 /**
267 * Return the time the response was committed (based on System.currentTimeMillis).
268 *
269 * @return the time the response was committed
270 */
271 public long getCommitTime() {
272 return commitTime;
273 }
274
275 // -----------------Error State --------------------
276
277 /**
278 * Set the error Exception that occurred during request processing.
279 *
280 * @param ex The exception that occurred
281 */
282 public void setErrorException(Exception ex) {
283 errorException = ex;
284 }
285
286
287 /**
288 * Get the Exception that occurred during request processing.
289 *
290 * @return The exception that occurred
291 */
292 public Exception getErrorException() {
293 return errorException;
294 }
295
296
297 public boolean isExceptionPresent() {
298 return ( errorException != null );
299 }
300
301
302 /**
303 * Set the error flag.
304 *
305 * @return <code>false</code> if the error flag was already set
306 */
307 public boolean setError() {
308 return errorState.compareAndSet(0, 1);
309 }
310
311
312 /**
313 * Error flag accessor.
314 *
315 * @return <code>true</code> if the response has encountered an error
316 */
317 public boolean isError() {
318 return errorState.get() > 0;
319 }
320
321
322 public boolean isErrorReportRequired() {
323 return errorState.get() == 1;
324 }
325
326
327 public boolean setErrorReported() {
328 return errorState.compareAndSet(1, 2);
329 }
330
331
332 // -------------------- Methods --------------------
333
334 public void reset() throws IllegalStateException {
335
336 if (committed) {
337 throw new IllegalStateException();
338 }
339
340 recycle();
341 }
342
343
344 // -------------------- Headers --------------------
345 /**
346 * Does the response contain the given header.
347 * <br>
348 * Warning: This method always returns <code>false</code> for Content-Type
349 * and Content-Length.
350 *
351 * @param name The name of the header of interest
352 *
353 * @return {@code true} if the response contains the header.
354 */
355 public boolean containsHeader(String name) {
356 return headers.getHeader(name) != null;
357 }
358
359
360 public void setHeader(String name, String value) {
361 char cc=name.charAt(0);
362 if( cc=='C' || cc=='c' ) {
363 if( checkSpecialHeader(name, value) )
364 return;
365 }
366 headers.setValue(name).setString( value);
367 }
368
369
370 public void addHeader(String name, String value) {
371 addHeader(name, value, null);
372 }
373
374
375 public void addHeader(String name, String value, Charset charset) {
376 char cc=name.charAt(0);
377 if( cc=='C' || cc=='c' ) {
378 if( checkSpecialHeader(name, value) )
379 return;
380 }
381 MessageBytes mb = headers.addValue(name);
382 if (charset != null) {
383 mb.setCharset(charset);
384 }
385 mb.setString(value);
386 }
387
388
389 public void setTrailerFields(Supplier<Map<String, String>> supplier) {
390 AtomicBoolean trailerFieldsSupported = new AtomicBoolean(false);
391 action(ActionCode.IS_TRAILER_FIELDS_SUPPORTED, trailerFieldsSupported);
392 if (!trailerFieldsSupported.get()) {
393 throw new IllegalStateException(sm.getString("response.noTrailers.notSupported"));
394 }
395
396 this.trailerFieldsSupplier = supplier;
397 }
398
399
400 public Supplier<Map<String, String>> getTrailerFields() {
401 return trailerFieldsSupplier;
402 }
403
404
405 /**
406 * Set internal fields for special header names.
407 * Called from set/addHeader.
408 * Return true if the header is special, no need to set the header.
409 */
410 private boolean checkSpecialHeader( String name, String value) {
411 // XXX Eliminate redundant fields !!!
412 // ( both header and in special fields )
413 if( name.equalsIgnoreCase( "Content-Type" ) ) {
414 setContentType( value );
415 return true;
416 }
417 if( name.equalsIgnoreCase( "Content-Length" ) ) {
418 try {
419 long cL=Long.parseLong( value );
420 setContentLength( cL );
421 return true;
422 } catch( NumberFormatException ex ) {
423 // Do nothing - the spec doesn't have any "throws"
424 // and the user might know what he's doing
425 return false;
426 }
427 }
428 return false;
429 }
430
431
432 /** Signal that we're done with the headers, and body will follow.
433 * Any implementation needs to notify ContextManager, to allow
434 * interceptors to fix headers.
435 */
436 public void sendHeaders() {
437 action(ActionCode.COMMIT, this);
438 setCommitted(true);
439 }
440
441
442 // -------------------- I18N --------------------
443
444
445 public Locale getLocale() {
446 return locale;
447 }
448
449 /**
450 * Called explicitly by user to set the Content-Language and the default
451 * encoding.
452 *
453 * @param locale The locale to use for this response
454 */
455 public void setLocale(Locale locale) {
456
457 if (locale == null) {
458 return; // throw an exception?
459 }
460
461 // Save the locale for use by getLocale()
462 this.locale = locale;
463
464 // Set the contentLanguage for header output
465 contentLanguage = locale.toLanguageTag();
466 }
467
468 /**
469 * Return the content language.
470 *
471 * @return The language code for the language currently associated with this
472 * response
473 */
474 public String getContentLanguage() {
475 return contentLanguage;
476 }
477
478
479 /**
480 * Overrides the character encoding used in the body of the response. This
481 * method must be called prior to writing output using getWriter().
482 *
483 * @param characterEncoding The name of character encoding.
484 *
485 * @throws UnsupportedEncodingException If the specified name is not
486 * recognised
487 */
488 public void setCharacterEncoding(String characterEncoding) throws UnsupportedEncodingException {
489 if (isCommitted()) {
490 return;
491 }
492 if (characterEncoding == null) {
493 return;
494 }
495
496 this.charset = B2CConverter.getCharset(characterEncoding);
497 this.characterEncoding = characterEncoding;
498 }
499
500
501 public Charset getCharset() {
502 return charset;
503 }
504
505
506 /**
507 * @return The name of the current encoding
508 */
509 public String getCharacterEncoding() {
510 return characterEncoding;
511 }
512
513
514 /**
515 * Sets the content type.
516 *
517 * This method must preserve any response charset that may already have
518 * been set via a call to response.setContentType(), response.setLocale(),
519 * or response.setCharacterEncoding().
520 *
521 * @param type the content type
522 */
523 public void setContentType(String type) {
524
525 if (type == null) {
526 this.contentType = null;
527 return;
528 }
529
530 MediaType m = null;
531 try {
532 m = MediaType.parseMediaType(new StringReader(type));
533 } catch (IOException e) {
534 // Ignore - null test below handles this
535 }
536 if (m == null) {
537 // Invalid - Assume no charset and just pass through whatever
538 // the user provided.
539 this.contentType = type;
540 return;
541 }
542
543 this.contentType = m.toStringNoCharset();
544
545 String charsetValue = m.getCharset();
546
547 if (charsetValue != null) {
548 charsetValue = charsetValue.trim();
549 if (charsetValue.length() > 0) {
550 try {
551 charset = B2CConverter.getCharset(charsetValue);
552 } catch (UnsupportedEncodingException e) {
553 log.warn(sm.getString("response.encoding.invalid", charsetValue), e);
554 }
555 }
556 }
557 }
558
559 public void setContentTypeNoCharset(String type) {
560 this.contentType = type;
561 }
562
563 public String getContentType() {
564
565 String ret = contentType;
566
567 if (ret != null && charset != null) {
568 ret = ret + ";charset=" + characterEncoding;
569 }
570
571 return ret;
572 }
573
574 public void setContentLength(long contentLength) {
575 this.contentLength = contentLength;
576 }
577
578 public int getContentLength() {
579 long length = getContentLengthLong();
580
581 if (length < Integer.MAX_VALUE) {
582 return (int) length;
583 }
584 return -1;
585 }
586
587 public long getContentLengthLong() {
588 return contentLength;
589 }
590
591
592 /**
593 * Write a chunk of bytes.
594 *
595 * @param chunk The ByteBuffer to write
596 *
597 * @throws IOException If an I/O error occurs during the write
598 */
599 public void doWrite(ByteBuffer chunk) throws IOException {
600 int len = chunk.remaining();
601 outputBuffer.doWrite(chunk);
602 contentWritten += len - chunk.remaining();
603 }
604
605 // --------------------
606
607 public void recycle() {
608
609 contentType = null;
610 contentLanguage = null;
611 locale = DEFAULT_LOCALE;
612 charset = null;
613 characterEncoding = null;
614 contentLength = -1;
615 status = 200;
616 message = null;
617 committed = false;
618 commitTime = -1;
619 errorException = null;
620 errorState.set(0);
621 headers.clear();
622 trailerFieldsSupplier = null;
623 // Servlet 3.1 non-blocking write listener
624 listener = null;
625 fireListener = false;
626 registeredForWrite = false;
627
628 // update counters
629 contentWritten=0;
630 }
631
632 /**
633 * Bytes written by application - i.e. before compression, chunking, etc.
634 *
635 * @return The total number of bytes written to the response by the
636 * application. This will not be the number of bytes written to the
637 * network which may be more or less than this value.
638 */
639 public long getContentWritten() {
640 return contentWritten;
641 }
642
643 /**
644 * Bytes written to socket - i.e. after compression, chunking, etc.
645 *
646 * @param flush Should any remaining bytes be flushed before returning the
647 * total? If {@code false} bytes remaining in the buffer will
648 * not be included in the returned value
649 *
650 * @return The total number of bytes written to the socket for this response
651 */
652 public long getBytesWritten(boolean flush) {
653 if (flush) {
654 action(ActionCode.CLIENT_FLUSH, this);
655 }
656 return outputBuffer.getBytesWritten();
657 }
658
659 /*
660 * State for non-blocking output is maintained here as it is the one point
661 * easily reachable from the CoyoteOutputStream and the Processor which both
662 * need access to state.
663 */
664 volatile WriteListener listener;
665 private boolean fireListener = false;
666 private boolean registeredForWrite = false;
667 private final Object nonBlockingStateLock = new Object();
668
669 public WriteListener getWriteListener() {
670 return listener;
671 }
672
673 public void setWriteListener(WriteListener listener) {
674 if (listener == null) {
675 throw new NullPointerException(
676 sm.getString("response.nullWriteListener"));
677 }
678 if (getWriteListener() != null) {
679 throw new IllegalStateException(
680 sm.getString("response.writeListenerSet"));
681 }
682 // Note: This class is not used for HTTP upgrade so only need to test
683 // for async
684 AtomicBoolean result = new AtomicBoolean(false);
685 action(ActionCode.ASYNC_IS_ASYNC, result);
686 if (!result.get()) {
687 throw new IllegalStateException(
688 sm.getString("response.notAsync"));
689 }
690
691 this.listener = listener;
692
693 // The container is responsible for the first call to
694 // listener.onWritePossible(). If isReady() returns true, the container
695 // needs to call listener.onWritePossible() from a new thread. If
696 // isReady() returns false, the socket will be registered for write and
697 // the container will call listener.onWritePossible() once data can be
698 // written.
699 if (isReady()) {
700 synchronized (nonBlockingStateLock) {
701 // Ensure we don't get multiple write registrations if
702 // ServletOutputStream.isReady() returns false during a call to
703 // onDataAvailable()
704 registeredForWrite = true;
705 // Need to set the fireListener flag otherwise when the
706 // container tries to trigger onWritePossible, nothing will
707 // happen
708 fireListener = true;
709 }
710 action(ActionCode.DISPATCH_WRITE, null);
711 if (!ContainerThreadMarker.isContainerThread()) {
712 // Not on a container thread so need to execute the dispatch
713 action(ActionCode.DISPATCH_EXECUTE, null);
714 }
715 }
716 }
717
718 public boolean isReady() {
719 if (listener == null) {
720 if (log.isDebugEnabled()) {
721 log.debug(sm.getString("response.notNonBlocking"));
722 }
723 return false;
724 }
725 // Assume write is not possible
726 boolean ready = false;
727 synchronized (nonBlockingStateLock) {
728 if (registeredForWrite) {
729 fireListener = true;
730 return false;
731 }
732 ready = checkRegisterForWrite();
733 fireListener = !ready;
734 }
735 return ready;
736 }
737
738 public boolean checkRegisterForWrite() {
739 AtomicBoolean ready = new AtomicBoolean(false);
740 synchronized (nonBlockingStateLock) {
741 if (!registeredForWrite) {
742 action(ActionCode.NB_WRITE_INTEREST, ready);
743 registeredForWrite = !ready.get();
744 }
745 }
746 return ready.get();
747 }
748
749 public void onWritePossible() throws IOException {
750 // Any buffered data left over from a previous non-blocking write is
751 // written in the Processor so if this point is reached the app is able
752 // to write data.
753 boolean fire = false;
754 synchronized (nonBlockingStateLock) {
755 registeredForWrite = false;
756 if (fireListener) {
757 fireListener = false;
758 fire = true;
759 }
760 }
761 if (fire) {
762 listener.onWritePossible();
763 }
764 }
765 }
766