View Javadoc

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