blob: d93a78614cddac993039470d52af79f61efc0159 [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.tomcat33;
import java.io.IOException;
import java.net.InetAddress;
import java.net.Socket;
import org.apache.ajp.Ajp13;
import org.apache.ajp.NegociationHandler;
import org.apache.ajp.RequestHandler;
import org.apache.tomcat.core.Context;
import org.apache.tomcat.core.ContextManager;
import org.apache.tomcat.core.Request;
import org.apache.tomcat.core.Response;
import org.apache.tomcat.core.TomcatException;
import org.apache.tomcat.modules.server.PoolTcpConnector;
import org.apache.tomcat.util.buf.UDecoder;
import org.apache.tomcat.util.http.BaseRequest;
import org.apache.tomcat.util.http.Cookies;
import org.apache.tomcat.util.http.HttpMessages;
import org.apache.tomcat.util.net.TcpConnection;
import org.apache.tomcat.util.net.TcpConnectionHandler;
/** Note. PoolTcpConnector is a convenience base class used for
TCP-based connectors in tomcat33. It allows all those modules
to share the thread pool and listener code.
In future it's likely other optimizations will be implemented in
the PoolTcpConnector, so it's better to use it if you don't have
a good reason not to ( like a connector for J2ME, where you want
minimal footprint and don't care about high load )
*/
/** Tomcat 33 module implementing the Ajp14 protocol.
*
* The actual protocol implementation is in Ajp14.java, this is just an
* adapter to plug it into tomcat.
*/
public class Ajp14Interceptor extends PoolTcpConnector
implements TcpConnectionHandler
{
int ajp14_note=-1;
String password;
RequestHandler reqHandler=new RequestHandler();
NegociationHandler negHandler=new NegociationHandler();
public Ajp14Interceptor()
{
super();
super.setSoLinger( 100 );
super.setTcpNoDelay( true );
}
// initialization
public void engineInit(ContextManager cm) throws TomcatException {
log("engineInit");
}
public void engineStart(ContextManager cm) throws TomcatException {
super.engineInit( cm );
ajp14_note=cm.getNoteId( ContextManager.REQUEST_NOTE, "ajp14" );
super.engineStart(cm);
}
// -------------------- Ajp14 specific parameters --------------------
public void setPassword( String s ) {
this.password=s;
}
/**
* Set the original entropy seed
*/
public void setSeed(String pseed)
{
negHandler.setSeed( pseed );
}
// -------------------- PoolTcpConnector --------------------
/** Called by PoolTcpConnector to allow childs to init.
*/
protected void localInit() throws Exception {
ep.setConnectionHandler( this );
}
// -------------------- Handler implementation --------------------
/* The TcpConnectionHandler interface is used by the PoolTcpConnector to
* handle incoming connections.
*/
/** Called by the thread pool when a new thread is added to the pool,
in order to create the (expensive) objects that will be stored
as thread data.
XXX we should use a single object, not array ( several reasons ),
XXX Ajp14 should be storead as a request note, to be available in
all modules
*/
public Object[] init()
{
if( debug > 0 ) log("Init ");
Object thData[]=new Object[1];
thData[0]=initRequest( null );
return thData;
}
/** Construct the request object, with probably unnecesary
sanity tests ( should work without thread pool - but that is
not supported in PoolTcpConnector, maybe in future )
*/
private Ajp14Request initRequest(Object thData[] ) {
if( ajp14_note < 0 ) throw new RuntimeException( "assert: ajp14_note>0" );
Ajp14Request req=null;
if( thData != null ) {
req=(Ajp14Request)thData[0];
}
if( req != null ) {
Response res=req.getResponse();
req.recycle();
res.recycle();
// make the note available to other modules
req.setNote( ajp14_note, req.ajp13);
return req;
}
// either thData==null or broken ( req==null)
Ajp13 ajp13=new Ajp13(reqHandler);
negHandler.init( ajp13 );
negHandler.setContainerSignature( ContextManager.TOMCAT_NAME +
" v" + ContextManager.TOMCAT_VERSION);
if( password!= null ) {
negHandler.setPassword( password );
ajp13.setBackward(false);
}
BaseRequest ajpreq=new BaseRequest();
req=new Ajp14Request(ajp13, ajpreq);
Ajp14Response res=new Ajp14Response(ajp13);
cm.initRequest(req, res);
return req;
}
/** Called whenever a new TCP connection is received. The connection
is reused.
*/
public void processConnection(TcpConnection connection, Object thData[])
{
try {
if( debug>0)
log( "Received ajp14 connection ");
Socket socket = connection.getSocket();
// assert: socket!=null, connection!=null ( checked by PoolTcpEndpoint )
socket.setSoLinger( true, 100);
Ajp14Request req=initRequest( thData );
Ajp14Response res= (Ajp14Response)req.getResponse();
Ajp13 ajp13=req.ajp13;
BaseRequest ajpReq=req.ajpReq;
ajp13.setSocket(socket);
// first request should be the loginit.
int status=ajp13.receiveNextRequest( ajpReq );
if( status != 304 ) { // XXX use better codes
log( "Failure in logInit ");
return;
}
status=ajp13.receiveNextRequest( ajpReq );
if( status != 304 ) { // XXX use better codes
log( "Failure in login ");
return;
}
boolean moreRequests = true;
while(moreRequests) {
status=ajp13.receiveNextRequest( ajpReq );
if( status==-2) {
// special case - shutdown
// XXX need better communication, refactor it
if( !doShutdown(socket.getLocalAddress(),
socket.getInetAddress())) {
moreRequests = false;
continue;
}
}
// 999 low level requests are just ignored (ie cping/cpong)
if( status == 200)
cm.service(req, res);
else if (status == 500) {
log( "Invalid request received " + req );
break;
}
req.recycle();
res.recycle();
}
if( debug > 0 ) log("Closing ajp14 connection");
ajp13.close();
socket.close();
} catch (Exception e) {
log("Processing connection " + connection, e);
}
}
// We don't need to check isSameAddress if we authenticate !!!
protected boolean doShutdown(InetAddress serverAddr,
InetAddress clientAddr)
{
try {
// close the socket connection before handling any signal
// but get the addresses first so they are not corrupted
if(isSameAddress(serverAddr, clientAddr)) {
cm.stop();
// same behavior as in past, because it seems that
// stopping everything doesn't work - need to figure
// out what happens with the threads ( XXX )
// XXX It should work now - but will fail if servlets create
// threads
System.exit(0);
}
} catch(Exception ignored) {
log("Ignored " + ignored);
}
log("Shutdown command ignored");
return false;
}
// legacy, should be removed
public void setServer(Object contextM)
{
this.cm=(ContextManager)contextM;
}
public Object getInfo( Context ctx, Request request,
int id, String key ) {
if( ! ( request instanceof Ajp14Request ) ) {
return null;
}
Ajp14Request ajp14req = (Ajp14Request)request;
return ajp14req.ajpReq.getAttribute( key );
}
public int setInfo( Context ctx, Request request,
int id, String key, Object obj ) {
if( ! ( request instanceof Ajp14Request ) ) {
return DECLINED;
}
Ajp14Request ajp14req = (Ajp14Request)request;
ajp14req.ajpReq.setAttribute(key, obj);
return OK;
}
}
//-------------------- Glue code for request/response.
// Probably not needed ( or can be simplified ), but it's
// not that bad.
class Ajp14Request extends Request
{
Ajp13 ajp13;
BaseRequest ajpReq;
public Ajp14Request(Ajp13 ajp13, BaseRequest ajpReq)
{
headers = ajpReq.headers();
methodMB=ajpReq.method();
protoMB=ajpReq.protocol();
uriMB = ajpReq.requestURI();
queryMB = ajpReq.queryString();
remoteAddrMB = ajpReq.remoteAddr();
remoteHostMB = ajpReq.remoteHost();
serverNameMB = ajpReq.serverName();
// XXX sync cookies
scookies = new Cookies( headers );
urlDecoder=new UDecoder();
// XXX sync headers
params.setQuery( queryMB );
params.setURLDecoder( urlDecoder );
params.setHeaders( headers );
initRequest();
this.ajp13=ajp13;
this.ajpReq=ajpReq;
}
// -------------------- Wrappers for changed method names, and to use the buffers
// XXX Move BaseRequest into util !!! ( it's just a stuct with some MessageBytes )
public int getServerPort() {
return ajpReq.getServerPort();
}
public void setServerPort(int i ) {
ajpReq.setServerPort( i );
}
public void setRemoteUser( String s ) {
super.setRemoteUser(s);
ajpReq.remoteUser().setString(s);
}
public String getRemoteUser() {
String s=ajpReq.remoteUser().toString();
if( s == null )
s=super.getRemoteUser();
return s;
}
public String getAuthType() {
return ajpReq.authType().toString();
}
public void setAuthType(String s ) {
ajpReq.authType().setString(s);
}
public String getJvmRoute() {
return ajpReq.jvmRoute().toString();
}
public void setJvmRoute(String s ) {
ajpReq.jvmRoute().setString(s);
}
// XXX scheme
public boolean isSecure() {
return ajpReq.getSecure();
}
public int getContentLength() {
int i=ajpReq.getContentLength();
if( i >= 0 ) return i;
i= super.getContentLength();
return i;
}
public void setContentLength( int i ) {
super.setContentLength(i); // XXX sync
}
// XXX broken
// public Iterator getAttributeNames() {
// return attributes.keySet().iterator();
// }
// --------------------
public void recycle() {
super.recycle();
ajpReq.recycle();
if( ajp13!=null) ajp13.recycle();
}
public String dumpRequest() {
return ajpReq.toString();
}
// --------------------
// XXX This should go away if we introduce an InputBuffer.
// We almost have it as result of encoding fixes, but for now
// just keep this here, doesn't hurt too much.
public int doRead() throws IOException
{
if( available <= 0 )
return -1;
available--;
return ajp13.reqHandler.doRead(ajp13);
}
public int doRead(byte[] b, int off, int len) throws IOException
{
if( available <= 0 )
return -1;
int rd=ajp13.reqHandler.doRead( ajp13, b,off, len );
available -= rd;
return rd;
}
}
class Ajp14Response extends Response
{
Ajp13 ajp13;
boolean finished=false;
public Ajp14Response(Ajp13 ajp13)
{
super();
this.ajp13=ajp13;
}
public void recycle() {
super.recycle();
finished=false;
}
// XXX if more headers that MAX_SIZE, send 2 packets!
// XXX Can be implemented using module notification, no need to extend
public void endHeaders() throws IOException
{
super.endHeaders();
if (request.protocol().isNull()) {
return;
}
ajp13.reqHandler.sendHeaders(ajp13, ajp13.outBuf, getStatus(),
HttpMessages.getMessage(status),
getMimeHeaders());
}
// XXX Can be implemented using module notification, no need to extend
public void finish() throws IOException
{
if(!finished) {
super.finish();
finished = true; // Avoid END_OF_RESPONSE sent 2 times
ajp13.reqHandler.finish(ajp13, ajp13.outBuf);
}
}
// XXX Can be implemented using the buffers, no need to extend
public void doWrite( byte b[], int off, int len) throws IOException
{
ajp13.reqHandler.doWrite(ajp13, ajp13.outBuf, b, off, len );
}
}