0001: // HttpBasicServer.java
0002: // $Id: HttpBasicServer.java,v 1.73 2005/06/03 09:13:35 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.www.protocol.http;
0007:
0008: import java.util.Date;
0009:
0010: import java.io.BufferedInputStream;
0011: import java.io.DataOutputStream;
0012: import java.io.IOException;
0013: import java.io.InputStream;
0014: import java.io.OutputStream;
0015: import java.io.PrintStream;
0016:
0017: import java.net.InetAddress;
0018: import java.net.UnknownHostException;
0019:
0020: import org.w3c.www.mime.MimeHeaderHolder;
0021: import org.w3c.www.mime.MimeParser;
0022: import org.w3c.www.mime.MimeParserException;
0023: import org.w3c.www.mime.MimeParserFactory;
0024:
0025: import org.w3c.www.http.HTTP;
0026: import org.w3c.www.http.HttpEntityMessage;
0027: import org.w3c.www.http.HttpMessage;
0028: import org.w3c.www.http.HttpReplyMessage;
0029: import org.w3c.www.http.HttpRequestMessage;
0030: import org.w3c.www.http.HttpStreamObserver;
0031: import org.w3c.www.http.ChunkedOutputStream;
0032:
0033: /**
0034: * The basic server class, to run requests.
0035: * A server instance (ie an object conforming to the <code>HttpServer</code>
0036: * interface is resposnsible for running all requests to a given host.
0037: * <p>To do so, it manages a connnnection pool (ie a set of available
0038: * connections) which it negotiates with the global HTTP manager. It keeps
0039: * track of the connections it creates in order to serialize requests
0040: * to the target server (when possible). In order to avoid deadlock due
0041: * to implicit ordering of requests, a server that manages persistent
0042: * (ie > 1.0) connections will always be able to open at least two
0043: * connections to the same target.
0044: * <p>Connections are kept track of by the server instance, which maintains
0045: * at all point in time a list of <em>idle</em> connections. The way this is
0046: * done may seem quite tricky, but beleive me, it's the best way I have
0047: * found to handle it (there is here a typical deadlock situation - the same
0048: * which is found in org.w3c.jigsaw.http.Client
0049: * - due to the fact that the server instances need to be locked in order
0050: * to change the connection pool, and at the same time each connection of
0051: * the pool might have to notify the pool of some events. This can easily
0052: * produce a deadlock...This situation is avoided here, by having the server
0053: * locked only when appropriate...
0054: */
0055:
0056: public class HttpBasicServer extends HttpServer {
0057: private static final String STATE_CONNECTION = "org.w3c.www.protocol.http.HttpBasicServer.connection";
0058:
0059: private static final String PROTOCOL = "http";
0060: private static final boolean debug = false;
0061:
0062: /**
0063: * Request mode - Full HTTP/1.1 compliant mode.
0064: */
0065: protected final static int RQ_HTTP11 = 1;
0066: /**
0067: * Request mode - Full two stage HTTP/1.1 compliant mode.
0068: */
0069: protected final static int RQ_HTTP11_TS = 2;
0070: /**
0071: * Request mode - HTTP/1.0 with no keep-alive support.
0072: */
0073: protected final static int RQ_HTTP10 = 3;
0074: /**
0075: * Request mode - HTTP/1.0 with keep-alive support.
0076: */
0077: protected final static int RQ_HTTP10_KA = 4;
0078: /**
0079: * Request mode - Unknown target server.
0080: */
0081: protected final static int RQ_UNKNOWN = 5;
0082:
0083: /**
0084: * Our central HTTP manager.
0085: */
0086: protected HttpManager manager = null;
0087: /**
0088: * The host name of the server we handle.
0089: */
0090: protected String host = null;
0091: /**
0092: * The port number of the server we handle.
0093: */
0094: protected int port = -1;
0095: /**
0096: * The timeout on the socket
0097: */
0098: protected int timeout = 300000;
0099: /**
0100: * The connectiontimeout for the socket
0101: */
0102: protected int conn_timeout = 3000;
0103:
0104: // Informations pertaining to the server:
0105: boolean contacted = false;
0106: short major = -1;
0107: short minor = -1;
0108: boolean keepalive = false;
0109:
0110: InetAddress addrs[] = null;
0111: int addrptr = 0;
0112: Date lookupLimit = null;
0113:
0114: /**
0115: * set the inet addresses of this host, and timestamp it
0116: * to avoid caching IPs forever
0117: * @param hostAddrs, an array of InetAddress
0118: */
0119: protected void setHostAddr(InetAddress hostAddrs[]) {
0120: this .addrs = hostAddrs;
0121: // reset the pointer in case the number of ip diminished
0122: addrptr = 0;
0123: // FIXME now defaults to 300s (5mn) but needs to be a parameter
0124: lookupLimit = new Date(System.currentTimeMillis() + 300 * 1000);
0125: }
0126:
0127: /**
0128: * check the validity of the host address
0129: * if invalid, it will update the InetAddress associated with this host
0130: */
0131: protected void updateHostAddr() throws HttpException {
0132: Date now = new Date();
0133: if ((lookupLimit == null) || lookupLimit.before(now)) {
0134: // Get this server IP addreses:
0135: try {
0136: InetAddress hostAddrs[];
0137: // Is the given string a valid IP address ?
0138: int hostlen = host.length();
0139: boolean isstring = true;
0140: for (int i = 0; i < hostlen; i++) {
0141: char c = host.charAt(i);
0142: if (isstring = !(((c <= '9') && (c >= '0')) || (c == '.'))) {
0143: break;
0144: }
0145: }
0146: if (isstring) {
0147: // String host name, get all IP addresses:
0148: hostAddrs = InetAddress.getAllByName(host);
0149: } else {
0150: // Numeric IP address, convert to InetAddress:
0151: hostAddrs = new InetAddress[1];
0152: hostAddrs[0] = InetAddress.getByName(host);
0153: }
0154: if (hostAddrs != null) {
0155: setHostAddr(hostAddrs);
0156: }
0157: } catch (UnknownHostException ex) {
0158: String msg = ("The host name [" + host
0159: + "] couldn't be resolved. " + "Details: \""
0160: + ex.getMessage() + "\"");
0161: HttpException hex = new HttpException(ex, msg);
0162: state.ex = hex;
0163: state.state = HttpServerState.ERROR;
0164: throw hex;
0165: }
0166: }
0167: }
0168:
0169: /**
0170: * HttpServer implementation - Get this servers' protocol.
0171: * @return A String encoding the protocol used to dialog with the target
0172: * server.
0173: */
0174:
0175: public String getProtocol() {
0176: return PROTOCOL;
0177: }
0178:
0179: /**
0180: * HttpServer implementation - Get this server's major version number.
0181: * @return The server's major number version, or <strong>-1</strong>
0182: * if still unknown.
0183: */
0184:
0185: public short getMajorVersion() {
0186: return major;
0187: }
0188:
0189: /**
0190: * HttpServer implementation - Get this server's minor version number.
0191: * @return The server's minor number version, or <strong>-1</strong>
0192: * if still unknown.
0193: */
0194:
0195: public short getMinorVersion() {
0196: return minor;
0197: }
0198:
0199: /**
0200: * Set the timeout for the next connections
0201: * @param timeout The timeout in milliseconds
0202: */
0203:
0204: public synchronized void setTimeout(int timeout) {
0205: this .timeout = timeout;
0206: }
0207:
0208: /**
0209: * Set the connection timeout for the next connections
0210: * @param timeout The timeout in milliseconds
0211: */
0212:
0213: public synchronized void setConnTimeout(int conn_timeout) {
0214: this .conn_timeout = conn_timeout;
0215: }
0216:
0217: /**
0218: * Connections management - Allocate a new connection for this server.
0219: * The connection is bound to the next available IP address, so that
0220: * we are able to round-robin on them. If one of the DNS advertized
0221: * IP address fails, we just try the next one, until one of them
0222: * succeed or all of them fail.
0223: * @return A freshly allocated connection, inserted in the idle connection
0224: * list.
0225: * @exception IOException If the server is unreachable through all of
0226: * Its IP addresses.
0227: */
0228:
0229: protected int connid = 0;
0230:
0231: protected HttpBasicConnection allocateConnection()
0232: throws IOException {
0233: // Create the new connection, try until all ip addrs have been checked
0234: HttpBasicConnection conn = null;
0235: try {
0236: updateHostAddr();
0237: } catch (HttpException hex) {
0238: // unable to update, will continue with the old version
0239: // as it may be a temporary DNS problem
0240: if (addrs == null) {
0241: throw new IOException(hex.getMessage());
0242: }
0243: }
0244: for (int loop = 0; loop < addrs.length; loop++) {
0245: InetAddress addr = null;
0246: int i = addrptr;
0247: do {
0248: if ((addr = addrs[i]) != null)
0249: break;
0250: i = (i + 1) % addrs.length;
0251: } while (i != addrptr);
0252: addrptr = (addrptr + 1) % addrs.length;
0253: if (addr == null) {
0254: throw new IOException("Host " + host
0255: + " resolved to a null" + " InetAddr");
0256: }
0257: try {
0258: int l_connid;
0259: synchronized (this ) {
0260: l_connid = connid++;
0261: }
0262: conn = new HttpBasicConnection(this , l_connid, addr,
0263: port, timeout, conn_timeout, manager
0264: .getReplyFactory());
0265: break;
0266: } catch (IOException ex) {
0267: ex.printStackTrace();
0268: }
0269: }
0270: // If successfull, register the connection as one of ours:
0271: if (conn == null)
0272: throw new IOException("Unable to connect to " + host);
0273: synchronized (manager) {
0274: state.incrConnectionCount();
0275: // state.allconns.add(conn); (used for debug)
0276: manager.notifyConnection(conn);
0277: }
0278: return conn;
0279: }
0280:
0281: /**
0282: * Connections management - Register a connection as being idle.
0283: * When a connection is created, or when it becomes idle again (after
0284: * having been used to handle a request), it has to be registered back to
0285: * the idle list of connection for the server. All connections
0286: * contained in this idle list are candidates for being elected to handle
0287: * some pending or incoming request to the host we manager.
0288: * @param conn The connection that is now idle.
0289: */
0290:
0291: public void registerConnection(HttpConnection conn) {
0292: synchronized (manager) {
0293: state.registerConnection(conn);
0294: manager.notifyIdle(conn);
0295: }
0296: }
0297:
0298: /**
0299: * Unregister a connection from the idle list.
0300: * Unregistering a connection means that the server shouldn't keep
0301: * track of it any more. This can happen in two situations:
0302: * <ul>
0303: * <li>The connection won't be reusable, so there is no point
0304: * for the server to try to keep track of it. In this case, the
0305: * connection is forgotten, and the caller will terminate it by invoking
0306: * the connection's input stream close method.
0307: * <li>The connection has successfully handle a connection, and the
0308: * connection is about to be reused. During the time of the request
0309: * processing, the server looses track of this connection, which will
0310: * register itself again when back to idle.
0311: * @param conn The connection to unregister from the idle list.
0312: */
0313:
0314: public synchronized void unregisterConnection(HttpConnection conn) {
0315: manager.notifyUse(conn);
0316: }
0317:
0318: public void deleteConnection(HttpConnection conn) {
0319: synchronized (manager) {
0320: state.decrConnectionCount();
0321: // state.allconns.remove(conn); (used for debug)
0322: state.unregisterConnection(conn);
0323: manager.deleteConnection(conn);
0324: }
0325: }
0326:
0327: /**
0328: * Connections management - Get an idle connection to run a request.
0329: * The server has been asked to run a new request, and it now wants
0330: * a connection to run it on. This method will try various ways of
0331: * aqcuiring a connection:
0332: * <ul>
0333: * <li>It will look for an idle connection.
0334: * <li>It will then try to negotiate with the HTTP manager the creation
0335: * of a new connection to the target server.
0336: * <li>If this fails too, it will just wait until a connection becomes
0337: * available.
0338: * </ul>
0339: * The connection returned is marked for use (ie it is unregistered from
0340: * the idle connection list), it is up to the caller to make sure that
0341: * if possible, the connection registers itself again to the idle list
0342: * after the processing is done.
0343: * <p>This method can return <strong>null</strong> to indicate to the
0344: * caller that it should try again, in the hope that the target
0345: * server has multiple (different) IP addresses.
0346: * @return A connection marked in use, and which should be marked as
0347: * idle after the processing it is use for is done, or <strong>null
0348: * </strong> if a fresh connection cannot be established.
0349: */
0350:
0351: protected HttpBasicConnection getConnection() throws IOException {
0352: int _waits = 0;
0353: while (_waits < 3) {
0354: // Ask for a spare connection first:
0355: HttpBasicConnection conn = null;
0356: while (true) {
0357: conn = (HttpBasicConnection) manager
0358: .getConnection(this );
0359: if (conn == null)
0360: break;
0361: try {
0362: if (conn.sock_m == null) {
0363: // otherwise handled by markUsed isAlive call
0364: conn.input.available();
0365: }
0366: } catch (IOException ex) {
0367: // mark that connection as dead
0368: // unregisterConnection(conn);
0369: conn.close();
0370: continue;
0371: }
0372: if (conn.markUsed()) {
0373: return conn;
0374: }
0375: }
0376: // Negotiate the creation of a new connection:
0377: if (manager.negotiateConnection(this )) {
0378: conn = allocateConnection();
0379: if (conn.markUsed()) {
0380: return conn;
0381: } else {
0382: // Failed to establish a fresh connection !
0383: return null;
0384: }
0385: }
0386: // Wait for a connection to become available:
0387: try {
0388: long _stime = System.currentTimeMillis();
0389: manager.waitForConnection(this );
0390: long _etime = System.currentTimeMillis();
0391: // if we waited enough... (it avoid bumping _waits if multiple
0392: // notify() are sent when closing a bunch of connections
0393: if (_etime - _stime > 20000) {
0394: if (debug) {
0395: System.err.println("wait " + _waits
0396: + " diff is " + (_etime - _stime));
0397: System.err.println("ManagerState: " + manager);
0398: }
0399: _waits++;
0400: }
0401: } catch (InterruptedException ex) {
0402: // interrupted, probably a timeout -> fail
0403: return null;
0404: }
0405: }
0406: return null;
0407: }
0408:
0409: /**
0410: * Display this server into a String.
0411: * @return A String based representation of the server object.
0412: */
0413:
0414: public String toString() {
0415: return host + ":" + port;
0416: }
0417:
0418: /**
0419: * A full round-trip has been run with the target server, update infos.
0420: * Each server instance maintains a set of informations to be reused
0421: * if needed when recontacting the server later. After a full round
0422: * trip has been performed with the server, it is time to update
0423: * the target server version number, and keeps-alive flag.
0424: * @param reply The first reply we got from this server.
0425: */
0426:
0427: protected synchronized void updateServerInfo(Reply reply) {
0428: if (contacted)
0429: return;
0430: major = reply.getMajorVersion();
0431: minor = reply.getMinorVersion();
0432: keepalive = reply.keepsAlive();
0433: contacted = true;
0434: if (debug)
0435: System.out.println("*** " + this + " major=" + major
0436: + ", minor=" + minor + ", ka=" + keepalive);
0437: }
0438:
0439: /**
0440: * HttpServer implementation - Initialize this server to its target.
0441: * @param manager The central HTTP manager.
0442: * @param state The manager's state for that server.
0443: * @param host The target server's host name.
0444: * @param port The target server's port number.
0445: * @param timeout The timeout for the connection handled by the server
0446: * @exception HttpException If the server host couldn't be resolved
0447: * to one or more IP addresses.
0448: */
0449:
0450: public void initialize(HttpManager manager, HttpServerState state,
0451: String host, int port, int timeout) throws HttpException {
0452: initialize(manager, state, host, port, timeout, conn_timeout);
0453: }
0454:
0455: /**
0456: * HttpServer implementation - Initialize this server to its target.
0457: * @param manager The central HTTP manager.
0458: * @param state The manager's state for that server.
0459: * @param host The target server's host name.
0460: * @param port The target server's port number.
0461: * @param timeout The timeout for the connection handled by the server
0462: * @param timeout The connection timeout in millisecond for the sockets
0463: * @exception HttpException If the server host couldn't be resolved
0464: * to one or more IP addresses.
0465: */
0466:
0467: public void initialize(HttpManager manager, HttpServerState state,
0468: String host, int port, int timeout, int conn_timeout)
0469: throws HttpException {
0470: this .manager = manager;
0471: this .state = state;
0472: this .host = host;
0473: this .port = port;
0474: this .timeout = timeout;
0475: this .conn_timeout = conn_timeout;
0476: // Get this server IP addreses:
0477: this .lookupLimit = null;
0478: updateHostAddr();
0479: this .state.state = HttpServerState.OK;
0480: }
0481:
0482: // FIXME doc
0483: protected void notifyObserver(RequestObserver obs, Request request,
0484: int code) {
0485: RequestEvent evt = new RequestEvent(this , request, code);
0486: obs.notifyProgress(evt);
0487: }
0488:
0489: protected void notifyObserver(RequestObserver obs, RequestEvent evt) {
0490: obs.notifyProgress(evt);
0491: }
0492:
0493: /**
0494: * Is this request a two stage request.
0495: * @return A boolean, <strong>true</strong> if the request is two
0496: * stage, <strong>false</strong> otherwise.
0497: */
0498:
0499: protected boolean isTwoStage_10(Request request) {
0500: return request.hasOutputStream();
0501: }
0502:
0503: /**
0504: * Is this request a two stage request.
0505: * @return A boolean, <strong>true</strong> if the request is two
0506: * stage, <strong>false</strong> otherwise.
0507: */
0508:
0509: protected boolean isTwoStage_11(Request request) {
0510: boolean hasOutput = request.hasOutputStream();
0511: if (hasOutput) {
0512: if (request.getExpect() != null) // don't test yet the 100-Continue
0513: return true;
0514: }
0515: return false;
0516: }
0517:
0518: /**
0519: * Get the current mode of running request for that server.
0520: * This method check our knowledge of the target server, and deduce
0521: * the mode in which the given request should be run.
0522: * @return An integer code, indicating the mode in which the request should
0523: * be run:
0524: * <dl>
0525: * <dt>RQ_HTTP11<dl>The request should be run as an HTTP/1.1 request.
0526: * <dt>RQ_HTTP10<dl>The request should be run as an HTTP/1.0 request.
0527: * <dt>RQ_HTTP10_KA<dl>HTTP/1.0 with keep-alive support.
0528: * <dt>RQ_UNKNOWN</dl>This is the first request, we don't know yet.
0529: * </dl>
0530: */
0531:
0532: protected int getRequestMode(Request request) {
0533: // Fast check for the server (we don't support HTTP/0.9):
0534: if ((!contacted) || (major < 1))
0535: return RQ_UNKNOWN;
0536: // Is this a 1.0 or 1.1 request ?
0537: if (minor < 1) {
0538: return keepalive ? RQ_HTTP10_KA : RQ_HTTP10;
0539: } else {
0540: // Check for the method now:
0541: return isTwoStage_11(request) ? RQ_HTTP11_TS : RQ_HTTP11;
0542: }
0543: }
0544:
0545: /**
0546: * Run a fully HTTP/1.1 compliant request.
0547: * This request has no body, so we can do whatever we want with it,
0548: * and retry as many time as we want.
0549: * @param conn The connection to run the request on.
0550: * @param request The request to run.
0551: * @return A Reply instance, if success; <strong>null</strong> if the
0552: * request should be retried.
0553: * @exception IOException If some IO error occured.
0554: * @exception MimeParserException If some MIME parsing error occured.
0555: */
0556:
0557: protected Reply http11_run(HttpBasicConnection conn, Request request)
0558: throws IOException, MimeParserException {
0559: if (debug)
0560: System.out.println(conn + ": runs[11] " + request.getURL());
0561: RequestObserver o = request.getObserver();
0562: OutputStream os = conn.getOutputStream();
0563: MimeParser p = conn.getParser();
0564: Reply reply = null;
0565: boolean needsChunk = false;
0566:
0567: if (request.hasOutputStream()) {
0568: if (request.getContentLength() < 0) {
0569: needsChunk = true;
0570: String tes[] = request.getTransferEncoding();
0571: if (tes == null) {
0572: // FIXME intern this
0573: request.addTransferEncoding("chunked");
0574: } else {
0575: boolean addIt = true;
0576: for (int i = 0; !addIt && (i < tes.length); i++) {
0577: addIt = addIt && !tes[i].equals("chunked");
0578: }
0579: if (addIt) {
0580: // FIXME intern this
0581: request.addTransferEncoding("chunked");
0582: } else {
0583: if (os instanceof ChunkedOutputStream) {
0584: needsChunk = false;
0585: }
0586: }
0587: }
0588: }
0589: }
0590:
0591: try {
0592: request.emit(os, Request.EMIT_HEADERS);
0593: os.flush();
0594: if (o != null) {
0595: notifyObserver(o, new ConnectedEvent(this , request, os));
0596: }
0597: // We don't expect a "100-continue" so let's dump the body
0598: // If any...
0599: if (request.hasOutputStream()) {
0600: String exp = request.getExpect();
0601: if ((exp != null)
0602: && (exp.equalsIgnoreCase("100-continue"))) {
0603: if (o != null) {
0604: // client is expecting a 100 continue let's fake one
0605: notifyObserver(o, new ContinueEvent(this ,
0606: request));
0607: }
0608: }
0609: if (needsChunk) {
0610: DataOutputStream dos = new DataOutputStream(os);
0611: ChunkedOutputStream cos = new ChunkedOutputStream(
0612: dos);
0613: request.emit(cos, Request.EMIT_BODY);
0614: cos.flush();
0615: cos.close(false);
0616: request.emit(os, Request.EMIT_FOOTERS);
0617: } else {
0618: request.emit(os, Request.EMIT_BODY
0619: | Request.EMIT_FOOTERS);
0620: }
0621: os.flush();
0622: }
0623: reply = (Reply) p.parse(manager.isLenient());
0624: // "eat" the 100 replies FIXME it indicates an error in
0625: // the upstream server
0626: while ((reply.getStatus() / 100) == 1) {
0627: if (o != null) {
0628: notifyObserver(o, new ContinueEvent(this , request,
0629: reply));
0630: }
0631: reply = (Reply) p.parse(manager.isLenient());
0632: }
0633: } catch (MimeParserException ex) {
0634: return null;
0635: } catch (IOException ioex) {
0636: return null;
0637: // Ok, we should give it another chance:
0638: }
0639: if (reply != null) {
0640: conn.setCloseOnEOF(reply.hasConnection("close"));
0641: }
0642: return reply;
0643: }
0644:
0645: /**
0646: * Run a two stage HTTP/1.1 compliant request.
0647: * The neat thing about this sort of request is that as they support
0648: * <strong>100</strong> status codes, we <em>know</em> when we have
0649: * to retry them.
0650: * @param conn The connection to run the request on.
0651: * @param request The request to run.
0652: * @return A Reply instance, if success; <strong>null</strong> if the
0653: * request should be retried.
0654: * @exception IOException If some IO error occured.
0655: * @exception MimeParserException If some MIME parsing error occured.
0656: */
0657:
0658: protected Reply http11_ts_run(HttpBasicConnection conn,
0659: Request request) throws IOException, MimeParserException {
0660: if (debug)
0661: System.out.println(conn + ": runs[11ts] "
0662: + request.getURL());
0663: RequestObserver o = request.getObserver();
0664: OutputStream os = conn.getOutputStream();
0665: MimeParser p = conn.getParser();
0666: Reply reply = null;
0667: boolean needsChunk = false;
0668:
0669: if (request.getContentLength() < 0) {
0670: needsChunk = true;
0671: String tes[] = request.getTransferEncoding();
0672: if (tes == null) {
0673: request.addTransferEncoding("chunked"); // FIXME intern this
0674: } else {
0675: boolean addIt = true;
0676: for (int i = 0; !addIt && (i < tes.length); i++) {
0677: addIt = addIt && !tes[i].equals("chunked");
0678: }
0679: if (addIt) {
0680: // FIXME intern thi
0681: request.addTransferEncoding("chunked");
0682: } else {
0683: if (os instanceof ChunkedOutputStream) {
0684: needsChunk = false;
0685: }
0686: }
0687: }
0688: }
0689:
0690: try {
0691: request.emit(os, Request.EMIT_HEADERS);
0692: os.flush();
0693: if (o != null)
0694: notifyObserver(o, new ConnectedEvent(this , request, os));
0695: reply = (Reply) p.parse(manager.isLenient());
0696:
0697: boolean bodySent = false;
0698: while ((reply.getStatus() / 100) == 1
0699: || reply.getStatus() == HTTP.EXPECTATION_FAILED) {
0700: if (reply.getStatus() == HTTP.EXPECTATION_FAILED)
0701: return reply; // FIXME observer?
0702: reply = null;
0703: // Notify the observer if any:
0704: if (o != null) {
0705: notifyObserver(o, new ContinueEvent(this , request,
0706: reply));
0707: }
0708: // Finish the request normally:
0709: if (!bodySent) {
0710: bodySent = true;
0711: if (needsChunk) {
0712: DataOutputStream dos = new DataOutputStream(os);
0713: ChunkedOutputStream cos = new ChunkedOutputStream(
0714: dos);
0715: request.emit(cos, Request.EMIT_BODY);
0716: cos.flush();
0717: cos.close(false);
0718: request.emit(os, Request.EMIT_FOOTERS);
0719: } else {
0720: request.emit(os, Request.EMIT_BODY
0721: | Request.EMIT_FOOTERS);
0722: }
0723: os.flush();
0724: }
0725: reply = (Reply) p.parse(manager.isLenient());
0726: // if we don't have any observer, eat the 100 continue!
0727: while ((reply.getStatus() / 100) == 1) {
0728: if (o != null) {
0729: notifyObserver(o, new ContinueEvent(this ,
0730: request, reply));
0731: }
0732: reply = (Reply) p.parse(manager.isLenient());
0733: }
0734: }
0735: } catch (MimeParserException ex) {
0736: return null;
0737: } catch (IOException ieox) {
0738: return null;
0739: }
0740: if (reply != null) {
0741: conn.setCloseOnEOF(reply.hasConnection("close"));
0742: }
0743: return reply;
0744: }
0745:
0746: /**
0747: * Run an HTTP/1.0 request that has support for keep alive.
0748: * This kind of request are the worst one with regard to the retry
0749: * strategy we can adopt.
0750: * @param conn The connection to run the request on.
0751: * @param request The request to run.
0752: * @return A Reply instance, if success; <strong>null</strong> if the
0753: * request should be retried.
0754: * @exception IOException If some IO error occured.
0755: * @exception MimeParserException If some MIME parsing error occured.
0756: */
0757:
0758: protected Reply http10_ka_run(HttpBasicConnection conn,
0759: Request request) throws IOException, MimeParserException {
0760: if (debug)
0761: System.out.println(conn + ": runs[10_ka] "
0762: + request.getURL());
0763: RequestObserver o = request.getObserver();
0764: OutputStream os = conn.getOutputStream();
0765: MimeParser p = conn.getParser();
0766: Reply reply = null;
0767: String exp = request.getExpect();
0768:
0769: if ((exp != null) && (exp.equalsIgnoreCase("100-continue"))) {
0770: reply = request.makeReply(HTTP.EXPECTATION_FAILED);
0771: reply
0772: .setContent("100-continue is not supported by upstream "
0773: + "HTTP/1.0 Server");
0774: return reply;
0775: }
0776: if (request.getConnection() == null) {
0777: if (request.hasProxy()) {
0778: request.addProxyConnection("Keep-Alive");
0779: } else {
0780: request.addConnection("Keep-Alive");
0781: }
0782: }
0783: try {
0784: request.emit(os, Request.EMIT_HEADERS);
0785: os.flush();
0786: } catch (IOException ioex) {
0787: return null;
0788: }
0789: if (o != null)
0790: notifyObserver(o, new ConnectedEvent(this , request, os));
0791: // If this is a two stage method, emit a fake continue event:
0792: if (isTwoStage_10(request)) {
0793: if (o != null)
0794: notifyObserver(o, new ContinueEvent(this , request));
0795: request.emit(os, Request.EMIT_BODY | Request.EMIT_FOOTERS);
0796: os.flush();
0797: }
0798: try {
0799: reply = (Reply) p.parse(manager.isLenient());
0800: // if ever we switch to 1.1 in the meantime...
0801: while ((reply.getStatus() / 100) == 1) {
0802: if (o != null) {
0803: notifyObserver(o, new ContinueEvent(this , request,
0804: reply));
0805: }
0806: reply = null;
0807: reply = (Reply) p.parse(manager.isLenient());
0808: }
0809: } catch (IOException ex) {
0810: // at this point, we try to parse the reply if we have a body
0811: if (isTwoStage_10(request)) {
0812: try {
0813: reply = (Reply) p.parse(manager.isLenient());
0814: } catch (MimeParserException mex) {
0815: // a stale connection?
0816: return null;
0817: }
0818: try {
0819: request.getOutputStream().close();
0820: } catch (Exception cex) {
0821: }
0822: ;
0823: return reply;
0824: }
0825: return null;
0826: } catch (MimeParserException ex) {
0827: }
0828: if (reply != null) {
0829: conn.setCloseOnEOF(reply.hasConnection("close"));
0830: }
0831: return reply;
0832: }
0833:
0834: /**
0835: * Run a simple HTTP/1.0 request.
0836: * This server doesn't support keep-alive, we know the connection is
0837: * always fresh, we don't need to go into the retry buisness.
0838: * <p>That's <strong>cool</strong> !
0839: * @param conn The connection to run the request on.
0840: * @param request The request to run.
0841: * @return A Reply instance, if success; <strong>null</strong> if the
0842: * request should be retried.
0843: * @exception IOException If some IO error occured.
0844: * @exception MimeParserException If some MIME parsing error occured.
0845: */
0846:
0847: protected Reply http10_run(HttpBasicConnection conn, Request request)
0848: throws IOException, MimeParserException {
0849: if (debug)
0850: System.out.println(conn + ": runs[10] " + request.getURL());
0851: RequestObserver o = request.getObserver();
0852: OutputStream os = conn.getOutputStream();
0853: MimeParser p = conn.getParser();
0854: Reply reply = null;
0855: String exp = request.getExpect();
0856:
0857: if ((exp != null) && (exp.equalsIgnoreCase("100-continue"))) {
0858: reply = request.makeReply(HTTP.EXPECTATION_FAILED);
0859: reply
0860: .setContent("100-continue is not supported by upstream "
0861: + "HTTP/1.0 Server");
0862: return reply;
0863: }
0864: // Emit the request headers:
0865: request.emit(os, Request.EMIT_HEADERS);
0866: os.flush();
0867: if (o != null) {
0868: notifyObserver(o, new ConnectedEvent(this , request, os));
0869: // If this is a two stage method, emit a fake continue event:
0870: if (isTwoStage_10(request))
0871: notifyObserver(o, new ContinueEvent(this , request));
0872: }
0873: // Then emit the body:
0874: try {
0875: request.emit(os, Request.EMIT_BODY | Request.EMIT_FOOTERS);
0876: os.flush();
0877: } catch (IOException ex) {
0878: // at this point, we try to parse the reply if we have a body
0879: if (isTwoStage_10(request)) {
0880: try {
0881: reply = (Reply) p.parse(manager.isLenient());
0882: } catch (MimeParserException mex) {
0883: // perhaps nothing so...
0884: throw ex;
0885: }
0886: if (reply != null) {
0887: // close the request stream
0888: try {
0889: request.getOutputStream().close();
0890: } catch (Exception cex) {
0891: }
0892: ;
0893: return reply;
0894: }
0895: }
0896: // no reply throw the exception again
0897: throw ex;
0898: }
0899: // HTTP 100 status codes *are* forbidden here, unless the server
0900: // switches...
0901: try {
0902: reply = (Reply) p.parse(manager.isLenient());
0903: while ((reply.getStatus() / 100) == 1) {
0904: if (o != null) {
0905: notifyObserver(o, new ContinueEvent(this , request,
0906: reply));
0907: }
0908: reply = null;
0909: reply = (Reply) p.parse(manager.isLenient());
0910: }
0911: } catch (MimeParserException ex) {
0912: // be nice to lamers
0913: }
0914: conn.setCloseOnEOF(true);
0915: return reply;
0916: }
0917:
0918: /**
0919: * Run that request, we don't know what server we have at the other end.
0920: * We know the connection is fresh, we use the <code>http10_run</code>
0921: * and update the server infos by the end of processing.
0922: * @param conn The connection to run the request on.
0923: * @param request The request to run.
0924: * @return A Reply instance, if success; <strong>null</strong> if the
0925: * request should be retried.
0926: * @exception IOException If some IO error occured.
0927: * @exception MimeParserException If some MIME parsing error occured.
0928: */
0929:
0930: protected Reply http_unknown(HttpBasicConnection conn,
0931: Request request) throws IOException, MimeParserException {
0932: boolean eventfaked = false;
0933: if (debug)
0934: System.out.println(conn + ": runs[unknown] "
0935: + request.getURL());
0936: // Update the request with server specific information:
0937: if (request.getConnection() == null) {
0938: if (request.hasProxy()) {
0939: request.addProxyConnection("Keep-Alive");
0940: } else {
0941: request.addConnection("Keep-Alive");
0942: }
0943: }
0944: // FIXME neeeds to handle a first request using a chunked body
0945: // or without body. we need to chack if the server is 1.1
0946: // then issue the request if it's ok. (expect 100 continue with
0947: // a timeout?
0948: // Emit the request:
0949: RequestObserver o = request.getObserver();
0950: OutputStream os = conn.getOutputStream();
0951: MimeParser p = conn.getParser();
0952: Reply reply = null;
0953: // Emit the request headers:
0954: request.emit(os, Request.EMIT_HEADERS);
0955: if (o != null) {
0956: notifyObserver(o, new ConnectedEvent(this , request, os));
0957: // If this is a two stage method, try to be nice:
0958: if (isTwoStage_10(request)) {
0959: // Always emit a fake continue event:
0960: eventfaked = true;
0961: notifyObserver(o, new ContinueEvent(this , request));
0962: }
0963: }
0964: // Then emit the body:
0965: try {
0966: request.emit(os, Request.EMIT_BODY | Request.EMIT_FOOTERS);
0967: os.flush();
0968: } catch (IOException ex) {
0969: // at this point, we try to parse the reply if we have a body
0970: if (isTwoStage_10(request)) {
0971: try {
0972: reply = (Reply) p.parse(manager.isLenient());
0973: } catch (MimeParserException mex) {
0974: // perhaps nothing so...
0975: throw ex;
0976: }
0977: if (reply != null) {
0978: // close the request stream
0979: try {
0980: request.getOutputStream().close();
0981: } catch (Exception cex) {
0982: }
0983: ;
0984: return reply;
0985: }
0986: }
0987: // no reply throw the exception again
0988: throw ex;
0989: }
0990: try {
0991: reply = (Reply) p.parse(manager.isLenient());
0992: while ((reply.getStatus() / 100) == 1) {
0993: // If we already faked an event, skip that one:
0994: if (eventfaked) {
0995: eventfaked = false;
0996: continue;
0997: }
0998: // Notify the observer, if any:
0999: if (o != null)
1000: notifyObserver(o, new ContinueEvent(this , request,
1001: reply));
1002: // Get next reply:
1003: reply = (Reply) p.parse(manager.isLenient());
1004: }
1005: // Now, we know about that server, update infos:
1006: if (reply != null) {
1007: updateServerInfo(reply);
1008: }
1009: } catch (MimeParserException ex) {
1010: // be nice to lamers
1011: }
1012: if (reply != null) {
1013: if ((major == 1) && ((minor == 1) || keepalive)) {
1014: conn.setCloseOnEOF(false);
1015: } else {
1016: conn.setCloseOnEOF(true);
1017: }
1018: }
1019: return reply;
1020: }
1021:
1022: /**
1023: * Exceute given request on given conection, according to server.
1024: * @param conn The connection to use to run that request.
1025: * @param request The request to execute.
1026: * @exception IOException If some IO error occurs, or if the
1027: * request is interrupted.
1028: * @exception MimeParserException If taregt server doesn't conform to
1029: * the HTTP specification.
1030: */
1031:
1032: protected Reply doRequest(HttpBasicConnection conn, Request request)
1033: throws IOException, MimeParserException {
1034: // Check for an interrupted request ?
1035: if (request.isInterrupted())
1036: throw new IOException("Interrupted Request");
1037: // Mark that request as being processed by that connection:
1038: request.setState(STATE_CONNECTION, conn);
1039: // Process the request:
1040: switch (getRequestMode(request)) {
1041: case RQ_HTTP11:
1042: return http11_run(conn, request);
1043: case RQ_HTTP11_TS:
1044: return http11_ts_run(conn, request);
1045: case RQ_HTTP10_KA:
1046: return http10_ka_run(conn, request);
1047: case RQ_HTTP10:
1048: return http10_run(conn, request);
1049: case RQ_UNKNOWN:
1050: return http_unknown(conn, request);
1051: default:
1052: throw new RuntimeException("Implementation bug.");
1053: }
1054: // not reached
1055: }
1056:
1057: /**
1058: * Interrupt given request, that was launched by ourself.
1059: * @param request The request to interrupt.
1060: */
1061:
1062: protected void interruptRequest(Request request) {
1063: HttpBasicConnection c = null;
1064: c = (HttpBasicConnection) request.getState(STATE_CONNECTION);
1065: if (c == null) {
1066: // That request has terminated, or has not started
1067: ;
1068: } else {
1069: // Just kill the connection, to trigger some IO exception:
1070: c.markIdle(true);
1071: }
1072: }
1073:
1074: /**
1075: * Run the given request in synchronous mode.
1076: * @param request The request to run.
1077: * @return A Reply instance, containing the reply headers, and the
1078: * optional reply entity, to be read by the calling thread.
1079: * @exception HttpException If the request processing failed.
1080: */
1081:
1082: public Reply runRequest(Request request) throws HttpException {
1083: RequestObserver o = request.getObserver();
1084: if (debug) {
1085: System.out.println("Running request: " + request.getURL());
1086: request.dump(System.out);
1087: }
1088: // Acquire a connection to run the request:
1089: HttpBasicConnection conn = null;
1090: Reply reply = null;
1091: Exception lastex = null;
1092: try {
1093: // Notify that request is being queued:
1094: if (o != null)
1095: notifyObserver(o, request, RequestEvent.EVT_QUEUED);
1096: // Allocate a connection and run the request:
1097: int maxretry = 1;
1098: if (!request.hasOutputStream()) {
1099: String method = request.getMethod();
1100: if (method.length() <= 6) {
1101: method = method.intern();
1102: if ((method == HTTP.GET) || (method == HTTP.HEAD)
1103: || (method == HTTP.OPTIONS)
1104: || (method == HTTP.TRACE)) {
1105: maxretry = 3;
1106: }
1107: }
1108: }
1109: for (int i = 0; (reply == null) && (i < maxretry); i++) {
1110: // getConnection can return null, check documentation:
1111: if ((conn = getConnection()) == null) {
1112: continue;
1113: }
1114: // if we can't check a connection is OK, we need a hack
1115: // (ie if jdk < 1.4...
1116: if (maxretry == 1) {
1117: if (manager.keepbody) {
1118: if (request.hasOutputStream()) {
1119: BufferedInputStream bis;
1120: bis = new BufferedInputStream(request
1121: .getOutputStream());
1122: request.setOutputStream(bis);
1123: int cl = request.getContentLength();
1124: if (cl < 0)
1125: cl = 65536;
1126: bis.mark(cl);
1127: }
1128: } else {
1129: // get only a fresh connection
1130: while (conn.cached) {
1131: conn.markIdle(true);
1132: conn = getConnection();
1133: }
1134: }
1135: // idempotent method, so we won't redo it
1136: }
1137: // Some connection ready, try using it:
1138: try {
1139: if ((reply = doRequest(conn, request)) == null) {
1140: // if a cached connection fails, it may be due
1141: // to a socket in bad shape...
1142: // workaround for the missing socket.isAlive() method
1143: // Get rid of that useless connection:
1144: if (conn.cached) {
1145: // conn.markIdle(true);
1146: --i;
1147: if (request.hasOutputStream()
1148: && manager.keepbody) {
1149: request.getOutputStream().reset();
1150: }
1151: }
1152: conn.markIdle(true);
1153: }
1154: } catch (MimeParserException mex) {
1155: lastex = mex;
1156: conn.markIdle(true);
1157: throw (mex);
1158: } catch (Exception ioex) {
1159: lastex = ioex;
1160: if (debug)
1161: ioex.printStackTrace();
1162: // connection 1.0 failed so it was not a cached connection
1163: // restore the body if ther was one.
1164: try {
1165: if (request.hasOutputStream()) {
1166: request.getOutputStream().reset();
1167: }
1168: } catch (IOException mrex) {
1169: // mark/reset not supported on the stream
1170: } finally {
1171: // Get rid of that useless connection:
1172: conn.markIdle(true);
1173: }
1174: } catch (Throwable thro) {
1175: conn.markIdle(true);
1176: }
1177: }
1178: if (request.hasOutputStream()) {
1179: request.getOutputStream().close();
1180: }
1181: // Did the request really failed ?
1182: if (reply == null) {
1183: String msg = ("Unable to contact target server " + this
1184: + " after " + maxretry + " tries.");
1185: if (o != null)
1186: notifyObserver(o, request,
1187: RequestEvent.EVT_UNREACHABLE);
1188: if (lastex != null) {
1189: throw new HttpException(request, null, lastex, msg);
1190: } else {
1191: throw new HttpException(request, msg);
1192: }
1193: }
1194: reply.matchesRequest(request);
1195: if (o != null)
1196: notifyObserver(o, request, RequestEvent.EVT_REPLIED);
1197: // Can we try to reuse the connection ?
1198: if (reply.keepsAlive()) {
1199: reply.setStreamObserver(conn);
1200: // If the reply has no input stream, register the connection:
1201: if (!reply.hasInputStream()) {
1202: conn.markIdle(false);
1203: }
1204: } else {
1205: conn.detach();
1206: }
1207: if (debug) {
1208: System.out.println("Request done !");
1209: reply.dump(System.out);
1210: }
1211: } catch (IOException ex) {
1212: ex.printStackTrace();
1213: if (conn != null)
1214: conn.markIdle(true);
1215: if (o != null)
1216: notifyObserver(o, request, RequestEvent.EVT_CLOSED);
1217: throw new HttpException(request, ex);
1218: } catch (MimeParserException ex) {
1219: ex.printStackTrace();
1220: if (conn != null)
1221: conn.markIdle(true);
1222: if (o != null)
1223: notifyObserver(o, request, RequestEvent.EVT_CLOSED);
1224: throw new HttpException(request, ex);
1225: }
1226: return reply;
1227: }
1228:
1229: HttpBasicServer() {
1230: }
1231: }
|