| /* |
| * 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(); |
| } |
| } |