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