Source code: org/apache/coyote/Response.java
1 /*
2 * Copyright 1999-2004 The Apache Software Foundation
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17 package org.apache.coyote;
18
19 import java.io.IOException;
20 import java.util.Locale;
21
22 import org.apache.tomcat.util.buf.ByteChunk;
23 import org.apache.tomcat.util.http.MimeHeaders;
24
25 /**
26 * Response object.
27 *
28 * @author James Duncan Davidson [duncan@eng.sun.com]
29 * @author Jason Hunter [jch@eng.sun.com]
30 * @author James Todd [gonzo@eng.sun.com]
31 * @author Harish Prabandham
32 * @author Hans Bergsten <hans@gefionsoftware.com>
33 * @author Remy Maucherat
34 */
35 public final class Response {
36
37
38 // ----------------------------------------------------------- Constructors
39
40
41 public Response() {
42 }
43
44
45 // ----------------------------------------------------- Class Variables
46
47 /**
48 * Default locale as mandated by the spec.
49 */
50 private static Locale DEFAULT_LOCALE = Locale.getDefault();
51
52
53 // ----------------------------------------------------- Instance Variables
54
55 /**
56 * Status code.
57 */
58 protected int status = 200;
59
60
61 /**
62 * Status message.
63 */
64 protected String message = null;
65
66
67 /**
68 * Response headers.
69 */
70 protected MimeHeaders headers = new MimeHeaders();
71
72
73 /**
74 * Associated output buffer.
75 */
76 protected OutputBuffer outputBuffer;
77
78
79 /**
80 * Notes.
81 */
82 protected Object notes[] = new Object[Constants.MAX_NOTES];
83
84
85 /**
86 * Committed flag.
87 */
88 protected boolean commited = false;
89
90
91 /**
92 * Action hook.
93 */
94 public ActionHook hook;
95
96
97 /**
98 * HTTP specific fields.
99 */
100 protected String contentType = null;
101 protected String contentLanguage = null;
102 protected String characterEncoding = Constants.DEFAULT_CHARACTER_ENCODING;
103 protected long contentLength = -1;
104 private Locale locale = DEFAULT_LOCALE;
105
106 // General informations
107 private long bytesWritten=0;
108
109 /**
110 * Holds request error exception.
111 */
112 protected Exception errorException = null;
113
114 /**
115 * Has the charset been explicitly set.
116 */
117 protected boolean charsetSet = false;
118
119 /**
120 * Request error URI.
121 */
122 protected String errorURI = null;
123
124 protected Request req;
125
126 // ------------------------------------------------------------- Properties
127
128 public Request getRequest() {
129 return req;
130 }
131
132 public void setRequest( Request req ) {
133 this.req=req;
134 }
135
136 public OutputBuffer getOutputBuffer() {
137 return outputBuffer;
138 }
139
140
141 public void setOutputBuffer(OutputBuffer outputBuffer) {
142 this.outputBuffer = outputBuffer;
143 }
144
145
146 public MimeHeaders getMimeHeaders() {
147 return headers;
148 }
149
150
151 public ActionHook getHook() {
152 return hook;
153 }
154
155
156 public void setHook(ActionHook hook) {
157 this.hook = hook;
158 }
159
160
161 // -------------------- Per-Response "notes" --------------------
162
163
164 public final void setNote(int pos, Object value) {
165 notes[pos] = value;
166 }
167
168
169 public final Object getNote(int pos) {
170 return notes[pos];
171 }
172
173
174 // -------------------- Actions --------------------
175
176
177 public void action(ActionCode actionCode, Object param) {
178 if (hook != null) {
179 if( param==null )
180 hook.action(actionCode, this);
181 else
182 hook.action(actionCode, param);
183 }
184 }
185
186
187 // -------------------- State --------------------
188
189
190 public int getStatus() {
191 return status;
192 }
193
194
195 /**
196 * Set the response status
197 */
198 public void setStatus( int status ) {
199 this.status = status;
200 }
201
202
203 /**
204 * Get the status message.
205 */
206 public String getMessage() {
207 return message;
208 }
209
210
211 /**
212 * Set the status message.
213 */
214 public void setMessage(String message) {
215 this.message = message;
216 }
217
218
219 public boolean isCommitted() {
220 return commited;
221 }
222
223
224 public void setCommitted(boolean v) {
225 this.commited = v;
226 }
227
228
229 // -----------------Error State --------------------
230
231
232 /**
233 * Set the error Exception that occurred during
234 * request processing.
235 */
236 public void setErrorException(Exception ex) {
237 errorException = ex;
238 }
239
240
241 /**
242 * Get the Exception that occurred during request
243 * processing.
244 */
245 public Exception getErrorException() {
246 return errorException;
247 }
248
249
250 public boolean isExceptionPresent() {
251 return ( errorException != null );
252 }
253
254
255 /**
256 * Set request URI that caused an error during
257 * request processing.
258 */
259 public void setErrorURI(String uri) {
260 errorURI = uri;
261 }
262
263
264 /** Get the request URI that caused the original error.
265 */
266 public String getErrorURI() {
267 return errorURI;
268 }
269
270
271 // -------------------- Methods --------------------
272
273
274 public void reset()
275 throws IllegalStateException {
276
277 // Reset the headers only if this is the main request,
278 // not for included
279 contentType = null;;
280 locale = DEFAULT_LOCALE;
281 contentLanguage = null;
282 characterEncoding = Constants.DEFAULT_CHARACTER_ENCODING;
283 contentLength = -1;
284 charsetSet = false;
285
286 status = 200;
287 message = null;
288 headers.clear();
289
290 // Force the PrintWriter to flush its data to the output
291 // stream before resetting the output stream
292 //
293 // Reset the stream
294 if (commited) {
295 //String msg = sm.getString("servletOutputStreamImpl.reset.ise");
296 throw new IllegalStateException();
297 }
298
299 action(ActionCode.ACTION_RESET, this);
300 }
301
302
303 public void finish() throws IOException {
304 action(ActionCode.ACTION_CLOSE, this);
305 }
306
307
308 public void acknowledge() throws IOException {
309 action(ActionCode.ACTION_ACK, this);
310 }
311
312
313 // -------------------- Headers --------------------
314 public boolean containsHeader(String name) {
315 return headers.getHeader(name) != null;
316 }
317
318
319 public void setHeader(String name, String value) {
320 char cc=name.charAt(0);
321 if( cc=='C' || cc=='c' ) {
322 if( checkSpecialHeader(name, value) )
323 return;
324 }
325 headers.setValue(name).setString( value);
326 }
327
328
329 public void addHeader(String name, String value) {
330 char cc=name.charAt(0);
331 if( cc=='C' || cc=='c' ) {
332 if( checkSpecialHeader(name, value) )
333 return;
334 }
335 headers.addValue(name).setString( value );
336 }
337
338
339 /**
340 * Set internal fields for special header names.
341 * Called from set/addHeader.
342 * Return true if the header is special, no need to set the header.
343 */
344 private boolean checkSpecialHeader( String name, String value) {
345 // XXX Eliminate redundant fields !!!
346 // ( both header and in special fields )
347 if( name.equalsIgnoreCase( "Content-Type" ) ) {
348 setContentType( value );
349 return true;
350 }
351 if( name.equalsIgnoreCase( "Content-Length" ) ) {
352 try {
353 long cL=Long.parseLong( value );
354 setContentLength( cL );
355 return true;
356 } catch( NumberFormatException ex ) {
357 // Do nothing - the spec doesn't have any "throws"
358 // and the user might know what he's doing
359 return false;
360 }
361 }
362 if( name.equalsIgnoreCase( "Content-Language" ) ) {
363 // XXX XXX Need to construct Locale or something else
364 }
365 return false;
366 }
367
368
369 /** Signal that we're done with the headers, and body will follow.
370 * Any implementation needs to notify ContextManager, to allow
371 * interceptors to fix headers.
372 */
373 public void sendHeaders() throws IOException {
374 action(ActionCode.ACTION_COMMIT, this);
375 commited = true;
376 }
377
378
379 // -------------------- I18N --------------------
380
381
382 public Locale getLocale() {
383 return locale;
384 }
385
386 /**
387 * Called explicitely by user to set the Content-Language and
388 * the default encoding
389 */
390 public void setLocale(Locale locale) {
391
392 if (locale == null) {
393 return; // throw an exception?
394 }
395
396 // Save the locale for use by getLocale()
397 this.locale = locale;
398
399 // Set the contentLanguage for header output
400 contentLanguage = locale.getLanguage();
401 if ((contentLanguage != null) && (contentLanguage.length() > 0)) {
402 String country = locale.getCountry();
403 StringBuffer value = new StringBuffer(contentLanguage);
404 if ((country != null) && (country.length() > 0)) {
405 value.append('-');
406 value.append(country);
407 }
408 contentLanguage = value.toString();
409 }
410
411 }
412
413 /**
414 * Return the content language.
415 */
416 public String getContentLanguage() {
417 return contentLanguage;
418 }
419
420 /*
421 * Overrides the name of the character encoding used in the body
422 * of the response. This method must be called prior to writing output
423 * using getWriter().
424 *
425 * @param charset String containing the name of the chararacter encoding.
426 */
427 public void setCharacterEncoding(String charset) {
428
429 if (isCommitted())
430 return;
431 if (charset == null)
432 return;
433
434 characterEncoding = charset;
435 charsetSet=true;
436 }
437
438 public String getCharacterEncoding() {
439 return characterEncoding;
440 }
441
442 /**
443 * Sets the content type.
444 *
445 * This method must preserve any response charset that may already have
446 * been set via a call to response.setContentType(), response.setLocale(),
447 * or response.setCharacterEncoding().
448 *
449 * @param type the content type
450 */
451 public void setContentType(String type) {
452
453 int semicolonIndex = -1;
454
455 if (type == null) {
456 this.contentType = null;
457 return;
458 }
459
460 /*
461 * Remove the charset param (if any) from the Content-Type, and use it
462 * to set the response encoding.
463 * The most recent response encoding setting will be appended to the
464 * response's Content-Type (as its charset param) by getContentType();
465 */
466 boolean hasCharset = false;
467 int len = type.length();
468 int index = type.indexOf(';');
469 while (index != -1) {
470 semicolonIndex = index;
471 index++;
472 while (index < len && Character.isSpace(type.charAt(index))) {
473 index++;
474 }
475 if (index+8 < len
476 && type.charAt(index) == 'c'
477 && type.charAt(index+1) == 'h'
478 && type.charAt(index+2) == 'a'
479 && type.charAt(index+3) == 'r'
480 && type.charAt(index+4) == 's'
481 && type.charAt(index+5) == 'e'
482 && type.charAt(index+6) == 't'
483 && type.charAt(index+7) == '=') {
484 hasCharset = true;
485 break;
486 }
487 index = type.indexOf(';', index);
488 }
489
490 if (!hasCharset) {
491 this.contentType = type;
492 return;
493 }
494
495 this.contentType = type.substring(0, semicolonIndex);
496 String tail = type.substring(index+8);
497 int nextParam = tail.indexOf(';');
498 String charsetValue = null;
499 if (nextParam != -1) {
500 this.contentType += tail.substring(nextParam);
501 charsetValue = tail.substring(0, nextParam);
502 } else {
503 charsetValue = tail;
504 }
505
506 // The charset value may be quoted, but must not contain any quotes.
507 if (charsetValue != null && charsetValue.length() > 0) {
508 charsetSet=true;
509 charsetValue = charsetValue.replace('"', ' ');
510 this.characterEncoding = charsetValue.trim();
511 }
512 }
513
514 public String getContentType() {
515
516 String ret = contentType;
517
518 if (ret != null
519 && characterEncoding != null
520 && charsetSet) {
521 ret = ret + ";charset=" + characterEncoding;
522 }
523
524 return ret;
525 }
526
527 public void setContentLength(int contentLength) {
528 this.contentLength = contentLength;
529 }
530
531 public void setContentLength(long contentLength) {
532 this.contentLength = contentLength;
533 }
534
535 public int getContentLength() {
536 long length = getContentLengthLong();
537
538 if (length < Integer.MAX_VALUE) {
539 return (int) length;
540 }
541 return -1;
542 }
543
544 public long getContentLengthLong() {
545 return contentLength;
546 }
547
548
549 /**
550 * Write a chunk of bytes.
551 */
552 public void doWrite(ByteChunk chunk/*byte buffer[], int pos, int count*/)
553 throws IOException
554 {
555 outputBuffer.doWrite(chunk, this);
556 bytesWritten+=chunk.getLength();
557 }
558
559 // --------------------
560
561 public void recycle() {
562
563 contentType = null;
564 contentLanguage = null;
565 locale = DEFAULT_LOCALE;
566 characterEncoding = Constants.DEFAULT_CHARACTER_ENCODING;
567 charsetSet = false;
568 contentLength = -1;
569 status = 200;
570 message = null;
571 commited = false;
572 errorException = null;
573 errorURI = null;
574 headers.clear();
575
576 // update counters
577 bytesWritten=0;
578 }
579
580 public long getBytesWritten() {
581 return bytesWritten;
582 }
583
584 public void setBytesWritten(long bytesWritten) {
585 this.bytesWritten = bytesWritten;
586 }
587 }