001: /*
002: * Copyright 1999-2004 The Apache Software Foundation
003: *
004: * Licensed under the Apache License, Version 2.0 (the "License");
005: * you may not use this file except in compliance with the License.
006: * You may obtain a copy of the License at
007: *
008: * http://www.apache.org/licenses/LICENSE-2.0
009: *
010: * Unless required by applicable law or agreed to in writing, software
011: * distributed under the License is distributed on an "AS IS" BASIS,
012: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013: * See the License for the specific language governing permissions and
014: * limitations under the License.
015: */
016:
017: package org.apache.jk.server;
018:
019: import java.io.ByteArrayInputStream;
020: import java.io.IOException;
021: import java.net.InetAddress;
022: import java.security.cert.CertificateFactory;
023: import java.security.cert.X509Certificate;
024: import java.security.PrivilegedExceptionAction;
025: import java.security.AccessController;
026: import java.security.PrivilegedActionException;
027: import java.security.PrivilegedAction;
028:
029: import javax.management.MBeanServer;
030: import javax.management.ObjectName;
031:
032: import org.apache.commons.modeler.Registry;
033: import org.apache.coyote.ActionCode;
034: import org.apache.coyote.ActionHook;
035: import org.apache.coyote.Adapter;
036: import org.apache.coyote.ProtocolHandler;
037: import org.apache.coyote.Request;
038: import org.apache.coyote.Response;
039: import org.apache.coyote.RequestInfo;
040: import org.apache.coyote.Constants;
041: import org.apache.jk.common.HandlerRequest;
042: import org.apache.jk.common.JkInputStream;
043: import org.apache.jk.common.MsgAjp;
044: import org.apache.jk.core.JkHandler;
045: import org.apache.jk.core.Msg;
046: import org.apache.jk.core.MsgContext;
047: import org.apache.jk.core.WorkerEnv;
048: import org.apache.jk.core.JkChannel;
049: import org.apache.tomcat.util.buf.ByteChunk;
050: import org.apache.tomcat.util.buf.C2BConverter;
051: import org.apache.tomcat.util.buf.MessageBytes;
052: import org.apache.tomcat.util.http.HttpMessages;
053: import org.apache.tomcat.util.http.MimeHeaders;
054: import org.apache.tomcat.util.net.SSLSupport;
055:
056: /** Plugs Jk2 into Coyote. Must be named "type=JkHandler,name=container"
057: *
058: * @jmx:notification-handler name="org.apache.jk.SEND_PACKET
059: * @jmx:notification-handler name="org.apache.coyote.ACTION_COMMIT
060: */
061: public class JkCoyoteHandler extends JkHandler implements
062: ProtocolHandler, ActionHook, org.apache.coyote.OutputBuffer,
063: org.apache.coyote.InputBuffer {
064: protected static org.apache.commons.logging.Log log = org.apache.commons.logging.LogFactory
065: .getLog(JkCoyoteHandler.class);
066: // Set debug on this logger to see the container request time
067: private static org.apache.commons.logging.Log logTime = org.apache.commons.logging.LogFactory
068: .getLog("org.apache.jk.REQ_TIME");
069:
070: // ----------------------------------------------------------- DoPrivileged
071: private final class StatusLinePrivilegedAction implements
072: PrivilegedAction {
073: int status;
074:
075: StatusLinePrivilegedAction(int status) {
076: this .status = status;
077: }
078:
079: public Object run() {
080: return HttpMessages.getMessage(status);
081: }
082: }
083:
084: int headersMsgNote;
085: int c2bConvertersNote;
086: int tmpMessageBytesNote;
087: int utfC2bNote;
088: int obNote;
089: int epNote;
090: int inputStreamNote;
091: private boolean paused = false;
092:
093: Adapter adapter;
094: protected JkMain jkMain = null;
095:
096: public final int JK_STATUS_NEW = 0;
097: public final int JK_STATUS_HEAD = 1;
098: public final int JK_STATUS_CLOSED = 2;
099:
100: /** Set a property. Name is a "component.property". JMX should
101: * be used instead.
102: */
103: public void setProperty(String name, String value) {
104: if (log.isTraceEnabled())
105: log.trace("setProperty " + name + " " + value);
106: getJkMain().setProperty(name, value);
107: properties.put(name, value);
108: }
109:
110: public String getProperty(String name) {
111: return properties.getProperty(name);
112: }
113:
114: /** Pass config info
115: */
116: public void setAttribute(String name, Object value) {
117: if (log.isDebugEnabled())
118: log.debug("setAttribute " + name + " " + value);
119: if (value instanceof String)
120: this .setProperty(name, (String) value);
121: }
122:
123: /**
124: * Retrieve config info.
125: * Primarily for use with the admin webapp.
126: */
127: public Object getAttribute(String name) {
128: return getJkMain().getProperty(name);
129: }
130:
131: /** The adapter, used to call the connector
132: */
133: public void setAdapter(Adapter adapter) {
134: this .adapter = adapter;
135: }
136:
137: public Adapter getAdapter() {
138: return adapter;
139: }
140:
141: public JkMain getJkMain() {
142: if (jkMain == null) {
143: jkMain = new JkMain();
144: jkMain.setWorkerEnv(wEnv);
145:
146: }
147: return jkMain;
148: }
149:
150: boolean started = false;
151:
152: /** Start the protocol
153: */
154: public void init() {
155: if (started)
156: return;
157:
158: started = true;
159:
160: if (wEnv == null) {
161: // we are probably not registered - not very good.
162: wEnv = getJkMain().getWorkerEnv();
163: wEnv.addHandler("container", this );
164: }
165:
166: try {
167: // jkMain.setJkHome() XXX;
168:
169: getJkMain().init();
170:
171: headersMsgNote = wEnv.getNoteId(WorkerEnv.ENDPOINT_NOTE,
172: "headerMsg");
173: tmpMessageBytesNote = wEnv.getNoteId(
174: WorkerEnv.ENDPOINT_NOTE, "tmpMessageBytes");
175: utfC2bNote = wEnv.getNoteId(WorkerEnv.ENDPOINT_NOTE,
176: "utfC2B");
177: epNote = wEnv.getNoteId(WorkerEnv.ENDPOINT_NOTE, "ep");
178: obNote = wEnv.getNoteId(WorkerEnv.ENDPOINT_NOTE,
179: "coyoteBuffer");
180: inputStreamNote = wEnv.getNoteId(WorkerEnv.ENDPOINT_NOTE,
181: "jkInputStream");
182:
183: } catch (Exception ex) {
184: log.error("Error during init", ex);
185: }
186: }
187:
188: public void start() {
189: try {
190: if (oname != null && getJkMain().getDomain() == null) {
191: try {
192: Registry.getRegistry().registerComponent(
193: getJkMain(), oname.getDomain(), "JkMain",
194: "type=JkMain");
195: } catch (Exception e) {
196: log.error("Error registering jkmain " + e);
197: }
198: }
199: getJkMain().start();
200: } catch (Exception ex) {
201: log.error("Error during startup", ex);
202: }
203: }
204:
205: public void pause() throws Exception {
206: if (!paused) {
207: paused = true;
208: getJkMain().pause();
209: }
210: }
211:
212: public void resume() throws Exception {
213: if (paused) {
214: paused = false;
215: getJkMain().resume();
216: }
217: }
218:
219: public void destroy() {
220: if (!started)
221: return;
222:
223: started = false;
224: getJkMain().stop();
225: }
226:
227: // -------------------- OutputBuffer implementation --------------------
228:
229: public int doWrite(ByteChunk chunk, Response res)
230: throws IOException {
231: if (!res.isCommitted()) {
232: // Send the connector a request for commit. The connector should
233: // then validate the headers, send them (using sendHeader) and
234: // set the filters accordingly.
235: res.sendHeaders();
236: }
237: MsgContext ep = (MsgContext) res.getNote(epNote);
238:
239: MsgAjp msg = (MsgAjp) ep.getNote(headersMsgNote);
240:
241: int len = chunk.getLength();
242: byte buf[] = msg.getBuffer();
243: // 4 - hardcoded, byte[] marshalling overhead
244: int chunkSize = buf.length - msg.getHeaderLength() - 4;
245: int off = 0;
246: while (len > 0) {
247: int this Time = len;
248: if (this Time > chunkSize) {
249: this Time = chunkSize;
250: }
251: len -= this Time;
252:
253: msg.reset();
254: msg.appendByte(HandlerRequest.JK_AJP13_SEND_BODY_CHUNK);
255: if (log.isDebugEnabled())
256: log
257: .debug("doWrite " + off + " " + this Time + " "
258: + len);
259: msg.appendBytes(chunk.getBytes(), chunk.getOffset() + off,
260: this Time);
261: off += this Time;
262: ep.setType(JkHandler.HANDLE_SEND_PACKET);
263: ep.getSource().send(msg, ep);
264: }
265: return 0;
266: }
267:
268: public int doRead(ByteChunk chunk, Request req) throws IOException {
269: Response res = req.getResponse();
270: if (log.isDebugEnabled())
271: log.debug("doRead " + chunk.getBytes() + " "
272: + chunk.getOffset() + " " + chunk.getLength());
273: MsgContext ep = (MsgContext) res.getNote(epNote);
274:
275: JkInputStream jkIS = (JkInputStream) ep
276: .getNote(inputStreamNote);
277: // return jkIS.read( chunk.getBytes(), chunk.getOffset(), chunk.getLength());
278: return jkIS.doRead(chunk);
279: }
280:
281: // -------------------- Jk handler implementation --------------------
282: // Jk Handler mehod
283: public int invoke(Msg msg, MsgContext ep) throws IOException {
284: if (logTime.isDebugEnabled())
285: ep.setLong(MsgContext.TIMER_PRE_REQUEST, System
286: .currentTimeMillis());
287:
288: org.apache.coyote.Request req = (org.apache.coyote.Request) ep
289: .getRequest();
290: org.apache.coyote.Response res = req.getResponse();
291: res.setHook(this );
292:
293: if (log.isDebugEnabled())
294: log.debug("Invoke " + req + " " + res + " "
295: + req.requestURI().toString());
296:
297: res.setOutputBuffer(this );
298: req.setInputBuffer(this );
299:
300: if (ep.getNote(headersMsgNote) == null) {
301: Msg msg2 = new MsgAjp();
302: ep.setNote(headersMsgNote, msg2);
303: }
304:
305: res.setNote(epNote, ep);
306: ep.setStatus(JK_STATUS_HEAD);
307: RequestInfo rp = req.getRequestProcessor();
308: rp.setStage(Constants.STAGE_SERVICE);
309: try {
310: adapter.service(req, res);
311: } catch (Exception ex) {
312: log.info("Error servicing request " + req, ex);
313: }
314: if (ep.getStatus() != JK_STATUS_CLOSED) {
315: res.finish();
316: }
317:
318: ep.setStatus(JK_STATUS_NEW);
319:
320: req.recycle();
321: req.updateCounters();
322: res.recycle();
323: rp.setStage(Constants.STAGE_KEEPALIVE);
324: return OK;
325: }
326:
327: private void appendHead(org.apache.coyote.Response res)
328: throws IOException {
329: if (log.isDebugEnabled())
330: log.debug("COMMIT sending headers " + res + " "
331: + res.getMimeHeaders());
332:
333: C2BConverter c2b = (C2BConverter) res.getNote(utfC2bNote);
334: if (c2b == null) {
335: if (System.getSecurityManager() != null) {
336: try {
337: c2b = (C2BConverter) AccessController
338: .doPrivileged(new PrivilegedExceptionAction() {
339: public Object run() throws IOException {
340: return new C2BConverter(
341: "iso-8859-1");
342: }
343: });
344: } catch (PrivilegedActionException pae) {
345: Exception ex = pae.getException();
346: if (ex instanceof IOException)
347: throw (IOException) ex;
348: }
349: } else {
350: c2b = new C2BConverter("iso-8859-1");
351: }
352: res.setNote(utfC2bNote, c2b);
353: }
354:
355: MsgContext ep = (MsgContext) res.getNote(epNote);
356: MsgAjp msg = (MsgAjp) ep.getNote(headersMsgNote);
357: msg.reset();
358: msg.appendByte(HandlerRequest.JK_AJP13_SEND_HEADERS);
359: msg.appendInt(res.getStatus());
360:
361: MessageBytes mb = (MessageBytes) ep
362: .getNote(tmpMessageBytesNote);
363: if (mb == null) {
364: mb = new MessageBytes();
365: ep.setNote(tmpMessageBytesNote, mb);
366: }
367: String message = res.getMessage();
368: if (message == null) {
369: if (System.getSecurityManager() != null) {
370: message = (String) AccessController
371: .doPrivileged(new StatusLinePrivilegedAction(
372: res.getStatus()));
373: } else {
374: message = HttpMessages.getMessage(res.getStatus());
375: }
376: } else {
377: message = message.replace('\n', ' ').replace('\r', ' ');
378: }
379: mb.setString(message);
380: c2b.convert(mb);
381: msg.appendBytes(mb);
382:
383: // XXX add headers
384:
385: MimeHeaders headers = res.getMimeHeaders();
386: String contentType = res.getContentType();
387: if (contentType != null) {
388: headers.setValue("Content-Type").setString(contentType);
389: }
390: String contentLanguage = res.getContentLanguage();
391: if (contentLanguage != null) {
392: headers.setValue("Content-Language").setString(
393: contentLanguage);
394: }
395: int contentLength = res.getContentLength();
396: if (contentLength >= 0) {
397: headers.setValue("Content-Length").setInt(contentLength);
398: }
399: int numHeaders = headers.size();
400: msg.appendInt(numHeaders);
401: for (int i = 0; i < numHeaders; i++) {
402: MessageBytes hN = headers.getName(i);
403: // no header to sc conversion - there's little benefit
404: // on this direction
405: c2b.convert(hN);
406: msg.appendBytes(hN);
407:
408: MessageBytes hV = headers.getValue(i);
409: c2b.convert(hV);
410: msg.appendBytes(hV);
411: }
412: ep.setType(JkHandler.HANDLE_SEND_PACKET);
413: ep.getSource().send(msg, ep);
414: }
415:
416: // -------------------- Coyote Action implementation --------------------
417:
418: public void action(ActionCode actionCode, Object param) {
419: try {
420: if (actionCode == ActionCode.ACTION_COMMIT) {
421: if (log.isDebugEnabled())
422: log.debug("COMMIT ");
423: org.apache.coyote.Response res = (org.apache.coyote.Response) param;
424:
425: if (res.isCommitted()) {
426: if (log.isInfoEnabled())
427: log.info("Response already commited ");
428: } else {
429: appendHead(res);
430: }
431: } else if (actionCode == ActionCode.ACTION_RESET) {
432: if (log.isDebugEnabled())
433: log.debug("RESET ");
434:
435: } else if (actionCode == ActionCode.ACTION_CLIENT_FLUSH) {
436: if (log.isDebugEnabled())
437: log.debug("CLIENT_FLUSH ");
438: org.apache.coyote.Response res = (org.apache.coyote.Response) param;
439: MsgContext ep = (MsgContext) res.getNote(epNote);
440: ep.setType(JkHandler.HANDLE_FLUSH);
441: ep.getSource().flush(null, ep);
442:
443: } else if (actionCode == ActionCode.ACTION_CLOSE) {
444: if (log.isDebugEnabled())
445: log.debug("CLOSE ");
446:
447: org.apache.coyote.Response res = (org.apache.coyote.Response) param;
448: MsgContext ep = (MsgContext) res.getNote(epNote);
449: if (ep.getStatus() == JK_STATUS_CLOSED) {
450: // Double close - it may happen with forward
451: if (log.isDebugEnabled())
452: log.debug("Double CLOSE - forward ? "
453: + res.getRequest().requestURI());
454: return;
455: }
456:
457: if (!res.isCommitted())
458: this .action(ActionCode.ACTION_COMMIT, param);
459:
460: MsgAjp msg = (MsgAjp) ep.getNote(headersMsgNote);
461: msg.reset();
462: msg.appendByte(HandlerRequest.JK_AJP13_END_RESPONSE);
463: msg.appendByte(1);
464:
465: try {
466: ep.setType(JkHandler.HANDLE_SEND_PACKET);
467: ep.getSource().send(msg, ep);
468:
469: ep.setType(JkHandler.HANDLE_FLUSH);
470: ep.getSource().flush(msg, ep);
471: } catch (IOException iex) {
472: log.debug("Connection error ending request.", iex);
473: }
474: ep.setStatus(JK_STATUS_CLOSED);
475:
476: if (logTime.isDebugEnabled())
477: logTime(res.getRequest(), res);
478: } else if (actionCode == ActionCode.ACTION_REQ_SSL_ATTRIBUTE) {
479: org.apache.coyote.Request req = (org.apache.coyote.Request) param;
480:
481: // Extract SSL certificate information (if requested)
482: MessageBytes certString = (MessageBytes) req
483: .getNote(WorkerEnv.SSL_CERT_NOTE);
484: if (certString != null && !certString.isNull()) {
485: ByteChunk certData = certString.getByteChunk();
486: ByteArrayInputStream bais = new ByteArrayInputStream(
487: certData.getBytes(), certData.getStart(),
488: certData.getLength());
489:
490: // Fill the first element.
491: X509Certificate jsseCerts[] = null;
492: try {
493: CertificateFactory cf = CertificateFactory
494: .getInstance("X.509");
495: X509Certificate cert = (X509Certificate) cf
496: .generateCertificate(bais);
497: jsseCerts = new X509Certificate[1];
498: jsseCerts[0] = cert;
499: } catch (java.security.cert.CertificateException e) {
500: log.error("Certificate convertion failed", e);
501: return;
502: }
503:
504: req.setAttribute(SSLSupport.CERTIFICATE_KEY,
505: jsseCerts);
506: }
507:
508: } else if (actionCode == ActionCode.ACTION_REQ_HOST_ATTRIBUTE) {
509: org.apache.coyote.Request req = (org.apache.coyote.Request) param;
510:
511: // If remoteHost not set by JK, get it's name from it's remoteAddr
512: if (req.remoteHost().isNull())
513: req.remoteHost().setString(
514: InetAddress.getByName(
515: req.remoteAddr().toString())
516: .getHostName());
517:
518: // } else if( actionCode==ActionCode.ACTION_POST_REQUEST ) {
519:
520: } else if (actionCode == ActionCode.ACTION_ACK) {
521: if (log.isDebugEnabled())
522: log.debug("ACK ");
523: // What should we do here ? Who calls it ?
524: }
525: } catch (Exception ex) {
526: log.error("Error in action code ", ex);
527: }
528: }
529:
530: private void logTime(Request req, Response res) {
531: // called after the request
532: // org.apache.coyote.Request req=(org.apache.coyote.Request)param;
533: // Response res=req.getResponse();
534: MsgContext ep = (MsgContext) res.getNote(epNote);
535: String uri = req.requestURI().toString();
536: if (uri.indexOf(".gif") > 0)
537: return;
538:
539: ep.setLong(MsgContext.TIMER_POST_REQUEST, System
540: .currentTimeMillis());
541: long t1 = ep.getLong(MsgContext.TIMER_PRE_REQUEST)
542: - ep.getLong(MsgContext.TIMER_RECEIVED);
543: long t2 = ep.getLong(MsgContext.TIMER_POST_REQUEST)
544: - ep.getLong(MsgContext.TIMER_PRE_REQUEST);
545:
546: logTime.debug("Time pre=" + t1 + "/ service=" + t2 + " "
547: + res.getContentLength() + " " + uri);
548: }
549:
550: public ObjectName preRegister(MBeanServer server, ObjectName oname)
551: throws Exception {
552: // override - we must be registered as "container"
553: this .name = "container";
554: return super.preRegister(server, oname);
555: }
556: }
|