0001: // Client.java
0002: // $Id: Client.java,v 1.80 2004/08/30 16:04:44 ylafon Exp $
0003: // (c) COPYRIGHT MIT and INRIA, 1996.
0004: // Please first read the full copyright statement in file COPYRIGHT.html
0005:
0006: package org.w3c.jigsaw.http;
0007:
0008: import java.io.DataOutputStream;
0009: import java.io.InputStream;
0010: import java.io.IOException;
0011: import java.io.PipedInputStream;
0012: import java.io.InterruptedIOException;
0013:
0014: import java.net.InetAddress;
0015: import java.net.URL;
0016:
0017: import org.w3c.www.http.HTTP;
0018: import org.w3c.www.http.HttpParserException;
0019:
0020: import org.w3c.www.mime.MimeParser;
0021: import org.w3c.www.mime.MimeParserException;
0022: import org.w3c.www.mime.MimeParserFactory;
0023:
0024: import org.w3c.tools.resources.ProtocolException;
0025: import org.w3c.tools.resources.ResourceException;
0026:
0027: import org.w3c.tools.timers.EventHandler;
0028:
0029: import org.w3c.jigsaw.servlet.JigsawHttpServletResponse;
0030: import org.w3c.jigsaw.servlet.ServletWrapper;
0031:
0032: /**
0033: * The request timeout event, to be delivered by the timer package.
0034: * Handling timers is expensive, and can be a bottleneck, this is the only
0035: * event Jigsaw will set during processing a request.
0036: * No other timeing events are used (ie closing a persistent connection is
0037: * not triggered by some timing, but rather by the load of the server).
0038: */
0039:
0040: class RequestTimeout {
0041: Client client = null;
0042:
0043: RequestTimeout(Client client) {
0044: this .client = client;
0045: }
0046: }
0047:
0048: /**
0049: * Client instances keep track of a specific connection with a browser.
0050: * This abstract class is responsible for handling an HTTP connection, as
0051: * described by an input and output stream, from right after the connection
0052: * is accepted, until the connection has to be shutdown. It provides
0053: * all the methods to run the HTTP dialog, and leave it to subclasses to
0054: * implement the accept connection and the persistent connections strategy.
0055: * <p>Customizing this class is done by subclassing it, and implementing
0056: * the abstract methods.
0057: * <p>For sample implementations, you can check the socket and mux sub
0058: * packages.
0059: * @see ClientFactory
0060: * @see org.w3c.jigsaw.http.socket.SocketClient
0061: * @see org.w3c.jigsaw.http.mux.MuxClient
0062: */
0063:
0064: public abstract class Client implements EventHandler {
0065: private static final boolean debuglog = false;
0066: private final static byte hexaTable[] = { (byte) '0', (byte) '1',
0067: (byte) '2', (byte) '3', (byte) '4', (byte) '5', (byte) '6',
0068: (byte) '7', (byte) '8', (byte) '9', (byte) 'A', (byte) 'B',
0069: (byte) 'C', (byte) 'D', (byte) 'E', (byte) 'F' };
0070: /**
0071: * The continue reply, if ever needed is created only once.
0072: */
0073: private Reply contreply = null;
0074: /**
0075: * The Mime factory instance we use to create requests.
0076: */
0077: private MimeParserFactory factory = null;
0078: /**
0079: * The uniq integer identifier for that client.
0080: */
0081: protected int identifier = -1;
0082: /**
0083: * The server context responsible for that client.
0084: */
0085: protected httpd server = null;
0086: /**
0087: * Is this client in debug mode ?
0088: */
0089: protected boolean debug = false;
0090: /**
0091: * The buffer used to emit copy data back to the client.
0092: */
0093: protected byte buffer[] = null;
0094: /**
0095: * Is this client currently <em>running</em>.
0096: * A client starts <em>running</em> when its <code>startConnection</code>
0097: * method is invoked, with the HTTP transport streams. It stops running
0098: * either because of an interruption, triggered by a call to
0099: * <code>interruptConnection</code> or by the <code>idleConnection</code>,
0100: * returning <strong>true</strong>, or because the connection has to be
0101: * closed.
0102: * <p>In all these cases, the <code>stopConnection</code> method is
0103: * invoked.
0104: */
0105: private boolean running = false;
0106: /**
0107: * When runnin, the HTTP major version used to discuss with that client.
0108: */
0109: private short major = -1;
0110: /**
0111: * WHen running, the HTTP minor version used to discuss with that client.
0112: */
0113: private short minor = -1;
0114: /**
0115: * When running, the input stream from which this client gets HTTP.
0116: */
0117: private InputStream input = null;
0118: /**
0119: * When running, the output stream to which this client emits HTTP.
0120: */
0121: private DataOutputStream output = null;
0122: /**
0123: * When processing a request, the handle to the pending timer.
0124: */
0125: private Object timer = null;
0126: /**
0127: * When running, the Mime parser instance to parse current input stream.
0128: */
0129: private MimeParser parser = null;
0130: /**
0131: * When running, the interrupt flag.
0132: */
0133: private boolean interrupted = false;
0134: /**
0135: * Number of requests handled within this client context.
0136: */
0137: protected int reqcount = 0;
0138:
0139: /**
0140: * flags to avoid multiple 100-Continue during multiple stages of a
0141: * request
0142: */
0143: protected boolean cont = false;
0144: /**
0145: * The number of bytes in the body of the previously handled request
0146: */
0147: protected long prev_body_count = 0;
0148: /**
0149: * HTTP lenient mode?
0150: */
0151: private boolean lenient = true;
0152:
0153: /**
0154: * the current URI
0155: */
0156: public URL currentURI = null;
0157: /**
0158: * timeout on IO?
0159: */
0160: private boolean timedout;
0161:
0162: /**
0163: * Sets this client next timer.
0164: * Timers are used only to limit the duration of a request processing.
0165: * Only one timer can be pending at any time for a given client,
0166: * setting some new timer if one is already pending will kill
0167: * the previous one.
0168: * @param ms The number of milliseconds after which the timer should
0169: * expire.
0170: * @param data The call data for the tevent timer handler.
0171: */
0172:
0173: private synchronized void setTimeout(int ms, Object data) {
0174: if (timer != null) {
0175: server.timer.recallTimer(timer);
0176: timer = null;
0177: }
0178: timer = server.timer.registerTimer(ms, this , data);
0179: }
0180:
0181: /**
0182: * Remove any pending timer.
0183: */
0184:
0185: private synchronized void removeTimeout() {
0186: if (timer != null)
0187: server.timer.recallTimer(timer);
0188: }
0189:
0190: /**
0191: * Handle timer events.
0192: * For the time being, timer events are only used
0193: * to detect an overrunning request, so this handler just kills the
0194: * correponding client.
0195: * @param data The timer closure.
0196: * @param time The absolute time at which the event was triggered.
0197: * @see org.w3c.tools.timers.EventManager
0198: * @see org.w3c.tools.timers.EventHandler
0199: */
0200:
0201: public synchronized void handleTimerEvent(Object data, long time) {
0202: timer = null;
0203: // This request has taken to long to fullfill, abort it
0204: Reply abort = new Reply(this );
0205: abort.setStatus(HTTP.REQUEST_TIMEOUT);
0206: interruptConnection(true);
0207: }
0208:
0209: /**
0210: * Terminate the connection that is currently handled.
0211: * This method closes the currently handled input and output stream,
0212: * removes any pending timers and cleanup the client state, so it is ready
0213: * to handle any new connection.
0214: * If the client is bound to a thread, the thread is throw an exception at
0215: */
0216:
0217: private synchronized void terminate() {
0218: if (!running)
0219: return;
0220: removeTimeout();
0221: try {
0222: if (output != null) {
0223: output.flush();
0224: output.close();
0225: }
0226: } catch (IOException ex) {
0227: }
0228: try {
0229: if (input != null)
0230: input.close();
0231: } catch (IOException ex) {
0232: }
0233: input = null;
0234: output = null;
0235: parser = null;
0236: major = -1;
0237: minor = -1;
0238: interrupted = false;
0239: running = false;
0240: }
0241:
0242: /**
0243: * Request has been processed into Reply, should we keep connection alive ?
0244: * Test wether we can keep the connection alive, after the given
0245: * reply has been emited.
0246: * @param request The request to examine.
0247: * @param reply Its computed reply.
0248: */
0249:
0250: protected boolean tryKeepConnection(Request request, Reply reply) {
0251: // The server doesn't want any keep connection
0252: if (!server.getClientKeepConnection())
0253: return false;
0254: if (!request.canKeepConnection()) {
0255: if (reply.tryKeepConnection()) {
0256: reply.addConnection("close");
0257: }
0258: return false;
0259: }
0260: return reply.tryKeepConnection();
0261: }
0262:
0263: /**
0264: * Read the next request from our current input stream.
0265: * @return a Request instance, or <strong>null</strong> if interrupted.
0266: * @exception IOException If some IO error occured.
0267: * @exception ClientException If either an IO error happened or bad
0268: * HTTP was received. In both cases the connection needs to be closed.
0269: */
0270:
0271: protected Request getNextRequest() throws ClientException,
0272: IOException {
0273: Request request = null;
0274: cont = false; // reinit the continue
0275: timedout = false;
0276: try {
0277: request = (Request) parser.parse(lenient);
0278: } catch (InterruptedIOException timex) {
0279: timedout = true;
0280: return null;
0281: } catch (IOException ex) {
0282: // The connection has probably been closed prematurely:
0283: return null;
0284: } catch (HttpParserException ex) {
0285: if (debug) {
0286: System.out.println("+++ " + this + " got exception:");
0287: ex.printStackTrace();
0288: }
0289: throw new ClientException(this , ex);
0290: } catch (MimeParserException ex) {
0291: if (debug) {
0292: System.out.println("+++ " + this + " got exception:");
0293: ex.printStackTrace();
0294: }
0295: throw new ClientException(this , ex);
0296: }
0297: if (debug)
0298: request.dump(System.out);
0299: return request;
0300: }
0301:
0302: /**
0303: * Run chunk encoding on the provided stream to emit reply's body.
0304: * @param is The reply's body that has to be chunk encoded.
0305: * @exception IOException If IO error occurs.
0306: */
0307:
0308: protected int chunkTransfer(InputStream is, Reply reply)
0309: throws IOException {
0310: byte zeroChunk[] = { ((byte) 48), ((byte) 13), ((byte) 10) };//0\r\n
0311: byte crlf[] = { ((byte) 13), ((byte) 10) };
0312: byte bheader[] = new byte[32];
0313: int blen = 0;
0314: int written = 0;
0315: int got = 0;
0316: int sgot;
0317: String header = null;
0318:
0319: try {
0320: // Emit the reply stream:
0321: while (got >= 0) {
0322: if (got == 0) {
0323: try {
0324: got = is.read(buffer);
0325: } catch (IOException ioex) {
0326: if (reply.hasState(ServletWrapper.RUNNER)
0327: && (is instanceof PipedInputStream)) {
0328: // here, the problem may be that multiple
0329: // threads are writing to the PipedOutputStream
0330: // and the IOError may exists
0331: if (!reply.hasState(ServletWrapper.ENDED)) {
0332: got = 0;
0333: continue;
0334: }
0335: }
0336: throw ioex;
0337: }
0338: continue;
0339: }
0340: // Emit a full chunk: header first, followed by the body
0341: // we dump the hexa size of the header backward
0342: sgot = got;
0343: blen = 3;
0344: bheader[30] = ((byte) 13); // \r
0345: bheader[31] = ((byte) 10); // \n
0346: while (sgot > 15) {
0347: bheader[32 - blen] = hexaTable[sgot % 16];
0348: sgot >>= 4;
0349: blen++;
0350: }
0351: bheader[32 - blen] = hexaTable[sgot];
0352: output.write(bheader, 32 - blen, blen);
0353: output.write(buffer, 0, got);
0354: output.write(crlf, 0, 2);
0355: output.flush();
0356: written += (blen + got);
0357: try {
0358: got = is.read(buffer);
0359: } catch (IOException ioex) {
0360: if (reply.hasState(ServletWrapper.RUNNER)
0361: && (is instanceof PipedInputStream)) {
0362: // here, the problem may be that multiple
0363: // threads are writing to the PipedOutputStream
0364: // and the IOError may exists
0365: if (!reply.hasState(ServletWrapper.ENDED)) {
0366: got = 0;
0367: continue;
0368: }
0369: }
0370: throw ioex;
0371: }
0372: }
0373: } catch (IOException ex) {
0374: // To cope with Java's exec bug
0375: // Anyway, if this really fails, the output will fail below too
0376: if (debug) {
0377: ex.printStackTrace();
0378: }
0379: }
0380: // Emit the 0 chunk:
0381: output.write(zeroChunk, 0, 3);
0382: // FIXME trailers should be sent here
0383: output.write(crlf, 0, 2);
0384: output.flush();
0385: return written + blen;
0386: }
0387:
0388: /**
0389: * Emit the given reply to the client.
0390: * @param reply The reply to be emited.
0391: * @return The number of body bytes emited, or <strong>-1</strong> if
0392: * no bytes needed to be emitted.
0393: * @exception IOException If some IO error occurs.
0394: */
0395:
0396: protected int emitReply(Reply reply) throws IOException {
0397: boolean chunkable = false;
0398: // Emit the reply if needed:
0399: if (reply.getStatus() == HTTP.DONE)
0400: return -1;
0401: InputStream is = reply.openStream();
0402: if (is == null) {
0403: if (debug)
0404: reply.dump(System.out);
0405: reply.emit(output);
0406: return -1;
0407: } else {
0408: chunkable = reply.canChunkTransfer();
0409: if (debug)
0410: reply.dump(System.out);
0411: if (reply.getStatus() != HTTP.NOHEADER)
0412: reply.emit(output);
0413: }
0414: // No shuffler available, perform the job ourselves.
0415: if (buffer == null)
0416: buffer = new byte[getServer().getClientBufferSize()];
0417: // Check if we can chunk the reply back:
0418: int written = 0;
0419: try {
0420: if (chunkable) {
0421: written = chunkTransfer(is, reply);
0422: } else {
0423: int got = 0;
0424: while (got >= 0) {
0425: output.write(buffer, 0, got);
0426: written += got;
0427: try {
0428: got = is.read(buffer, 0, buffer.length);
0429: } catch (IOException ioex) {
0430: if (reply.hasState(ServletWrapper.RUNNER)
0431: && (is instanceof PipedInputStream)) {
0432: // here, the problem may be that multiple
0433: // threads are writing to the PipedOutputStream
0434: // and the IOError may exists
0435: if (!reply.hasState(ServletWrapper.ENDED)) {
0436: got = 0;
0437: continue;
0438: }
0439: }
0440: throw ioex;
0441: }
0442: }
0443: }
0444: } finally {
0445: is.close();
0446: }
0447: return written;
0448: }
0449:
0450: /**
0451: * Process a request.
0452: * This methods processs the request to the point that a reply is
0453: * available. This methods sets a timeout, to limit the duration of this
0454: * request processing.
0455: * @param request The request to process.
0456: * @exception ClientException If either the timeout expires or the entity
0457: * was unable to handle the request.
0458: */
0459:
0460: protected Reply processRequest(Request request)
0461: throws ClientException {
0462: Reply reply = null;
0463: setTimeout(server.getRequestTimeOut(), new RequestTimeout(this ));
0464: try {
0465: reply = (Reply) server.perform(request);
0466: } catch (ProtocolException ex) {
0467: if (debug) {
0468: System.out.println("+++ " + this + " got exception:");
0469: ex.printStackTrace();
0470: }
0471: if ((reply != null) && reply.hasStream()) {
0472: try {
0473: reply.openStream().close();
0474: } catch (Exception cex) {
0475: }
0476: }
0477: if (ex.hasReply()) {
0478: return (Reply) ex.getReply();
0479: } else {
0480: throw new ClientException(this , ex);
0481: }
0482: } catch (ResourceException ex2) {
0483: throw new ClientException(this , ex2);
0484: }
0485: if (reply == null) {
0486: String errmsg = "target resource emited a null Reply.";
0487: throw new ClientException(this , errmsg);
0488: }
0489: reqcount++;
0490: return reply;
0491: }
0492:
0493: /**
0494: * Start processing the given connection.
0495: * This is the entry point for sub classes, in order to make the client
0496: * start processing the HTTP protocol on the given input and output
0497: * streams.
0498: * <p>Before this method returns, both provided streams are <em>always
0499: * </em> closed, and the <code>stopConnection</code> method invoked.
0500: * @param in The input stream to receive HTTP requests.
0501: * @param out The output stream to send HTTP replies.
0502: * @return A boolean <strong>true</strong> if this method returns because
0503: * of an interruption, <strong>false</strong> otherwsie (ie the connection
0504: * was gracefully shutdown).
0505: * @exception ClientException If some severe error has occured and the
0506: * current connection needs to be terminated.
0507: */
0508:
0509: protected boolean startConnection(InputStream in,
0510: DataOutputStream out) throws ClientException {
0511: boolean keep = true;
0512: long tstart = 0;
0513: long tend = 0;
0514: Request request = null;
0515: Reply reply = null;
0516: int sent = 0;
0517: int processed = 0;
0518: ClientException err = null;
0519:
0520: this .input = in;
0521: this .output = out;
0522: this .parser = new MimeParser(input, factory);
0523: try {
0524: running = true;
0525: alive_loop: while ((!interrupted) && keep) {
0526: // Get the next available request, and mark client as used:
0527: try {
0528: // mark the stream, if some bytes are to be eaten
0529: if (prev_body_count > 0) {
0530: input.mark(2048);
0531: }
0532: if (processed == 0) {
0533: // Always run for the first request,
0534: // update client's infos
0535: if ((request = getNextRequest()) == null)
0536: break alive_loop;
0537: major = request.getMajorVersion();
0538: minor = request.getMinorVersion();
0539: } else {
0540: if (interrupted = idleConnection())
0541: break alive_loop;
0542: while ((request = getNextRequest()) == null) {
0543: if (timedout) {
0544: continue;
0545: } else {
0546: break alive_loop;
0547: }
0548: }
0549: // the gateway case, the major/minor _may_ change
0550: major = request.getMajorVersion();
0551: minor = request.getMinorVersion();
0552: usedConnection();
0553: }
0554: } catch (ClientException cex) {
0555: if (cex.ex != null
0556: && (cex.ex instanceof HttpParserException)) {
0557: HttpParserException hex = (HttpParserException) cex.ex;
0558: if (!hex.hasRequest()) {
0559: throw (cex);
0560: }
0561: request = (Request) hex.getRequest();
0562: if (processed == 0) {
0563: major = request.getMajorVersion();
0564: minor = request.getMinorVersion();
0565: }
0566: usedConnection();
0567: reply = (Reply) request.makeBadRequestReply();
0568: reply.setContentLength(0);
0569: reply.addConnection("close");
0570: // Inital keep alive check, emit the reply:
0571: keep = false;
0572: sent = emitReply(reply);
0573: // Semi-fake log entry
0574: log(request, reply, 0, 0);
0575: processed++;
0576: // a bad request is still a request...
0577: reqcount++;
0578: break alive_loop;
0579: } else if (cex.ex != null
0580: && (cex.ex instanceof MimeParserException)) {
0581: reply = new Reply(this );
0582: reply.setStatus(HTTP.BAD_REQUEST);
0583: reply.setContentLength(0);
0584: reply.addConnection("close");
0585: keep = false;
0586: sent = emitReply(reply);
0587: // Semi-fake log entry
0588: log(request, reply, 0, 0);
0589: processed++;
0590: // a bad request is still a request...
0591: reqcount++;
0592: break alive_loop;
0593: }
0594: throw (cex);
0595: } catch (Exception genex) {
0596: if (debug)
0597: genex.printStackTrace();
0598: // some bytes may have to be eaten
0599: // abomination, very ugly hack!
0600: if (genex.getMessage().startsWith("Bad request")
0601: && (prev_body_count > 0)) {
0602: // check if the error is due to a
0603: usedConnection();
0604: if (debug)
0605: System.out.println("Error after a body "
0606: + "request! (Skipping: "
0607: + prev_body_count + " bytes)");
0608: // now try to eat some data...
0609: input.reset();
0610: byte b[] = new byte[(int) prev_body_count];
0611: prev_body_count -= input.read(b);
0612: prev_body_count -= input.skip(prev_body_count);
0613: break alive_loop;
0614: }
0615: throw (genex);
0616: }
0617: // Some traces if required:
0618: if (debuglog)
0619: System.out.println(this + ": request "
0620: + request.getURL());
0621: // Process request, and time it:
0622: currentURI = request.getURL();
0623: tstart = System.currentTimeMillis();
0624: reply = processRequest(request);
0625: tend = System.currentTimeMillis();
0626: currentURI = null;
0627: // Inital keep alive check, emit the reply:
0628: if (keep)
0629: keep = tryKeepConnection(request, reply);
0630: sent = emitReply(reply);
0631: // Second keep alive check:
0632: if (reply.hasContentLength() && (sent >= 0)
0633: && (reply.getContentLength() != sent))
0634: keep = false;
0635: // Log and/or traces:
0636: if (debuglog)
0637: System.out.println(this + ", cl="
0638: + reply.getContentLength() + ", size="
0639: + sent);
0640: // will be -1 if there is no body
0641: prev_body_count = request.getContentLength();
0642: log(request, reply, sent, tend - tstart);
0643: processed++;
0644: // If we can keep alive, and if the client doesn't do
0645: // pipelining, be clever:
0646: // We should compare against 0 here, we use 2 because of a
0647: // well-known client bug, that emits an extra CRLF at the end
0648: // of forms POSTing
0649: // Note we're doing that very last, so that if the socket is
0650: // closed by peer, we really have *already* done everything
0651: if (keep /*&& (in.available() <= 2)*/)
0652: output.flush();
0653: // be clever again... in case of protocol switching,
0654: // we must free everything and let the other client use the
0655: // streams
0656: if (reply.getStatus() == HTTP.SWITCHING) {
0657: input = null;
0658: output = null;
0659: throw new Exception("Switching");
0660: }
0661: // hack for servlets
0662: if (request.hasState(JigsawHttpServletResponse.STREAM)) {
0663: try {
0664: PipedInputStream pis;
0665: pis = (PipedInputStream) request
0666: .getState(JigsawHttpServletResponse.STREAM);
0667: pis.close();
0668: } catch (ClassCastException ccex) {
0669: // do nothing
0670: } catch (IOException pisioex) {
0671: // fail silently also
0672: }
0673: }
0674: }
0675: } catch (IOException ex) {
0676: if (debug) {
0677: System.out.println("+++ " + this + " got IOException:");
0678: ex.printStackTrace();
0679: }
0680: // Close any stream pending
0681: try {
0682: InputStream i = null;
0683: if (reply != null) {
0684: if ((i = reply.openStream()) != null)
0685: i.close();
0686: }
0687: } catch (IOException ioex) {
0688: }
0689: err = new ClientException(this , ex);
0690: } catch (ClientException ex) {
0691: if (debug) {
0692: System.out.println("+++ " + this
0693: + " got ClientException:");
0694: ex.printStackTrace();
0695: }
0696: try {
0697: InputStream i = null;
0698: if (reply != null) {
0699: if ((i = reply.openStream()) != null)
0700: i.close();
0701: }
0702: } catch (IOException ioex) {
0703: }
0704: err = ex;
0705: } catch (NullPointerException nex) {
0706: if (debug) {
0707: System.out.println("+++ " + this + " got exception:");
0708: nex.printStackTrace();
0709: }
0710: if (currentURI != null) {
0711: err = new ClientException(this , nex, currentURI
0712: .toString());
0713: } else {
0714: err = new ClientException(this , nex);
0715: }
0716: } catch (Exception ex) {
0717: if (debug) {
0718: System.out.println("+++ " + this + " got exception:");
0719: ex.printStackTrace();
0720: }
0721: err = new ClientException(this , ex);
0722: } finally {
0723: currentURI = null;
0724: // Absorb incoming data to avoid RST TCP packets:
0725: if ((err == null) && (request != null)
0726: && (request.getContentLength() > 0)
0727: && (reply != null)
0728: && (reply.getStatus() != HTTP.SWITCHING)
0729: && (reply.getStatus() / 100 != 2)) {
0730: // The request may have failed...
0731: try {
0732: InputStream i = request.getInputStream();
0733: if (i != null) {
0734: while (i.available() > 0)
0735: i.read(buffer, 0, buffer.length);
0736: }
0737: } catch (Exception ex) {
0738: }
0739: }
0740: if ((request != null)
0741: && (request
0742: .hasState(JigsawHttpServletResponse.STREAM))) {
0743: try {
0744: PipedInputStream pis;
0745: pis = (PipedInputStream) request
0746: .getState(JigsawHttpServletResponse.STREAM);
0747: pis.close();
0748: } catch (ClassCastException ccex) {
0749: // do nothing
0750: } catch (IOException pisioex) {
0751: // fail silently also
0752: } catch (Exception ex) {
0753: // be sure not to cause any trouble :)
0754: }
0755: }
0756: terminate();
0757: if (reply == null || (reply.getStatus() != HTTP.SWITCHING)) {
0758: stopConnection();
0759: if (reply != null) {
0760: try {
0761: reply.openStream().close();
0762: } catch (Exception roex) {
0763: }
0764: ;
0765: }
0766: }
0767: if (err != null)
0768: throw err;
0769: }
0770: return interrupted;
0771: }
0772:
0773: /**
0774: * Interrupt the currently handled connection.
0775: * This method will make best effort to interrupt any thread currently
0776: * processing the connection.
0777: * @param now Make sure the thread is interrupted right now if
0778: * <strong>true</strong>, otherwise, just schedule an interruption
0779: * after the current request (if any) has been processed.
0780: */
0781:
0782: protected synchronized void interruptConnection(boolean now) {
0783: if (running) {
0784: interrupted = true;
0785: if (now)
0786: terminate();
0787: }
0788: }
0789:
0790: /**
0791: * Send a 100 HTTP continue message on the currently handled connection.
0792: * This method will take care of creating the appropriate HTTP
0793: * continue reply, and will emit that reply only if the spoken HTTP
0794: * version allows for it.
0795: * @exception IOException If some IO error occured.
0796: */
0797:
0798: public int sendContinue() throws IOException {
0799: if (cont)
0800: return -1;
0801: if ((major > 1) || ((major == 1) && (minor >= 1))) {
0802: if (contreply == null)
0803: contreply = new Reply(this , null, major, minor,
0804: HTTP.CONTINUE);
0805: int len = emitReply(contreply);
0806: output.flush();
0807: cont = true;
0808: return len;
0809: }
0810: return -1;
0811: }
0812:
0813: /**
0814: * Send a 100 HTTP continue message on the currently handled connection.
0815: * This method will take care of creating the appropriate HTTP
0816: * continue reply, and will emit that reply only if the spoken HTTP
0817: * version allows for it.
0818: * @exception IOException If some IO error occured.
0819: */
0820:
0821: public int sendContinue(Reply contReply) throws IOException {
0822: if (contReply == null) {
0823: return sendContinue();
0824: }
0825: if ((major > 1) || ((major == 1) && (minor >= 1))) {
0826: int len = emitReply(contReply);
0827: output.flush();
0828: cont = true;
0829: return len;
0830: }
0831: return -1;
0832: }
0833:
0834: /**
0835: * Get this client identifier.
0836: * @return An integer identifying uniquely this client's context.
0837: */
0838:
0839: public final int getIdentifier() {
0840: return identifier;
0841: }
0842:
0843: /**
0844: * Is this client currently <em>running</em> for a connection.
0845: * @return A boolean, <strong>true</strong> if it is,
0846: * <strong>false</strong> otherwise.
0847: */
0848:
0849: public final synchronized boolean isRunning() {
0850: return running;
0851: }
0852:
0853: /**
0854: * Get the HTTP major version number spoken on the current connection.
0855: * @return The HTTP major version number, or <strong>-1</strong> if that
0856: * client is not running.
0857: */
0858:
0859: public final short getMajorVersion() {
0860: return major;
0861: }
0862:
0863: /**
0864: * Get the HTTP minor version number spoken on the current connection.
0865: * @return The HTTP minor version number, or <strong>-1</strong> if
0866: * that client is not running.
0867: */
0868:
0869: public final short getMinorVersion() {
0870: return minor;
0871: }
0872:
0873: /**
0874: * Does this client has an interrupt pending ?
0875: * @return A boolean, <strong>true</strong> if an interrupt is pending,
0876: * <strong>false</strong> otherwise.
0877: */
0878:
0879: public final boolean isInterrupted() {
0880: return interrupted;
0881: }
0882:
0883: /**
0884: * Get the total number of requests handled within this client context.
0885: * @return An integer giving the number of requests handled.
0886: */
0887:
0888: public final int getRequestCount() {
0889: return reqcount;
0890: }
0891:
0892: /**
0893: * Get the server context responsible for this client context.
0894: * @return An httpd instance.
0895: */
0896:
0897: public final httpd getServer() {
0898: return server;
0899: }
0900:
0901: /**
0902: * Emit an error message on behalf of that client.
0903: * @param msg The error message to output.
0904: */
0905:
0906: public final void error(String msg) {
0907: server.errlog(this , msg);
0908: }
0909:
0910: /**
0911: * Emit a trace on behalf of the given client.
0912: * @param msg The trace to output.
0913: */
0914:
0915: public final void trace(String msg) {
0916: server.trace(this , msg);
0917: }
0918:
0919: /**
0920: * Log the given HTTP transaction.
0921: * @param request The request that has been processed.
0922: * @param reply The generated reply.
0923: * @param nbytes Number of content bytes sent along with the reply.
0924: * @param duration The processing time for that request in milliseconds.
0925: */
0926:
0927: public void log(Request request, Reply reply, int nbytes,
0928: long duration) {
0929: server.log(this , request, reply, nbytes, duration);
0930: }
0931:
0932: /**
0933: * Get this client input stream.
0934: * @return An instance of InputStream, or <strong>null</strong> if the
0935: * client is not handling any connection at that time.
0936: */
0937:
0938: public InputStream getInputStream() {
0939: return input;
0940: }
0941:
0942: /**
0943: * Get this client output stream.
0944: * @return An instance of OutputStream, or <strong>null</strong> if the
0945: * client is not handling any connection at that time.
0946: */
0947:
0948: public DataOutputStream getOutputStream() {
0949: return output;
0950: }
0951:
0952: /**
0953: * Get the IP address of the host that runs the client described by this
0954: * context.
0955: * @return An InetAddress instance, or <strong>null</strong> if that client
0956: * is not handling any connection at that given time.
0957: */
0958:
0959: abstract public InetAddress getInetAddress();
0960:
0961: /**
0962: * Client callback - The client is about to block, getting next request.
0963: * This method is triggered by the client instance itself, before
0964: * reading next request from the input stream provided at
0965: * <code>startConnection</code> time.
0966: * @return A boolean, if <strong>true</strong>, the client will consider
0967: * itself interrupted, and terminate the connection it is current handling.
0968: */
0969:
0970: abstract protected boolean idleConnection();
0971:
0972: /**
0973: * Client callback - A full request has been received on input stream.
0974: * This method is called by the client itself, before starting processing
0975: * the newly received request. The purpose of this callback is typically
0976: * to mark that client <em>buzy</em>.
0977: */
0978:
0979: abstract protected void usedConnection();
0980:
0981: /**
0982: * Client callback - The current connection has been terminated.
0983: * This client has finished processing the connection provided
0984: * at <code>startConnection</code> time, it is now stopped.
0985: */
0986:
0987: abstract protected void stopConnection();
0988:
0989: /**
0990: * Get the thread powering that client, if any.
0991: * This method is called to kill the client (by interrupting the thread
0992: * used to run it).
0993: * @return A Thread instance, or <strong>null</strong>.
0994: */
0995:
0996: abstract protected Thread getThread();
0997:
0998: /**
0999: * Initialize this client.
1000: * It is up to this method to initialize:
1001: * <dl>
1002: * <dt>parser<dd>The MimeParser to be used to parse incomminf requests.
1003: * </dl>
1004: * @param server The server responsible for that client.
1005: * @param factory The factory that created this client.
1006: * @param identifier The uniq identifier for this client.
1007: */
1008:
1009: protected void initialize(httpd server, int identifier) {
1010: this.server = server;
1011: this.lenient = server.isLenient();
1012: this.identifier = identifier;
1013: this.debug = server.getClientDebug();
1014: this.factory = server.getMimeClientFactory(this);
1015: }
1016:
1017: }
|