/* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You 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.juli.logging.Log log = org.apache.juli.logging.LogFactory.getLog(MsgContext.class); private static org.apache.juli.logging.Log logTime= org.apache.juli.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; 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(int bsize) { try { c2b = new C2BConverter("iso-8859-1"); } catch(IOException iex) { log.warn("Can't happen", iex); } jkIS = new JkInputStream(this, bsize); } /** * @deprecated */ public MsgContext() { this(8*1024); } 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.isDebugEnabled() ) log.debug("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 " ); Response res = (Response)param; if(!res.isCommitted()) { action(ActionCode.ACTION_COMMIT, res); } try { source.flush( null, this ); } catch(IOException iex) { // This is logged elsewhere, so debug only here log.debug("Error during flush",iex); 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 || getStatus() == JK_STATUS_ERROR) { // 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.debug("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 all elements. X509Certificate jsseCerts[] = null; try { CertificateFactory cf = CertificateFactory.getInstance("X.509"); int i = 0; while (bais.available() > 0) { X509Certificate cert = (X509Certificate) cf.generateCertificate(bais); if (jsseCerts == null) { jsseCerts = new X509Certificate[1]; } else { X509Certificate tmpJsseCerts[] = new X509Certificate[jsseCerts.length + 1]; System.arraycopy(jsseCerts, 0, tmpJsseCerts, 0, jsseCerts.length); jsseCerts = tmpJsseCerts; } jsseCerts[i++] = 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; req.setContentLength(bc.getLength()); jkIS.setReplay(bc); } else if (actionCode == ActionCode.ACTION_REQ_LOCAL_ADDR_ATTRIBUTE) { Request req=(Request)param; // Automatically populated during prepareRequest() when using // modern AJP forwarder, otherwise copy from local name if (req.localAddr().isNull()) { req.localAddr().setString(req.localName().toString()); } } } 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(); } }