org.apache.jk.server.JkCoyoteHandler.java Source code

Java tutorial

Introduction

Here is the source code for org.apache.jk.server.JkCoyoteHandler.java

Source

/*
 * ====================================================================
 *
 * The Apache Software License, Version 1.1
 *
 * Copyright (c) 1999 The Apache Software Foundation.  All rights
 * reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 *
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in
 *    the documentation and/or other materials provided with the
 *    distribution.
 *
 * 3. The end-user documentation included with the redistribution, if
 *    any, must include the following acknowlegement:
 *       "This product includes software developed by the
 *        Apache Software Foundation (http://www.apache.org/)."
 *    Alternately, this acknowlegement may appear in the software itself,
 *    if and wherever such third-party acknowlegements normally appear.
 *
 * 4. The names "The Jakarta Project", "Tomcat", and "Apache Software
 *    Foundation" must not be used to endorse or promote products derived
 *    from this software without prior written permission. For written
 *    permission, please contact apache@apache.org.
 *
 * 5. Products derived from this software may not be called "Apache"
 *    nor may "Apache" appear in their names without prior written
 *    permission of the Apache Group.
 *
 * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
 * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED.  IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR
 * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
 * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
 * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
 * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 * SUCH DAMAGE.
 * ====================================================================
 *
 * This software consists of voluntary contributions made by many
 * individuals on behalf of the Apache Software Foundation.  For more
 * information on the Apache Software Foundation, please see
 * <http://www.apache.org/>.
 *
 * [Additional notices, if required by prior licensing conditions]
 *
 */

package org.apache.jk.server;

import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.net.InetAddress;
import java.security.cert.CertificateFactory;
import java.security.cert.X509Certificate;
import java.security.PrivilegedExceptionAction;
import java.security.AccessController;
import java.security.PrivilegedActionException;
import java.security.PrivilegedAction;

import javax.management.MBeanServer;
import javax.management.ObjectName;

import org.apache.commons.modeler.Registry;
import org.apache.coyote.ActionCode;
import org.apache.coyote.ActionHook;
import org.apache.coyote.Adapter;
import org.apache.coyote.ProtocolHandler;
import org.apache.coyote.Request;
import org.apache.coyote.Response;
import org.apache.jk.common.HandlerRequest;
import org.apache.jk.common.JkInputStream;
import org.apache.jk.common.MsgAjp;
import org.apache.jk.core.JkHandler;
import org.apache.jk.core.Msg;
import org.apache.jk.core.MsgContext;
import org.apache.jk.core.WorkerEnv;
import org.apache.tomcat.util.buf.ByteChunk;
import org.apache.tomcat.util.buf.C2BConverter;
import org.apache.tomcat.util.buf.MessageBytes;
import org.apache.tomcat.util.http.HttpMessages;
import org.apache.tomcat.util.http.MimeHeaders;
import org.apache.tomcat.util.net.SSLSupport;

/** Plugs Jk2 into Coyote. Must be named "type=JkHandler,name=container"
 *
 * @jmx:notification-handler name="org.apache.jk.SEND_PACKET
 * @jmx:notification-handler name="org.apache.coyote.ACTION_COMMIT
 */
public class JkCoyoteHandler extends JkHandler
        implements ProtocolHandler, ActionHook, org.apache.coyote.OutputBuffer, org.apache.coyote.InputBuffer {
    protected static org.apache.commons.logging.Log log = org.apache.commons.logging.LogFactory
            .getLog(JkCoyoteHandler.class);
    // Set debug on this logger to see the container request time
    private static org.apache.commons.logging.Log logTime = org.apache.commons.logging.LogFactory
            .getLog("org.apache.jk.REQ_TIME");

    // ----------------------------------------------------------- DoPrivileged
    private final class StatusLinePrivilegedAction implements PrivilegedAction {
        int status;

        StatusLinePrivilegedAction(int status) {
            this.status = status;
        }

        public Object run() {
            return HttpMessages.getMessage(status);
        }
    }

    int headersMsgNote;
    int c2bConvertersNote;
    int tmpMessageBytesNote;
    int utfC2bNote;
    int obNote;
    int epNote;
    int inputStreamNote;

    Adapter adapter;
    protected JkMain jkMain = null;

    public final int JK_STATUS_NEW = 0;
    public final int JK_STATUS_HEAD = 1;
    public final int JK_STATUS_CLOSED = 2;

    /** Set a property. Name is a "component.property". JMX should
     * be used instead.
     */
    public void setProperty(String name, String value) {
        if (log.isTraceEnabled())
            log.trace("setProperty " + name + " " + value);
        getJkMain().setProperty(name, value);
        properties.put(name, value);
    }

    public String getProperty(String name) {
        return properties.getProperty(name);
    }

    /** Pass config info
     */
    public void setAttribute(String name, Object value) {
        if (log.isDebugEnabled())
            log.debug("setAttribute " + name + " " + value);
        if (value instanceof String)
            this.setProperty(name, (String) value);
    }

    /**
     * Retrieve config info.
     * Primarily for use with the admin webapp.
     */
    public Object getAttribute(String name) {
        return getJkMain().getProperty(name);
    }

    /** The adapter, used to call the connector 
     */
    public void setAdapter(Adapter adapter) {
        this.adapter = adapter;
    }

    public Adapter getAdapter() {
        return adapter;
    }

    public JkMain getJkMain() {
        if (jkMain == null) {
            jkMain = new JkMain();
            jkMain.setWorkerEnv(wEnv);

            if (oname != null) {
                try {
                    Registry.getRegistry().registerComponent(jkMain, oname.getDomain(), "JkMain", "type=JkMain");
                } catch (Exception e) {
                    log.error("Error registering jkmain " + e);
                }
            }
        }
        return jkMain;
    }

    boolean started = false;

    /** Start the protocol
     */
    public void init() {
        if (started)
            return;

        started = true;

        if (wEnv == null) {
            // we are probably not registered - not very good.
            wEnv = getJkMain().getWorkerEnv();
            wEnv.addHandler("container", this);
        }

        try {
            // jkMain.setJkHome() XXX;

            getJkMain().init();

            headersMsgNote = wEnv.getNoteId(WorkerEnv.ENDPOINT_NOTE, "headerMsg");
            tmpMessageBytesNote = wEnv.getNoteId(WorkerEnv.ENDPOINT_NOTE, "tmpMessageBytes");
            utfC2bNote = wEnv.getNoteId(WorkerEnv.ENDPOINT_NOTE, "utfC2B");
            epNote = wEnv.getNoteId(WorkerEnv.ENDPOINT_NOTE, "ep");
            obNote = wEnv.getNoteId(WorkerEnv.ENDPOINT_NOTE, "coyoteBuffer");
            inputStreamNote = wEnv.getNoteId(WorkerEnv.ENDPOINT_NOTE, "jkInputStream");

        } catch (Exception ex) {
            ex.printStackTrace();
        }
    }

    public void start() {
        try {
            getJkMain().start();
        } catch (Exception ex) {
            ex.printStackTrace();
        }
    }

    public void destroy() {
        if (!started)
            return;

        started = false;
        getJkMain().stop();
    }

    // -------------------- OutputBuffer implementation --------------------

    public int doWrite(ByteChunk chunk, Response res) throws IOException {
        if (!res.isCommitted()) {
            // Send the connector a request for commit. The connector should
            // then validate the headers, send them (using sendHeader) and 
            // set the filters accordingly.
            res.sendHeaders();
        }
        MsgContext ep = (MsgContext) res.getNote(epNote);

        MsgAjp msg = (MsgAjp) ep.getNote(headersMsgNote);

        int len = chunk.getLength();
        byte buf[] = msg.getBuffer();
        // 4 - hardcoded, byte[] marshalling overhead 
        int chunkSize = buf.length - msg.getHeaderLength() - 4;
        int off = 0;
        while (len > 0) {
            int thisTime = len;
            if (thisTime > chunkSize) {
                thisTime = chunkSize;
            }
            len -= thisTime;

            msg.reset();
            msg.appendByte(HandlerRequest.JK_AJP13_SEND_BODY_CHUNK);
            if (log.isDebugEnabled())
                log.debug("doWrite " + off + " " + thisTime + " " + len);
            msg.appendBytes(chunk.getBytes(), chunk.getOffset() + off, thisTime);
            off += thisTime;
            ep.setType(JkHandler.HANDLE_SEND_PACKET);
            ep.getSource().invoke(msg, ep);
        }
        return 0;
    }

    public int doRead(ByteChunk chunk, Request req) throws IOException {
        Response res = req.getResponse();
        if (log.isDebugEnabled())
            log.debug("doRead " + chunk.getBytes() + " " + chunk.getOffset() + " " + chunk.getLength());
        MsgContext ep = (MsgContext) res.getNote(epNote);

        JkInputStream jkIS = (JkInputStream) ep.getNote(inputStreamNote);
        // return jkIS.read( chunk.getBytes(), chunk.getOffset(), chunk.getLength());
        return jkIS.doRead(chunk);
    }

    // -------------------- Jk handler implementation --------------------
    // Jk Handler mehod
    public int invoke(Msg msg, MsgContext ep) throws IOException {
        if (logTime.isDebugEnabled())
            ep.setLong(MsgContext.TIMER_PRE_REQUEST, System.currentTimeMillis());

        org.apache.coyote.Request req = (org.apache.coyote.Request) ep.getRequest();
        org.apache.coyote.Response res = req.getResponse();
        res.setHook(this);

        if (log.isDebugEnabled())
            log.debug("Invoke " + req + " " + res + " " + req.requestURI().toString());

        res.setOutputBuffer(this);
        req.setInputBuffer(this);

        if (ep.getNote(headersMsgNote) == null) {
            Msg msg2 = new MsgAjp();
            ep.setNote(headersMsgNote, msg2);
        }

        res.setNote(epNote, ep);
        ep.setStatus(JK_STATUS_HEAD);

        try {
            adapter.service(req, res);
        } catch (Exception ex) {
            ex.printStackTrace();
        }
        if (ep.getStatus() != JK_STATUS_CLOSED) {
            res.finish();
        }

        ep.setStatus(JK_STATUS_NEW);

        req.recycle();
        req.updateCounters();
        res.recycle();
        return OK;
    }

    private void appendHead(org.apache.coyote.Response res) throws IOException {
        if (log.isDebugEnabled())
            log.debug("COMMIT sending headers " + res + " " + res.getMimeHeaders());

        C2BConverter c2b = (C2BConverter) res.getNote(utfC2bNote);
        if (c2b == null) {
            if (System.getSecurityManager() != null) {
                try {
                    c2b = (C2BConverter) AccessController.doPrivileged(new PrivilegedExceptionAction() {
                        public Object run() throws IOException {
                            return new C2BConverter("UTF8");
                        }
                    });
                } catch (PrivilegedActionException pae) {
                    Exception ex = pae.getException();
                    if (ex instanceof IOException)
                        throw (IOException) ex;
                }
            } else {
                c2b = new C2BConverter("UTF8");
            }
            res.setNote(utfC2bNote, c2b);
        }

        MsgContext ep = (MsgContext) res.getNote(epNote);
        MsgAjp msg = (MsgAjp) ep.getNote(headersMsgNote);
        msg.reset();
        msg.appendByte(HandlerRequest.JK_AJP13_SEND_HEADERS);
        msg.appendInt(res.getStatus());

        MessageBytes mb = (MessageBytes) ep.getNote(tmpMessageBytesNote);
        if (mb == null) {
            mb = new MessageBytes();
            ep.setNote(tmpMessageBytesNote, mb);
        }
        String message = res.getMessage();
        if (message == null) {
            if (System.getSecurityManager() != null) {
                message = (String) AccessController.doPrivileged(new StatusLinePrivilegedAction(res.getStatus()));
            } else {
                message = HttpMessages.getMessage(res.getStatus());
            }
        } else {
            message = message.replace('\n', ' ').replace('\r', ' ');
        }
        mb.setString(message);
        c2b.convert(mb);
        msg.appendBytes(mb);

        // XXX add headers

        MimeHeaders headers = res.getMimeHeaders();
        String contentType = res.getContentType();
        if (contentType != null) {
            headers.setValue("Content-Type").setString(contentType);
        }
        String contentLanguage = res.getContentLanguage();
        if (contentLanguage != null) {
            headers.setValue("Content-Language").setString(contentLanguage);
        }
        int contentLength = res.getContentLength();
        if (contentLength >= 0) {
            headers.setValue("Content-Length").setInt(contentLength);
        }
        int numHeaders = headers.size();
        msg.appendInt(numHeaders);
        for (int i = 0; i < numHeaders; i++) {
            MessageBytes hN = headers.getName(i);
            // no header to sc conversion - there's little benefit
            // on this direction
            c2b.convert(hN);
            msg.appendBytes(hN);

            MessageBytes hV = headers.getValue(i);
            c2b.convert(hV);
            msg.appendBytes(hV);
        }
        ep.setType(JkHandler.HANDLE_SEND_PACKET);
        ep.getSource().invoke(msg, ep);
    }

    // -------------------- Coyote Action implementation --------------------

    public void action(ActionCode actionCode, Object param) {
        try {
            if (actionCode == ActionCode.ACTION_COMMIT) {
                if (log.isDebugEnabled())
                    log.debug("COMMIT ");
                org.apache.coyote.Response res = (org.apache.coyote.Response) param;

                if (res.isCommitted()) {
                    if (log.isInfoEnabled())
                        log.info("Response already commited ");
                } else {
                    appendHead(res);
                }
            } else if (actionCode == ActionCode.ACTION_RESET) {
                if (log.isDebugEnabled())
                    log.debug("RESET ");

            } else if (actionCode == ActionCode.ACTION_CLIENT_FLUSH) {
                if (log.isDebugEnabled())
                    log.debug("CLIENT_FLUSH ");
                org.apache.coyote.Response res = (org.apache.coyote.Response) param;
                MsgContext ep = (MsgContext) res.getNote(epNote);
                ep.setType(JkHandler.HANDLE_FLUSH);
                ep.getSource().invoke(null, ep);

            } else if (actionCode == ActionCode.ACTION_CLOSE) {
                if (log.isDebugEnabled())
                    log.debug("CLOSE ");

                org.apache.coyote.Response res = (org.apache.coyote.Response) param;
                MsgContext ep = (MsgContext) res.getNote(epNote);
                if (ep.getStatus() == JK_STATUS_CLOSED) {
                    // Double close - it may happen with forward 
                    if (log.isDebugEnabled())
                        log.debug("Double CLOSE - forward ? " + res.getRequest().requestURI());
                    return;
                }

                if (!res.isCommitted())
                    this.action(ActionCode.ACTION_COMMIT, param);

                MsgAjp msg = (MsgAjp) ep.getNote(headersMsgNote);
                msg.reset();
                msg.appendByte(HandlerRequest.JK_AJP13_END_RESPONSE);
                msg.appendByte(1);

                ep.setType(JkHandler.HANDLE_SEND_PACKET);
                ep.getSource().invoke(msg, ep);

                ep.setType(JkHandler.HANDLE_FLUSH);
                ep.getSource().invoke(msg, ep);

                ep.setStatus(JK_STATUS_CLOSED);

                if (logTime.isDebugEnabled())
                    logTime(res.getRequest(), res);
            } else if (actionCode == ActionCode.ACTION_REQ_SSL_ATTRIBUTE) {
                org.apache.coyote.Request req = (org.apache.coyote.Request) param;

                // Extract SSL certificate information (if requested)
                MessageBytes certString = (MessageBytes) req.getNote(WorkerEnv.SSL_CERT_NOTE);
                if (certString != null && !certString.isNull()) {
                    ByteChunk certData = certString.getByteChunk();
                    ByteArrayInputStream bais = new ByteArrayInputStream(certData.getBytes(), certData.getStart(),
                            certData.getLength());

                    // Fill the first element.
                    X509Certificate jsseCerts[] = null;
                    try {
                        CertificateFactory cf = CertificateFactory.getInstance("X.509");
                        X509Certificate cert = (X509Certificate) cf.generateCertificate(bais);
                        jsseCerts = new X509Certificate[1];
                        jsseCerts[0] = cert;
                    } catch (java.security.cert.CertificateException e) {
                        log.error("Certificate convertion failed", e);
                        return;
                    }

                    req.setAttribute(SSLSupport.CERTIFICATE_KEY, jsseCerts);
                }

            } else if (actionCode == ActionCode.ACTION_REQ_HOST_ATTRIBUTE) {
                org.apache.coyote.Request req = (org.apache.coyote.Request) param;

                // If remoteHost not set by JK, get it's name from it's remoteAddr
                if (req.remoteHost().isNull())
                    req.remoteHost().setString(InetAddress.getByName(req.remoteAddr().toString()).getHostName());

                // } else if( actionCode==ActionCode.ACTION_POST_REQUEST ) {

            } else if (actionCode == ActionCode.ACTION_ACK) {
                if (log.isDebugEnabled())
                    log.debug("ACK ");
                // What should we do here ? Who calls it ? 
            }
        } catch (Exception ex) {
            log.error("Error in action code ", ex);
        }
    }

    private void logTime(Request req, Response res) {
        // called after the request
        //            org.apache.coyote.Request req=(org.apache.coyote.Request)param;
        //            Response res=req.getResponse();
        MsgContext ep = (MsgContext) res.getNote(epNote);
        String uri = req.requestURI().toString();
        if (uri.indexOf(".gif") > 0)
            return;

        ep.setLong(MsgContext.TIMER_POST_REQUEST, System.currentTimeMillis());
        long t1 = ep.getLong(MsgContext.TIMER_PRE_REQUEST) - ep.getLong(MsgContext.TIMER_RECEIVED);
        long t2 = ep.getLong(MsgContext.TIMER_POST_REQUEST) - ep.getLong(MsgContext.TIMER_PRE_REQUEST);

        logTime.debug("Time pre=" + t1 + "/ service=" + t2 + " " + res.getContentLength() + " " + uri);
    }

    public ObjectName preRegister(MBeanServer server, ObjectName oname) throws Exception {
        // override - we must be registered as "container"
        this.name = "container";
        return super.preRegister(server, oname);
    }
}