blob: a20833d892fd020a0ef6325da5488ec115d368f9 [file] [log] [blame]
/*
* 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.Socket;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletResponse;
import org.apache.ajp.Ajp13;
import org.apache.catalina.Lifecycle;
import org.apache.catalina.LifecycleException;
import org.apache.catalina.LifecycleListener;
import org.apache.catalina.util.LifecycleSupport;
import org.apache.catalina.util.StringManager;
import org.apache.tomcat.util.http.BaseRequest;
/**
* @author Kevin Seguin
* @version $Revision$ $Date$
*/
final class Ajp13Processor
implements Lifecycle, Runnable {
/**
* A simple class to provide synchronized access
* to a boolean.
*/
private class Bool {
private boolean b = false;
Bool() {
}
Bool(boolean b) {
this.b = b;
}
synchronized boolean value() {
return b;
}
synchronized void set(boolean b) {
this.b = b;
}
}
// ----------------------------------------------------------- Constructors
/**
* Construct a new Ajp13Processor associated with the specified connector.
*
* @param connector Ajp13Connector that owns this processor
* @param id Identifier of this Ajp13Processor (unique per connector)
* @param threadGroup The thread group any threads created by the processor
* should be in.
*/
public Ajp13Processor(Ajp13Connector connector,
int id,
ThreadGroup threadGroup) {
super();
this.connector = connector;
this.debug = connector.getDebug();
this.id = id;
this.request = (Ajp13Request) connector.createRequest();
this.request.setConnector(connector);
this.request.setConnector(connector);
this.response = (Ajp13Response) connector.createResponse();
this.response.setConnector(connector);
this.threadName =
"Ajp13Processor[" + connector.getPort() + "][" + id + "]";
this.threadGroup = threadGroup;
this.logger.setConnector(connector);
this.logger.setName(this.threadName);
}
// ----------------------------------------------------- Instance Variables
private Ajp13Logger logger = new Ajp13Logger();
private BaseRequest ajpRequest = new BaseRequest();
/**
* Is there a new socket available?
*/
private boolean available = false;
/**
* The Ajp13Connector with which this processor is associated.
*/
private Ajp13Connector connector = null;
/**
* The debugging detail level for this component.
*/
private int debug = 0;
/**
* The identifier of this processor, unique per connector.
*/
private int id = 0;
/**
* The lifecycle event support for this component.
*/
private LifecycleSupport lifecycle = new LifecycleSupport(this);
/**
* The AJP13 request object we will pass to our associated container.
*/
private Ajp13Request request = null;
/**
* The AJP13 response object we will pass to our associated container.
*/
private Ajp13Response response = null;
/**
* The string manager for this package.
*/
protected StringManager sm =
StringManager.getManager(Constants.PACKAGE);
/**
* The socket we are currently processing a request for. This object
* is used for inter-thread communication only.
*/
private Socket socket = null;
/**
* Has this component been started yet?
*/
private boolean started = false;
/**
* The shutdown signal to our background thread
*/
private Bool stopped = new Bool(true);
/**
* Are we currently handling a request?
*/
private Bool handlingRequest = new Bool(false);
/**
* The background thread.
*/
private Thread thread = null;
/**
* The name to register for the background thread.
*/
private String threadName = null;
/**
* This processor's thread group.
*/
private ThreadGroup threadGroup = null;
/**
* The thread synchronization object.
*/
private Object threadSync = new Object();
// -------------------------------------------------------- Package Methods
/**
* Process an incoming TCP/IP connection on the specified socket. Any
* exception that occurs during processing must be logged and swallowed.
* <b>NOTE</b>: This method is called from our Connector's thread. We
* must assign it to our own thread so that multiple simultaneous
* requests can be handled.
*
* @param socket TCP socket to process
*/
synchronized void assign(Socket socket) {
// Wait for the Processor to get the previous Socket
while (available) {
try {
wait();
} catch (InterruptedException e) {
}
}
// Store the newly available Socket and notify our thread
this.socket = socket;
available = true;
notifyAll();
if ((debug > 0) && (socket != null))
logger.log(" An incoming request is being assigned");
}
// -------------------------------------------------------- Private Methods
/**
* Await a newly assigned Socket from our Connector, or <code>null</code>
* if we are supposed to shut down.
*/
private synchronized Socket await() {
// Wait for the Connector to provide a new Socket
while (!available) {
try {
wait();
} catch (InterruptedException e) {
}
}
// Notify the Connector that we have received this Socket
Socket socket = this.socket;
available = false;
notifyAll();
if ((debug > 0) && (socket != null))
logger.log(" The incoming request has been awaited");
return (socket);
}
/**
* Parse and record the connection parameters related to this request.
*
* @param socket The socket on which we are connected
*
* @exception IOException if an input/output error occurs
* @exception ServletException if a parsing error occurs
*/
private void parseConnection(Socket socket)
throws IOException, ServletException {
if (debug > 1)
logger.log(" parseConnection: address=" + socket.getInetAddress() +
", port=" + connector.getPort());
request.setServerPort(connector.getPort());
request.setSocket(socket);
}
/**
* Process an incoming AJP13 request on the Socket that has been assigned
* to this Processor. Any exceptions that occur during processing must be
* swallowed and dealt with.
*
* @param socket The socket on which we are connected to the client
*/
private void process(Socket socket) {
Ajp13 ajp13 = new Ajp13();
ajp13.setDebug(debug);
ajp13.setLogger(new org.apache.ajp.Logger() {
public void log(String msg) {
logger.log("[Ajp13] " + msg);
}
public void log(String msg, Throwable t) {
logger.log("[Ajp13] " + msg, t);
}
});
Ajp13InputStream input = new Ajp13InputStream(ajp13);
Ajp13OutputStream output = new Ajp13OutputStream(ajp13);
response.setAjp13(ajp13);
try {
ajp13.setSocket(socket);
} catch (IOException e) {
logger.log("process: ajp13.setSocket", e);
}
boolean moreRequests = true;
String expectedSecret=connector.getSecret();
boolean needAuth= ( expectedSecret != null );
while (moreRequests && !stopped.value()) {
int status = 0;
try {
if (debug > 0) {
logger.log("waiting on next request...");
}
status = ajp13.receiveNextRequest(ajpRequest);
if (debug > 0) {
logger.log("received next request, status=" + status);
}
} catch (IOException e) {
logger.log("process: ajp13.receiveNextRequest", e);
}
if( needAuth ) {
String connSecret=ajp13.getSecret();
if( connSecret == null ) {
logger.log( "Connection without password, " +
"tomcat is configured to require one" );
break;
}
if( ! connSecret.equals(expectedSecret) ) {
logger.log( "Connection with wrong password" );
break;
}
needAuth=false;
}
if (stopped.value()) {
if (debug > 0) {
logger.log("process: received request, but we're stopped");
}
break;
}
if( status==-2) {
// special case - shutdown
// XXX need better communication, refactor it
// if( !doShutdown(socket.getLocalAddress(),
// socket.getInetAddress())) {
// moreRequests = false;
// continue;
// }
break;
}
// Allready handled by low level proto, don't go farther
if( status == 999 )
{
ajpRequest.recycle();
request.recycle();
// recycle ajp13 object
ajp13.recycle();
continue;
}
if( status != 200 )
break;
try {
// set flag
handlingRequest.set(true);
boolean bad_request = false;
// set up request
try {
request.setAjpRequest(ajpRequest);
} catch (IllegalArgumentException e) {
bad_request = true;
}
request.setResponse(response);
request.setStream(input);
// setup response
response.setRequest(request);
response.setStream(output);
if (debug > 0) {
logger.log("invoking...");
}
if (!bad_request) {
try {
connector.getContainer().invoke(request, response);
} catch (IOException ioe) {
// Pass the IOException through
throw ioe;
} catch (Throwable e) {
// A throwable here could be caused by a Valve,
// Filter, or other component in the chain.
// Processing of the request failed, return an
// Internal Server Error
logger.log("process: invoke", e);
response.sendError
(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
}
} else {
response.sendError
(HttpServletResponse.SC_BAD_REQUEST);
}
if (debug > 0) {
logger.log("done invoking, finishing request/response....");
}
response.finishResponse();
request.finishRequest();
if (debug > 0) {
logger.log("finished handling request.");
}
} catch (IOException ioe) {
// Normally this catches a socket Broken Pipe caused by the
// remote client aborting the request. Don't print the stack
// trace in this case. Then let the Processor recycle.
logger.log("process: IOException " + ioe.getMessage());
moreRequests = false;
} catch (Throwable e) {
// Processing the request and sending the response failed.
// We don't know what the state of the Ajp Connector socket
// is in. Bail out and recycle the Processor.
logger.log("process: finish", e);
moreRequests = false;
}
// Recycling the request and the response objects
if (debug > 0) {
logger.log("recyling objects ...");
}
ajpRequest.recycle();
request.recycle();
response.recycle();
// recycle ajp13 object
ajp13.recycle();
// reset flag
handlingRequest.set(false);
}
try {
if (debug > 0) {
logger.log("closing ajp13 object...");
}
ajp13.close();
if (debug > 0) {
logger.log("ajp13 object closed.");
}
} catch (IOException e) {
logger.log("process: ajp13.close", e);
}
try {
if (debug > 0) {
logger.log("closing socket...");
}
socket.close();
if (debug > 0) {
logger.log("socket closed.");
}
} catch (IOException e) {
logger.log("process: socket.close", e);
}
socket = null;
if (debug > 0) {
logger.log("process: done");
}
}
// ---------------------------------------------- Background Thread Methods
/**
* The background thread that listens for incoming TCP/IP connections and
* hands them off to an appropriate processor.
*/
public void run() {
// Process requests until we receive a shutdown signal
while (!stopped.value()) {
// Wait for the next socket to be assigned
if (debug > 0) {
logger.log("waiting for next socket to be assigned...");
}
Socket socket = await();
if (socket == null)
continue;
if (debug > 0) {
logger.log("socket assigned.");
}
// Process the request from this socket
process(socket);
// Finish up this request
if (debug > 0) {
logger.log("recycling myself ...");
}
connector.recycle(this);
}
// Tell threadStop() we have shut ourselves down successfully
synchronized (threadSync) {
threadSync.notifyAll();
}
}
/**
* Start the background processing thread.
*/
private void threadStart() {
logger.log(sm.getString("ajp13Processor.starting"));
stopped.set(false);
thread = new Thread(threadGroup, this, threadName);
thread.setDaemon(true);
thread.start();
if (debug > 0)
logger.log(" Background thread has been started");
}
/**
* Stop the background processing thread.
*/
private void threadStop() {
logger.log(sm.getString("ajp13Processor.stopping"));
stopped.set(true);
assign(null);
synchronized (threadSync) {
try {
if (handlingRequest.value()) {
if (debug > 0) {
logger.log
("currentling handling a request, so waiting....");
}
threadSync.wait(5000);
} else {
if (debug > 0) {
logger.log
("not currently handling a request, not waiting.");
}
}
} 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);
}
/**
* Start the background thread we will use for request processing.
*
* @exception LifecycleException if a fatal startup error occurs
*/
public void start() throws LifecycleException {
if (started)
throw new LifecycleException
(sm.getString("ajp13Processor.alreadyStarted"));
lifecycle.fireLifecycleEvent(START_EVENT, null);
started = true;
threadStart();
}
/**
* Stop the background thread we will use for request processing.
*
* @exception LifecycleException if a fatal shutdown error occurs
*/
public void stop() throws LifecycleException {
if (!started)
throw new LifecycleException
(sm.getString("ajp13Processor.notStarted"));
lifecycle.fireLifecycleEvent(STOP_EVENT, null);
started = false;
threadStop();
}
}