1 /*
2 * Licensed to the Apache Software Foundation (ASF) under one or more
3 * contributor license agreements. See the NOTICE file distributed with
4 * this work for additional information regarding copyright ownership.
5 * The ASF licenses this file to You under the Apache License, Version 2.0
6 * (the "License"); you may not use this file except in compliance with
7 * the License. You may obtain a copy of the License at
8 *
9 * http://www.apache.org/licenses/LICENSE-2.0
10 *
11 * Unless required by applicable law or agreed to in writing, software
12 * distributed under the License is distributed on an "AS IS" BASIS,
13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 * See the License for the specific language governing permissions and
15 * limitations under the License.
16 */
17
18 package org.apache.jk.core;
19
20 import java.io.IOException;
21 import java.io.ByteArrayInputStream;
22 import java.net.InetAddress;
23 import java.security.cert.CertificateFactory;
24 import java.security.cert.X509Certificate;
25
26 import org.apache.coyote.ActionCode;
27 import org.apache.coyote.ActionHook;
28 import org.apache.coyote.Request;
29 import org.apache.coyote.Response;
30
31 import org.apache.tomcat.util.buf.C2BConverter;
32 import org.apache.tomcat.util.buf.MessageBytes;
33 import org.apache.tomcat.util.buf.ByteChunk;
34 import org.apache.tomcat.util.net.SSLSupport;
35 import org.apache.jk.common.JkInputStream;
36
37
38 /**
39 *
40 * @author Henri Gomez [hgomez@apache.org]
41 * @author Dan Milstein [danmil@shore.net]
42 * @author Keith Wannamaker [Keith@Wannamaker.org]
43 * @author Kevin Seguin
44 * @author Costin Manolache
45 */
46 public class MsgContext implements ActionHook {
47 private static org.apache.juli.logging.Log log =
48 org.apache.juli.logging.LogFactory.getLog(MsgContext.class);
49 private static org.apache.juli.logging.Log logTime=
50 org.apache.juli.logging.LogFactory.getLog( "org.apache.jk.REQ_TIME" );
51
52 private int type;
53 private Object notes[]=new Object[32];
54 private JkHandler next;
55 private JkChannel source;
56 private JkInputStream jkIS;
57 private C2BConverter c2b;
58 private Request req;
59 private WorkerEnv wEnv;
60 private Msg msgs[]=new Msg[10];
61 private int status=0;
62 // Control object
63 private Object control;
64
65 // Application managed, like notes
66 private long timers[]=new long[20];
67
68 // The context can be used by JNI components as well
69 private long jkEndpointP;
70 private long xEnvP;
71
72 // Temp: use notes and dynamic strings
73 public static final int TIMER_RECEIVED=0;
74 public static final int TIMER_PRE_REQUEST=1;
75 public static final int TIMER_POST_REQUEST=2;
76
77 // Status codes
78 public static final int JK_STATUS_NEW=0;
79 public static final int JK_STATUS_HEAD=1;
80 public static final int JK_STATUS_CLOSED=2;
81 public static final int JK_STATUS_ERROR=3;
82
83 public MsgContext(int bsize) {
84 try {
85 c2b = new C2BConverter("iso-8859-1");
86 } catch(IOException iex) {
87 log.warn("Can't happen", iex);
88 }
89 jkIS = new JkInputStream(this, bsize);
90 }
91 /**
92 * @deprecated
93 */
94 public MsgContext() {
95 this(8*1024);
96 }
97
98 public final Object getNote( int id ) {
99 return notes[id];
100 }
101
102 public final void setNote( int id, Object o ) {
103 notes[id]=o;
104 }
105
106 /** The id of the chain */
107 public final int getType() {
108 return type;
109 }
110
111 public final void setType(int i) {
112 type=i;
113 }
114
115 public final void setLong( int i, long l) {
116 timers[i]=l;
117 }
118
119 public final long getLong( int i) {
120 return timers[i];
121 }
122
123 // Common attributes ( XXX should be notes for flexibility ? )
124
125 public final WorkerEnv getWorkerEnv() {
126 return wEnv;
127 }
128
129 public final void setWorkerEnv( WorkerEnv we ) {
130 this.wEnv=we;
131 }
132
133 public final JkChannel getSource() {
134 return source;
135 }
136
137 public final void setSource(JkChannel ch) {
138 this.source=ch;
139 }
140
141 public final int getStatus() {
142 return status;
143 }
144
145 public final void setStatus( int s ) {
146 status=s;
147 }
148
149 public final JkHandler getNext() {
150 return next;
151 }
152
153 public final void setNext(JkHandler ch) {
154 this.next=ch;
155 }
156
157 /** The high level request object associated with this context
158 */
159 public final void setRequest( Request req ) {
160 this.req=req;
161 req.setInputBuffer(jkIS);
162 Response res = req.getResponse();
163 res.setOutputBuffer(jkIS);
164 res.setHook(this);
165 }
166
167 public final Request getRequest() {
168 return req;
169 }
170
171 /** The context may store a number of messages ( buffers + marshalling )
172 */
173 public final Msg getMsg(int i) {
174 return msgs[i];
175 }
176
177 public final void setMsg(int i, Msg msg) {
178 this.msgs[i]=msg;
179 }
180
181 public final C2BConverter getConverter() {
182 return c2b;
183 }
184
185 public final void setConverter(C2BConverter c2b) {
186 this.c2b = c2b;
187 }
188
189 public final boolean isLogTimeEnabled() {
190 return logTime.isDebugEnabled();
191 }
192
193 public JkInputStream getInputStream() {
194 return jkIS;
195 }
196
197 /** Each context contains a number of byte[] buffers used for communication.
198 * The C side will contain a char * equivalent - both buffers are long-lived
199 * and recycled.
200 *
201 * This will be called at init time. A long-lived global reference to the byte[]
202 * will be stored in the C context.
203 */
204 public byte[] getBuffer( int id ) {
205 // We use a single buffer right now.
206 if( msgs[id]==null ) {
207 return null;
208 }
209 return msgs[id].getBuffer();
210 }
211
212 /** Invoke a java hook. The xEnv is the representation of the current execution
213 * environment ( the jni_env_t * )
214 */
215 public int execute() throws IOException {
216 int status=next.invoke(msgs[0], this);
217 return status;
218 }
219
220 // -------------------- Jni support --------------------
221
222 /** Store native execution context data when this handler is called
223 * from JNI. This will change on each call, represent temproary
224 * call data.
225 */
226 public void setJniEnv( long xEnvP ) {
227 this.xEnvP=xEnvP;
228 }
229
230 public long getJniEnv() {
231 return xEnvP;
232 }
233
234 /** The long-lived JNI context associated with this java context.
235 * The 2 share pointers to buffers and cache data to avoid expensive
236 * jni calls.
237 */
238 public void setJniContext( long cContext ) {
239 this.jkEndpointP=cContext;
240 }
241
242 public long getJniContext() {
243 return jkEndpointP;
244 }
245
246 public Object getControl() {
247 return control;
248 }
249
250 public void setControl(Object control) {
251 this.control = control;
252 }
253
254 // -------------------- Coyote Action implementation --------------------
255
256 public void action(ActionCode actionCode, Object param) {
257 if( actionCode==ActionCode.ACTION_COMMIT ) {
258 if( log.isDebugEnabled() ) log.debug("COMMIT " );
259 Response res=(Response)param;
260
261 if( res.isCommitted() ) {
262 if( log.isDebugEnabled() )
263 log.debug("Response already committed " );
264 } else {
265 try {
266 jkIS.appendHead( res );
267 } catch(IOException iex) {
268 log.warn("Unable to send headers",iex);
269 setStatus(JK_STATUS_ERROR);
270 }
271 }
272 } else if( actionCode==ActionCode.ACTION_RESET ) {
273 if( log.isDebugEnabled() )
274 log.debug("RESET " );
275
276 } else if( actionCode==ActionCode.ACTION_CLIENT_FLUSH ) {
277 if( log.isDebugEnabled() ) log.debug("CLIENT_FLUSH " );
278 Response res = (Response)param;
279 if(!res.isCommitted()) {
280 action(ActionCode.ACTION_COMMIT, res);
281 }
282 try {
283 source.flush( null, this );
284 } catch(IOException iex) {
285 // This is logged elsewhere, so debug only here
286 log.debug("Error during flush",iex);
287 res.setErrorException(iex);
288 setStatus(JK_STATUS_ERROR);
289 }
290
291 } else if( actionCode==ActionCode.ACTION_CLOSE ) {
292 if( log.isDebugEnabled() ) log.debug("CLOSE " );
293
294 Response res=(Response)param;
295 if( getStatus()== JK_STATUS_CLOSED || getStatus() == JK_STATUS_ERROR) {
296 // Double close - it may happen with forward
297 if( log.isDebugEnabled() ) log.debug("Double CLOSE - forward ? " + res.getRequest().requestURI() );
298 return;
299 }
300
301 if( !res.isCommitted() )
302 this.action( ActionCode.ACTION_COMMIT, param );
303 try {
304 jkIS.endMessage();
305 } catch(IOException iex) {
306 log.warn("Error sending end packet",iex);
307 setStatus(JK_STATUS_ERROR);
308 }
309 if(getStatus() != JK_STATUS_ERROR) {
310 setStatus(JK_STATUS_CLOSED );
311 }
312
313 if( logTime.isDebugEnabled() )
314 logTime(res.getRequest(), res);
315 } else if( actionCode==ActionCode.ACTION_REQ_SSL_ATTRIBUTE ) {
316 Request req=(Request)param;
317
318 // Extract SSL certificate information (if requested)
319 MessageBytes certString = (MessageBytes)req.getNote(WorkerEnv.SSL_CERT_NOTE);
320 if( certString != null && !certString.isNull() ) {
321 ByteChunk certData = certString.getByteChunk();
322 ByteArrayInputStream bais =
323 new ByteArrayInputStream(certData.getBytes(),
324 certData.getStart(),
325 certData.getLength());
326
327 // Fill the first element.
328 X509Certificate jsseCerts[] = null;
329 try {
330 CertificateFactory cf =
331 CertificateFactory.getInstance("X.509");
332 X509Certificate cert = (X509Certificate)
333 cf.generateCertificate(bais);
334 jsseCerts = new X509Certificate[1];
335 jsseCerts[0] = cert;
336 } catch(java.security.cert.CertificateException e) {
337 log.error("Certificate convertion failed" , e );
338 return;
339 }
340
341 req.setAttribute(SSLSupport.CERTIFICATE_KEY,
342 jsseCerts);
343 }
344
345 } else if( actionCode==ActionCode.ACTION_REQ_HOST_ATTRIBUTE ) {
346 Request req=(Request)param;
347
348 // If remoteHost not set by JK, get it's name from it's remoteAddr
349 if( req.remoteHost().isNull()) {
350 try {
351 req.remoteHost().setString(InetAddress.getByName(
352 req.remoteAddr().toString()).
353 getHostName());
354 } catch(IOException iex) {
355 if(log.isDebugEnabled())
356 log.debug("Unable to resolve "+req.remoteAddr());
357 }
358 }
359 } else if( actionCode==ActionCode.ACTION_ACK ) {
360 if( log.isTraceEnabled() )
361 log.trace("ACK " );
362 } else if ( actionCode == ActionCode.ACTION_REQ_SET_BODY_REPLAY ) {
363 if( log.isTraceEnabled() )
364 log.trace("Replay ");
365 ByteChunk bc = (ByteChunk)param;
366 req.setContentLength(bc.getLength());
367 jkIS.setReplay(bc);
368 }
369 }
370
371
372 private void logTime(Request req, Response res ) {
373 // called after the request
374 // org.apache.coyote.Request req=(org.apache.coyote.Request)param;
375 // Response res=req.getResponse();
376 String uri=req.requestURI().toString();
377 if( uri.indexOf( ".gif" ) >0 ) return;
378
379 setLong( MsgContext.TIMER_POST_REQUEST, System.currentTimeMillis());
380 long t1= getLong( MsgContext.TIMER_PRE_REQUEST ) -
381 getLong( MsgContext.TIMER_RECEIVED );
382 long t2= getLong( MsgContext.TIMER_POST_REQUEST ) -
383 getLong( MsgContext.TIMER_PRE_REQUEST );
384
385 logTime.debug("Time pre=" + t1 + "/ service=" + t2 + " " +
386 res.getContentLength() + " " +
387 uri );
388 }
389
390 public void recycle() {
391 jkIS.recycle();
392 }
393 }