org.ireland.jnetty.http.HttpServletResponseImpl.java Source code

Java tutorial

Introduction

Here is the source code for org.ireland.jnetty.http.HttpServletResponseImpl.java

Source

//
//  ========================================================================
//  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, "&", "&amp;");
                message = StringUtil.replace(message, "<", "&lt;");
                message = StringUtil.replace(message, ">", "&gt;");
            }
            String uri = request.getUri();
            if (uri != null) {
                uri = StringUtil.replace(uri, "&", "&amp;");
                uri = StringUtil.replace(uri, "<", "&lt;");
                uri = StringUtil.replace(uri, ">", "&gt;");
            }

            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;
    }
}