blob: ea53ab3536d7b776f4f21ac675e67e83275b06cc [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.jk.core;
import java.io.IOException;
import java.io.ByteArrayInputStream;
import java.net.InetAddress;
import java.security.cert.CertificateFactory;
import java.security.cert.X509Certificate;
import org.apache.coyote.ActionCode;
import org.apache.coyote.ActionHook;
import org.apache.coyote.Request;
import org.apache.coyote.Response;
import org.apache.tomcat.util.buf.C2BConverter;
import org.apache.tomcat.util.buf.MessageBytes;
import org.apache.tomcat.util.buf.ByteChunk;
import org.apache.tomcat.util.net.SSLSupport;
import org.apache.jk.common.JkInputStream;
/**
*
* @author Henri Gomez [hgomez@apache.org]
* @author Dan Milstein [danmil@shore.net]
* @author Keith Wannamaker [Keith@Wannamaker.org]
* @author Kevin Seguin
* @author Costin Manolache
*/
public class MsgContext implements ActionHook {
private static org.apache.commons.logging.Log log =
org.apache.commons.logging.LogFactory.getLog(MsgContext.class);
private static org.apache.commons.logging.Log logTime=
org.apache.commons.logging.LogFactory.getLog( "org.apache.jk.REQ_TIME" );
private int type;
private Object notes[]=new Object[32];
private JkHandler next;
private JkChannel source;
private JkInputStream jkIS = new JkInputStream(this);
private C2BConverter c2b;
private Request req;
private WorkerEnv wEnv;
private Msg msgs[]=new Msg[10];
private int status=0;
// Control object
private Object control;
// Application managed, like notes
private long timers[]=new long[20];
// The context can be used by JNI components as well
private long jkEndpointP;
private long xEnvP;
// Temp: use notes and dynamic strings
public static final int TIMER_RECEIVED=0;
public static final int TIMER_PRE_REQUEST=1;
public static final int TIMER_POST_REQUEST=2;
// Status codes
public static final int JK_STATUS_NEW=0;
public static final int JK_STATUS_HEAD=1;
public static final int JK_STATUS_CLOSED=2;
public static final int JK_STATUS_ERROR=3;
public MsgContext() {
try {
c2b = new C2BConverter("iso-8859-1");
} catch(IOException iex) {
log.warn("Can't happen", iex);
}
}
public final Object getNote( int id ) {
return notes[id];
}
public final void setNote( int id, Object o ) {
notes[id]=o;
}
/** The id of the chain */
public final int getType() {
return type;
}
public final void setType(int i) {
type=i;
}
public final void setLong( int i, long l) {
timers[i]=l;
}
public final long getLong( int i) {
return timers[i];
}
// Common attributes ( XXX should be notes for flexibility ? )
public final WorkerEnv getWorkerEnv() {
return wEnv;
}
public final void setWorkerEnv( WorkerEnv we ) {
this.wEnv=we;
}
public final JkChannel getSource() {
return source;
}
public final void setSource(JkChannel ch) {
this.source=ch;
}
public final int getStatus() {
return status;
}
public final void setStatus( int s ) {
status=s;
}
public final JkHandler getNext() {
return next;
}
public final void setNext(JkHandler ch) {
this.next=ch;
}
/** The high level request object associated with this context
*/
public final void setRequest( Request req ) {
this.req=req;
req.setInputBuffer(jkIS);
Response res = req.getResponse();
res.setOutputBuffer(jkIS);
res.setHook(this);
}
public final Request getRequest() {
return req;
}
/** The context may store a number of messages ( buffers + marshalling )
*/
public final Msg getMsg(int i) {
return msgs[i];
}
public final void setMsg(int i, Msg msg) {
this.msgs[i]=msg;
}
public final C2BConverter getConverter() {
return c2b;
}
public final void setConverter(C2BConverter c2b) {
this.c2b = c2b;
}
public final boolean isLogTimeEnabled() {
return logTime.isDebugEnabled();
}
public JkInputStream getInputStream() {
return jkIS;
}
/** Each context contains a number of byte[] buffers used for communication.
* The C side will contain a char * equivalent - both buffers are long-lived
* and recycled.
*
* This will be called at init time. A long-lived global reference to the byte[]
* will be stored in the C context.
*/
public byte[] getBuffer( int id ) {
// We use a single buffer right now.
if( msgs[id]==null ) {
return null;
}
return msgs[id].getBuffer();
}
/** Invoke a java hook. The xEnv is the representation of the current execution
* environment ( the jni_env_t * )
*/
public int execute() throws IOException {
int status=next.invoke(msgs[0], this);
return status;
}
// -------------------- Jni support --------------------
/** Store native execution context data when this handler is called
* from JNI. This will change on each call, represent temproary
* call data.
*/
public void setJniEnv( long xEnvP ) {
this.xEnvP=xEnvP;
}
public long getJniEnv() {
return xEnvP;
}
/** The long-lived JNI context associated with this java context.
* The 2 share pointers to buffers and cache data to avoid expensive
* jni calls.
*/
public void setJniContext( long cContext ) {
this.jkEndpointP=cContext;
}
public long getJniContext() {
return jkEndpointP;
}
public Object getControl() {
return control;
}
public void setControl(Object control) {
this.control = control;
}
// -------------------- Coyote Action implementation --------------------
public void action(ActionCode actionCode, Object param) {
if( actionCode==ActionCode.ACTION_COMMIT ) {
if( log.isDebugEnabled() ) log.debug("COMMIT " );
Response res=(Response)param;
if( res.isCommitted() ) {
if( log.isInfoEnabled() )
log.info("Response already committed " );
} else {
try {
jkIS.appendHead( res );
} catch(IOException iex) {
log.warn("Unable to send headers",iex);
setStatus(JK_STATUS_ERROR);
}
}
} 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 " );
try {
source.flush( null, this );
} catch(IOException iex) {
// This is logged elsewhere, so debug only here
log.debug("Error during flush",iex);
Response res = (Response)param;
res.setErrorException(iex);
setStatus(JK_STATUS_ERROR);
}
} else if( actionCode==ActionCode.ACTION_CLOSE ) {
if( log.isDebugEnabled() ) log.debug("CLOSE " );
Response res=(Response)param;
if( 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 );
try {
jkIS.endMessage();
} catch(IOException iex) {
log.warn("Error sending end packet",iex);
setStatus(JK_STATUS_ERROR);
}
if(getStatus() != JK_STATUS_ERROR) {
setStatus(JK_STATUS_CLOSED );
}
if( logTime.isDebugEnabled() )
logTime(res.getRequest(), res);
} else if( actionCode==ActionCode.ACTION_REQ_SSL_ATTRIBUTE ) {
Request req=(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 ) {
Request req=(Request)param;
// If remoteHost not set by JK, get it's name from it's remoteAddr
if( req.remoteHost().isNull()) {
try {
req.remoteHost().setString(InetAddress.getByName(
req.remoteAddr().toString()).
getHostName());
} catch(IOException iex) {
if(log.isDebugEnabled())
log.debug("Unable to resolve "+req.remoteAddr());
}
}
} else if( actionCode==ActionCode.ACTION_ACK ) {
if( log.isTraceEnabled() )
log.trace("ACK " );
} else if ( actionCode == ActionCode.ACTION_REQ_SET_BODY_REPLAY ) {
if( log.isTraceEnabled() )
log.trace("Replay ");
ByteChunk bc = (ByteChunk)param;
jkIS.setReplay(bc);
}
}
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();
String uri=req.requestURI().toString();
if( uri.indexOf( ".gif" ) >0 ) return;
setLong( MsgContext.TIMER_POST_REQUEST, System.currentTimeMillis());
long t1= getLong( MsgContext.TIMER_PRE_REQUEST ) -
getLong( MsgContext.TIMER_RECEIVED );
long t2= getLong( MsgContext.TIMER_POST_REQUEST ) -
getLong( MsgContext.TIMER_PRE_REQUEST );
logTime.debug("Time pre=" + t1 + "/ service=" + t2 + " " +
res.getContentLength() + " " +
uri );
}
public void recycle() {
jkIS.recycle();
}
}