View Javadoc

1   //
2   //  ========================================================================
3   //  Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
4   //  ------------------------------------------------------------------------
5   //  All rights reserved. This program and the accompanying materials
6   //  are made available under the terms of the Eclipse Public License v1.0
7   //  and Apache License v2.0 which accompanies this distribution.
8   //
9   //      The Eclipse Public License is available at
10  //      http://www.eclipse.org/legal/epl-v10.html
11  //
12  //      The Apache License v2.0 is available at
13  //      http://www.opensource.org/licenses/apache2.0.php
14  //
15  //  You may elect to redistribute this code under either of these licenses.
16  //  ========================================================================
17  //
18  
19  package org.eclipse.jetty.server;
20  
21  import java.io.IOException;
22  import java.io.PrintWriter;
23  import java.nio.channels.IllegalSelectorException;
24  import java.util.ArrayList;
25  import java.util.Collection;
26  import java.util.Collections;
27  import java.util.EnumSet;
28  import java.util.Enumeration;
29  import java.util.Locale;
30  import java.util.concurrent.atomic.AtomicInteger;
31  
32  import javax.servlet.RequestDispatcher;
33  import javax.servlet.ServletOutputStream;
34  import javax.servlet.SessionTrackingMode;
35  import javax.servlet.http.Cookie;
36  import javax.servlet.http.HttpServletResponse;
37  import javax.servlet.http.HttpSession;
38  
39  import org.eclipse.jetty.http.HttpContent;
40  import org.eclipse.jetty.http.HttpCookie;
41  import org.eclipse.jetty.http.HttpField;
42  import org.eclipse.jetty.http.HttpFields;
43  import org.eclipse.jetty.http.HttpGenerator;
44  import org.eclipse.jetty.http.HttpGenerator.ResponseInfo;
45  import org.eclipse.jetty.http.HttpHeader;
46  import org.eclipse.jetty.http.HttpHeaderValue;
47  import org.eclipse.jetty.http.HttpScheme;
48  import org.eclipse.jetty.http.HttpStatus;
49  import org.eclipse.jetty.http.HttpURI;
50  import org.eclipse.jetty.http.HttpVersion;
51  import org.eclipse.jetty.http.MimeTypes;
52  import org.eclipse.jetty.io.RuntimeIOException;
53  import org.eclipse.jetty.server.handler.ContextHandler;
54  import org.eclipse.jetty.server.handler.ErrorHandler;
55  import org.eclipse.jetty.util.ByteArrayISO8859Writer;
56  import org.eclipse.jetty.util.StringUtil;
57  import org.eclipse.jetty.util.URIUtil;
58  import org.eclipse.jetty.util.log.Log;
59  import org.eclipse.jetty.util.log.Logger;
60  
61  /**
62   * <p>{@link Response} provides the implementation for {@link HttpServletResponse}.</p>
63   */
64  public class Response implements HttpServletResponse
65  {
66      private static final Logger LOG = Log.getLogger(Response.class);
67  
68      /* ------------------------------------------------------------ */
69      public static Response getResponse(HttpServletResponse response)
70      {
71          if (response instanceof Response)
72              return (Response)response;
73          return HttpChannel.getCurrentHttpChannel().getResponse();
74      }
75      
76      
77      public enum OutputType
78      {
79          NONE, STREAM, WRITER
80      }
81  
82      /**
83       * If a header name starts with this string,  the header (stripped of the prefix)
84       * can be set during include using only {@link #setHeader(String, String)} or
85       * {@link #addHeader(String, String)}.
86       */
87      public final static String SET_INCLUDE_HEADER_PREFIX = "org.eclipse.jetty.server.include.";
88  
89      /**
90       * If this string is found within the comment of a cookie added with {@link #addCookie(Cookie)}, then the cookie
91       * will be set as HTTP ONLY.
92       */
93      public final static String HTTP_ONLY_COMMENT = "__HTTP_ONLY__";
94  
95      private final HttpChannel<?> _channel;
96      private final HttpOutput _out;
97      private final HttpFields _fields = new HttpFields();
98      private final AtomicInteger _include = new AtomicInteger();
99      private int _status = HttpStatus.NOT_SET_000;
100     private String _reason;
101     private Locale _locale;
102     private MimeTypes.Type _mimeType;
103     private String _characterEncoding;
104     private String _contentType;
105     private OutputType _outputType = OutputType.NONE;
106     private ResponseWriter _writer;
107     private long _contentLength = -1;
108     
109 
110     public Response(HttpChannel<?> channel, HttpOutput out)
111     {
112         _channel = channel;
113         _out = out;
114     }
115 
116     protected HttpChannel<?> getHttpChannel()
117     {
118         return _channel;
119     }
120 
121     protected void recycle()
122     {
123         _status = HttpStatus.NOT_SET_000;
124         _reason = null;
125         _locale = null;
126         _mimeType = null;
127         _characterEncoding = null;
128         _contentType = null;
129         _outputType = OutputType.NONE;
130         _contentLength = -1;
131         _out.reset();
132         _fields.clear();
133     }
134 
135     public void setHeaders(HttpContent httpContent)
136     {
137         Response response = _channel.getResponse();
138         String contentType = httpContent.getContentType();
139         if (contentType != null && !response.getHttpFields().containsKey(HttpHeader.CONTENT_TYPE.asString()))
140             setContentType(contentType);
141         
142         if (httpContent.getContentLength() > 0)
143             setLongContentLength(httpContent.getContentLength());
144 
145         String lm = httpContent.getLastModified();
146         if (lm != null)
147             response.getHttpFields().put(HttpHeader.LAST_MODIFIED, lm);
148         else if (httpContent.getResource() != null)
149         {
150             long lml = httpContent.getResource().lastModified();
151             if (lml != -1)
152                 response.getHttpFields().putDateField(HttpHeader.LAST_MODIFIED, lml);
153         }
154 
155         String etag=httpContent.getETag();
156         if (etag!=null)
157             response.getHttpFields().put(HttpHeader.ETAG,etag);
158     }
159     
160     public HttpOutput getHttpOutput()
161     {
162         return _out;
163     }
164 
165     public boolean isIncluding()
166     {
167         return _include.get() > 0;
168     }
169 
170     public void include()
171     {
172         _include.incrementAndGet();
173     }
174 
175     public void included()
176     {
177         _include.decrementAndGet();
178         _out.reopen();
179     }
180 
181     public void addCookie(HttpCookie cookie)
182     {
183         _fields.addSetCookie(cookie);
184     }
185 
186     @Override
187     public void addCookie(Cookie cookie)
188     {
189         String comment = cookie.getComment();
190         boolean httpOnly = false;
191 
192         if (comment != null)
193         {
194             int i = comment.indexOf(HTTP_ONLY_COMMENT);
195             if (i >= 0)
196             {
197                 httpOnly = true;
198                 comment = comment.replace(HTTP_ONLY_COMMENT, "").trim();
199                 if (comment.length() == 0)
200                     comment = null;
201             }
202         }
203         _fields.addSetCookie(cookie.getName(),
204                 cookie.getValue(),
205                 cookie.getDomain(),
206                 cookie.getPath(),
207                 cookie.getMaxAge(),
208                 comment,
209                 cookie.getSecure(),
210                 httpOnly || cookie.isHttpOnly(),
211                 cookie.getVersion());
212     }
213 
214     @Override
215     public boolean containsHeader(String name)
216     {
217         return _fields.containsKey(name);
218     }
219 
220     @Override
221     public String encodeURL(String url)
222     {
223         final Request request = _channel.getRequest();
224         SessionManager sessionManager = request.getSessionManager();
225         if (sessionManager == null)
226             return url;
227 
228         HttpURI uri = null;
229         if (sessionManager.isCheckingRemoteSessionIdEncoding() && URIUtil.hasScheme(url))
230         {
231             uri = new HttpURI(url);
232             String path = uri.getPath();
233             path = (path == null ? "" : path);
234             int port = uri.getPort();
235             if (port < 0)
236                 port = HttpScheme.HTTPS.asString().equalsIgnoreCase(uri.getScheme()) ? 443 : 80;
237             if (!request.getServerName().equalsIgnoreCase(uri.getHost()) ||
238                     request.getServerPort() != port ||
239                     !path.startsWith(request.getContextPath())) //TODO the root context path is "", with which every non null string starts
240                 return url;
241         }
242 
243         String sessionURLPrefix = sessionManager.getSessionIdPathParameterNamePrefix();
244         if (sessionURLPrefix == null)
245             return url;
246 
247         if (url == null)
248             return null;
249 
250         // should not encode if cookies in evidence
251         if ((sessionManager.isUsingCookies() && request.isRequestedSessionIdFromCookie()) || !sessionManager.isUsingURLs()) 
252         {
253             int prefix = url.indexOf(sessionURLPrefix);
254             if (prefix != -1)
255             {
256                 int suffix = url.indexOf("?", prefix);
257                 if (suffix < 0)
258                     suffix = url.indexOf("#", prefix);
259 
260                 if (suffix <= prefix)
261                     return url.substring(0, prefix);
262                 return url.substring(0, prefix) + url.substring(suffix);
263             }
264             return url;
265         }
266 
267         // get session;
268         HttpSession session = request.getSession(false);
269 
270         // no session
271         if (session == null)
272             return url;
273 
274         // invalid session
275         if (!sessionManager.isValid(session))
276             return url;
277 
278         String id = sessionManager.getNodeId(session);
279 
280         if (uri == null)
281             uri = new HttpURI(url);
282 
283 
284         // Already encoded
285         int prefix = url.indexOf(sessionURLPrefix);
286         if (prefix != -1)
287         {
288             int suffix = url.indexOf("?", prefix);
289             if (suffix < 0)
290                 suffix = url.indexOf("#", prefix);
291 
292             if (suffix <= prefix)
293                 return url.substring(0, prefix + sessionURLPrefix.length()) + id;
294             return url.substring(0, prefix + sessionURLPrefix.length()) + id +
295                     url.substring(suffix);
296         }
297 
298         // edit the session
299         int suffix = url.indexOf('?');
300         if (suffix < 0)
301             suffix = url.indexOf('#');
302         if (suffix < 0)
303         {
304             return url +
305                     ((HttpScheme.HTTPS.is(uri.getScheme()) || HttpScheme.HTTP.is(uri.getScheme())) && uri.getPath() == null ? "/" : "") + //if no path, insert the root path
306                     sessionURLPrefix + id;
307         }
308 
309 
310         return url.substring(0, suffix) +
311                 ((HttpScheme.HTTPS.is(uri.getScheme()) || HttpScheme.HTTP.is(uri.getScheme())) && uri.getPath() == null ? "/" : "") + //if no path so insert the root path
312                 sessionURLPrefix + id + url.substring(suffix);
313     }
314 
315     @Override
316     public String encodeRedirectURL(String url)
317     {
318         return encodeURL(url);
319     }
320 
321     @Override
322     @Deprecated
323     public String encodeUrl(String url)
324     {
325         return encodeURL(url);
326     }
327 
328     @Override
329     @Deprecated
330     public String encodeRedirectUrl(String url)
331     {
332         return encodeRedirectURL(url);
333     }
334 
335     @Override
336     public void sendError(int sc) throws IOException
337     {
338         if (sc == 102)
339             sendProcessing();
340         else
341             sendError(sc, null);
342     }
343 
344     @Override
345     public void sendError(int code, String message) throws IOException
346     {
347         if (isIncluding())
348             return;
349 
350         if (isCommitted())
351             LOG.warn("Committed before "+code+" "+message);
352 
353         resetBuffer();
354         _characterEncoding=null;
355         setHeader(HttpHeader.EXPIRES,null);
356         setHeader(HttpHeader.LAST_MODIFIED,null);
357         setHeader(HttpHeader.CACHE_CONTROL,null);
358         setHeader(HttpHeader.CONTENT_TYPE,null);
359         setHeader(HttpHeader.CONTENT_LENGTH,null);
360 
361         _outputType = OutputType.NONE;
362         setStatus(code);
363         _reason=message;
364 
365         Request request = _channel.getRequest();
366         Throwable cause = (Throwable)request.getAttribute(Dispatcher.ERROR_EXCEPTION);
367         if (message==null)
368             message=cause==null?HttpStatus.getMessage(code):cause.toString();
369 
370         // If we are allowed to have a body
371         if (code!=SC_NO_CONTENT &&
372             code!=SC_NOT_MODIFIED &&
373             code!=SC_PARTIAL_CONTENT &&
374             code>=SC_OK)
375         {
376 
377             ErrorHandler error_handler = null;
378             ContextHandler.Context context = request.getContext();
379             if (context!=null)
380                 error_handler=context.getContextHandler().getErrorHandler();
381             if (error_handler==null)
382                 error_handler = _channel.getServer().getBean(ErrorHandler.class);
383             if (error_handler!=null)
384             {
385                 request.setAttribute(RequestDispatcher.ERROR_STATUS_CODE,new Integer(code));
386                 request.setAttribute(RequestDispatcher.ERROR_MESSAGE, message);
387                 request.setAttribute(RequestDispatcher.ERROR_REQUEST_URI, request.getRequestURI());
388                 request.setAttribute(RequestDispatcher.ERROR_SERVLET_NAME,request.getServletName());
389                 error_handler.handle(null,_channel.getRequest(),_channel.getRequest(),this );
390             }
391             else
392             {
393                 setHeader(HttpHeader.CACHE_CONTROL, "must-revalidate,no-cache,no-store");
394                 setContentType(MimeTypes.Type.TEXT_HTML_8859_1.toString());
395                 ByteArrayISO8859Writer writer= new ByteArrayISO8859Writer(2048);
396                 if (message != null)
397                 {
398                     message= StringUtil.replace(message, "&", "&amp;");
399                     message= StringUtil.replace(message, "<", "&lt;");
400                     message= StringUtil.replace(message, ">", "&gt;");
401                 }
402                 String uri= request.getRequestURI();
403                 if (uri!=null)
404                 {
405                     uri= StringUtil.replace(uri, "&", "&amp;");
406                     uri= StringUtil.replace(uri, "<", "&lt;");
407                     uri= StringUtil.replace(uri, ">", "&gt;");
408                 }
409 
410                 writer.write("<html>\n<head>\n<meta http-equiv=\"Content-Type\" content=\"text/html;charset=ISO-8859-1\"/>\n");
411                 writer.write("<title>Error ");
412                 writer.write(Integer.toString(code));
413                 writer.write(' ');
414                 if (message==null)
415                 writer.write(message);
416                 writer.write("</title>\n</head>\n<body>\n<h2>HTTP ERROR: ");
417                 writer.write(Integer.toString(code));
418                 writer.write("</h2>\n<p>Problem accessing ");
419                 writer.write(uri);
420                 writer.write(". Reason:\n<pre>    ");
421                 writer.write(message);
422                 writer.write("</pre>");
423                 writer.write("</p>\n<hr /><i><small>Powered by Jetty://</small></i>");
424                 writer.write("\n</body>\n</html>\n");
425 
426                 writer.flush();
427                 setContentLength(writer.size());
428                 try (ServletOutputStream outputStream = getOutputStream())
429                 {
430                     writer.writeTo(outputStream);
431                     writer.destroy();
432                 }
433             }
434         }
435         else if (code!=SC_PARTIAL_CONTENT)
436         {
437             // TODO work out why this is required?
438             _channel.getRequest().getHttpFields().remove(HttpHeader.CONTENT_TYPE);
439             _channel.getRequest().getHttpFields().remove(HttpHeader.CONTENT_LENGTH);
440             _characterEncoding=null;
441             _mimeType=null;
442         }
443 
444         closeOutput();
445     }
446 
447     /**
448      * Sends a 102-Processing response.
449      * If the connection is a HTTP connection, the version is 1.1 and the
450      * request has a Expect header starting with 102, then a 102 response is
451      * sent. This indicates that the request still be processed and real response
452      * can still be sent.   This method is called by sendError if it is passed 102.
453      * @see javax.servlet.http.HttpServletResponse#sendError(int)
454      */
455     public void sendProcessing() throws IOException
456     {
457         if (_channel.isExpecting102Processing() && !isCommitted())
458         {
459             _channel.sendResponse(HttpGenerator.PROGRESS_102_INFO, null, true);
460         }
461     }
462 
463     @Override
464     public void sendRedirect(String location) throws IOException
465     {
466         if (isIncluding())
467             return;
468 
469         if (location == null)
470             throw new IllegalArgumentException();
471 
472         if (!URIUtil.hasScheme(location))
473         {
474             StringBuilder buf = _channel.getRequest().getRootURL();
475             if (location.startsWith("/"))
476                 buf.append(location);
477             else
478             {
479                 String path = _channel.getRequest().getRequestURI();
480                 String parent = (path.endsWith("/")) ? path : URIUtil.parentPath(path);
481                 location = URIUtil.addPaths(parent, location);
482                 if (location == null)
483                     throw new IllegalStateException("path cannot be above root");
484                 if (!location.startsWith("/"))
485                     buf.append('/');
486                 buf.append(location);
487             }
488 
489             location = buf.toString();
490             HttpURI uri = new HttpURI(location);
491             String path = uri.getDecodedPath();
492             String canonical = URIUtil.canonicalPath(path);
493             if (canonical == null)
494                 throw new IllegalArgumentException();
495             if (!canonical.equals(path))
496             {
497                 buf = _channel.getRequest().getRootURL();
498                 buf.append(URIUtil.encodePath(canonical));
499                 String param=uri.getParam();
500                 if (param!=null)
501                 {
502                     buf.append(';');
503                     buf.append(param);
504                 }
505                 String query=uri.getQuery();
506                 if (query!=null)
507                 {
508                     buf.append('?');
509                     buf.append(query);
510                 }
511                 String fragment=uri.getFragment();
512                 if (fragment!=null)
513                 {
514                     buf.append('#');
515                     buf.append(fragment);
516                 }
517                 location = buf.toString();
518             }
519         }
520 
521         resetBuffer();
522         setHeader(HttpHeader.LOCATION, location);
523         setStatus(HttpServletResponse.SC_MOVED_TEMPORARILY);
524         closeOutput();
525     }
526 
527     @Override
528     public void setDateHeader(String name, long date)
529     {
530         if (!isIncluding())
531             _fields.putDateField(name, date);
532     }
533 
534     @Override
535     public void addDateHeader(String name, long date)
536     {
537         if (!isIncluding())
538             _fields.addDateField(name, date);
539     }
540 
541     public void setHeader(HttpHeader name, String value)
542     {
543         if (HttpHeader.CONTENT_TYPE == name)
544             setContentType(value);
545         else
546         {
547             if (isIncluding())
548                 return;
549 
550             _fields.put(name, value);
551 
552             if (HttpHeader.CONTENT_LENGTH == name)
553             {
554                 if (value == null)
555                     _contentLength = -1l;
556                 else
557                     _contentLength = Long.parseLong(value);
558             }
559         }
560     }
561 
562     @Override
563     public void setHeader(String name, String value)
564     {
565         if (HttpHeader.CONTENT_TYPE.is(name))
566             setContentType(value);
567         else
568         {
569             if (isIncluding())
570             {
571                 if (name.startsWith(SET_INCLUDE_HEADER_PREFIX))
572                     name = name.substring(SET_INCLUDE_HEADER_PREFIX.length());
573                 else
574                     return;
575             }
576             _fields.put(name, value);
577             if (HttpHeader.CONTENT_LENGTH.is(name))
578             {
579                 if (value == null)
580                     _contentLength = -1l;
581                 else
582                     _contentLength = Long.parseLong(value);
583             }
584         }
585     }
586 
587     @Override
588     public Collection<String> getHeaderNames()
589     {
590         final HttpFields fields = _fields;
591         return fields.getFieldNamesCollection();
592     }
593 
594     @Override
595     public String getHeader(String name)
596     {
597         return _fields.getStringField(name);
598     }
599 
600     @Override
601     public Collection<String> getHeaders(String name)
602     {
603         final HttpFields fields = _fields;
604         Collection<String> i = fields.getValuesCollection(name);
605         if (i == null)
606             return Collections.emptyList();
607         return i;
608     }
609 
610     @Override
611     public void addHeader(String name, String value)
612     {
613         if (isIncluding())
614         {
615             if (name.startsWith(SET_INCLUDE_HEADER_PREFIX))
616                 name = name.substring(SET_INCLUDE_HEADER_PREFIX.length());
617             else
618                 return;
619         }
620 
621         if (HttpHeader.CONTENT_TYPE.is(name))
622         {
623             setContentType(value);
624             return;
625         }
626         
627         if (HttpHeader.CONTENT_LENGTH.is(name))
628         {
629             setHeader(name,value);
630             return;
631         }
632         
633         _fields.add(name, value);
634     }
635 
636     @Override
637     public void setIntHeader(String name, int value)
638     {
639         if (!isIncluding())
640         {
641             _fields.putLongField(name, value);
642             if (HttpHeader.CONTENT_LENGTH.is(name))
643                 _contentLength = value;
644         }
645     }
646 
647     @Override
648     public void addIntHeader(String name, int value)
649     {
650         if (!isIncluding())
651         {
652             _fields.add(name, Integer.toString(value));
653             if (HttpHeader.CONTENT_LENGTH.is(name))
654                 _contentLength = value;
655         }
656     }
657 
658     @Override
659     public void setStatus(int sc)
660     {
661         if (sc <= 0)
662             throw new IllegalArgumentException();
663         if (!isIncluding())
664         {
665             _status = sc;
666             _reason = null;
667         }
668     }
669 
670     @Override
671     @Deprecated
672     public void setStatus(int sc, String sm)
673     {
674         setStatusWithReason(sc,sm);
675     }
676     
677     public void setStatusWithReason(int sc, String sm)
678     {
679         if (sc <= 0)
680             throw new IllegalArgumentException();
681         if (!isIncluding())
682         {
683             _status = sc;
684             _reason = sm;
685         }
686     }
687 
688     @Override
689     public String getCharacterEncoding()
690     {
691         if (_characterEncoding == null)
692             _characterEncoding = StringUtil.__ISO_8859_1;
693         return _characterEncoding;
694     }
695 
696     @Override
697     public String getContentType()
698     {
699         return _contentType;
700     }
701 
702     @Override
703     public ServletOutputStream getOutputStream() throws IOException
704     {
705         if (_outputType == OutputType.WRITER)
706             throw new IllegalStateException("WRITER");
707         _outputType = OutputType.STREAM;
708         return _out;
709     }
710 
711     public boolean isWriting()
712     {
713         return _outputType == OutputType.WRITER;
714     }
715 
716     @Override
717     public PrintWriter getWriter() throws IOException
718     {
719         if (_outputType == OutputType.STREAM)
720             throw new IllegalStateException("STREAM");
721 
722         if (_outputType == OutputType.NONE)
723         {
724             /* get encoding from Content-Type header */
725             String encoding = _characterEncoding;
726             if (encoding == null)
727             {
728                 encoding = MimeTypes.inferCharsetFromContentType(_contentType);
729                 if (encoding == null)
730                     encoding = StringUtil.__ISO_8859_1;
731                 setCharacterEncoding(encoding);
732             }
733             
734             if (_writer != null && _writer.isFor(encoding))
735                 _writer.reopen();
736             else
737             {
738                 if (StringUtil.__ISO_8859_1.equalsIgnoreCase(encoding))
739                     _writer = new ResponseWriter(new Iso88591HttpWriter(_out),encoding);
740                 else if (StringUtil.__UTF8.equalsIgnoreCase(encoding))
741                     _writer = new ResponseWriter(new Utf8HttpWriter(_out),encoding);
742                 else
743                     _writer = new ResponseWriter(new EncodingHttpWriter(_out, encoding),encoding);
744             }
745             
746             // Set the output type at the end, because setCharacterEncoding() checks for it
747             _outputType = OutputType.WRITER;
748         }
749         return _writer;
750     }
751 
752     @Override
753     public void setContentLength(int len)
754     {
755         // Protect from setting after committed as default handling
756         // of a servlet HEAD request ALWAYS sets _content length, even
757         // if the getHandling committed the response!
758         if (isCommitted() || isIncluding())
759             return;
760 
761         long written = _out.getWritten();
762         if (written > len)
763             throw new IllegalArgumentException("setContentLength(" + len + ") when already written " + written);
764 
765         _contentLength = len;
766         _fields.putLongField(HttpHeader.CONTENT_LENGTH.toString(), len);
767 
768         if (_contentLength > 0)
769         {
770             if (isAllContentWritten(written))
771             {
772                 try
773                 {
774                     closeOutput();
775                 }
776                 catch(IOException e)
777                 {
778                     throw new RuntimeIOException(e);
779                 }
780             }
781         }
782     }
783 
784     public boolean isAllContentWritten(long written)
785     {
786         return (_contentLength >= 0 && written >= _contentLength);
787     }
788 
789     public void closeOutput() throws IOException
790     {
791         switch (_outputType)
792         {
793             case WRITER:
794                 _writer.close();
795                 break;
796             case STREAM:
797                 getOutputStream().close();
798                 break;
799             default:
800                 _out.close();
801         }
802     }
803 
804     public long getLongContentLength()
805     {
806         return _contentLength;
807     }
808 
809     public void setLongContentLength(long len)
810     {
811         // Protect from setting after committed as default handling
812         // of a servlet HEAD request ALWAYS sets _content length, even
813         // if the getHandling committed the response!
814         if (isCommitted() || isIncluding())
815             return;
816         _contentLength = len;
817         _fields.putLongField(HttpHeader.CONTENT_LENGTH.toString(), len);
818     }
819 
820     @Override
821     public void setCharacterEncoding(String encoding)
822     {
823         if (isIncluding())
824             return;
825 
826         if (_outputType == OutputType.NONE && !isCommitted())
827         {
828             if (encoding == null)
829             {
830                 // Clear any encoding.
831                 if (_characterEncoding != null)
832                 {
833                     _characterEncoding = null;
834                     if (_contentType != null)
835                     {
836                         _contentType = MimeTypes.getContentTypeWithoutCharset(_contentType);
837                         HttpField field = HttpField.CONTENT_TYPE.get(_contentType);
838                         if (field!=null)
839                             _fields.put(field);
840                         else
841                             _fields.put(HttpHeader.CONTENT_TYPE, _contentType);
842                     }
843                 }
844             }
845             else
846             {
847                 // No, so just add this one to the mimetype
848                 _characterEncoding = StringUtil.normalizeCharset(encoding);
849                 if (_contentType != null)
850                 {
851                     _contentType = MimeTypes.getContentTypeWithoutCharset(_contentType) + ";charset=" + _characterEncoding;
852                     HttpField field = HttpField.CONTENT_TYPE.get(_contentType);
853                     if (field!=null)
854                         _fields.put(field);
855                     else
856                         _fields.put(HttpHeader.CONTENT_TYPE, _contentType);
857                 }
858             }
859         }
860     }
861 
862     @Override
863     public void setContentType(String contentType)
864     {
865         if (isCommitted() || isIncluding())
866             return;
867 
868         if (contentType == null)
869         {
870             if (isWriting() && _characterEncoding != null)
871                 throw new IllegalSelectorException();
872 
873             if (_locale == null)
874                 _characterEncoding = null;
875             _mimeType = null;
876             _contentType = null;
877             _fields.remove(HttpHeader.CONTENT_TYPE);
878         }
879         else
880         {
881             _contentType = contentType;
882             _mimeType = MimeTypes.CACHE.get(contentType);
883             String charset;
884             if (_mimeType != null && _mimeType.getCharset() != null)
885                 charset = _mimeType.getCharset().toString();
886             else
887                 charset = MimeTypes.getCharsetFromContentType(contentType);
888 
889             if (charset == null)
890             {
891                 if (_characterEncoding != null)
892                 {
893                     _contentType = contentType + ";charset=" + _characterEncoding;
894                     _mimeType = null;
895                 }
896             }
897             else if (isWriting() && !charset.equals(_characterEncoding))
898             {
899                 // too late to change the character encoding;
900                 _mimeType = null;
901                 _contentType = MimeTypes.getContentTypeWithoutCharset(_contentType);
902                 if (_characterEncoding != null)
903                     _contentType = _contentType + ";charset=" + _characterEncoding;
904             }
905             else
906             {
907                 _characterEncoding = charset;
908             }
909 
910             HttpField field = HttpField.CONTENT_TYPE.get(_contentType);
911             if (field!=null)
912                 _fields.put(field);
913             else
914                 _fields.put(HttpHeader.CONTENT_TYPE, _contentType);
915         }
916     }
917 
918     @Override
919     public void setBufferSize(int size)
920     {
921         if (isCommitted() || getContentCount() > 0)
922             throw new IllegalStateException("Committed or content written");
923         _out.setBufferSize(size);
924     }
925 
926     @Override
927     public int getBufferSize()
928     {
929         return _out.getBufferSize();
930     }
931 
932     @Override
933     public void flushBuffer() throws IOException
934     {
935         if (!_out.isClosed())
936             _out.flush();
937     }
938 
939     @Override
940     public void reset()
941     {
942         resetForForward();
943         _status = 200;
944         _reason = null;
945         _contentLength = -1;
946         _fields.clear();
947 
948         String connection = _channel.getRequest().getHttpFields().getStringField(HttpHeader.CONNECTION);
949         if (connection != null)
950         {
951             String[] values = connection.split(",");
952             for (int i = 0; values != null && i < values.length; i++)
953             {
954                 HttpHeaderValue cb = HttpHeaderValue.CACHE.get(values[0].trim());
955 
956                 if (cb != null)
957                 {
958                     switch (cb)
959                     {
960                         case CLOSE:
961                             _fields.put(HttpHeader.CONNECTION, HttpHeaderValue.CLOSE.toString());
962                             break;
963 
964                         case KEEP_ALIVE:
965                             if (HttpVersion.HTTP_1_0.is(_channel.getRequest().getProtocol()))
966                                 _fields.put(HttpHeader.CONNECTION, HttpHeaderValue.KEEP_ALIVE.toString());
967                             break;
968                         case TE:
969                             _fields.put(HttpHeader.CONNECTION, HttpHeaderValue.TE.toString());
970                             break;
971                         default:
972                     }
973                 }
974             }
975         }
976     }
977 
978     public void reset(boolean preserveCookies)
979     { 
980         if (!preserveCookies)
981             reset();
982         else
983         {
984             ArrayList<String> cookieValues = new ArrayList<String>(5);
985             Enumeration<String> vals = _fields.getValues(HttpHeader.SET_COOKIE.asString());
986             while (vals.hasMoreElements())
987                 cookieValues.add(vals.nextElement());
988             reset();
989             for (String v:cookieValues)
990                 _fields.add(HttpHeader.SET_COOKIE, v);
991         }
992     }
993 
994     public void resetForForward()
995     {
996         resetBuffer();
997         _outputType = OutputType.NONE;
998     }
999 
1000     @Override
1001     public void resetBuffer()
1002     {
1003         if (isCommitted())
1004             throw new IllegalStateException("Committed");
1005 
1006         switch (_outputType)
1007         {
1008             case STREAM:
1009             case WRITER:
1010                 _out.reset();
1011                 break;
1012             default:
1013         }
1014 
1015         _out.resetBuffer();
1016     }
1017 
1018     protected ResponseInfo newResponseInfo()
1019     {
1020         if (_status == HttpStatus.NOT_SET_000)
1021             _status = HttpStatus.OK_200;
1022         return new ResponseInfo(_channel.getRequest().getHttpVersion(), _fields, getLongContentLength(), getStatus(), getReason(), _channel.getRequest().isHead());
1023     }
1024 
1025     @Override
1026     public boolean isCommitted()
1027     {
1028         return _channel.isCommitted();
1029     }
1030 
1031     @Override
1032     public void setLocale(Locale locale)
1033     {
1034         if (locale == null || isCommitted() || isIncluding())
1035             return;
1036 
1037         _locale = locale;
1038         _fields.put(HttpHeader.CONTENT_LANGUAGE, locale.toString().replace('_', '-'));
1039 
1040         if (_outputType != OutputType.NONE)
1041             return;
1042 
1043         if (_channel.getRequest().getContext() == null)
1044             return;
1045 
1046         String charset = _channel.getRequest().getContext().getContextHandler().getLocaleEncoding(locale);
1047 
1048         if (charset != null && charset.length() > 0 && _characterEncoding == null)
1049             setCharacterEncoding(charset);
1050     }
1051 
1052     @Override
1053     public Locale getLocale()
1054     {
1055         if (_locale == null)
1056             return Locale.getDefault();
1057         return _locale;
1058     }
1059 
1060     @Override
1061     public int getStatus()
1062     {
1063         return _status;
1064     }
1065 
1066     public String getReason()
1067     {
1068         return _reason;
1069     }
1070 
1071     public HttpFields getHttpFields()
1072     {
1073         return _fields;
1074     }
1075 
1076     public long getContentCount()
1077     {
1078         return _out.getWritten();
1079     }
1080 
1081     @Override
1082     public String toString()
1083     {
1084         return String.format("%s %d %s%n%s", _channel.getRequest().getHttpVersion(), _status, _reason == null ? "" : _reason, _fields);
1085     }
1086     
1087 
1088     private static class ResponseWriter extends PrintWriter
1089     {
1090         private final String _encoding;
1091         private final HttpWriter _httpWriter;
1092         
1093         public ResponseWriter(HttpWriter httpWriter,String encoding)
1094         {
1095             super(httpWriter);
1096             _httpWriter=httpWriter;
1097             _encoding=encoding;
1098         }
1099 
1100         public boolean isFor(String encoding)
1101         {
1102             return _encoding.equalsIgnoreCase(encoding);
1103         }
1104         
1105         protected void reopen()
1106         {
1107             super.clearError();
1108             out=_httpWriter;
1109         }
1110     }
1111 }