2 // ========================================================================
3 // Copyright (c) 1995-2014 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.
9 // The Eclipse Public License is available at
10 // http://www.eclipse.org/legal/epl-v10.html
12 // The Apache License v2.0 is available at
13 // http://www.opensource.org/licenses/apache2.0.php
15 // You may elect to redistribute this code under either of these licenses.
16 // ========================================================================
19 package org.eclipse.jetty.server;
21 import static org.eclipse.jetty.util.QuotedStringTokenizer.isQuoted;
23 import java.io.IOException;
24 import java.io.PrintWriter;
25 import java.nio.channels.IllegalSelectorException;
26 import java.util.ArrayList;
27 import java.util.Collection;
28 import java.util.Collections;
29 import java.util.Enumeration;
30 import java.util.Iterator;
31 import java.util.Locale;
32 import java.util.concurrent.atomic.AtomicInteger;
34 import javax.servlet.RequestDispatcher;
35 import javax.servlet.ServletOutputStream;
36 import javax.servlet.http.Cookie;
37 import javax.servlet.http.HttpServletResponse;
38 import javax.servlet.http.HttpSession;
40 import org.eclipse.jetty.http.DateGenerator;
41 import org.eclipse.jetty.http.HttpContent;
42 import org.eclipse.jetty.http.HttpCookie;
43 import org.eclipse.jetty.http.HttpField;
44 import org.eclipse.jetty.http.HttpFields;
45 import org.eclipse.jetty.http.HttpGenerator;
46 import org.eclipse.jetty.http.HttpGenerator.ResponseInfo;
47 import org.eclipse.jetty.http.HttpHeader;
48 import org.eclipse.jetty.http.HttpHeaderValue;
49 import org.eclipse.jetty.http.HttpScheme;
50 import org.eclipse.jetty.http.HttpStatus;
51 import org.eclipse.jetty.http.HttpURI;
52 import org.eclipse.jetty.http.HttpVersion;
53 import org.eclipse.jetty.http.MimeTypes;
54 import org.eclipse.jetty.io.RuntimeIOException;
55 import org.eclipse.jetty.server.handler.ErrorHandler;
56 import org.eclipse.jetty.util.ByteArrayISO8859Writer;
57 import org.eclipse.jetty.util.QuotedStringTokenizer;
58 import org.eclipse.jetty.util.StringUtil;
59 import org.eclipse.jetty.util.URIUtil;
60 import org.eclipse.jetty.util.log.Log;
61 import org.eclipse.jetty.util.log.Logger;
64 * <p>{@link Response} provides the implementation for {@link HttpServletResponse}.</p>
66 public class Response implements HttpServletResponse
68 private static final Logger LOG = Log.getLogger(Response.class);
69 private static final String __COOKIE_DELIM="\",;\\ \t";
70 private final static String __01Jan1970_COOKIE = DateGenerator.formatCookieDate(0).trim();
71 private final static int __MIN_BUFFER_SIZE = 1;
74 // Cookie building buffer. Reduce garbage for cookie using applications
75 private static final ThreadLocal<StringBuilder> __cookieBuilder = new ThreadLocal<StringBuilder>()
78 protected StringBuilder initialValue()
80 return new StringBuilder(128);
84 /* ------------------------------------------------------------ */
85 public static Response getResponse(HttpServletResponse response)
87 if (response instanceof Response)
88 return (Response)response;
89 return HttpChannel.getCurrentHttpChannel().getResponse();
93 public enum OutputType
99 * If a header name starts with this string, the header (stripped of the prefix)
100 * can be set during include using only {@link #setHeader(String, String)} or
101 * {@link #addHeader(String, String)}.
103 public final static String SET_INCLUDE_HEADER_PREFIX = "org.eclipse.jetty.server.include.";
106 * If this string is found within the comment of a cookie added with {@link #addCookie(Cookie)}, then the cookie
107 * will be set as HTTP ONLY.
109 public final static String HTTP_ONLY_COMMENT = "__HTTP_ONLY__";
111 private final HttpChannel<?> _channel;
112 private final HttpFields _fields = new HttpFields();
113 private final AtomicInteger _include = new AtomicInteger();
114 private HttpOutput _out;
115 private int _status = HttpStatus.NOT_SET_000;
116 private String _reason;
117 private Locale _locale;
118 private MimeTypes.Type _mimeType;
119 private String _characterEncoding;
120 private boolean _explicitEncoding;
121 private String _contentType;
122 private OutputType _outputType = OutputType.NONE;
123 private ResponseWriter _writer;
124 private long _contentLength = -1;
127 public Response(HttpChannel<?> channel, HttpOutput out)
133 protected HttpChannel<?> getHttpChannel()
138 protected void recycle()
140 _status = HttpStatus.NOT_SET_000;
144 _characterEncoding = null;
146 _outputType = OutputType.NONE;
150 _explicitEncoding=false;
153 public void setHeaders(HttpContent httpContent)
155 Response response = _channel.getResponse();
156 String contentType = httpContent.getContentType();
157 if (contentType != null && !response.getHttpFields().containsKey(HttpHeader.CONTENT_TYPE.asString()))
158 setContentType(contentType);
160 if (httpContent.getContentLength() > 0)
161 setLongContentLength(httpContent.getContentLength());
163 String lm = httpContent.getLastModified();
165 response.getHttpFields().put(HttpHeader.LAST_MODIFIED, lm);
166 else if (httpContent.getResource() != null)
168 long lml = httpContent.getResource().lastModified();
170 response.getHttpFields().putDateField(HttpHeader.LAST_MODIFIED, lml);
173 String etag=httpContent.getETag();
175 response.getHttpFields().put(HttpHeader.ETAG,etag);
178 public HttpOutput getHttpOutput()
183 public void setHttpOutput(HttpOutput out)
188 public boolean isIncluding()
190 return _include.get() > 0;
193 public void include()
195 _include.incrementAndGet();
198 public void included()
200 _include.decrementAndGet();
201 if (_outputType == OutputType.WRITER)
208 public void addCookie(HttpCookie cookie)
219 cookie.getVersion());;
223 public void addCookie(Cookie cookie)
225 String comment = cookie.getComment();
226 boolean httpOnly = false;
230 int i = comment.indexOf(HTTP_ONLY_COMMENT);
234 comment = comment.replace(HTTP_ONLY_COMMENT, "").trim();
235 if (comment.length() == 0)
239 addSetCookie(cookie.getName(),
246 httpOnly || cookie.isHttpOnly(),
247 cookie.getVersion());
252 * Format a set cookie value
254 * @param name the name
255 * @param value the value
256 * @param domain the domain
257 * @param path the path
258 * @param maxAge the maximum age
259 * @param comment the comment (only present on versions > 0)
260 * @param isSecure true if secure cookie
261 * @param isHttpOnly true if for http only
262 * @param version version of cookie logic to use (0 == default behavior)
264 public void addSetCookie(
270 final String comment,
271 final boolean isSecure,
272 final boolean isHttpOnly,
276 if (name == null || name.length() == 0)
277 throw new IllegalArgumentException("Bad cookie name");
279 // Format value and params
280 StringBuilder buf = __cookieBuilder.get();
283 // Name is checked for legality by servlet spec, but can also be passed directly so check again for quoting
284 boolean quote_name=isQuoteNeededForCookie(name);
285 quoteOnlyOrAppend(buf,name,quote_name);
289 // Remember name= part to look for other matching set-cookie
290 String name_equals=buf.toString();
293 boolean quote_value=isQuoteNeededForCookie(value);
294 quoteOnlyOrAppend(buf,value,quote_value);
296 // Look for domain and path fields and check if they need to be quoted
297 boolean has_domain = domain!=null && domain.length()>0;
298 boolean quote_domain = has_domain && isQuoteNeededForCookie(domain);
299 boolean has_path = path!=null && path.length()>0;
300 boolean quote_path = has_path && isQuoteNeededForCookie(path);
302 // Upgrade the version if we have a comment or we need to quote value/path/domain or if they were already quoted
303 if (version==0 && ( comment!=null || quote_name || quote_value || quote_domain || quote_path || isQuoted(name) || isQuoted(value) || isQuoted(path) || isQuoted(domain)))
308 buf.append (";Version=1");
310 buf.append (";Version=").append(version);
315 buf.append(";Path=");
316 quoteOnlyOrAppend(buf,path,quote_path);
322 buf.append(";Domain=");
323 quoteOnlyOrAppend(buf,domain,quote_domain);
326 // Handle max-age and/or expires
329 // Always use expires
330 // This is required as some browser (M$ this means you!) don't handle max-age even with v1 cookies
331 buf.append(";Expires=");
333 buf.append(__01Jan1970_COOKIE);
335 DateGenerator.formatCookieDate(buf, System.currentTimeMillis() + 1000L * maxAge);
337 // for v1 cookies, also send max-age
340 buf.append(";Max-Age=");
345 // add the other fields
347 buf.append(";Secure");
349 buf.append(";HttpOnly");
352 buf.append(";Comment=");
353 quoteOnlyOrAppend(buf,comment,isQuoteNeededForCookie(comment));
356 // remove any existing set-cookie fields of same name
357 Iterator<HttpField> i=_fields.iterator();
360 HttpField field=i.next();
361 if (field.getHeader()==HttpHeader.SET_COOKIE)
363 String val = field.getValue();
364 if (val!=null && val.startsWith(name_equals))
366 //existing cookie has same name, does it also match domain and path?
367 if (((!has_domain && !val.contains("Domain")) || (has_domain && val.contains(domain))) &&
368 ((!has_path && !val.contains("Path")) || (has_path && val.contains(path))))
376 // add the set cookie
377 _fields.add(HttpHeader.SET_COOKIE.toString(), buf.toString());
379 // Expire responses with set-cookie headers so they do not get cached.
380 _fields.put(HttpHeader.EXPIRES.toString(), DateGenerator.__01Jan1970);
384 /* ------------------------------------------------------------ */
385 /** Does a cookie value need to be quoted?
386 * @param s value string
387 * @return true if quoted;
388 * @throws IllegalArgumentException If there a control characters in the string
390 private static boolean isQuoteNeededForCookie(String s)
392 if (s==null || s.length()==0)
395 if (QuotedStringTokenizer.isQuoted(s))
398 for (int i=0;i<s.length();i++)
400 char c = s.charAt(i);
401 if (__COOKIE_DELIM.indexOf(c)>=0)
404 if (c<0x20 || c>=0x7f)
405 throw new IllegalArgumentException("Illegal character in cookie value");
412 private static void quoteOnlyOrAppend(StringBuilder buf, String s, boolean quote)
415 QuotedStringTokenizer.quoteOnly(buf,s);
421 public boolean containsHeader(String name)
423 return _fields.containsKey(name);
427 public String encodeURL(String url)
429 final Request request = _channel.getRequest();
430 SessionManager sessionManager = request.getSessionManager();
431 if (sessionManager == null)
435 if (sessionManager.isCheckingRemoteSessionIdEncoding() && URIUtil.hasScheme(url))
437 uri = new HttpURI(url);
438 String path = uri.getPath();
439 path = (path == null ? "" : path);
440 int port = uri.getPort();
442 port = HttpScheme.HTTPS.asString().equalsIgnoreCase(uri.getScheme()) ? 443 : 80;
443 if (!request.getServerName().equalsIgnoreCase(uri.getHost()) ||
444 request.getServerPort() != port ||
445 !path.startsWith(request.getContextPath())) //TODO the root context path is "", with which every non null string starts
449 String sessionURLPrefix = sessionManager.getSessionIdPathParameterNamePrefix();
450 if (sessionURLPrefix == null)
456 // should not encode if cookies in evidence
457 if ((sessionManager.isUsingCookies() && request.isRequestedSessionIdFromCookie()) || !sessionManager.isUsingURLs())
459 int prefix = url.indexOf(sessionURLPrefix);
462 int suffix = url.indexOf("?", prefix);
464 suffix = url.indexOf("#", prefix);
466 if (suffix <= prefix)
467 return url.substring(0, prefix);
468 return url.substring(0, prefix) + url.substring(suffix);
474 HttpSession session = request.getSession(false);
481 if (!sessionManager.isValid(session))
484 String id = sessionManager.getNodeId(session);
487 uri = new HttpURI(url);
491 int prefix = url.indexOf(sessionURLPrefix);
494 int suffix = url.indexOf("?", prefix);
496 suffix = url.indexOf("#", prefix);
498 if (suffix <= prefix)
499 return url.substring(0, prefix + sessionURLPrefix.length()) + id;
500 return url.substring(0, prefix + sessionURLPrefix.length()) + id +
501 url.substring(suffix);
505 int suffix = url.indexOf('?');
507 suffix = url.indexOf('#');
511 ((HttpScheme.HTTPS.is(uri.getScheme()) || HttpScheme.HTTP.is(uri.getScheme())) && uri.getPath() == null ? "/" : "") + //if no path, insert the root path
512 sessionURLPrefix + id;
516 return url.substring(0, suffix) +
517 ((HttpScheme.HTTPS.is(uri.getScheme()) || HttpScheme.HTTP.is(uri.getScheme())) && uri.getPath() == null ? "/" : "") + //if no path so insert the root path
518 sessionURLPrefix + id + url.substring(suffix);
522 public String encodeRedirectURL(String url)
524 return encodeURL(url);
529 public String encodeUrl(String url)
531 return encodeURL(url);
536 public String encodeRedirectUrl(String url)
538 return encodeRedirectURL(url);
542 public void sendError(int sc) throws IOException
551 public void sendError(int code, String message) throws IOException
557 LOG.warn("Committed before "+code+" "+message);
560 _characterEncoding=null;
561 setHeader(HttpHeader.EXPIRES,null);
562 setHeader(HttpHeader.LAST_MODIFIED,null);
563 setHeader(HttpHeader.CACHE_CONTROL,null);
564 setHeader(HttpHeader.CONTENT_TYPE,null);
565 setHeader(HttpHeader.CONTENT_LENGTH,null);
567 _outputType = OutputType.NONE;
571 Request request = _channel.getRequest();
572 Throwable cause = (Throwable)request.getAttribute(Dispatcher.ERROR_EXCEPTION);
574 message=cause==null?HttpStatus.getMessage(code):cause.toString();
576 // If we are allowed to have a body
577 if (code!=SC_NO_CONTENT &&
578 code!=SC_NOT_MODIFIED &&
579 code!=SC_PARTIAL_CONTENT &&
582 ErrorHandler error_handler = ErrorHandler.getErrorHandler(_channel.getServer(),request.getContext()==null?null:request.getContext().getContextHandler());
583 if (error_handler!=null)
585 request.setAttribute(RequestDispatcher.ERROR_STATUS_CODE,new Integer(code));
586 request.setAttribute(RequestDispatcher.ERROR_MESSAGE, message);
587 request.setAttribute(RequestDispatcher.ERROR_REQUEST_URI, request.getRequestURI());
588 request.setAttribute(RequestDispatcher.ERROR_SERVLET_NAME,request.getServletName());
589 error_handler.handle(null,_channel.getRequest(),_channel.getRequest(),this );
593 setHeader(HttpHeader.CACHE_CONTROL, "must-revalidate,no-cache,no-store");
594 setContentType(MimeTypes.Type.TEXT_HTML_8859_1.toString());
595 try (ByteArrayISO8859Writer writer= new ByteArrayISO8859Writer(2048);)
599 message= StringUtil.replace(message, "&", "&");
600 message= StringUtil.replace(message, "<", "<");
601 message= StringUtil.replace(message, ">", ">");
603 String uri= request.getRequestURI();
606 uri= StringUtil.replace(uri, "&", "&");
607 uri= StringUtil.replace(uri, "<", "<");
608 uri= StringUtil.replace(uri, ">", ">");
611 writer.write("<html>\n<head>\n<meta http-equiv=\"Content-Type\" content=\"text/html;charset=ISO-8859-1\"/>\n");
612 writer.write("<title>Error ");
613 writer.write(Integer.toString(code));
616 writer.write(message);
617 writer.write("</title>\n</head>\n<body>\n<h2>HTTP ERROR: ");
618 writer.write(Integer.toString(code));
619 writer.write("</h2>\n<p>Problem accessing ");
621 writer.write(". Reason:\n<pre> ");
622 writer.write(message);
623 writer.write("</pre>");
624 writer.write("</p>\n<hr /><i><small>Powered by Jetty://</small></i>");
625 writer.write("\n</body>\n</html>\n");
628 setContentLength(writer.size());
629 try (ServletOutputStream outputStream = getOutputStream())
631 writer.writeTo(outputStream);
637 else if (code!=SC_PARTIAL_CONTENT)
639 // TODO work out why this is required?
640 _channel.getRequest().getHttpFields().remove(HttpHeader.CONTENT_TYPE);
641 _channel.getRequest().getHttpFields().remove(HttpHeader.CONTENT_LENGTH);
642 _characterEncoding=null;
650 * Sends a 102-Processing response.
651 * If the connection is a HTTP connection, the version is 1.1 and the
652 * request has a Expect header starting with 102, then a 102 response is
653 * sent. This indicates that the request still be processed and real response
654 * can still be sent. This method is called by sendError if it is passed 102.
655 * @see javax.servlet.http.HttpServletResponse#sendError(int)
657 public void sendProcessing() throws IOException
659 if (_channel.isExpecting102Processing() && !isCommitted())
661 _channel.sendResponse(HttpGenerator.PROGRESS_102_INFO, null, true);
666 * Sends a response with one of the 300 series redirection codes.
669 * @throws IOException
671 public void sendRedirect(int code, String location) throws IOException
673 if ((code < HttpServletResponse.SC_MULTIPLE_CHOICES) || (code >= HttpServletResponse.SC_BAD_REQUEST))
674 throw new IllegalArgumentException("Not a 3xx redirect code");
679 if (location == null)
680 throw new IllegalArgumentException();
682 if (!URIUtil.hasScheme(location))
684 StringBuilder buf = _channel.getRequest().getRootURL();
686 if (location.startsWith("//"))
688 buf.delete(0, buf.length());
689 buf.append(_channel.getRequest().getScheme());
691 buf.append(location);
693 else if (location.startsWith("/"))
694 buf.append(location);
697 String path = _channel.getRequest().getRequestURI();
698 String parent = (path.endsWith("/")) ? path : URIUtil.parentPath(path);
699 location = URIUtil.addPaths(parent, location);
700 if (location == null)
701 throw new IllegalStateException("path cannot be above root");
702 if (!location.startsWith("/"))
704 buf.append(location);
707 location = buf.toString();
708 HttpURI uri = new HttpURI(location);
709 String path = uri.getDecodedPath();
710 String canonical = URIUtil.canonicalPath(path);
711 if (canonical == null)
712 throw new IllegalArgumentException();
713 if (!canonical.equals(path))
715 buf = _channel.getRequest().getRootURL();
716 buf.append(URIUtil.encodePath(canonical));
717 String param=uri.getParam();
723 String query=uri.getQuery();
729 String fragment=uri.getFragment();
733 buf.append(fragment);
735 location = buf.toString();
740 setHeader(HttpHeader.LOCATION, location);
746 public void sendRedirect(String location) throws IOException
748 sendRedirect(HttpServletResponse.SC_MOVED_TEMPORARILY, location);
752 public void setDateHeader(String name, long date)
755 _fields.putDateField(name, date);
759 public void addDateHeader(String name, long date)
762 _fields.addDateField(name, date);
765 public void setHeader(HttpHeader name, String value)
767 if (HttpHeader.CONTENT_TYPE == name)
768 setContentType(value);
774 _fields.put(name, value);
776 if (HttpHeader.CONTENT_LENGTH == name)
779 _contentLength = -1l;
781 _contentLength = Long.parseLong(value);
787 public void setHeader(String name, String value)
789 if (HttpHeader.CONTENT_TYPE.is(name))
790 setContentType(value);
795 if (name.startsWith(SET_INCLUDE_HEADER_PREFIX))
796 name = name.substring(SET_INCLUDE_HEADER_PREFIX.length());
800 _fields.put(name, value);
801 if (HttpHeader.CONTENT_LENGTH.is(name))
804 _contentLength = -1l;
806 _contentLength = Long.parseLong(value);
812 public Collection<String> getHeaderNames()
814 final HttpFields fields = _fields;
815 return fields.getFieldNamesCollection();
819 public String getHeader(String name)
821 return _fields.getStringField(name);
825 public Collection<String> getHeaders(String name)
827 final HttpFields fields = _fields;
828 Collection<String> i = fields.getValuesList(name);
830 return Collections.emptyList();
835 public void addHeader(String name, String value)
839 if (name.startsWith(SET_INCLUDE_HEADER_PREFIX))
840 name = name.substring(SET_INCLUDE_HEADER_PREFIX.length());
845 if (HttpHeader.CONTENT_TYPE.is(name))
847 setContentType(value);
851 if (HttpHeader.CONTENT_LENGTH.is(name))
853 setHeader(name,value);
857 _fields.add(name, value);
861 public void setIntHeader(String name, int value)
865 _fields.putLongField(name, value);
866 if (HttpHeader.CONTENT_LENGTH.is(name))
867 _contentLength = value;
872 public void addIntHeader(String name, int value)
876 _fields.add(name, Integer.toString(value));
877 if (HttpHeader.CONTENT_LENGTH.is(name))
878 _contentLength = value;
883 public void setStatus(int sc)
886 throw new IllegalArgumentException();
896 public void setStatus(int sc, String sm)
898 setStatusWithReason(sc,sm);
901 public void setStatusWithReason(int sc, String sm)
904 throw new IllegalArgumentException();
913 public String getCharacterEncoding()
915 if (_characterEncoding == null)
916 _characterEncoding = StringUtil.__ISO_8859_1;
917 return _characterEncoding;
921 public String getContentType()
927 public ServletOutputStream getOutputStream() throws IOException
929 if (_outputType == OutputType.WRITER)
930 throw new IllegalStateException("WRITER");
931 _outputType = OutputType.STREAM;
935 public boolean isWriting()
937 return _outputType == OutputType.WRITER;
941 public PrintWriter getWriter() throws IOException
943 if (_outputType == OutputType.STREAM)
944 throw new IllegalStateException("STREAM");
946 if (_outputType == OutputType.NONE)
948 /* get encoding from Content-Type header */
949 String encoding = _characterEncoding;
950 if (encoding == null)
952 if (_mimeType!=null && _mimeType.isCharsetAssumed())
953 encoding=_mimeType.getCharset().toString();
956 encoding = MimeTypes.inferCharsetFromContentType(_contentType);
957 if (encoding == null)
958 encoding = StringUtil.__ISO_8859_1;
959 setCharacterEncoding(encoding,false);
963 if (_writer != null && _writer.isFor(encoding))
967 if (StringUtil.__ISO_8859_1.equalsIgnoreCase(encoding))
968 _writer = new ResponseWriter(new Iso88591HttpWriter(_out),encoding);
969 else if (StringUtil.__UTF8.equalsIgnoreCase(encoding))
970 _writer = new ResponseWriter(new Utf8HttpWriter(_out),encoding);
972 _writer = new ResponseWriter(new EncodingHttpWriter(_out, encoding),encoding);
975 // Set the output type at the end, because setCharacterEncoding() checks for it
976 _outputType = OutputType.WRITER;
982 public void setContentLength(int len)
984 // Protect from setting after committed as default handling
985 // of a servlet HEAD request ALWAYS sets _content length, even
986 // if the getHandling committed the response!
987 if (isCommitted() || isIncluding())
990 _contentLength = len;
991 if (_contentLength > 0)
993 long written = _out.getWritten();
995 throw new IllegalArgumentException("setContentLength(" + len + ") when already written " + written);
997 _fields.putLongField(HttpHeader.CONTENT_LENGTH, len);
998 if (isAllContentWritten(written))
1004 catch(IOException e)
1006 throw new RuntimeIOException(e);
1010 else if (_contentLength==0)
1012 long written = _out.getWritten();
1014 throw new IllegalArgumentException("setContentLength(0) when already written " + written);
1015 _fields.put(HttpHeader.CONTENT_LENGTH, "0");
1018 _fields.remove(HttpHeader.CONTENT_LENGTH);
1021 public long getContentLength()
1023 return _contentLength;
1026 public boolean isAllContentWritten(long written)
1028 return (_contentLength >= 0 && written >= _contentLength);
1031 public void closeOutput() throws IOException
1033 switch (_outputType)
1037 if (!_out.isClosed())
1041 getOutputStream().close();
1048 public long getLongContentLength()
1050 return _contentLength;
1053 public void setLongContentLength(long len)
1055 // Protect from setting after committed as default handling
1056 // of a servlet HEAD request ALWAYS sets _content length, even
1057 // if the getHandling committed the response!
1058 if (isCommitted() || isIncluding())
1060 _contentLength = len;
1061 _fields.putLongField(HttpHeader.CONTENT_LENGTH.toString(), len);
1065 public void setContentLengthLong(long length)
1067 setLongContentLength(length);
1071 public void setCharacterEncoding(String encoding)
1073 setCharacterEncoding(encoding,true);
1076 private void setCharacterEncoding(String encoding, boolean explicit)
1078 if (isIncluding() || isWriting())
1081 if (_outputType == OutputType.NONE && !isCommitted())
1083 if (encoding == null)
1085 _explicitEncoding=false;
1087 // Clear any encoding.
1088 if (_characterEncoding != null)
1090 _characterEncoding = null;
1092 if (_mimeType!=null)
1094 _mimeType=_mimeType.getBaseType();
1095 _contentType=_mimeType.asString();
1096 _fields.put(_mimeType.getContentTypeField());
1098 else if (_contentType != null)
1100 _contentType = MimeTypes.getContentTypeWithoutCharset(_contentType);
1101 _fields.put(HttpHeader.CONTENT_TYPE, _contentType);
1107 // No, so just add this one to the mimetype
1108 _explicitEncoding = explicit;
1109 _characterEncoding = HttpGenerator.__STRICT?encoding:StringUtil.normalizeCharset(encoding);
1110 if (_mimeType!=null)
1112 _contentType=_mimeType.getBaseType().asString()+ "; charset=" + _characterEncoding;
1113 _mimeType = MimeTypes.CACHE.get(_contentType);
1114 if (_mimeType==null || HttpGenerator.__STRICT)
1115 _fields.put(HttpHeader.CONTENT_TYPE, _contentType);
1117 _fields.put(_mimeType.getContentTypeField());
1119 else if (_contentType != null)
1121 _contentType = MimeTypes.getContentTypeWithoutCharset(_contentType) + "; charset=" + _characterEncoding;
1122 _fields.put(HttpHeader.CONTENT_TYPE, _contentType);
1129 public void setContentType(String contentType)
1131 if (isCommitted() || isIncluding())
1134 if (contentType == null)
1136 if (isWriting() && _characterEncoding != null)
1137 throw new IllegalSelectorException();
1139 if (_locale == null)
1140 _characterEncoding = null;
1142 _contentType = null;
1143 _fields.remove(HttpHeader.CONTENT_TYPE);
1147 _contentType = contentType;
1148 _mimeType = MimeTypes.CACHE.get(contentType);
1151 if (_mimeType!=null && _mimeType.getCharset()!=null && !_mimeType.isCharsetAssumed())
1152 charset=_mimeType.getCharset().toString();
1154 charset = MimeTypes.getCharsetFromContentType(contentType);
1156 if (charset == null)
1158 if (_characterEncoding != null)
1160 _contentType = contentType + "; charset=" + _characterEncoding;
1164 else if (isWriting() && !charset.equals(_characterEncoding))
1166 // too late to change the character encoding;
1168 _contentType = MimeTypes.getContentTypeWithoutCharset(_contentType);
1169 if (_characterEncoding != null)
1170 _contentType = _contentType + "; charset=" + _characterEncoding;
1174 _characterEncoding = charset;
1175 _explicitEncoding = true;
1178 if (HttpGenerator.__STRICT || _mimeType==null)
1179 _fields.put(HttpHeader.CONTENT_TYPE, _contentType);
1182 _contentType=_mimeType.asString();
1183 _fields.put(_mimeType.getContentTypeField());
1190 public void setBufferSize(int size)
1192 if (isCommitted() || getContentCount() > 0)
1193 throw new IllegalStateException("Committed or content written");
1195 size = __MIN_BUFFER_SIZE;
1196 _out.setBufferSize(size);
1200 public int getBufferSize()
1202 return _out.getBufferSize();
1206 public void flushBuffer() throws IOException
1208 if (!_out.isClosed())
1218 _contentLength = -1;
1221 String connection = _channel.getRequest().getHttpFields().getStringField(HttpHeader.CONNECTION);
1222 if (connection != null)
1224 String[] values = connection.split(",");
1225 for (int i = 0; values != null && i < values.length; i++)
1227 HttpHeaderValue cb = HttpHeaderValue.CACHE.get(values[0].trim());
1234 _fields.put(HttpHeader.CONNECTION, HttpHeaderValue.CLOSE.toString());
1238 if (HttpVersion.HTTP_1_0.is(_channel.getRequest().getProtocol()))
1239 _fields.put(HttpHeader.CONNECTION, HttpHeaderValue.KEEP_ALIVE.toString());
1242 _fields.put(HttpHeader.CONNECTION, HttpHeaderValue.TE.toString());
1251 public void reset(boolean preserveCookies)
1253 if (!preserveCookies)
1257 ArrayList<String> cookieValues = new ArrayList<String>(5);
1258 Enumeration<String> vals = _fields.getValues(HttpHeader.SET_COOKIE.asString());
1259 while (vals.hasMoreElements())
1260 cookieValues.add(vals.nextElement());
1262 for (String v:cookieValues)
1263 _fields.add(HttpHeader.SET_COOKIE, v);
1267 public void resetForForward()
1270 _outputType = OutputType.NONE;
1274 public void resetBuffer()
1277 throw new IllegalStateException("Committed");
1279 switch (_outputType)
1291 protected ResponseInfo newResponseInfo()
1293 if (_status == HttpStatus.NOT_SET_000)
1294 _status = HttpStatus.OK_200;
1295 return new ResponseInfo(_channel.getRequest().getHttpVersion(), _fields, getLongContentLength(), getStatus(), getReason(), _channel.getRequest().isHead());
1299 public boolean isCommitted()
1301 return _channel.isCommitted();
1305 public void setLocale(Locale locale)
1307 if (locale == null || isCommitted() || isIncluding())
1311 _fields.put(HttpHeader.CONTENT_LANGUAGE, locale.toString().replace('_', '-'));
1313 if (_outputType != OutputType.NONE)
1316 if (_channel.getRequest().getContext() == null)
1319 String charset = _channel.getRequest().getContext().getContextHandler().getLocaleEncoding(locale);
1321 if (charset != null && charset.length() > 0 && !_explicitEncoding)
1322 setCharacterEncoding(charset,false);
1326 public Locale getLocale()
1328 if (_locale == null)
1329 return Locale.getDefault();
1334 public int getStatus()
1339 public String getReason()
1344 public HttpFields getHttpFields()
1349 public long getContentCount()
1351 return _out.getWritten();
1355 public String toString()
1357 return String.format("%s %d %s%n%s", _channel.getRequest().getHttpVersion(), _status, _reason == null ? "" : _reason, _fields);
1361 private static class ResponseWriter extends PrintWriter
1363 private final String _encoding;
1364 private final HttpWriter _httpWriter;
1366 public ResponseWriter(HttpWriter httpWriter,String encoding)
1369 _httpWriter=httpWriter;
1373 public boolean isFor(String encoding)
1375 return _encoding.equalsIgnoreCase(encoding);
1378 protected void reopen()