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.ajp;
018:
019: import java.io.ByteArrayInputStream;
020: import java.io.IOException;
021: import java.security.cert.CertificateFactory;
022: import java.security.cert.X509Certificate;
023:
024: import org.apache.tomcat.util.buf.ByteChunk;
025: import org.apache.tomcat.util.buf.MessageBytes;
026: import org.apache.tomcat.util.http.BaseRequest;
027: import org.apache.tomcat.util.http.HttpMessages;
028: import org.apache.tomcat.util.http.MimeHeaders;
029:
030: /**
031: * Handle messages related with basic request information.
032: *
033: * This object can handle the following incoming messages:
034: * - "FORWARD_REQUEST" input message ( sent when a request is passed from the web server )
035: * - "PING REQUEST" input message (sent by the web server to determine if tomcat is not frozen,
036: * a PONG REPLY will be sent back)
037: * - "RECEIVE_BODY_CHUNK" input ( sent by container to pass more body, in response to GET_BODY_CHUNK )
038: *
039: * It can handle the following outgoing messages:
040: * - SEND_HEADERS. Pass the status code and headers.
041: * - SEND_BODY_CHUNK. Send a chunk of body
042: * - GET_BODY_CHUNK. Request a chunk of body data
043: * - END_RESPONSE. Notify the end of a request processing.
044: *
045: * @author Henri Gomez [hgomez@apache.org]
046: * @author Dan Milstein [danmil@shore.net]
047: * @author Keith Wannamaker [Keith@Wannamaker.org]
048: * @author Costin Manolache
049: */
050: public class RequestHandler extends AjpHandler {
051: // XXX Will move to a registry system.
052:
053: // Prefix codes for message types from server to container
054: public static final byte JK_AJP13_FORWARD_REQUEST = 2;
055: public static final byte JK_AJP13_SHUTDOWN = 7;
056: public static final byte JK_AJP13_PING_REQUEST = 8;
057: public static final byte JK_AJP13_CPING_REQUEST = 10;
058:
059: // Prefix codes for message types from container to server
060: public static final byte JK_AJP13_SEND_BODY_CHUNK = 3;
061: public static final byte JK_AJP13_SEND_HEADERS = 4;
062: public static final byte JK_AJP13_END_RESPONSE = 5;
063: public static final byte JK_AJP13_GET_BODY_CHUNK = 6;
064: public static final byte JK_AJP13_CPONG_REPLY = 9;
065:
066: // Integer codes for common response header strings
067: public static final int SC_RESP_CONTENT_TYPE = 0xA001;
068: public static final int SC_RESP_CONTENT_LANGUAGE = 0xA002;
069: public static final int SC_RESP_CONTENT_LENGTH = 0xA003;
070: public static final int SC_RESP_DATE = 0xA004;
071: public static final int SC_RESP_LAST_MODIFIED = 0xA005;
072: public static final int SC_RESP_LOCATION = 0xA006;
073: public static final int SC_RESP_SET_COOKIE = 0xA007;
074: public static final int SC_RESP_SET_COOKIE2 = 0xA008;
075: public static final int SC_RESP_SERVLET_ENGINE = 0xA009;
076: public static final int SC_RESP_STATUS = 0xA00A;
077: public static final int SC_RESP_WWW_AUTHENTICATE = 0xA00B;
078:
079: // Integer codes for common (optional) request attribute names
080: public static final byte SC_A_CONTEXT = 1; // XXX Unused
081: public static final byte SC_A_SERVLET_PATH = 2; // XXX Unused
082: public static final byte SC_A_REMOTE_USER = 3;
083: public static final byte SC_A_AUTH_TYPE = 4;
084: public static final byte SC_A_QUERY_STRING = 5;
085: public static final byte SC_A_JVM_ROUTE = 6;
086: public static final byte SC_A_SSL_CERT = 7;
087: public static final byte SC_A_SSL_CIPHER = 8;
088: public static final byte SC_A_SSL_SESSION = 9;
089: public static final byte SC_A_SSL_KEY_SIZE = 11; // ajp14 originally, now in ajp13 with jk 1.2/2.0
090: public static final byte SC_A_SECRET = 12;
091: public static final byte SC_A_STORED_METHOD = 13;
092:
093: // Used for attributes which are not in the list above
094: public static final byte SC_A_REQ_ATTRIBUTE = 10;
095:
096: // Terminates list of attributes
097: public static final byte SC_A_ARE_DONE = (byte) 0xFF;
098:
099: // Translates integer codes to names of HTTP methods
100: public static final String[] methodTransArray = { "OPTIONS", "GET",
101: "HEAD", "POST", "PUT", "DELETE", "TRACE", "PROPFIND",
102: "PROPPATCH", "MKCOL", "COPY", "MOVE", "LOCK", "UNLOCK",
103: "ACL", "REPORT", "VERSION-CONTROL", "CHECKIN", "CHECKOUT",
104: "UNCHECKOUT", "SEARCH", "MKWORKSPACE", "UPDATE", "LABEL",
105: "MERGE", "BASELINE-CONTROL", "MKACTIVITY" };
106: public static final int SC_M_JK_STORED = (byte) 0xFF;
107:
108: // id's for common request headers
109: public static final int SC_REQ_ACCEPT = 1;
110: public static final int SC_REQ_ACCEPT_CHARSET = 2;
111: public static final int SC_REQ_ACCEPT_ENCODING = 3;
112: public static final int SC_REQ_ACCEPT_LANGUAGE = 4;
113: public static final int SC_REQ_AUTHORIZATION = 5;
114: public static final int SC_REQ_CONNECTION = 6;
115: public static final int SC_REQ_CONTENT_TYPE = 7;
116: public static final int SC_REQ_CONTENT_LENGTH = 8;
117: public static final int SC_REQ_COOKIE = 9;
118: public static final int SC_REQ_COOKIE2 = 10;
119: public static final int SC_REQ_HOST = 11;
120: public static final int SC_REQ_PRAGMA = 12;
121: public static final int SC_REQ_REFERER = 13;
122: public static final int SC_REQ_USER_AGENT = 14;
123:
124: // Translates integer codes to request header names
125: public static final String[] headerTransArray = { "accept",
126: "accept-charset", "accept-encoding", "accept-language",
127: "authorization", "connection", "content-type",
128: "content-length", "cookie", "cookie2", "host", "pragma",
129: "referer", "user-agent" };
130:
131: public RequestHandler() {
132: }
133:
134: public void init(Ajp13 ajp14) {
135: // register incoming message handlers
136: ajp14.registerMessageType(JK_AJP13_FORWARD_REQUEST,
137: "JK_AJP13_FORWARD_REQUEST", this , null); // 2
138: // register outgoing messages handler
139: ajp14.registerMessageType(JK_AJP13_SEND_BODY_CHUNK, // 3
140: "JK_AJP13_SEND_BODY_CHUNK", this , null);
141: ajp14.registerMessageType(JK_AJP13_SEND_HEADERS, // 4
142: "JK_AJP13_SEND_HEADERS", this , null);
143: ajp14.registerMessageType(JK_AJP13_END_RESPONSE, // 5
144: "JK_AJP13_END_RESPONSE", this , null);
145: ajp14.registerMessageType(JK_AJP13_GET_BODY_CHUNK, // 6
146: "JK_AJP13_GET_BODY_CHUNK", this , null);
147: ajp14.registerMessageType(JK_AJP13_CPING_REQUEST,
148: "JK_AJP13_PING_REQUEST", this , null); // 10
149: ajp14.registerMessageType(JK_AJP13_CPONG_REPLY,
150: "JK_AJP13_PONG_REPLY", this , null); // 9
151: }
152:
153: /**
154: * Send a CPONG REPLY to web server to its CPING request
155: *
156: * @param ch the Ajp13 channel
157: * @param outBuf the Ajp13Packet output packet to use
158: */
159: public int sendCPong(Ajp13 ch, Ajp13Packet outBuf) {
160: outBuf.reset();
161: outBuf.appendByte(JK_AJP13_CPONG_REPLY);
162:
163: try {
164: ch.send(outBuf);
165: } catch (IOException ioe) {
166: log("can't send pong reply");
167: }
168:
169: return (999); // success but no need to process farther
170: }
171:
172: // -------------------- Incoming message --------------------
173: public int handleAjpMessage(int type, Ajp13 channel,
174: Ajp13Packet ajp, BaseRequest req) throws IOException {
175: switch (type) {
176: case RequestHandler.JK_AJP13_FORWARD_REQUEST:
177: return decodeRequest(channel, channel.hBuf, req);
178:
179: default:
180: return UNKNOWN;
181: }
182: }
183:
184: /**
185: * Parse a FORWARD_REQUEST packet from the web server and store its
186: * properties in the passed-in request object.
187: *
188: * @param req An empty (newly-recycled) request object.
189: * @param msg Holds the packet which has just been sent by the web
190: * server, with its read position just past the packet header (which in
191: * this case includes the prefix code for FORWARD_REQUEST).
192: *
193: * @return 200 in case of a successful decoduing, 500 in case of error.
194: */
195: protected int decodeRequest(Ajp13 ch, Ajp13Packet msg,
196: BaseRequest req) throws IOException {
197:
198: if (debug > 0) {
199: log("decodeRequest()");
200: }
201:
202: // XXX Awful return values
203:
204: boolean isSSL = false;
205:
206: // Translate the HTTP method code to a String.
207: byte methodCode = msg.getByte();
208: if (methodCode != SC_M_JK_STORED)
209: req.method().setString(
210: methodTransArray[(int) methodCode - 1]);
211:
212: msg.getMessageBytes(req.protocol());
213: msg.getMessageBytes(req.requestURI());
214:
215: msg.getMessageBytes(req.remoteAddr());
216: msg.getMessageBytes(req.remoteHost());
217: msg.getMessageBytes(req.serverName());
218: req.setServerPort(msg.getInt());
219:
220: isSSL = msg.getBool();
221:
222: // Decode headers
223: MimeHeaders headers = req.headers();
224: int hCount = msg.getInt();
225: for (int i = 0; i < hCount; i++) {
226: String hName = null;
227:
228: // Header names are encoded as either an integer code starting
229: // with 0xA0, or as a normal string (in which case the first
230: // two bytes are the length).
231: int isc = msg.peekInt();
232: int hId = isc & 0xFF;
233:
234: MessageBytes vMB = null;
235: isc &= 0xFF00;
236: if (0xA000 == isc) {
237: //
238: // header name is encoded as an int
239: //
240: msg.getInt(); // To advance the read position
241: hName = headerTransArray[hId - 1];
242: vMB = headers.addValue(hName);
243: msg.getMessageBytes(vMB);
244:
245: if (hId == SC_REQ_CONTENT_LENGTH) {
246: // just read content-length header
247: int contentLength = (vMB == null) ? -1 : vMB
248: .getInt();
249: req.setContentLength(contentLength);
250: } else if (hId == SC_REQ_CONTENT_TYPE) {
251: // just read content-type header
252: ByteChunk bchunk = vMB.getByteChunk();
253: req.contentType().setBytes(bchunk.getBytes(),
254: bchunk.getOffset(), bchunk.getLength());
255: } else if (hId == SC_REQ_AUTHORIZATION) {
256: ByteChunk bchunk = vMB.getByteChunk();
257: req.authorization().setBytes(bchunk.getBytes(),
258: bchunk.getOffset(), bchunk.getLength());
259: }
260: } else {
261: //
262: // header name is a string
263: //
264: // XXX Not very elegant
265: vMB = msg.addHeader(headers);
266: if (vMB == null) {
267: return 500; // wrong packet
268: }
269: msg.getMessageBytes(vMB);
270: }
271: }
272:
273: byte attributeCode;
274: for (attributeCode = msg.getByte(); attributeCode != SC_A_ARE_DONE; attributeCode = msg
275: .getByte()) {
276: switch (attributeCode) {
277: case SC_A_CONTEXT:
278: break;
279:
280: case SC_A_SERVLET_PATH:
281: break;
282:
283: case SC_A_REMOTE_USER:
284: msg.getMessageBytes(req.remoteUser());
285: break;
286:
287: case SC_A_AUTH_TYPE:
288: msg.getMessageBytes(req.authType());
289: break;
290:
291: case SC_A_QUERY_STRING:
292: msg.getMessageBytes(req.queryString());
293: break;
294:
295: case SC_A_JVM_ROUTE:
296: msg.getMessageBytes(req.jvmRoute());
297: break;
298:
299: case SC_A_SSL_CERT:
300: isSSL = true;
301: // Transform the string into certificate.
302: String certString = msg.getString();
303: byte[] certData = certString.getBytes();
304: ByteArrayInputStream bais = new ByteArrayInputStream(
305: certData);
306:
307: // Fill the first element.
308: X509Certificate jsseCerts[] = null;
309: try {
310: CertificateFactory cf = CertificateFactory
311: .getInstance("X.509");
312: X509Certificate cert = (X509Certificate) cf
313: .generateCertificate(bais);
314: jsseCerts = new X509Certificate[1];
315: jsseCerts[0] = cert;
316: } catch (java.security.cert.CertificateException e) {
317: log("Certificate convertion failed" + e);
318: }
319:
320: req.setAttribute(
321: "javax.servlet.request.X509Certificate",
322: jsseCerts);
323: break;
324:
325: case SC_A_SSL_CIPHER:
326: isSSL = true;
327: req.setAttribute("javax.servlet.request.cipher_suite",
328: msg.getString());
329: break;
330:
331: case SC_A_SECRET:
332: // If a request has a secret attribute, set it on
333: // channel - it'll be visible to the caller ( Interceptor,
334: // Connector ) and it can check it against its settings before
335: // trusting us.
336: String secret = msg.getString();
337: if (secret != null) {
338: ch.setSecret(secret);
339: }
340: break;
341:
342: case SC_A_SSL_SESSION:
343: isSSL = true;
344: req.setAttribute("javax.servlet.request.ssl_session",
345: msg.getString());
346: break;
347:
348: case SC_A_REQ_ATTRIBUTE:
349: req.setAttribute(msg.getString(), msg.getString());
350: break;
351:
352: case SC_A_SSL_KEY_SIZE: // Ajp13 !
353: isSSL = true;
354: req.setAttribute("javax.servlet.request.key_size",
355: Integer.toString(msg.getInt()));
356: break;
357:
358: case SC_A_STORED_METHOD:
359: req.method().setString(msg.getString());
360: break;
361:
362: default:
363: // Ignore. Assume a single-string value - we shouldn't
364: // allow anything else.
365: msg.getString();
366: break;
367: }
368: }
369:
370: if (isSSL) {
371: req.setScheme(req.SCHEME_HTTPS);
372: req.setSecure(true);
373: }
374:
375: // set cookies on request now that we have all headers
376: req.cookies().setHeaders(req.headers());
377:
378: // Check to see if there should be a body packet coming along
379: // immediately after
380: if (req.getContentLength() > 0) {
381:
382: /* Read present data */
383: int err = ch.receive(ch.inBuf);
384: if (err < 0) {
385: return 500;
386: }
387:
388: ch.blen = ch.inBuf.peekInt();
389: ch.pos = 0;
390: ch.inBuf.getBytes(ch.bodyBuff);
391: }
392:
393: if (debug > 5) {
394: log(req.toString());
395: }
396:
397: return 200; // Success
398: }
399:
400: // -------------------- Messages from container to server ------------------
401:
402: /**
403: * Send the HTTP headers back to the web server and on to the browser.
404: *
405: * @param status The HTTP status code to send.
406: * @param statusMessage the HTTP status message to send.
407: * @param headers The set of all headers.
408: */
409: public void sendHeaders(Ajp13 ch, Ajp13Packet outBuf, int status,
410: String statusMessage, MimeHeaders headers)
411: throws IOException {
412: // XXX if more headers that MAX_SIZE, send 2 packets!
413: if (statusMessage == null)
414: statusMessage = HttpMessages.getMessage(status);
415: outBuf.reset();
416: outBuf.appendByte(JK_AJP13_SEND_HEADERS);
417: outBuf.appendInt(status);
418:
419: outBuf.appendString(statusMessage);
420:
421: int numHeaders = headers.size();
422: outBuf.appendInt(numHeaders);
423:
424: for (int i = 0; i < numHeaders; i++) {
425: String headerName = headers.getName(i).toString();
426: int sc = headerNameToSc(headerName);
427: if (-1 != sc) {
428: outBuf.appendInt(sc);
429: } else {
430: outBuf.appendString(headerName);
431: }
432: outBuf.appendString(headers.getValue(i).toString());
433: }
434:
435: outBuf.end();
436: ch.send(outBuf);
437: }
438:
439: /**
440: * Signal the web server that the servlet has finished handling this
441: * request, and that the connection can be reused.
442: */
443: public void finish(Ajp13 ch, Ajp13Packet outBuf) throws IOException {
444: if (debug > 0)
445: log("finish()");
446:
447: outBuf.reset();
448: outBuf.appendByte(JK_AJP13_END_RESPONSE);
449: outBuf.appendBool(true); // Reuse this connection
450: outBuf.end();
451: ch.send(outBuf);
452: }
453:
454: /**
455: * Send a chunk of response body data to the web server and on to the
456: * browser.
457: *
458: * @param b A huffer of bytes to send.
459: * @param off The offset into the buffer from which to start sending.
460: * @param len The number of bytes to send.
461: */
462: public void doWrite(Ajp13 ch, Ajp13Packet outBuf, byte b[],
463: int off, int len) throws IOException {
464: if (debug > 0)
465: log("doWrite(byte[], " + off + ", " + len + ")");
466:
467: int sent = 0;
468: while (sent < len) {
469: int to_send = len - sent;
470: to_send = to_send > Ajp13.MAX_SEND_SIZE ? Ajp13.MAX_SEND_SIZE
471: : to_send;
472:
473: outBuf.reset();
474: outBuf.appendByte(JK_AJP13_SEND_BODY_CHUNK);
475: outBuf.appendBytes(b, off + sent, to_send);
476: ch.send(outBuf);
477: sent += to_send;
478: }
479: }
480:
481: // -------------------- Utils --------------------
482:
483: /**
484: * Translate an HTTP response header name to an integer code if
485: * possible. Case is ignored.
486: *
487: * @param name The name of the response header to translate.
488: *
489: * @return The code for that header name, or -1 if no code exists.
490: */
491: protected int headerNameToSc(String name) {
492: switch (name.charAt(0)) {
493: case 'c':
494: case 'C':
495: if (name.equalsIgnoreCase("Content-Type")) {
496: return SC_RESP_CONTENT_TYPE;
497: } else if (name.equalsIgnoreCase("Content-Language")) {
498: return SC_RESP_CONTENT_LANGUAGE;
499: } else if (name.equalsIgnoreCase("Content-Length")) {
500: return SC_RESP_CONTENT_LENGTH;
501: }
502: break;
503:
504: case 'd':
505: case 'D':
506: if (name.equalsIgnoreCase("Date")) {
507: return SC_RESP_DATE;
508: }
509: break;
510:
511: case 'l':
512: case 'L':
513: if (name.equalsIgnoreCase("Last-Modified")) {
514: return SC_RESP_LAST_MODIFIED;
515: } else if (name.equalsIgnoreCase("Location")) {
516: return SC_RESP_LOCATION;
517: }
518: break;
519:
520: case 's':
521: case 'S':
522: if (name.equalsIgnoreCase("Set-Cookie")) {
523: return SC_RESP_SET_COOKIE;
524: } else if (name.equalsIgnoreCase("Set-Cookie2")) {
525: return SC_RESP_SET_COOKIE2;
526: }
527: break;
528:
529: case 'w':
530: case 'W':
531: if (name.equalsIgnoreCase("WWW-Authenticate")) {
532: return SC_RESP_WWW_AUTHENTICATE;
533: }
534: break;
535: }
536:
537: return -1;
538: }
539:
540: private int debug = 0;
541: private Logger logger = new Logger();
542:
543: public void setDebug(int debug) {
544: this .debug = debug;
545: }
546:
547: public void setLogger(Logger l) {
548: this .logger = l;
549: }
550:
551: void log(String s) {
552: logger.log("[RequestHandler] " + s);
553: }
554:
555: // ==================== Servlet Input Support =================
556: // XXX DEPRECATED
557:
558: public int available(Ajp13 ch) throws IOException {
559: if (debug > 0) {
560: log("available()");
561: }
562:
563: if (ch.pos >= ch.blen) {
564: if (!refillReadBuffer(ch)) {
565: return 0;
566: }
567: }
568: return ch.blen - ch.pos;
569: }
570:
571: /**
572: * Return the next byte of request body data (to a servlet).
573: *
574: * @see Request#doRead
575: */
576: public int doRead(Ajp13 ch) throws IOException {
577: if (debug > 0) {
578: log("doRead()");
579: }
580:
581: if (ch.pos >= ch.blen) {
582: if (!refillReadBuffer(ch)) {
583: return -1;
584: }
585: }
586: return ch.bodyBuff[ch.pos++] & 0xFF;
587: }
588:
589: /**
590: * Store a chunk of request data into the passed-in byte buffer.
591: *
592: * @param b A buffer to fill with data from the request.
593: * @param off The offset in the buffer at which to start filling.
594: * @param len The number of bytes to copy into the buffer.
595: *
596: * @return The number of bytes actually copied into the buffer, or -1
597: * if the end of the stream has been reached.
598: *
599: * @see Request#doRead
600: */
601: public int doRead(Ajp13 ch, byte[] b, int off, int len)
602: throws IOException {
603: if (debug > 0) {
604: log("doRead(byte[], int, int)");
605: }
606:
607: if (ch.pos >= ch.blen) {
608: if (!refillReadBuffer(ch)) {
609: return -1;
610: }
611: }
612:
613: if (ch.pos + len <= ch.blen) { // Fear the off by one error
614: // Sanity check b.length > off + len?
615: System.arraycopy(ch.bodyBuff, ch.pos, b, off, len);
616: ch.pos += len;
617: return len;
618: }
619:
620: // Not enough data (blen < pos + len)
621: int toCopy = len;
622: while (toCopy > 0) {
623: int bytesRemaining = ch.blen - ch.pos;
624: if (bytesRemaining < 0)
625: bytesRemaining = 0;
626: int c = bytesRemaining < toCopy ? bytesRemaining : toCopy;
627:
628: System.arraycopy(ch.bodyBuff, ch.pos, b, off, c);
629:
630: toCopy -= c;
631:
632: off += c;
633: ch.pos += c; // In case we exactly consume the buffer
634:
635: if (toCopy > 0)
636: if (!refillReadBuffer(ch)) { // Resets blen and pos
637: break;
638: }
639: }
640:
641: return len - toCopy;
642: }
643:
644: /**
645: * Get more request body data from the web server and store it in the
646: * internal buffer.
647: *
648: * @return true if there is more data, false if not.
649: */
650: public boolean refillReadBuffer(Ajp13 ch) throws IOException {
651: if (debug > 0) {
652: log("refillReadBuffer()");
653: }
654:
655: // If the server returns an empty packet, assume that that end of
656: // the stream has been reached (yuck -- fix protocol??).
657:
658: // Why not use outBuf??
659: ch.inBuf.reset();
660: ch.inBuf.appendByte(JK_AJP13_GET_BODY_CHUNK);
661: ch.inBuf.appendInt(Ajp13.MAX_READ_SIZE);
662: ch.send(ch.inBuf);
663:
664: int err = ch.receive(ch.inBuf);
665: if (err < 0) {
666: throw new IOException();
667: }
668:
669: // check for empty packet, which means end of stream
670: if (ch.inBuf.getLen() == 0) {
671: if (debug > 0) {
672: log("refillReadBuffer(): "
673: + "received empty packet -> end of stream");
674: }
675: ch.blen = 0;
676: ch.pos = 0;
677: return false;
678: }
679:
680: ch.blen = ch.inBuf.peekInt();
681: ch.pos = 0;
682: ch.inBuf.getBytes(ch.bodyBuff);
683:
684: return (ch.blen > 0);
685: }
686:
687: // ==================== Servlet Output Support =================
688:
689: /**
690: */
691: public void beginSendHeaders(Ajp13 ch, Ajp13Packet outBuf,
692: int status, String statusMessage, int numHeaders)
693: throws IOException {
694:
695: if (debug > 0) {
696: log("sendHeaders()");
697: }
698:
699: // XXX if more headers that MAX_SIZE, send 2 packets!
700:
701: outBuf.reset();
702: outBuf.appendByte(JK_AJP13_SEND_HEADERS);
703:
704: if (debug > 0) {
705: log("status is: " + status + "(" + statusMessage + ")");
706: }
707:
708: // set status code and message
709: outBuf.appendInt(status);
710: outBuf.appendString(statusMessage);
711:
712: // write the number of headers...
713: outBuf.appendInt(numHeaders);
714: }
715:
716: public void sendHeader(Ajp13Packet outBuf, String name, String value)
717: throws IOException {
718: int sc = headerNameToSc(name);
719: if (-1 != sc) {
720: outBuf.appendInt(sc);
721: } else {
722: outBuf.appendString(name);
723: }
724: outBuf.appendString(value);
725: }
726:
727: public void endSendHeaders(Ajp13 ch, Ajp13Packet outBuf)
728: throws IOException {
729: outBuf.end();
730: ch.send(outBuf);
731: }
732:
733: /**
734: * Send the HTTP headers back to the web server and on to the browser.
735: *
736: * @param status The HTTP status code to send.
737: * @param headers The set of all headers.
738: */
739: public void sendHeaders(Ajp13 ch, Ajp13Packet outBuf, int status,
740: MimeHeaders headers) throws IOException {
741: sendHeaders(ch, outBuf, status,
742: HttpMessages.getMessage(status), headers);
743: }
744:
745: }
|