alvinalexander.com | career | drupal | java | mac | mysql | perl | scala | uml | unix  

What this is

This file is included in the DevDaily.com "Java Source Code Warehouse" project. The intent of this project is to help you "Learn Java by Example" TM.

Other links

The source code

/*
 *  Copyright 1999-2004 The Apache Software Foundation
 *
 *  Licensed under the Apache License, Version 2.0 (the "License");
 *  you may not use this file except in compliance with the License.
 *  You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 *  Unless required by applicable law or agreed to in writing, software
 *  distributed under the License is distributed on an "AS IS" BASIS,
 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *  See the License for the specific language governing permissions and
 *  limitations under the License.
 */

package org.apache.ajp.tomcat4;

import java.io.IOException;
import java.net.InetAddress;
import java.net.ServerSocket;
import java.net.Socket;
import java.security.AccessControlException;
import java.util.Stack;
import java.util.Vector;

import org.apache.catalina.Connector;
import org.apache.catalina.Container;
import org.apache.catalina.Lifecycle;
import org.apache.catalina.LifecycleException;
import org.apache.catalina.LifecycleListener;
import org.apache.catalina.Request;
import org.apache.catalina.Response;
import org.apache.catalina.Service;
import org.apache.catalina.net.DefaultServerSocketFactory;
import org.apache.catalina.net.ServerSocketFactory;
import org.apache.catalina.util.LifecycleSupport;
import org.apache.catalina.util.StringManager;

/**
 * Implementation of an Ajp13 connector.
 *
 * @author Kevin Seguin
 * @version $Revision: 1.20 $ $Date: 2004/02/24 08:48:41 $
 */


public final class Ajp13Connector
    implements Connector, Lifecycle, Runnable {


    // ----------------------------------------------------- Instance Variables


    /**
     * The accept count for this Connector.
     */
    private int acceptCount = 10;


    /**
     * The IP address on which to bind, if any.  If null, all
     * addresses on the server will be bound.
     */
    private String address = null;


    /**
     * The input buffer size we should create on input streams.
     */
    private int bufferSize = 2048;


    /**
     * The Container used for processing requests received by this Connector.
     */
    protected Container container = null;


    /**
     * The set of processors that have ever been created.
     */
    private Vector created = new Vector();


    /**
     * The current number of processors that have been created.
     */
    private int curProcessors = 0;


    /**
     * The debugging detail level for this component.
     */
    private int debug = 0;


    /**
     * The server socket factory for this component.
     */
    private ServerSocketFactory factory = null;


    /**
     * Descriptive information about this Connector implementation.
     */
    private static final String info =
        "org.apache.catalina.connector.ajp.Ajp13Connector/1.0";


    /**
     * redirect port.
     */
    private int redirectPort = -1;

    /**
     * enable DNS lookups.
     */
    private boolean enableLookups = false;

    /**
     * The lifecycle event support for this component.
     */
    protected LifecycleSupport lifecycle = new LifecycleSupport(this);


    /**
     * The minimum number of processors to start at initialization time.
     */
    protected int minProcessors = 5;


    /**
     * The maximum number of processors allowed, or <0 for unlimited.
     */
    private int maxProcessors = 20;


    /**
     * Timeout value on the incoming connection.
     * Note : a value of 0 means no timeout.
     */
    private int connectionTimeout = -1;


    /**
     * Linger value to be used on socket close.
     * Note : a value of -1 means no linger used on close.
     */
    private int connectionLinger = -1;


    /**
     * The port number on which we listen for ajp13 requests.
     */
    private int port = 8009;


    /**
     * The set of processors that have been created but are not currently
     * being used to process a request.
     */
    private Stack processors = new Stack();


    /**
     * The request scheme that will be set on all requests received
     * through this connector.
     */
    private String scheme = "http";


    /**
     * The secure connection flag that will be set on all requests received
     * through this connector.
     */
    private boolean secure = false;


    /**
     * The server socket through which we listen for incoming TCP connections.
     */
    private ServerSocket serverSocket = null;


    /**
     * The string manager for this package.
     */
    private StringManager sm =
	StringManager.getManager(Constants.PACKAGE);


    /**
     * Has this component been started yet?
     */
    private boolean started = false;


    /**
     * The shutdown signal to our background thread
     */
    private boolean stopped = false;


    /**
     * The background thread.
     */
    private Thread thread = null;


    /**
     * This connector's thread group.
     */
    private ThreadGroup threadGroup = null;


    /**
     * The name to register for the background thread.
     */
    private String threadName = null;


    /**
     * A thread that periodically logs debug info if debug > 0.
     */
    private DebugThread debugThread = null;


    /**
     * The thread synchronization object.
     */
    private Object threadSync = new Object();

    private Ajp13Logger logger = new Ajp13Logger();

    /**
     * The service which which the connector is associated
     */
    private Service service = null;

    private String secret = null;


    /**
     * Tomcat authentication flag. If true, the authnetication is done by
     * Tomcat, otherwise, it is done by the native webserver.
     */
    private boolean tomcatAuthentication = true;


    // ------------------------------------------------------------- Properties


    /**
     * Return the connection timeout for this Connector.
     */
    public int getConnectionTimeout() {

	return (connectionTimeout);

    }


    /**
     * Set the connection timeout for this Connector.
     *
     * @param connectionTimeout The new connection timeout
     */
    public void setConnectionTimeout(int connectionTimeout) {

	this.connectionTimeout = connectionTimeout;

    }

    /**
     * Return the connection linger settings for this Connector.
     */
    public int getConnectionLinger() {

	return (connectionLinger);

    }


    /**
     * Set the connection linger for this Connector.
     *
     * @param connectionLinger The new connection linger
     */
    public void setConnectionLinger(int connectionLinger) {

	this.connectionLinger = connectionLinger;

    }

    public void setSecret( String s ) {
        secret=s;
    }

    public String getSecret() {
        return secret;
    }
    

    /**
     * Return the accept count for this Connector.
     */
    public int getAcceptCount() {

	return (acceptCount);

    }


    /**
     * Set the accept count for this Connector.
     *
     * @param count The new accept count
     */
    public void setAcceptCount(int count) {

	this.acceptCount = count;

    }



    /**
     * Return the bind IP address for this Connector.
     */
    public String getAddress() {

	return (this.address);

    }


    /**
     * Set the bind IP address for this Connector.
     *
     * @param address The bind IP address
     */
    public void setAddress(String address) {

	this.address = address;

    }


    /**
     * Is this connector available for processing requests?
     */
    public boolean isAvailable() {

	return (started);

    }


    /**
     * Return the input buffer size for this Connector.
     */
    public int getBufferSize() {

	return (this.bufferSize);

    }


    /**
     * Set the input buffer size for this Connector.
     *
     * @param bufferSize The new input buffer size.
     */
    public void setBufferSize(int bufferSize) {

	this.bufferSize = bufferSize;

    }


    /**
     * Return the Container used for processing requests received by this
     * Connector.
     */
    public Container getContainer() {

	return (container);

    }


    /**
     * Set the Container used for processing requests received by this
     * Connector.
     *
     * @param container The new Container to use
     */
    public void setContainer(Container container) {

	this.container = container;

    }


    /**
     * Return the current number of processors that have been created.
     */
    public int getCurProcessors() {

	return (curProcessors);

    }


    /**
     * Return the debugging detail level for this component.
     */
    public int getDebug() {

        return (debug);

    }


    /**
     * Set the debugging detail level for this component.
     *
     * @param debug The new debugging detail level
     */
    public void setDebug(int debug) {

        this.debug = debug;

    }

    /**
     * Return the "enable DNS lookups" flag.
     */
    public boolean getEnableLookups() {
        return this.enableLookups;
    }

    /**
     * Set the "enable DNS lookups" flag.
     *
     * @param enableLookups The new "enable DNS lookups" flag value
     */
    public void setEnableLookups(boolean enableLookups) {
        this.enableLookups = enableLookups;
    }

    /**
     * Return the port number to which a request should be redirected if
     * it comes in on a non-SSL port and is subject to a security constraint
     * with a transport guarantee that requires SSL.
     */
    public int getRedirectPort() {
        return this.redirectPort;
    }


    /**
     * Set the redirect port number.
     *
     * @param redirectPort The redirect port number (non-SSL to SSL)
     */
    public void setRedirectPort(int redirectPort) {
        this.redirectPort = redirectPort;
    }

    /**
     * Return the server socket factory used by this Container.
     */
    public ServerSocketFactory getFactory() {

        if (this.factory == null) {
            synchronized (this) {
                this.factory = new DefaultServerSocketFactory();
            }
        }
        return (this.factory);

    }


    /**
     * Set the server socket factory used by this Container.
     *
     * @param factory The new server socket factory
     */
    public void setFactory(ServerSocketFactory factory) {

        this.factory = factory;

    }


    /**
     * Return descriptive information about this Connector implementation.
     */
    public String getInfo() {

	return (info);

    }


    /**
     * Return the minimum number of processors to start at initialization.
     */
    public int getMinProcessors() {

	return (minProcessors);

    }


    /**
     * Set the minimum number of processors to start at initialization.
     *
     * @param minProcessors The new minimum processors
     */
    public void setMinProcessors(int minProcessors) {

	this.minProcessors = minProcessors;

    }


    /**
     * Return the maximum number of processors allowed, or <0 for unlimited.
     */
    public int getMaxProcessors() {

	return (maxProcessors);

    }


    /**
     * Set the maximum number of processors allowed, or <0 for unlimited.
     *
     * @param maxProcessors The new maximum processors
     */
    public void setMaxProcessors(int maxProcessors) {

	this.maxProcessors = maxProcessors;

    }


    /**
     * Return the port number on which we listen for AJP13 requests.
     */
    public int getPort() {

	return (this.port);

    }


    /**
     * Set the port number on which we listen for AJP13 requests.
     *
     * @param port The new port number
     */
    public void setPort(int port) {

	this.port = port;

    }


    /**
     * Return the scheme that will be assigned to requests received
     * through this connector.  Default value is "http".
     */
    public String getScheme() {

	return (this.scheme);

    }


    /**
     * Set the scheme that will be assigned to requests received through
     * this connector.
     *
     * @param scheme The new scheme
     */
    public void setScheme(String scheme) {

	this.scheme = scheme;

    }


    /**
     * Return the secure connection flag that will be assigned to requests
     * received through this connector.  Default value is "false".
     */
    public boolean getSecure() {

	return (this.secure);

    }


    /**
     * Set the secure connection flag that will be assigned to requests
     * received through this connector.
     *
     * @param secure The new secure connection flag
     */
    public void setSecure(boolean secure) {

	this.secure = secure;

    }


    /**
     * Returns the Service with which we are associated.
     */
    public Service getService() {
	return service;
    }


    /**
     * Set the Service with which we are associated.
     */
    public void setService(Service service) {
	this.service = service;
    }


    /**
     * Get the value of the tomcatAuthentication flag.
     */
    public boolean getTomcatAuthentication() {
        return tomcatAuthentication;
    }


    /**
     * Set the value of the tomcatAuthentication flag.
     */
    public void setTomcatAuthentication(boolean tomcatAuthentication) {
        this.tomcatAuthentication = tomcatAuthentication;
    }


    // --------------------------------------------------------- Public Methods


    /**
     * Create (or allocate) and return a Request object suitable for
     * specifying the contents of a Request to the responsible Container.
     */
    public Request createRequest() {

	Ajp13Request request = new Ajp13Request(this);
	request.setConnector(this);
	return (request);

    }


    /**
     * Create (or allocate) and return a Response object suitable for
     * receiving the contents of a Response from the responsible Container.
     */
    public Response createResponse() {

	Ajp13Response response = new Ajp13Response();
	response.setConnector(this);
	return (response);

    }

    /**
     * Invoke a pre-startup initialization. This is used to allow connectors
     * to bind to restricted ports under Unix operating environments.
     * ServerSocket (we start as root and change user? or I miss something?).
     */
    public void initialize() throws LifecycleException {
    }


    // -------------------------------------------------------- Package Methods


    /**
     * Recycle the specified Processor so that it can be used again.
     *
     * @param processor The processor to be recycled
     */
    void recycle(Ajp13Processor processor) {

        synchronized(processors) {
            if (debug > 0) {
                logger.log("added processor to available processors, available="
                           + processors.size());
            }
            processors.push(processor);
        }

    }


    // -------------------------------------------------------- Private Methods


    /**
     * Create (or allocate) and return an available processor for use in
     * processing a specific AJP13 request, if possible.  If the maximum
     * allowed processors have already been created and are in use, return
     * null instead.
     */
    private Ajp13Processor createProcessor() {

	synchronized (processors) {
	    if (processors.size() > 0)
		return ((Ajp13Processor) processors.pop());
	    if ((maxProcessors > 0) && (curProcessors < maxProcessors))
	        return (newProcessor());
	    else
	        return (null);
	}

    }


    /**
     * Create and return a new processor suitable for processing AJP13
     * requests and returning the corresponding responses.
     */
    private Ajp13Processor newProcessor() {

        Ajp13Processor processor = new Ajp13Processor(this, curProcessors++, threadGroup);
	if (processor instanceof Lifecycle) {
	    try {
	        ((Lifecycle) processor).start();
	    } catch (LifecycleException e) {
	        logger.log("newProcessor", e);
                curProcessors--;
	        return (null);
	    }
	}
	created.addElement(processor);
	return (processor);

    }


    /**
     * Open and return the server socket for this Connector.  If an IP
     * address has been specified, the socket will be opened only on that
     * address; otherwise it will be opened on all addresses.
     *
     * @exception IOException if an input/output error occurs
     */
    private ServerSocket open() throws IOException {

        // Acquire the server socket factory for this Connector
        ServerSocketFactory factory = getFactory();

	// If no address is specified, open a connection on all addresses
        if (address == null) {
	    logger.log(sm.getString("ajp13Connector.allAddresses"));
            try {
		return (factory.createSocket(port, acceptCount));
	    } catch(Exception ex ) {
		ex.printStackTrace();
		return null;
	    }
	}

	// Open a server socket on the specified address
        try {
            InetAddress is = InetAddress.getByName(address);
	    logger.log(sm.getString("ajp13Connector.anAddress", address));
            return (factory.createSocket(port, acceptCount, is));
	} catch (Exception e) {
	    try {
		logger.log(sm.getString("ajp13Connector.noAddress", address));
		return (factory.createSocket(port, acceptCount));
	    } catch( Exception e1 ) {
		e1.printStackTrace();
		return null;
	    }
	}

    }


    // ---------------------------------------------- Background Thread Methods


    /**
     * The background thread that listens for incoming TCP/IP connections and
     * hands them off to an appropriate processor.
     */
    public void run() {

        // Loop until we receive a shutdown command
	while (!stopped) {

	    // Accept the next incoming connection from the server socket
	    Socket socket = null;
	    try {
                if (debug > 0) {
                    logger.log("accepting socket...");
                }
                
		socket = serverSocket.accept();

                if (debug > 0) {
                    logger.log("accepted socket, assigning to processor.");
                }
                
                /* Warning :
                 * 
                 * To be able to close more quickly a connection, it's recommanded
                 * to set linger to a small value.
                 * 
                 * AJP13 connection SHOULD be closed under webserver responsability and 
                 * in such case it's safe to close socket on Tomcat side without delay,
                 * which may be also the case for HTTP connectors.
                 * 
                 * I (henri) recommand to set Linger to 0, making socket closed immediatly
                 * so the OS will free faster the underlying io descriptor and resources.
                 * It's very important under heavy load !
                 */
                
                if (connectionLinger < 0)
                	socket.setSoLinger(false, 0);
                else	
                	socket.setSoLinger(true, connectionLinger);
                	
                /* We don't need it since it's the native side which 
                 * will set the connection with keep alive
                 * if specified in workers.properties.
                 * 
                 * socket.setKeepAlive(true); 
                 */
                
                /* Warning :
                 * 
                 * AJP13 shouldn't use socket timeout on tomcat site since
                 * when Tomcat close a connection after a timeout is reached
                 * the socket stay in half-closed state until the webserver
                 * try to send a request to tomcat and detect the socket close
                 * when it will try to read the reply.
                 * 
                 * On many Unix platforms the write() call didn't told
                 * webserver that the socket is closed.
                 */
                 
                if (connectionTimeout >= 0) {
                    socket.setSoTimeout(connectionTimeout);
                }
            } catch (AccessControlException ace) {
                logger.log("socket accept security exception: "
                           + ace.getMessage());
                continue;
	    } catch (IOException e) {
		if (started && !stopped)
		    logger.log("accept: ", e);
		try {
                    if (serverSocket != null) {
                        serverSocket.close();
                    }
                    if (stopped) {
                        if (debug > 0) {
                            logger.log("run():  stopped, so breaking");
                        }
                        break;
                    } else {
                        if (debug > 0) {
                            logger.log("run():  not stopped, " +
                                       "so reopening server socket");
                        }
                        serverSocket = open();
                    }
                } catch (IOException ex) {
                    // If reopening fails, exit
                    logger.log("socket reopen: ", ex);
                    break;
                }
                continue;
	    }

	    // Hand this socket off to an appropriate processor
            if (debug > 0) {
                synchronized(processors) {
                    logger.log("about to create a processor, available="
                               + processors.size() + ", created=" + created.size()
                               + ", maxProcessors=" + maxProcessors);
                }
            }
	    Ajp13Processor processor = createProcessor();
	    if (processor == null) {
		try {
		    logger.log(sm.getString("ajp13Connector.noProcessor"));
		    socket.close();
		} catch (IOException e) {
		    ;
		}
		continue;
	    }
	    processor.assign(socket);

	    // The processor will recycle itself when it finishes

	}

	// Notify the threadStop() method that we have shut ourselves down
	synchronized (threadSync) {
	    threadSync.notifyAll();
	}

    }


    /**
     * Start the background processing thread.
     */
    private void threadStart() {

	logger.log(sm.getString("ajp13Connector.starting"));

	thread = new Thread(threadGroup, this, threadName);
	thread.setDaemon(true);
	thread.start();

    }


    /**
     * Stop the background processing thread.
     */
    private void threadStop() {

	logger.log(sm.getString("ajp13Connector.stopping"));

	stopped = true;
	synchronized (threadSync) {
	    try {
		threadSync.wait(5000);
	    } catch (InterruptedException e) {
		;
	    }
	}
	thread = null;

    }


    // ------------------------------------------------------ Lifecycle Methods


    /**
     * Add a lifecycle event listener to this component.
     *
     * @param listener The listener to add
     */
    public void addLifecycleListener(LifecycleListener listener) {

	lifecycle.addLifecycleListener(listener);

    }

    /**
     * Get the lifecycle listeners associated with this lifecycle. If this
     * Lifecycle has no listeners registered, a zero-length array is returned.
     */
    public LifecycleListener[] findLifecycleListeners() {
        return null; // FIXME: lifecycle.findLifecycleListeners();
    }


    /**
     * Remove a lifecycle event listener from this component.
     *
     * @param listener The listener to add
     */
    public void removeLifecycleListener(LifecycleListener listener) {

	lifecycle.removeLifecycleListener(listener);

    }


    /**
     * Begin processing requests via this Connector.
     *
     * @exception LifecycleException if a fatal startup error occurs
     */
    public void start() throws LifecycleException {

	// Validate and update our current state
	if (started)
	    throw new LifecycleException
		(sm.getString("ajp13Connector.alreadyStarted"));

        if (debug > 0) {
            debugThread = new DebugThread();
            debugThread.setDaemon(true);
            debugThread.start();
        }

        threadName = "Ajp13Connector[" + port + "]";
        threadGroup = new ThreadGroup(threadName);
        threadGroup.setDaemon(true);
        logger.setConnector(this);
        logger.setName(threadName);
	lifecycle.fireLifecycleEvent(START_EVENT, null);
	started = true;

	// Establish a server socket on the specified port
	try {
	    serverSocket = open();
	} catch (IOException e) {
	    throw new LifecycleException(threadName + ".open", e);
	}

	// Start our background thread
	threadStart();

	// Create the specified minimum number of processors
	while (curProcessors < minProcessors) {
	    if ((maxProcessors > 0) && (curProcessors >= maxProcessors))
		break;
	    Ajp13Processor processor = newProcessor();
	    recycle(processor);
	}

    }


    /**
     * Terminate processing requests via this Connector.
     *
     * @exception LifecycleException if a fatal shutdown error occurs
     */
    public void stop() throws LifecycleException {

	// Validate and update our current state
	if (!started)
	    throw new LifecycleException
		(sm.getString("ajp13Connector.notStarted"));
	lifecycle.fireLifecycleEvent(STOP_EVENT, null);
	started = false;

	// Gracefully shut down all processors we have created
	for (int i = created.size() - 1; i >= 0; i--) {
	    Ajp13Processor processor = (Ajp13Processor) created.elementAt(i);
	    if (processor instanceof Lifecycle) {
		try {
		    ((Lifecycle) processor).stop();
		} catch (LifecycleException e) {
		    logger.log("Ajp13Connector.stop", e);
		}
	    }
	}

	// Stop our background thread
	threadStop();

	// Close the server socket we were using
	if (serverSocket != null) {
	    try {
		serverSocket.close();
	    } catch (IOException e) {
		;
	    }
	    serverSocket = null;
	}

    }

    /**
     * Debugging thread used to debug thread activity in this
     * connector.
     */
    private class DebugThread extends Thread
    {
        public void run() {
            while (true) {
                try {
                    sleep(60 * 1000);
                } catch (InterruptedException e) {
                    break;
                }
                logger.log("active threads=" + threadGroup.activeCount());
                System.out.println("===================================");
                System.out.println("Ajp13Connector active threads="
                                   + threadGroup.activeCount());
                threadGroup.list();
                System.out.println("===================================");
            }
        }
    }

}
... this post is sponsored by my books ...

#1 New Release!

FP Best Seller

 

new blog posts

 

Copyright 1998-2024 Alvin Alexander, alvinalexander.com
All Rights Reserved.

A percentage of advertising revenue from
pages under the /java/jwarehouse URI on this website is
paid back to open source projects.