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 trueif 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