// // ======================================================================== // Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd. // ------------------------------------------------------------------------ // All rights reserved. This program and the accompanying materials // are made available under the terms of the Eclipse Public License v1.0 // and Apache License v2.0 which accompanies this distribution. // // The Eclipse Public License is available at // http://www.eclipse.org/legal/epl-v10.html // // The Apache License v2.0 is available at // http://www.opensource.org/licenses/apache2.0.php // // You may elect to redistribute this code under either of these licenses. // ======================================================================== // package org.ireland.jnetty.http; import static io.netty.handler.codec.http.HttpHeaders.Names.CONNECTION; import static io.netty.handler.codec.http.HttpHeaders.Names.CONTENT_LENGTH; import io.netty.channel.Channel; import io.netty.channel.ChannelFutureListener; import io.netty.channel.ChannelHandlerContext; import io.netty.channel.socket.SocketChannel; import io.netty.handler.codec.http.FullHttpRequest; import io.netty.handler.codec.http.FullHttpResponse; import io.netty.handler.codec.http.HttpContent; import io.netty.handler.codec.http.HttpHeaders; import io.netty.handler.codec.http.HttpResponseStatus; import io.netty.handler.codec.http.LastHttpContent; import java.io.IOException; import java.io.PrintWriter; import java.nio.channels.IllegalSelectorException; import java.util.ArrayList; import java.util.Collection; import java.util.Date; import java.util.List; import java.util.Locale; import java.util.concurrent.atomic.AtomicInteger; import javax.servlet.ServletContext; import javax.servlet.ServletOutputStream; import javax.servlet.http.Cookie; import javax.servlet.http.HttpServletResponse; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.eclipse.jetty.http.MimeTypes; import org.eclipse.jetty.util.ByteArrayISO8859Writer; import org.eclipse.jetty.util.StringUtil; import org.ireland.jnetty.http.io.ByteBufServletOutputStream; import org.ireland.jnetty.http.io.EncodingHttpWriter; import org.ireland.jnetty.http.io.HttpWriter; import org.ireland.jnetty.http.io.Iso88591HttpWriter; import org.ireland.jnetty.http.io.Utf8HttpWriter; import org.ireland.jnetty.util.http.ServletServerCookieEncoder; import org.ireland.jnetty.webapp.WebApp; /** * <p> * {@link HttpServletResponseImpl} provides the implementation for {@link HttpServletResponse}. * </p> */ public class HttpServletResponseImpl implements HttpServletResponse { /** * Logger available to subclasses. */ protected static final Log LOG = LogFactory.getLog(HttpServletResponseImpl.class); // ----------------------------------------------------- Class Variables /** * Default locale as mandated by the spec. */ private static Locale DEFAULT_LOCALE = Locale.getDefault(); public static final String DEFAULT_CHARACTER_ENCODING = "ISO-8859-1"; private ServletContext servletContext; // Netty private final SocketChannel socketChannel; private final ChannelHandlerContext ctx; private final FullHttpRequest request; private HttpServletRequestImpl _httpRequest; // response private final FullHttpResponse response; // response header private final HttpHeaders headers; // response body private final LastHttpContent body; // response end private final WebApp _webApp; /** * Using output stream flag. */ protected boolean usingOutputStream = false; /** * The associated output stream. */ protected ServletOutputStream outputStream; /** * Using writer flag. */ protected boolean usingWriter = false; /** * Committed flag. */ protected boolean commited = false; private List<Cookie> _cookiesOut; private final AtomicInteger _include = new AtomicInteger(); /* Headers--------------------------------------- */ /** * Status code. */ protected int _status = SC_OK; private String _reason; private Locale _locale; private String contentLanguage = null; private MimeTypes.Type _mimeType; /** * The characterEncoding flag(eplicitly set flag) */ private boolean isCharacterEncodingSet = false; private String _characterEncoding; private String _contentType; private PrintWriter _writer; private long _contentLength = -1; public HttpServletResponseImpl(WebApp webApp, SocketChannel socketChannel, ChannelHandlerContext ctx, FullHttpResponse response, FullHttpRequest request) { this.socketChannel = socketChannel; this.ctx = ctx; this.request = request; // Response this.response = response; // Response Header this.headers = response.headers(); // Response Body this.body = response; this._webApp = webApp; } protected SocketChannel getHttpChannel() { return socketChannel; } protected void recycle() { _status = SC_OK; _reason = null; _locale = null; _characterEncoding = null; _contentType = null; _contentLength = -1; headers.clear(); } public HttpContent getResponseBody() { return body; } public boolean isIncluding() { return _include.get() > 0; } public void include() { _include.incrementAndGet(); } public void included() { _include.decrementAndGet(); // body.reopen(); } @Override // OK public void addCookie(Cookie cookie) { // Ignore any call from an included servlet if (isIncluding() || isCommitted()) { return; } if (_cookiesOut == null) _cookiesOut = new ArrayList<Cookie>(); _cookiesOut.add(cookie); // if we reached here, no exception, cookie is valid // the header name is Set-Cookie for both "old" and v.1 ( RFC2109 ) // RFC2965 is not supported by browsers and the Servlet spec // asks for 2109. headers.set("Set-Cookie", ServletServerCookieEncoder.encode(_cookiesOut)); } @Override // OK public boolean containsHeader(String name) { if (name == null || name.length() == 0) { return false; } // Need special handling for Content-Type and Content-Length due to // special handling of these in coyoteResponse char cc = name.charAt(0); if (cc == 'C' || cc == 'c') { if (name.equalsIgnoreCase(HttpHeaders.Names.CONTENT_TYPE)) { // Will return null if this has not been set return (_contentType != null); } if (name.equalsIgnoreCase(HttpHeaders.Names.CONTENT_LENGTH)) { // -1 means not known and is not sent to client return (_contentLength != -1); } } // Need special handling for Set-Cookie if (cc == 'S' || cc == 's') { if (name.equalsIgnoreCase(HttpHeaders.Names.SET_COOKIE)) { return (_cookiesOut != null) && (_cookiesOut.size() > 0); } } return headers.contains(name); } @Override // TODO public String encodeURL(String url) { /* * SessionManager sessionManager = httpServletRequest.getSessionManager(); if (sessionManager == null) return * url; * * HttpURI uri = null; if (sessionManager.isCheckingRemoteSessionIdEncoding() && URIUtil.hasScheme(url)) { uri = * new HttpURI(url); String path = uri.getPath(); path = (path == null ? "" : path); int port = uri.getPort(); * if (port < 0) port = HttpScheme.HTTPS.asString().equalsIgnoreCase(uri.getScheme()) ? 443 : 80; if * (!httpServletRequest.getServerName().equalsIgnoreCase(uri.getHost()) || httpServletRequest.getServerPort() != * port || !path.startsWith(httpServletRequest.getContextPath())) //TODO the root context path is "", with which * every non null string starts return url; } * * String sessionURLPrefix = sessionManager.getSessionIdPathParameterNamePrefix(); if (sessionURLPrefix == null) * return url; * * if (url == null) return null; * * // should not encode if cookies in evidence if (httpServletRequest.isRequestedSessionIdFromCookie()) { int * prefix = url.indexOf(sessionURLPrefix); if (prefix != -1) { int suffix = url.indexOf("?", prefix); if (suffix * < 0) suffix = url.indexOf("#", prefix); * * if (suffix <= prefix) return url.substring(0, prefix); return url.substring(0, prefix) + * url.substring(suffix); } return url; } * * // get session; HttpSession session = httpServletRequest.getSession(false); * * // no session if (session == null) return url; * * // invalid session if (!sessionManager.isValid(session)) return url; * * String id = sessionManager.getNodeId(session); * * if (uri == null) uri = new HttpURI(url); * * * // Already encoded int prefix = url.indexOf(sessionURLPrefix); if (prefix != -1) { int suffix = * url.indexOf("?", prefix); if (suffix < 0) suffix = url.indexOf("#", prefix); * * if (suffix <= prefix) return url.substring(0, prefix + sessionURLPrefix.length()) + id; return * url.substring(0, prefix + sessionURLPrefix.length()) + id + url.substring(suffix); } * * // edit the session int suffix = url.indexOf('?'); if (suffix < 0) suffix = url.indexOf('#'); if (suffix < 0) * { return url + ((HttpScheme.HTTPS.is(uri.getScheme()) || HttpScheme.HTTP.is(uri.getScheme())) && * uri.getPath() == null ? "/" : "") + //if no path, insert the root path sessionURLPrefix + id; } * * * return url.substring(0, suffix) + ((HttpScheme.HTTPS.is(uri.getScheme()) || * HttpScheme.HTTP.is(uri.getScheme())) && uri.getPath() == null ? "/" : "") + //if no path so insert the root * path sessionURLPrefix + id + url.substring(suffix); */ return url; } @Override public String encodeRedirectURL(String url) { return encodeURL(url); } @Override @Deprecated public String encodeUrl(String url) { return encodeURL(url); } @Override @Deprecated public String encodeRedirectUrl(String url) { return encodeRedirectURL(url); } @Override public void sendError(int sc) throws IOException { if (sc == 102) sendProcessing(); else sendError(sc, null); } @Override public void sendError(int code, String message) throws IOException { if (isIncluding()) return; if (isCommitted()) LOG.warn("Committed before " + code + " " + message); resetBuffer(); _characterEncoding = null; setHeader(HttpHeaders.Names.EXPIRES, null); setHeader(HttpHeaders.Names.LAST_MODIFIED, null); setHeader(HttpHeaders.Names.CACHE_CONTROL, null); setHeader(HttpHeaders.Names.CONTENT_TYPE, null); setHeader(HttpHeaders.Names.CONTENT_LENGTH, null); usingOutputStream = false; usingWriter = false; setStatus(code); _reason = message; if (message == null) message = HttpResponseStatus.valueOf(code).reasonPhrase(); // If we are allowed to have a body if (code != SC_NO_CONTENT && code != SC_NOT_MODIFIED && code != SC_PARTIAL_CONTENT && code >= SC_OK) { setHeader(HttpHeaders.Names.CACHE_CONTROL, "must-revalidate,no-cache,no-store"); setContentType("text/html;charset=ISO-8859-1"); ByteArrayISO8859Writer writer = new ByteArrayISO8859Writer(2048); if (message != null) { message = StringUtil.replace(message, "&", "&"); message = StringUtil.replace(message, "<", "<"); message = StringUtil.replace(message, ">", ">"); } String uri = request.getUri(); if (uri != null) { uri = StringUtil.replace(uri, "&", "&"); uri = StringUtil.replace(uri, "<", "<"); uri = StringUtil.replace(uri, ">", ">"); } writer.write("<html>\n<head>\n<meta http-equiv=\"Content-Type\" content=\"text/html;charset=ISO-8859-1\"/>\n"); writer.write("<title>Error "); writer.write(Integer.toString(code)); writer.write(' '); if (message == null) message = HttpResponseStatus.valueOf(code).reasonPhrase(); writer.write(message); writer.write("</title>\n</head>\n<body>\n<h2>HTTP ERROR: "); writer.write(Integer.toString(code)); writer.write("</h2>\n<p>Problem accessing "); writer.write(uri); writer.write(". Reason:\n<pre> "); writer.write(message); writer.write("</pre>"); writer.write("</p>\n<hr /><i><small>Powered by JNetty(IrelandKen)://</small></i>"); writer.write("\n</body>\n</html>\n"); writer.flush(); setContentLength(writer.size()); writer.writeTo(getOutputStream()); writer.destroy(); } complete(); } /** * Sends a 102-Processing response. If the connection is a HTTP connection, the version is 1.1 and the * httpServletRequest has a Expect header starting with 102, then a 102 response is sent. This indicates that the * httpServletRequest still be processed and real response can still be sent. This method is called by sendError if * it is passed 102. * * @see javax.servlet.http.HttpServletResponse#sendError(int) */ public void sendProcessing() throws IOException { /* * if (socketChannel.isExpecting102Processing() && !isCommitted()) { * socketChannel.commitResponse(HttpGenerator.PROGRESS_102_INFO, null, true); } */ } @Override public void sendRedirect(String location) throws IOException { if (isCommitted()) { throw new IllegalStateException("Can't sendRedirect() after data has committed to the client."); } // Ignore any call from an included servlet if (isIncluding()) { return; } // Clear any data content that has been buffered resetBuffer(); // Generate a temporary redirect to the specified location String absolute = encodeAbsoluteRedirect(location); setStatus(SC_FOUND); setHeader(HttpHeaders.Names.LOCATION, absolute); // Commit the response flushBuffer(); } /** * Convert a URL to a AbsoluteUrl * * @param url * @return */ public String encodeAbsoluteRedirect(String url) { String path = getAbsolutePath(url); // Bug #3051 String encoding = getCharacterEncoding(); boolean isLatin1 = "iso-8859-1".equalsIgnoreCase(encoding); return escapeUrl(path, isLatin1); } private String escapeUrl(String path, boolean isLatin1) { StringBuilder cb = new StringBuilder(); for (int i = 0; i < path.length(); i++) { char ch = path.charAt(i); if (ch == '<') cb.append("%3c"); else if (ch == '"') { cb.append("%22"); } else if (ch < 0x80) cb.append(ch); else if (isLatin1) { addHex(cb, ch); } else if (ch < 0x800) { int d1 = 0xc0 + ((ch >> 6) & 0x1f); int d2 = 0x80 + (ch & 0x3f); addHex(cb, d1); addHex(cb, d2); } else if (ch < 0x8000) { int d1 = 0xe0 + ((ch >> 12) & 0xf); int d2 = 0x80 + ((ch >> 6) & 0x3f); int d3 = 0x80 + (ch & 0x3f); addHex(cb, d1); addHex(cb, d2); addHex(cb, d3); } } return cb.toString(); } /** * Returns the absolute path for a given relative path. * * @param path * the possibly relative url to send to the browser */ private String getAbsolutePath(String path) { int slash = path.indexOf('/'); int len = path.length(); for (int i = 0; i < len; i++) { char ch = path.charAt(i); if (ch == ':') return path; else if (ch >= 'a' && ch <= 'z' || ch >= 'A' && ch <= 'Z') continue; else break; } String hostPrefix = null; String hostAndPort = request.headers().get("Host"); String serverName = _httpRequest.getServerName(); int port = _httpRequest.getServerPort(); if (serverName.startsWith("http:") || serverName.startsWith("https:")) hostPrefix = serverName; else if (hostAndPort != null) { hostPrefix = _httpRequest.getScheme() + "://" + hostAndPort; } else { hostPrefix = _httpRequest.getScheme() + "://" + serverName; if (serverName.indexOf(':') < 0 && port != 0 && port != 80 && port != 443) hostPrefix += ":" + port; } if (slash == 0) return hostPrefix + path; String uri = _httpRequest.getRequestURI(); String contextPath = _httpRequest.getContextPath(); String queryString = null; int p = path.indexOf('?'); if (p > 0) { queryString = path.substring(p + 1); path = path.substring(0, p); } if (uri.equals(contextPath)) { path = uri + "/" + path; } else { p = uri.lastIndexOf('/'); if (p >= 0) path = uri.substring(0, p + 1) + path; } if (queryString != null) return hostPrefix + _webApp.getURIDecoder().normalizeUri(path) + '?' + queryString; else return hostPrefix + _webApp.getURIDecoder().normalizeUri(path); } private void addHex(StringBuilder cb, int hex) { int d1 = (hex >> 4) & 0xf; int d2 = (hex) & 0xf; cb.append('%'); cb.append(d1 < 10 ? (char) (d1 + '0') : (char) (d1 - 10 + 'a')); cb.append(d2 < 10 ? (char) (d2 + '0') : (char) (d2 - 10 + 'a')); } @Override public void setDateHeader(String name, long date) { if (name == null || name.length() == 0) { return; } if (isCommitted()) { return; } // Ignore any call from an included servlet if (isIncluding()) { return; } HttpHeaders.setDateHeader(response, name, new Date(date)); } @Override public void addDateHeader(String name, long date) { if (name == null || name.length() == 0) { return; } if (isCommitted()) { return; } // Ignore any call from an included servlet if (isIncluding()) { return; } HttpHeaders.addDateHeader(response, name, new Date(date)); } /* * public void setHeader(HttpHeader name, String value) { if (name == null || name.length() == 0 || value == null) { * return; } * * if (isCommitted()) { return; } * * // Ignore any call from an included servlet if (isIncluding()) { return; } * * char cc=name.charAt(0); if (cc=='C' || cc=='c') { * * if (HttpHeaders.Names.CONTENT_TYPE.equalsIgnoreCase(name)) setContentType(value); else { if (isIncluding()) * return; * * if (HttpHeaders.Names.CONTENT_LENGTH.equalsIgnoreCase(name)) { if (value == null) _contentLength = -1l; else * _contentLength = Long.parseLong(value); } } * * } * * //what about setCookie? * * HttpHeaders.setHeader(response, name, value); } */ @Override public void setHeader(String name, String value) { if (name == null || name.length() == 0 || value == null) { return; } if (isCommitted()) { return; } // Ignore any call from an included servlet if (isIncluding()) { return; } char cc = name.charAt(0); if (cc == 'C' || cc == 'c') { if (HttpHeaders.Names.CONTENT_TYPE.equalsIgnoreCase(name)) setContentType(value); else { if (HttpHeaders.Names.CONTENT_LENGTH.equalsIgnoreCase(name)) { if (value == null) _contentLength = -1; else _contentLength = Long.parseLong(value); } } } // what about setCookie? HttpHeaders.setHeader(response, name, value); } @Override // OK public Collection<String> getHeaderNames() { return headers.names(); } @Override // OK public String getHeader(String name) { return headers.get(name); } @Override // OK public Collection<String> getHeaders(String name) { return headers.getAll(name); } @Override // ok public void addHeader(String name, String value) { if (name == null || name.length() == 0 || value == null) { return; } if (isCommitted()) { return; } // Ignore any call from an included servlet if (isIncluding()) { return; } char cc = name.charAt(0); if (cc == 'C' || cc == 'c') { if (HttpHeaders.Names.CONTENT_TYPE.equalsIgnoreCase(name)) setContentType(value); else { if (HttpHeaders.Names.CONTENT_LENGTH.equalsIgnoreCase(name)) { if (value == null) _contentLength = -1l; else _contentLength = Long.parseLong(value); } } } HttpHeaders.addHeader(response, name, value); } @Override // OK public void setIntHeader(String name, int value) { if (name == null || name.length() == 0) { return; } if (isCommitted()) { return; } // Ignore any call from an included servlet if (isIncluding()) { return; } char cc = name.charAt(0); if (cc == 'C' || cc == 'c') { if (HttpHeaders.Names.CONTENT_LENGTH.equalsIgnoreCase(name)) { if (value < 0) _contentLength = -1l; else _contentLength = value; } } HttpHeaders.setHeader(response, name, value); } @Override // ok public void addIntHeader(String name, int value) { if (name == null || name.length() == 0) { return; } if (isCommitted()) { return; } // Ignore any call from an included servlet if (isIncluding()) { return; } char cc = name.charAt(0); if (cc == 'C' || cc == 'c') { if (HttpHeaders.Names.CONTENT_LENGTH.equalsIgnoreCase(name)) { if (value < 0) _contentLength = -1l; else _contentLength = value; } } HttpHeaders.addHeader(response, name, value); } @Override // OK public void setStatus(int sc) { setStatus(sc, null); } @Override @Deprecated // ok public void setStatus(int sc, String sm) { if (isCommitted()) { return; } // Ignore any call from an included servlet if (isIncluding()) { return; } if (sc <= 0) throw new IllegalArgumentException(); _status = sc; _reason = sm; } @Override // ok public String getCharacterEncoding() { if (_characterEncoding == null) { /* get encoding from Content-Type header */ if (_characterEncoding == null) { _characterEncoding = MimeTypes.inferCharsetFromContentType(getContentType()); if (_characterEncoding == null) _characterEncoding = StringUtil.__ISO_8859_1; } } return _characterEncoding; } /** * @see javax.servlet.ServletResponse.getContentType() for example, text/html; charset=UTF-8, or null */ @Override // OK public String getContentType() { return _contentType; } @Override // OK public ServletOutputStream getOutputStream() throws IOException { if (usingWriter) { throw new IllegalStateException("Already using Writer"); } usingOutputStream = true; if (outputStream == null) { outputStream = new ByteBufServletOutputStream(this, body.content()); } return outputStream; } private ServletOutputStream getOutputStreamWithoutCheck() throws IOException { if (outputStream == null) { outputStream = new ByteBufServletOutputStream(this, body.content()); } return outputStream; } public boolean isWriting() { return usingWriter; } @Override public PrintWriter getWriter() throws IOException { if (usingOutputStream) { throw new IllegalStateException("Already using OutputStream"); } /* * If the response's character encoding has not been specified as described in <code>getCharacterEncoding</code> * (i.e., the method just returns the default value <code>ISO-8859-1</code>), <code>getWriter</code> updates it * to <code>ISO-8859-1</code> (with the effect that a subsequent call to getContentType() will include a * charset=ISO-8859-1 component which will also be reflected in the Content-Type response header, thereby * satisfying the Servlet spec requirement that containers must communicate the character encoding used for the * servlet response's writer to the client). * * setCharacterEncoding(getCharacterEncoding()); */ if (_writer == null) { /* get encoding from Content-Type header */ String encoding = _characterEncoding; if (encoding == null) { encoding = MimeTypes.inferCharsetFromContentType(_contentType); if (encoding == null) encoding = StringUtil.__ISO_8859_1; setCharacterEncoding(encoding); } if (StringUtil.__ISO_8859_1.equalsIgnoreCase(encoding)) _writer = new ResponseWriter(new Iso88591HttpWriter(getOutputStreamWithoutCheck()), encoding); else if (StringUtil.__UTF8.equalsIgnoreCase(encoding)) _writer = new ResponseWriter(new Utf8HttpWriter(getOutputStreamWithoutCheck()), encoding); else _writer = new ResponseWriter(new EncodingHttpWriter(getOutputStreamWithoutCheck(), encoding), encoding); } // Set the output type at the end, because setCharacterEncoding() // checks for it usingWriter = true; return _writer; } @Override // OK public void setContentLength(int len) { // Protect from setting after committed as default handling // of a servlet HEAD httpServletRequest ALWAYS sets _content length, even // if the getHandling committed the response! if (isCommitted() || isIncluding()) return; _contentLength = len; headers.set(HttpHeaders.Names.CONTENT_LENGTH, len); } public long getLongContentLength() { return _contentLength; } public void setLongContentLength(long len) { // Protect from setting after committed as default handling // of a servlet HEAD httpServletRequest ALWAYS sets _content length, even // if the getHandling committed the response! if (isCommitted() || isIncluding()) return; _contentLength = len; headers.set(HttpHeaders.Names.CONTENT_LENGTH.toString(), len); } @Override // OK public void setCharacterEncoding(String encoding) { if (encoding == null) return; if (isCommitted()) { return; } // Ignore any call from an included servlet if (isIncluding()) { return; } // Ignore any call made after the getWriter has been invoked // The default should be used if (usingWriter) { return; } _characterEncoding = encoding; isCharacterEncodingSet = true; } @Override // OK public void setContentType(String contentType) { if (isCommitted() || isIncluding()) return; if (contentType == null) // clear the contentType { if (isWriting() && _characterEncoding != null) throw new IllegalSelectorException(); if (_locale == null) _characterEncoding = null; _mimeType = null; _contentType = null; headers.remove(HttpHeaders.Names.CONTENT_TYPE); } else { _contentType = contentType; _mimeType = MimeTypes.CACHE.get(contentType); String charset; if (_mimeType != null && _mimeType.getCharset() != null) charset = _mimeType.getCharset().toString(); else charset = MimeTypes.getCharsetFromContentType(contentType); if (charset == null) { if (_characterEncoding != null) { _contentType = contentType + ";charset=" + _characterEncoding; _mimeType = null; } } else if (isWriting() && !charset.equals(_characterEncoding)) { // too late to change the character encoding; _mimeType = null; _contentType = MimeTypes.getContentTypeWithoutCharset(_contentType); if (_characterEncoding != null) _contentType = _contentType + ";charset=" + _characterEncoding; } else { _characterEncoding = charset; } headers.set(HttpHeaders.Names.CONTENT_TYPE, _contentType); } } /** * Must be called before any response body content is written; * */ @Override // OK public void setBufferSize(int size) { if (isCommitted() || getContentCount() > 0) throw new IllegalStateException("Committed or content written"); body.content().capacity(size); } @Override // OK public int getBufferSize() { return body.content().capacity(); } /** * Flush the buffer and commit this response. * */ @Override public void flushBuffer() throws IOException { if (isCommitted()) // committed,need not to do again return; // we sure that getOutputStream() and getWriter() 不会缓存有数据,所有数据已经写到body里 writeResponse(ctx, request, response); commited = true; } /** * 向客户端返回响应 * * @param ctx * @param request * @param response */ private void writeResponse(ChannelHandlerContext ctx, FullHttpRequest request, FullHttpResponse response) { // Set the Status Code response.setStatus(_reason == null ? HttpResponseStatus.valueOf(_status) : new HttpResponseStatus(_status, _reason)); // set the Servlet Header :) response.headers().set(HttpHeaders.Names.SERVER, "JNetty"); // Set the "Content-Length" Header response.headers().set(CONTENT_LENGTH, response.content().readableBytes()); boolean keepAlive = true; if (headers.get(HttpHeaders.Names.CONNECTION) != null) { keepAlive = HttpHeaders.isKeepAlive(response); // 用户显式设置KeepAlive } else // 用户未设置CONNECTION响应头 { // Decide whether to close the connection or not. keepAlive = HttpHeaders.isKeepAlive(request); // 用户未设置CONNECTION,则保持未请求头的CONNECTION一致 // TODO:think about how to decide keepAlive or not if (keepAlive) { // Add keep alive header as per: // http://www.w3.org/Protocols/HTTP/1.1/draft-ietf-http-v11-spec-01.html#Connection response.headers().set(CONNECTION, HttpHeaders.Values.KEEP_ALIVE); } else { response.headers().set(CONNECTION, HttpHeaders.Values.CLOSE); } } // Write the response. ctx.nextOutboundMessageBuffer().add(response); if (keepAlive) { ctx.flush(); } else { //CONNECTION == "close" Close the non-keep-alive connection after the write operation is done. ctx.flush().addListener(ChannelFutureListener.CLOSE); } } @Override // OK public void reset() { if (isIncluding()) { return; // Ignore any call from an included servlet } if (isCommitted()) throw new IllegalStateException("Committed"); resetHeader(); resetBuffer(); usingOutputStream = false; usingWriter = false; } /* * public void reset(boolean preserveCookies) { if (!preserveCookies) reset(); else { ArrayList<String> cookieValues * = new ArrayList<String>(5); Enumeration<String> vals = headers.getValues(HttpHeader.SET_COOKIE.asString()); while * (vals.hasMoreElements()) cookieValues.add(vals.nextElement()); reset(); for (String v:cookieValues) * headers.add(HttpHeader.SET_COOKIE, v); } } */ /* * public void resetForForward() { resetBuffer(); usingOutputStream = false; usingWriter = false; } */ // ok /** * */ public void resetHeader() { if (isIncluding()) { return; // Ignore any call from an included servlet } if (isCommitted()) throw new IllegalStateException("Committed"); // Reset the headers only if this is the main httpServletRequest, // not for included _contentType = null; _locale = DEFAULT_LOCALE; _characterEncoding = "ISO-8859-1";// Constants.DEFAULT_CHARACTER_ENCODING; _contentLength = -1; _status = 200; _reason = null; headers.clear(); } @Override // OK public void resetBuffer() { if (isCommitted()) throw new IllegalStateException("Committed"); body.content().clear(); } /* * protected ResponseInfo newResponseInfo() { if (_status == HttpStatus.NOT_SET_000) _status = HttpStatus.OK_200; * return new ResponseInfo(socketChannel.getRequest().getHttpVersion(), headers, getLongContentLength(), * getStatus(), getReason(), socketChannel.getRequest().isHead()); } */ @Override public boolean isCommitted() { return commited; } @Override // OK WITH TODO public void setLocale(Locale locale) { if (locale == null || isCommitted()) { return; } // Ignore any call from an included servlet if (isIncluding()) { return; } // Save the locale for use by getLocale() _locale = locale; // Set the contentLanguage for header output contentLanguage = locale.getLanguage(); if ((contentLanguage != null) && (contentLanguage.length() > 0)) { String country = locale.getCountry(); StringBuilder value = new StringBuilder(contentLanguage); if ((country != null) && (country.length() > 0)) { value.append('-'); value.append(country); } contentLanguage = value.toString(); } // Ignore any call made after the getWriter has been invoked. // The default should be used if (usingWriter) { return; } if (isCharacterEncodingSet) { return; } // :TODO /* * String charset = servletContext.getCharset(locale); if (charset != null) { * coyoteResponse.setCharacterEncoding(charset); } */ } @Override public Locale getLocale() { if (_locale == null) return Locale.getDefault(); return _locale; } @Override // ok public int getStatus() { return _status; } public String getReason() { return _reason; } public void complete() throws IOException { } public long getContentCount() { return body.content().writerIndex(); } @Override // OK public String toString() { return String.format("%s %d %s%n%s", request.getProtocolVersion(), _status, _reason == null ? "" : _reason, headers); } private class ResponseWriter extends PrintWriter { private final String _encoding; private final HttpWriter _httpWriter; public ResponseWriter(HttpWriter httpWriter, String encoding) { super(httpWriter); _httpWriter = httpWriter; _encoding = encoding; } } /** * Returns a channel where the I/O operation associated with this response takes place. */ public Channel channel() { return this.socketChannel; } /** * invoke before forward */ public void resetForForward() { resetBuffer(); usingOutputStream = false; usingWriter = false; } public void setHttpServletRequest(HttpServletRequestImpl httpRequest) { _httpRequest = httpRequest; } }