1 package com.meterware.servletunit;
2 /********************************************************************************************************************
3 * $Id: ServletUnitHttpResponse.java 751 2006-03-24 19:59:12Z russgold $
5 * Copyright (c) 2000-2004,2006, Russell Gold
7 * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
8 * documentation files (the "Software"), to deal in the Software without restriction, including without limitation
9 * the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and
10 * to permit persons to whom the Software is furnished to do so, subject to the following conditions:
12 * The above copyright notice and this permission notice shall be included in all copies or substantial portions
15 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO
16 * THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF
18 * CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
19 * DEALINGS IN THE SOFTWARE.
21 *******************************************************************************************************************/
22 import com.meterware.httpunit.HttpUnitUtils;
24 import java.io.ByteArrayOutputStream;
25 import java.io.IOException;
26 import java.io.OutputStreamWriter;
27 import java.io.PrintWriter;
28 import java.io.UnsupportedEncodingException;
31 import java.text.SimpleDateFormat;
33 import javax.servlet.ServletOutputStream;
34 import javax.servlet.http.Cookie;
35 import javax.servlet.http.HttpServletResponse;
38 class ServletUnitHttpResponse implements HttpServletResponse {
40 // rfc1123-date is "Sun, 06 Nov 1994 08:49:37 GMT"
41 private static final String RFC1123_DATE_SPEC = "EEE, dd MMM yyyy HH:mm:ss z";
42 private boolean _committed;
43 private Locale _locale = Locale.getDefault();
45 private static final Hashtable ENCODING_MAP = new Hashtable();
48 * @deprecated Use encodeURL(String url)
50 public String encodeUrl( String url ) {
51 return encodeURL( url );
56 * Adds the specified cookie to the response. It can be called
57 * multiple times to set more than one cookie.
59 public void addCookie( Cookie cookie ) {
60 _cookies.addElement( cookie );
65 * Checks whether the response message header has a field with
68 public boolean containsHeader( String name ) {
69 return _headers.containsKey( name.toUpperCase() );
74 * @deprecated Use encodeRedirectURL(String url)
76 public String encodeRedirectUrl( String url ) {
77 return encodeRedirectURL( url );
82 * Encodes the specified URL by including the session ID in it,
83 * or, if encoding is not needed, returns the URL unchanged.
84 * The implementation of this method should include the logic to
85 * determine whether the session ID needs to be encoded in the URL.
86 * For example, if the browser supports cookies, or session
87 * tracking is turned off, URL encoding is unnecessary.
89 public String encodeURL( String url ) {
95 * Encodes the specified URL for use in the
96 * <code>sendRedirect</code> method or, if encoding is not needed,
97 * returns the URL unchanged. The implementation of this method
98 * should include the logic to determine whether the session ID
99 * needs to be encoded in the URL. Because the rules for making
100 * this determination differ from those used to decide whether to
101 * encode a normal link, this method is seperate from the
102 * <code>encodeUrl</code> method.
104 public String encodeRedirectURL( String url ) {
110 * Sends a temporary redirect response to the client using the
111 * specified redirect location URL. The URL must be absolute (for
112 * example, <code><em>https://hostname/path/file.html</em></code>).
113 * Relative URLs are not permitted here.
115 public void sendRedirect( String location ) throws IOException {
116 setStatus( HttpServletResponse.SC_MOVED_TEMPORARILY );
117 setHeader( "Location", location );
122 * Sends an error response to the client using the specified status
123 * code and descriptive message. If setStatus has previously been
124 * called, it is reset to the error status code. The message is
125 * sent as the body of an HTML page, which is returned to the user
126 * to describe the problem. The page is sent with a default HTML
127 * header; the message is enclosed in simple body tags
128 * (<body></body>).
130 public void sendError( int sc ) throws IOException {
136 * Sends an error response to the client using the specified status
137 * code and descriptive message. If setStatus has previously been
138 * called, it is reset to the error status code. The message is
139 * sent as the body of an HTML page, which is returned to the user
140 * to describe the problem. The page is sent with a default HTML
141 * header; the message is enclosed in simple body tags
142 * (<body></body>).
144 public void sendError(int sc, String msg) throws IOException {
146 _statusMessage = msg;
149 _servletStream = null;
151 setContentType( "text/html" );
152 getWriter().println( "<html><head><title>" + msg + "</title></head><body>" + msg + "</body></html>" );
157 * Sets the status code for this response. This method is used to
158 * set the return status code when there is no error (for example,
159 * for the status codes SC_OK or SC_MOVED_TEMPORARILY). If there
160 * is an error, the <code>sendError</code> method should be used
163 public void setStatus( int sc ) {
169 * @deprecated As of version 2.1, due to ambiguous meaning of the message parameter.
170 * To set a status code use setStatus(int), to send an error with a description
171 * use sendError(int, String). Sets the status code and message for this response.
173 public void setStatus( int sc, String msg ) {
179 * Adds a field to the response header with the given name and value.
180 * If the field had already been set, the new value overwrites the
181 * previous one. The <code>containsHeader</code> method can be
182 * used to test for the presence of a header before setting its
185 public void setHeader( String name, String value ) {
186 ArrayList values = new ArrayList();
188 synchronized (_headers) {
189 _headers.put( name.toUpperCase(), values );
195 * Adds a field to the response header with the given name and
196 * integer value. If the field had already been set, the new value
197 * overwrites the previous one. The <code>containsHeader</code>
198 * method can be used to test for the presence of a header before
201 public void setIntHeader( String name, int value ) {
202 setHeader( name, asHeaderValue( value ) );
206 private String asHeaderValue( int value ) {
207 return Integer.toString( value );
212 * Adds a field to the response header with the given name and
213 * date-valued field. The date is specified in terms of
214 * milliseconds since the epoch. If the date field had already
215 * been set, the new value overwrites the previous one. The
216 * <code>containsHeader</code> method can be used to test for the
217 * presence of a header before setting its value.
219 public void setDateHeader( String name, long date ) {
220 setHeader( name, asDateHeaderValue( date ) );
224 private String asDateHeaderValue( long date ) {
225 Date value = new Date( date );
226 SimpleDateFormat formatter = new SimpleDateFormat( RFC1123_DATE_SPEC, Locale.US );
227 formatter.setTimeZone( TimeZone.getTimeZone( "Greenwich Mean Time" ) );
228 return formatter.format( value );
233 * Returns the name of the character set encoding used for
234 * the MIME body sent by this response.
236 public String getCharacterEncoding() {
237 return _encoding == null ? HttpUnitUtils.DEFAULT_CHARACTER_SET : _encoding;
242 * Sets the content type of the response the server sends to
243 * the client. The content type may include the type of character
244 * encoding used, for example, <code>text/html; charset=ISO-8859-4</code>.
246 * <p>You can only use this method once, and you should call it
247 * before you obtain a <code>PrintWriter</code> or
248 * {@link ServletOutputStream} object to return a response.
250 public void setContentType( String type ) {
251 String[] typeAndEncoding = HttpUnitUtils.parseContentTypeHeader( type );
253 _contentType = typeAndEncoding[0];
254 if (typeAndEncoding[1] != null) _encoding = typeAndEncoding[1];
259 * Returns a {@link ServletOutputStream} suitable for writing binary
260 * data in the response. The servlet engine does not encode the
263 * @exception IllegalStateException if you have already called the <code>getWriter</code> method
265 public ServletOutputStream getOutputStream() throws IOException {
266 if (_writer != null) throw new IllegalStateException( "Tried to create output stream; writer already exists" );
267 if (_servletStream == null) {
268 _outputStream = new ByteArrayOutputStream();
269 _servletStream = new ServletUnitOutputStream( _outputStream );
271 return _servletStream;
276 * Returns a <code>PrintWriter</code> object that you
277 * can use to send character text to the client.
278 * The character encoding used is the one specified
279 * in the <code>charset=</code> property of the
280 * {@link #setContentType} method, which you must call
281 * <i>before</i> you call this method.
283 * <p>If necessary, the MIME type of the response is
284 * modified to reflect the character encoding used.
286 * <p> You cannot use this method if you have already
287 * called {@link #getOutputStream} for this
288 * <code>ServletResponse</code> object.
290 * @exception UnsupportedEncodingException if the character encoding specified in
291 * <code>setContentType</code> cannot be
294 * @exception IllegalStateException if the <code>getOutputStream</code>
295 * method has already been called for this
296 * response object; in that case, you can't
300 public PrintWriter getWriter() throws UnsupportedEncodingException {
301 if (_servletStream != null) throw new IllegalStateException( "Tried to create writer; output stream already exists" );
302 if (_writer == null) {
303 _outputStream = new ByteArrayOutputStream();
304 _writer = new PrintWriter( new OutputStreamWriter( _outputStream, getCharacterEncoding() ) );
311 * Sets the length of the content the server returns
312 * to the client. In HTTP servlets, this method sets the
313 * HTTP Content-Length header.
315 public void setContentLength( int len ) {
316 setIntHeader( "Content-Length", len );
320 //------------------------------- the following methods are new in JSDK 2.2 ----------------------
324 * Adds a response header with the given name and value. This method allows response headers to have multiple values.
326 public void addHeader( String name, String value ) {
327 synchronized (_headers) {
328 String key = name.toUpperCase();
329 ArrayList values = (ArrayList) _headers.get( key );
330 if (values == null) {
331 values = new ArrayList();
332 _headers.put( key, values );
340 * Adds a response header with the given name and value. This method allows response headers to have multiple values.
342 public void addIntHeader( String name, int value ) {
343 addHeader( name, asHeaderValue( value ) );
348 * Adds a response header with the given name and value. This method allows response headers to have multiple values.
350 public void addDateHeader( String name, long value ) {
351 addHeader( name, asDateHeaderValue( value ) );
356 * Sets the preferred buffer size for the body of the response. The servlet container
357 * will use a buffer at least as large as the size requested. The actual buffer size
358 * used can be found using getBufferSize.
360 public void setBufferSize( int size ) {
361 if (getContents().length != 0) throw new IllegalStateException( "May not set buffer size after data is written" );
366 * Returns the actual buffer size used for the response. If no buffering is used, this method returns 0.
368 public int getBufferSize() {
374 * Returns a boolean indicating if the response has been committed. A committed response has
375 * already had its status code and headers written.
377 public boolean isCommitted() {
383 * Forces any content in the buffer to be written to the client. A call to this method automatically
384 * commits the response, meaning the status code and headers will be written.
386 public void flushBuffer() throws IOException {
392 * Clears any data that exists in the buffer as well as the status code and headers.
393 * If the response has been committed, this method throws an IllegalStateException.
395 public void reset() {
398 _headersComplete = false;
404 * Sets the locale of the response, setting the headers (including the Content-Type's charset)
405 * as appropriate. This method should be called before a call to getWriter().
406 * By default, the response locale is the default locale for the server.
408 public void setLocale( Locale locale ) {
410 if (_encoding == null) {
411 for (Iterator it = ENCODING_MAP.entrySet().iterator(); it.hasNext();) {
412 Map.Entry entry = (Map.Entry) it.next();
413 String locales = (String) entry.getValue();
414 if (locales.indexOf( locale.getLanguage() ) >= 0 || locales.indexOf( locale.toString() ) >= 0) {
415 _encoding = (String) entry.getKey();
424 * Returns the locale assigned to the response.
426 public Locale getLocale() {
431 //----------------------------- methods added to ServletResponse in JSDK 2.3 --------------------------------------
435 * Clears the content of the underlying buffer in the response without clearing headers or status code.
436 * If the response has been committed, this method throws an IllegalStateException.
440 public void resetBuffer() {
441 if (_committed) throw new IllegalStateException( "May not resetBuffer after response is committed" );
442 _outputStream = null;
443 _servletStream = null;
448 //---------------------------------------------- package methods --------------------------------------------------
451 * Returns the contents of this response.
453 byte[] getContents() {
454 if (_outputStream == null) {
457 if (_writer != null) _writer.flush();
458 return _outputStream.toByteArray();
464 * Returns the status of this response.
472 * Returns the message associated with this response's status.
474 String getMessage() {
475 return _statusMessage;
479 public String[] getHeaderFieldNames() {
480 if (!_headersComplete) completeHeaders();
481 Vector names = new Vector();
482 for (Enumeration e = _headers.keys(); e.hasMoreElements();) {
483 names.addElement( e.nextElement() );
485 String[] result = new String[ names.size() ];
486 names.copyInto( result );
492 * Returns the headers defined for this response.
494 String getHeaderField( String name ) {
495 if (!_headersComplete) completeHeaders();
498 synchronized (_headers) {
499 values = (ArrayList) _headers.get( name.toUpperCase() );
502 return values == null ? null : (String) values.get( 0 );
507 * Return an array of all the header values associated with the
508 * specified header name, or an zero-length array if there are no such
511 * @param name Header name to look up
513 public String[] getHeaderFields(String name) {
514 if (!_headersComplete) completeHeaders();
516 synchronized (_headers) {
517 values = (ArrayList) _headers.get(name.toUpperCase());
520 return (new String[0]);
521 String results[] = new String[values.size()];
522 return ((String[]) values.toArray(results));
526 //--------------------------------------- methods added to ServletRequest in Servlet API 2.4 ----------------------------
528 public void setCharacterEncoding(String string) {
533 * Returns the content type defined for this response.
535 public String getContentType() {
540 //------------------------------------------- private members ------------------------------------
543 private String _contentType = "text/plain";
545 private String _encoding;
547 private PrintWriter _writer;
549 private ServletOutputStream _servletStream;
551 private ByteArrayOutputStream _outputStream;
553 private int _status = SC_OK;
555 private String _statusMessage = "OK";
557 private final Hashtable _headers = new Hashtable();
559 private boolean _headersComplete;
561 private Vector _cookies = new Vector();
564 private void completeHeaders() {
565 if (_headersComplete) return;
567 setHeader( "Content-Type", _contentType + "; charset=" + getCharacterEncoding() );
568 _headersComplete = true;
572 private void addCookieHeader() {
573 if (_cookies.isEmpty()) return;
575 StringBuffer sb = new StringBuffer();
576 for (Enumeration e = _cookies.elements(); e.hasMoreElements();) {
577 Cookie cookie = (Cookie) e.nextElement();
578 sb.append( cookie.getName() ).append( '=' ).append( cookie.getValue() );
579 if (cookie.getPath() != null) sb.append( ";path=" ).append( cookie.getPath() );
580 if (cookie.getDomain() != null) sb.append( ";domain=" ).append( cookie.getDomain() );
581 if (e.hasMoreElements()) sb.append( ',' );
583 setHeader( "Set-Cookie", sb.toString() );
589 ENCODING_MAP.put( "iso-8859-1", "ca da de en es fi fr is it nl no pt sv " );
590 ENCODING_MAP.put( "iso-8859-2", "cs hr hu pl ro sh sk sl sq " );
591 ENCODING_MAP.put( "iso-8859-4", "et lt lv ");
592 ENCODING_MAP.put( "iso-8859-5", "be bg mk ru sr uk " );
593 ENCODING_MAP.put( "iso-8859-6", "ar " );
594 ENCODING_MAP.put( "iso-8859-7", "el " );
595 ENCODING_MAP.put( "iso-8859-8", "iw he " );
596 ENCODING_MAP.put( "iso-8859-9", "tr " );
598 ENCODING_MAP.put("Shift_JIS", "ja ");
599 ENCODING_MAP.put("EUC-KR", "ko ");
600 ENCODING_MAP.put("TIS-620", "th ");
601 ENCODING_MAP.put("GB2312", "zh " );
602 ENCODING_MAP.put("Big5", "zh_TW zh_HK " );
609 class ServletUnitOutputStream extends ServletOutputStream {
611 ServletUnitOutputStream( ByteArrayOutputStream stream ) {
616 public void write( int aByte ) throws IOException {
617 _stream.write( aByte );
620 private ByteArrayOutputStream _stream;