001: /*
002: * Licensed to the Apache Software Foundation (ASF) under one or more
003: * contributor license agreements. See the NOTICE file distributed with
004: * this work for additional information regarding copyright ownership.
005: * The ASF licenses this file to You under the Apache License, Version 2.0
006: * (the "License"); you may not use this file except in compliance with
007: * the License. You may obtain a copy of the License at
008: *
009: * http://www.apache.org/licenses/LICENSE-2.0
010: *
011: * Unless required by applicable law or agreed to in writing, software
012: * distributed under the License is distributed on an "AS IS" BASIS,
013: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014: * See the License for the specific language governing permissions and
015: * limitations under the License.
016: */
017:
018: package org.apache.jk.core;
019:
020: import java.io.IOException;
021: import java.io.ByteArrayInputStream;
022: import java.net.InetAddress;
023: import java.security.cert.CertificateFactory;
024: import java.security.cert.X509Certificate;
025:
026: import org.apache.coyote.ActionCode;
027: import org.apache.coyote.ActionHook;
028: import org.apache.coyote.Request;
029: import org.apache.coyote.Response;
030:
031: import org.apache.tomcat.util.buf.C2BConverter;
032: import org.apache.tomcat.util.buf.MessageBytes;
033: import org.apache.tomcat.util.buf.ByteChunk;
034: import org.apache.tomcat.util.net.SSLSupport;
035: import org.apache.jk.common.JkInputStream;
036:
037: /**
038: *
039: * @author Henri Gomez [hgomez@apache.org]
040: * @author Dan Milstein [danmil@shore.net]
041: * @author Keith Wannamaker [Keith@Wannamaker.org]
042: * @author Kevin Seguin
043: * @author Costin Manolache
044: */
045: public class MsgContext implements ActionHook {
046: private static org.apache.juli.logging.Log log = org.apache.juli.logging.LogFactory
047: .getLog(MsgContext.class);
048: private static org.apache.juli.logging.Log logTime = org.apache.juli.logging.LogFactory
049: .getLog("org.apache.jk.REQ_TIME");
050:
051: private int type;
052: private Object notes[] = new Object[32];
053: private JkHandler next;
054: private JkChannel source;
055: private JkInputStream jkIS;
056: private C2BConverter c2b;
057: private Request req;
058: private WorkerEnv wEnv;
059: private Msg msgs[] = new Msg[10];
060: private int status = 0;
061: // Control object
062: private Object control;
063:
064: // Application managed, like notes
065: private long timers[] = new long[20];
066:
067: // The context can be used by JNI components as well
068: private long jkEndpointP;
069: private long xEnvP;
070:
071: // Temp: use notes and dynamic strings
072: public static final int TIMER_RECEIVED = 0;
073: public static final int TIMER_PRE_REQUEST = 1;
074: public static final int TIMER_POST_REQUEST = 2;
075:
076: // Status codes
077: public static final int JK_STATUS_NEW = 0;
078: public static final int JK_STATUS_HEAD = 1;
079: public static final int JK_STATUS_CLOSED = 2;
080: public static final int JK_STATUS_ERROR = 3;
081:
082: public MsgContext(int bsize) {
083: try {
084: c2b = new C2BConverter("iso-8859-1");
085: } catch (IOException iex) {
086: log.warn("Can't happen", iex);
087: }
088: jkIS = new JkInputStream(this , bsize);
089: }
090:
091: /**
092: * @deprecated
093: */
094: public MsgContext() {
095: this (8 * 1024);
096: }
097:
098: public final Object getNote(int id) {
099: 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())
259: log.debug("COMMIT ");
260: Response res = (Response) param;
261:
262: if (res.isCommitted()) {
263: if (log.isDebugEnabled())
264: log.debug("Response already committed ");
265: } else {
266: try {
267: jkIS.appendHead(res);
268: } catch (IOException iex) {
269: log.warn("Unable to send headers", iex);
270: setStatus(JK_STATUS_ERROR);
271: }
272: }
273: } else if (actionCode == ActionCode.ACTION_RESET) {
274: if (log.isDebugEnabled())
275: log.debug("RESET ");
276:
277: } else if (actionCode == ActionCode.ACTION_CLIENT_FLUSH) {
278: if (log.isDebugEnabled())
279: log.debug("CLIENT_FLUSH ");
280: try {
281: source.flush(null, this );
282: } catch (IOException iex) {
283: // This is logged elsewhere, so debug only here
284: log.debug("Error during flush", iex);
285: Response res = (Response) param;
286: res.setErrorException(iex);
287: setStatus(JK_STATUS_ERROR);
288: }
289:
290: } else if (actionCode == ActionCode.ACTION_CLOSE) {
291: if (log.isDebugEnabled())
292: log.debug("CLOSE ");
293:
294: Response res = (Response) param;
295: if (getStatus() == JK_STATUS_CLOSED
296: || getStatus() == JK_STATUS_ERROR) {
297: // Double close - it may happen with forward
298: if (log.isDebugEnabled())
299: log.debug("Double CLOSE - forward ? "
300: + res.getRequest().requestURI());
301: return;
302: }
303:
304: if (!res.isCommitted())
305: this .action(ActionCode.ACTION_COMMIT, param);
306: try {
307: jkIS.endMessage();
308: } catch (IOException iex) {
309: log.warn("Error sending end packet", iex);
310: setStatus(JK_STATUS_ERROR);
311: }
312: if (getStatus() != JK_STATUS_ERROR) {
313: setStatus(JK_STATUS_CLOSED);
314: }
315:
316: if (logTime.isDebugEnabled())
317: logTime(res.getRequest(), res);
318: } else if (actionCode == ActionCode.ACTION_REQ_SSL_ATTRIBUTE) {
319: Request req = (Request) param;
320:
321: // Extract SSL certificate information (if requested)
322: MessageBytes certString = (MessageBytes) req
323: .getNote(WorkerEnv.SSL_CERT_NOTE);
324: if (certString != null && !certString.isNull()) {
325: ByteChunk certData = certString.getByteChunk();
326: ByteArrayInputStream bais = new ByteArrayInputStream(
327: certData.getBytes(), certData.getStart(),
328: certData.getLength());
329:
330: // Fill the first element.
331: X509Certificate jsseCerts[] = null;
332: try {
333: CertificateFactory cf = CertificateFactory
334: .getInstance("X.509");
335: X509Certificate cert = (X509Certificate) cf
336: .generateCertificate(bais);
337: jsseCerts = new X509Certificate[1];
338: jsseCerts[0] = cert;
339: } catch (java.security.cert.CertificateException e) {
340: log.error("Certificate convertion failed", e);
341: return;
342: }
343:
344: req.setAttribute(SSLSupport.CERTIFICATE_KEY, jsseCerts);
345: }
346:
347: } else if (actionCode == ActionCode.ACTION_REQ_HOST_ATTRIBUTE) {
348: Request req = (Request) param;
349:
350: // If remoteHost not set by JK, get it's name from it's remoteAddr
351: if (req.remoteHost().isNull()) {
352: try {
353: req.remoteHost().setString(
354: InetAddress.getByName(
355: req.remoteAddr().toString())
356: .getHostName());
357: } catch (IOException iex) {
358: if (log.isDebugEnabled())
359: log.debug("Unable to resolve "
360: + req.remoteAddr());
361: }
362: }
363: } else if (actionCode == ActionCode.ACTION_ACK) {
364: if (log.isTraceEnabled())
365: log.trace("ACK ");
366: } else if (actionCode == ActionCode.ACTION_REQ_SET_BODY_REPLAY) {
367: if (log.isTraceEnabled())
368: log.trace("Replay ");
369: ByteChunk bc = (ByteChunk) param;
370: req.setContentLength(bc.getLength());
371: jkIS.setReplay(bc);
372: }
373: }
374:
375: private void logTime(Request req, Response res) {
376: // called after the request
377: // org.apache.coyote.Request req=(org.apache.coyote.Request)param;
378: // Response res=req.getResponse();
379: String uri = req.requestURI().toString();
380: if (uri.indexOf(".gif") > 0)
381: return;
382:
383: setLong(MsgContext.TIMER_POST_REQUEST, System
384: .currentTimeMillis());
385: long t1 = getLong(MsgContext.TIMER_PRE_REQUEST)
386: - getLong(MsgContext.TIMER_RECEIVED);
387: long t2 = getLong(MsgContext.TIMER_POST_REQUEST)
388: - getLong(MsgContext.TIMER_PRE_REQUEST);
389:
390: logTime.debug("Time pre=" + t1 + "/ service=" + t2 + " "
391: + res.getContentLength() + " " + uri);
392: }
393:
394: public void recycle() {
395: jkIS.recycle();
396: }
397: }
|