From b6f59c9d2c6db950d116f8f38b0d3dc48eaf670d Mon Sep 17 00:00:00 2001 From: costin Date: Mon, 14 Dec 2009 07:35:57 +0000 Subject: [PATCH] One more week of (almost 20%) hacking on tomcat-lite: - protocol handler is almost useable with both tomcat7 and 6.x. It's missing most options of the other connectors - will need to select what fits with 'lite', SSL is not hooked - run the old watchdog tests ( in junit form ) - they seem to catch few problems, improved a bit the runner to make eclipse happy. - few load tests - and associated fixes. If anyone tries it out with maven - one test will look for examples webapp, the one in tomcat7 has a RequestDumperFilter that needs to be commented out. Ant runs more tests - some are failing. For either - you need a build of the watchdog. git-svn-id: https://svn.apache.org/repos/asf/tomcat/trunk@890206 13f79535-47bb-0310-9956-ffa450edef68 --- modules/tomcat-lite/.classpath | 2 +- .../apache/coyote/lite/LiteProtocolHandler.java | 306 ++++++- .../apache/tomcat/lite/http/Http11Connection.java | 139 ++-- .../org/apache/tomcat/lite/http/HttpChannel.java | 29 +- .../org/apache/tomcat/lite/http/HttpConnector.java | 103 ++- .../org/apache/tomcat/lite/http/HttpMessage.java | 7 +- .../org/apache/tomcat/lite/io/IOConnector.java | 13 +- .../apache/tomcat/lite/io/MemoryIOConnector.java | 5 + .../java/org/apache/tomcat/lite/io/NioThread.java | 16 +- .../org/apache/tomcat/lite/io/SocketConnector.java | 2 + .../org/apache/tomcat/lite/service/IOStatus.java | 8 +- modules/tomcat-lite/pom.xml | 1 - .../test/org/apache/coyote/lite/ServletTests.java | 67 ++ .../test/org/apache/coyote/lite/Tomcat.java | 912 +++++++++++++++++++++ .../apache/coyote/lite/TomcatLiteCoyoteTest.java | 28 +- .../tomcat/lite/load/LiveHttpThreadedTest.java | 234 ++++-- .../org/apache/tomcat/lite/load/ThreadRunner.java | 65 ++ .../tomcat/lite/servlet/TomcatLiteWatchdog.java | 10 +- .../tomcat/test/watchdog/WatchdogClient.java | 38 +- .../tomcat/test/watchdog/WatchdogHttpClient.java | 2 +- .../tomcat/test/watchdog/WatchdogTestCase.java | 38 +- 21 files changed, 1734 insertions(+), 291 deletions(-) create mode 100644 modules/tomcat-lite/test/org/apache/coyote/lite/ServletTests.java create mode 100644 modules/tomcat-lite/test/org/apache/coyote/lite/Tomcat.java create mode 100644 modules/tomcat-lite/test/org/apache/tomcat/lite/load/ThreadRunner.java diff --git a/modules/tomcat-lite/.classpath b/modules/tomcat-lite/.classpath index b9d529ba9..fe5e2dfbf 100644 --- a/modules/tomcat-lite/.classpath +++ b/modules/tomcat-lite/.classpath @@ -1,7 +1,7 @@ - + diff --git a/modules/tomcat-lite/java/org/apache/coyote/lite/LiteProtocolHandler.java b/modules/tomcat-lite/java/org/apache/coyote/lite/LiteProtocolHandler.java index 1c6da20ef..3c35c2203 100644 --- a/modules/tomcat-lite/java/org/apache/coyote/lite/LiteProtocolHandler.java +++ b/modules/tomcat-lite/java/org/apache/coyote/lite/LiteProtocolHandler.java @@ -5,6 +5,8 @@ package org.apache.coyote.lite; import java.io.IOException; import java.util.Iterator; +import org.apache.coyote.ActionCode; +import org.apache.coyote.ActionHook; import org.apache.coyote.Adapter; import org.apache.coyote.InputBuffer; import org.apache.coyote.OutputBuffer; @@ -14,11 +16,14 @@ import org.apache.coyote.Response; import org.apache.tomcat.lite.http.HttpConnector; import org.apache.tomcat.lite.http.HttpRequest; import org.apache.tomcat.lite.http.HttpResponse; +import org.apache.tomcat.lite.http.MultiMap; import org.apache.tomcat.lite.http.HttpChannel.HttpService; +import org.apache.tomcat.lite.http.MultiMap.Entry; import org.apache.tomcat.lite.io.CBuffer; import org.apache.tomcat.lite.io.SocketConnector; import org.apache.tomcat.util.buf.ByteChunk; import org.apache.tomcat.util.buf.MessageBytes; +import org.apache.tomcat.util.http.MimeHeaders; /** * Work in progress - use the refactored http as a coyote connector. @@ -104,19 +109,23 @@ public class LiteProtocolHandler implements ProtocolHandler { buffer.length()); } - private void coyoteService(HttpRequest httpReq, final HttpResponse httpRes) { - Request req = new Request(); - req.setInputBuffer(new InputBuffer() { - @Override - public int doRead(ByteChunk chunk, Request request) - throws IOException { - // TODO - return 0; - } - }); - Response res = new Response(); - res.setOutputBuffer(new OutputBuffer() { - + private void coyoteService(final HttpRequest httpReq, final HttpResponse httpRes) { + RequestData rc = new RequestData(); + rc.init(httpReq, httpRes); + + try { + adapter.service(rc.req, rc.res); + } catch (Exception e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } + } + + /** + * Per request data. + */ + public class RequestData implements ActionHook { + private final class LiteOutputBuffer implements OutputBuffer { @Override public int doWrite(org.apache.tomcat.util.buf.ByteChunk chunk, Response response) throws IOException { @@ -124,28 +133,263 @@ public class LiteProtocolHandler implements ProtocolHandler { chunk.getLength()); return chunk.getLength(); } - - }); - - // TODO: turn http request into a coyote request - copy all fields, - // add hooks where needed. + } + + OutputBuffer outputBuffer = new LiteOutputBuffer(); + // TODO: recycle, etc. + Request req = new Request(); + + Response res = new Response(); + HttpResponse httpRes; + HttpRequest httpReq; + InputBuffer inputBuffer = new InputBuffer() { + @Override + public int doRead(ByteChunk bchunk, Request request) + throws IOException { + httpReq.getBody().waitData(httpReq.getHttpChannel().getIOTimeout()); + int rd = + httpReq.getBody().read(bchunk.getBytes(), + bchunk.getStart(), bchunk.getBytes().length); + if (rd > 0) { + bchunk.setEnd(bchunk.getEnd() + rd); + } + return rd; + } + }; - wrap(req.decodedURI(), httpReq.decodedURI()); - wrap(req.method(), httpReq.method()); - wrap(req.protocol(), httpReq.protocol()); - wrap(req.requestURI(), httpReq.requestURI()); - // Same for response. + public RequestData() { + req.setInputBuffer(inputBuffer); + res.setOutputBuffer(outputBuffer); + req.setResponse(res); + res.setRequest(req); + res.setHook(this); + } - try { + public void init(HttpRequest httpReq, HttpResponse httpRes) { + this.httpRes = httpRes; + this.httpReq = httpReq; + // TODO: turn http request into a coyote request - copy all fields, + // add hooks where needed. - adapter.service(req, res); + wrap(req.decodedURI(), httpReq.decodedURI()); + wrap(req.method(), httpReq.method()); + wrap(req.protocol(), httpReq.protocol()); + wrap(req.requestURI(), httpReq.requestURI()); + wrap(req.queryString(), httpReq.queryString()); + + req.setServerPort(httpReq.getServerPort()); + req.serverName().setString(req.localName().toString()); - } catch (Exception e) { - // TODO Auto-generated catch block - e.printStackTrace(); + MultiMap mimeHeaders = httpReq.getMimeHeaders(); + MimeHeaders coyoteHeaders = req.getMimeHeaders(); + for (int i = 0; i < mimeHeaders.size(); i++ ) { + Entry entry = mimeHeaders.getEntry(i); + MessageBytes val = + coyoteHeaders.addValue(entry.getName().toString()); + val.setString(entry.getValue().toString()); + } } + + /** + * Send an action to the connector. + * + * @param actionCode Type of the action + * @param param Action parameter + */ + public void action(ActionCode actionCode, Object param) { + + if (actionCode == ActionCode.ACTION_COMMIT) { + if (res.isCommitted()) + return; + + // TODO: copy headers, fields + httpRes.setStatus(res.getStatus()); + httpRes.setMessage(res.getMessage()); + MultiMap mimeHeaders = httpRes.getMimeHeaders(); + MimeHeaders coyoteHeaders = res.getMimeHeaders(); + for (int i = 0; i < coyoteHeaders.size(); i++ ) { + MessageBytes name = coyoteHeaders.getName(i); + MessageBytes val = coyoteHeaders.getValue(i); + Entry entry = mimeHeaders.addEntry(name.toString()); + entry.getValue().set(val.toString()); + } + String contentType = res.getContentType(); + if (contentType != null) { + mimeHeaders.addEntry("Content-Type").getValue().set(contentType); + } + String contentLang = res.getContentType(); + if (contentLang != null) { + mimeHeaders.addEntry("Content-Language").getValue().set(contentLang); + } + long contentLength = res.getContentLengthLong(); + if (contentLength != -1) { + httpRes.setContentLength(contentLength); + } + String lang = res.getContentLanguage(); + if (lang != null) { + httpRes.setHeader("Content-Language", lang); + } + + try { + httpReq.send(); + } catch (IOException e) { + e.printStackTrace(); + } + } else if (actionCode == ActionCode.ACTION_ACK) { + // Done automatically by http connector + } else if (actionCode == ActionCode.ACTION_CLIENT_FLUSH) { + try { + httpReq.send(); + } catch (IOException e) { + httpReq.getHttpChannel().abort(e); + res.setErrorException(e); + } + + } else if (actionCode == ActionCode.ACTION_CLOSE) { + // Close + + // End the processing of the current request, and stop any further + // transactions with the client + +// comet = false; +// try { +// outputBuffer.endRequest(); +// } catch (IOException e) { +// // Set error flag +// error = true; +// } + + } else if (actionCode == ActionCode.ACTION_RESET) { + // Reset response + // Note: This must be called before the response is committed + httpRes.getBody().clear(); + + } else if (actionCode == ActionCode.ACTION_CUSTOM) { + + // Do nothing + + } else if (actionCode == ActionCode.ACTION_REQ_HOST_ADDR_ATTRIBUTE) { + req.remoteAddr().setString(httpReq.remoteAddr().toString()); + } else if (actionCode == ActionCode.ACTION_REQ_LOCAL_NAME_ATTRIBUTE) { + req.localName().setString(httpReq.localName().toString()); + } else if (actionCode == ActionCode.ACTION_REQ_HOST_ATTRIBUTE) { + req.remoteHost().setString(httpReq.remoteHost().toString()); + } else if (actionCode == ActionCode.ACTION_REQ_LOCAL_ADDR_ATTRIBUTE) { + req.localAddr().setString(httpReq.localAddr().toString()); + } else if (actionCode == ActionCode.ACTION_REQ_REMOTEPORT_ATTRIBUTE) { + req.setRemotePort(httpReq.getRemotePort()); + } else if (actionCode == ActionCode.ACTION_REQ_LOCALPORT_ATTRIBUTE) { + req.setLocalPort(httpReq.getLocalPort()); + } else if (actionCode == ActionCode.ACTION_REQ_SSL_ATTRIBUTE ) { + +// if (ssl && (socket != 0)) { +// try { +// // Cipher suite +// Object sslO = SSLSocket.getInfoS(socket, SSL.SSL_INFO_CIPHER); +// if (sslO != null) { +// request.setAttribute(AprEndpoint.CIPHER_SUITE_KEY, sslO); +// } +// // Get client certificate and the certificate chain if present +// // certLength == -1 indicates an error +// int certLength = SSLSocket.getInfoI(socket, SSL.SSL_INFO_CLIENT_CERT_CHAIN); +// byte[] clientCert = SSLSocket.getInfoB(socket, SSL.SSL_INFO_CLIENT_CERT); +// X509Certificate[] certs = null; +// if (clientCert != null && certLength > -1) { +// certs = new X509Certificate[certLength + 1]; +// CertificateFactory cf = CertificateFactory.getInstance("X.509"); +// certs[0] = (X509Certificate) cf.generateCertificate(new ByteArrayInputStream(clientCert)); +// for (int i = 0; i < certLength; i++) { +// byte[] data = SSLSocket.getInfoB(socket, SSL.SSL_INFO_CLIENT_CERT_CHAIN + i); +// certs[i+1] = (X509Certificate) cf.generateCertificate(new ByteArrayInputStream(data)); +// } +// } +// if (certs != null) { +// request.setAttribute(AprEndpoint.CERTIFICATE_KEY, certs); +// } +// // User key size +// sslO = new Integer(SSLSocket.getInfoI(socket, SSL.SSL_INFO_CIPHER_USEKEYSIZE)); +// request.setAttribute(AprEndpoint.KEY_SIZE_KEY, sslO); +// +// // SSL session ID +// sslO = SSLSocket.getInfoS(socket, SSL.SSL_INFO_SESSION_ID); +// if (sslO != null) { +// request.setAttribute(AprEndpoint.SESSION_ID_KEY, sslO); +// } +// //TODO provide a hook to enable the SSL session to be +// // invalidated. Set AprEndpoint.SESSION_MGR req attr +// } catch (Exception e) { +// log.warn(sm.getString("http11processor.socket.ssl"), e); +// } +// } + + } else if (actionCode == ActionCode.ACTION_REQ_SSL_CERTIFICATE) { + +// if (ssl && (socket != 0)) { +// // Consume and buffer the request body, so that it does not +// // interfere with the client's handshake messages +// InputFilter[] inputFilters = inputBuffer.getFilters(); +// ((BufferedInputFilter) inputFilters[Constants.BUFFERED_FILTER]).setLimit(maxSavePostSize); +// inputBuffer.addActiveFilter(inputFilters[Constants.BUFFERED_FILTER]); +// try { +// // Configure connection to require a certificate +// SSLSocket.setVerify(socket, SSL.SSL_CVERIFY_REQUIRE, +// endpoint.getSSLVerifyDepth()); +// // Renegotiate certificates +// if (SSLSocket.renegotiate(socket) == 0) { +// // Don't look for certs unless we know renegotiation worked. +// // Get client certificate and the certificate chain if present +// // certLength == -1 indicates an error +// int certLength = SSLSocket.getInfoI(socket,SSL.SSL_INFO_CLIENT_CERT_CHAIN); +// byte[] clientCert = SSLSocket.getInfoB(socket, SSL.SSL_INFO_CLIENT_CERT); +// X509Certificate[] certs = null; +// if (clientCert != null && certLength > -1) { +// certs = new X509Certificate[certLength + 1]; +// CertificateFactory cf = CertificateFactory.getInstance("X.509"); +// certs[0] = (X509Certificate) cf.generateCertificate(new ByteArrayInputStream(clientCert)); +// for (int i = 0; i < certLength; i++) { +// byte[] data = SSLSocket.getInfoB(socket, SSL.SSL_INFO_CLIENT_CERT_CHAIN + i); +// certs[i+1] = (X509Certificate) cf.generateCertificate(new ByteArrayInputStream(data)); +// } +// } +// if (certs != null) { +// request.setAttribute(AprEndpoint.CERTIFICATE_KEY, certs); +// } +// } +// } catch (Exception e) { +// log.warn(sm.getString("http11processor.socket.ssl"), e); +// } +// } + + } else if (actionCode == ActionCode.ACTION_REQ_SET_BODY_REPLAY) { +// ByteChunk body = (ByteChunk) param; +// +// InputFilter savedBody = new SavedRequestInputFilter(body); +// savedBody.setRequest(request); +// +// InternalAprInputBuffer internalBuffer = (InternalAprInputBuffer) +// request.getInputBuffer(); +// internalBuffer.addActiveFilter(savedBody); + + } else if (actionCode == ActionCode.ACTION_AVAILABLE) { + req.setAvailable(httpReq.getBody().available()); + } else if (actionCode == ActionCode.ACTION_COMET_BEGIN) { +// comet = true; + } else if (actionCode == ActionCode.ACTION_COMET_END) { +// comet = false; + } else if (actionCode == ActionCode.ACTION_COMET_CLOSE) { + //no op + } else if (actionCode == ActionCode.ACTION_COMET_SETTIMEOUT) { + //no op +// } else if (actionCode == ActionCode.ACTION_ASYNC_START) { +// //TODO SERVLET3 - async +// } else if (actionCode == ActionCode.ACTION_ASYNC_COMPLETE) { +// //TODO SERVLET3 - async +// } else if (actionCode == ActionCode.ACTION_ASYNC_SETTIMEOUT) { +// //TODO SERVLET3 - async + } + + + } + } - - - } diff --git a/modules/tomcat-lite/java/org/apache/tomcat/lite/http/Http11Connection.java b/modules/tomcat-lite/java/org/apache/tomcat/lite/http/Http11Connection.java index f1bbba6d4..220e453a8 100644 --- a/modules/tomcat-lite/java/org/apache/tomcat/lite/http/Http11Connection.java +++ b/modules/tomcat-lite/java/org/apache/tomcat/lite/http/Http11Connection.java @@ -16,6 +16,9 @@ import org.apache.tomcat.lite.io.FastHttpDateFormat; import org.apache.tomcat.lite.io.Hex; import org.apache.tomcat.lite.io.IOBuffer; import org.apache.tomcat.lite.io.IOChannel; +import org.apache.tomcat.lite.io.IOConnector; +import org.apache.tomcat.lite.io.NioThread; +import org.apache.tomcat.lite.io.SocketIOChannel; public class Http11Connection extends HttpConnection { public static final String CHUNKED = "chunked"; @@ -61,6 +64,10 @@ public class Http11Connection extends HttpConnection { protected boolean http09 = false; HttpConnection switchedProtocol = null; + + private int requestCount = 0; + + private Object readLock = new Object(); public Http11Connection(HttpConnector httpConnector) { this.httpConnector = httpConnector; @@ -91,7 +98,7 @@ public class Http11Connection extends HttpConnection { private boolean readHead() throws IOException { while (true) { int read; - if (headRecvBuf.remaining() < 4) { + if (requestCount == 0 && headRecvBuf.remaining() < 4) { // requests have at least 4 bytes - detect protocol read = net.getIn().read(headRecvBuf, 4); if (read < 0) { @@ -168,63 +175,70 @@ public class Http11Connection extends HttpConnection { switchedProtocol.handleReceived(netx); return; } - + //trace("handleReceived " + headersReceived); if (!checkKeepAliveClient()) { return; // we were in client keep alive mode } - - if (!headersReceived) { - if (!readHead()) { - return; + // endSendReceived uses same lock - it will call this + // to check outstanding bytes + synchronized (readLock) { + if (bodyReceived) { + return; // leave data in net buffer, for next req } - } - - // We have a header - if (activeHttp == null) { - if (checkHttpChannel() == null) { - return; + + if (!headersReceived) { + if (!readHead()) { + return; + } } - } - - IOBuffer receiveBody = activeHttp.receiveBody; - if (!headersReceived) { - headRecvBuf.wrapTo(headW); - parseMessage(activeHttp, headW); - if (serverMode && activeHttp.httpReq.decodedUri.remaining() == 0) { - abort(activeHttp, "Invalid url"); + // We have a header + if (activeHttp == null) { + if (checkHttpChannel() == null) { + return; + } } - headersReceived = true; - // Send header callbacks - we process any incoming data - // first, so callbacks have more info - activeHttp.handleHeadersReceived(activeHttp.inMessage); - } - - // any remaining data will be processed as part of the - // body - or left in the channel until endSendReceive() - - if (!bodyReceived) { - // Will close receiveBody when it consummed enough - rawDataReceived(activeHttp, receiveBody, net.getIn()); - // Did we process anything ? - if (receiveBody.getBufferCount() > 0) { - activeHttp.sendHandleReceivedCallback(); // callback + IOBuffer receiveBody = activeHttp.receiveBody; + + if (!headersReceived) { + headRecvBuf.wrapTo(headW); + parseMessage(activeHttp, headW); + if (serverMode && activeHttp.httpReq.decodedUri.remaining() == 0) { + abort(activeHttp, "Invalid url"); + } + + headersReceived = true; + // Send header callbacks - we process any incoming data + // first, so callbacks have more info + trace("Send headers received callback " + activeHttp.httpService); + activeHttp.handleHeadersReceived(activeHttp.inMessage); } + // any remaining data will be processed as part of the + // body - or left in the channel until endSendReceive() + + if (!bodyReceived) { + // Will close receiveBody when it consummed enough + rawDataReceived(activeHttp, receiveBody, net.getIn()); + // Did we process anything ? + if (receiveBody.getBufferCount() > 0) { + activeHttp.sendHandleReceivedCallback(); // callback + } + } // Receive has marked the body as closed if (receiveBody.isAppendClosed()) { - activeHttp.handleEndReceive(); bodyReceived = true; + activeHttp.handleEndReceive(); } - } - if (net.getIn().isClosedAndEmpty()) { - // If not already closed. - closeStreamOnEnd("closed after body"); - } + if (net.getIn().isClosedAndEmpty()) { + // If not already closed. + closeStreamOnEnd("closed after body"); + } + } } /** @@ -300,7 +314,6 @@ public class Http11Connection extends HttpConnection { switchedProtocol.endSendReceive(http); return; } - activeHttp = null; if (!keepAlive()) { if (debug) { @@ -308,31 +321,30 @@ public class Http11Connection extends HttpConnection { } if (net != null) { net.close(); -// net.getOut().close(); // shutdown output if not done -// net.getIn().close(); // this should close the socket net.startSending(); - + } beforeRequest(); return; } - - beforeRequest(); // will clear head buffer - - if (serverMode) { - handleReceived(net); // will attempt to read next req - if (debug) { - log.info(">>> server socket KEEP_ALIVE " + net.getTarget() + - " " + net); - } - - } else { - if (debug) { - log.info(">>> client socket KEEP_ALIVE " + net.getTarget() + - " " + net); + synchronized (readLock) { + beforeRequest(); // will clear head buffer + requestCount++; + if (serverMode) { + if (debug) { + log.info(">>> server socket KEEP_ALIVE " + net.getTarget() + + " " + net + " " + net.getIn().available()); + } + handleReceived(net); // will attempt to read next req + } else { + if (debug) { + log.info(">>> client socket KEEP_ALIVE " + net.getTarget() + + " " + net); + } + httpConnector.cpool.returnChannel(this); } - httpConnector.cpool.returnChannel(this); } + } private void trace(String s) { @@ -1384,8 +1396,11 @@ public class Http11Connection extends HttpConnection { return switchedProtocol.toString(); } - return (serverMode ? "S11 " : "C11 ") + - (keepAlive() ? " KA " : ""); + return (serverMode ? "SR " : "CL ") + + (keepAlive() ? " KA " : "") + + (headersReceived ? " HEAD " : "") + + (bodyReceived ? " BODY " : "") + ; } } diff --git a/modules/tomcat-lite/java/org/apache/tomcat/lite/http/HttpChannel.java b/modules/tomcat-lite/java/org/apache/tomcat/lite/http/HttpChannel.java index 170dbaf9e..25815cc75 100644 --- a/modules/tomcat-lite/java/org/apache/tomcat/lite/http/HttpChannel.java +++ b/modules/tomcat-lite/java/org/apache/tomcat/lite/http/HttpChannel.java @@ -166,29 +166,32 @@ public class HttpChannel extends IOChannel { * * @throws IOException */ - public void abort(Throwable t) throws IOException { + public void abort(Throwable t) { abort(t.toString()); } - public void abort(String t) throws IOException { + public void abort(String t) { synchronized (this) { if (abortDone) { return; } abortDone = true; } - - checkRelease(); - trace("abort " + t); - log.info("Abort connection " + t); - if (conn != null) { - conn.abort(this, t); + try { + checkRelease(); + trace("abort " + t); + log.info("Abort connection " + t); + if (conn != null) { + conn.abort(this, t); + } + inMessage.state = HttpMessage.State.DONE; + outMessage.state = HttpMessage.State.DONE; + sendReceiveDone = true; + error = true; + handleEndSendReceive(); + } catch (Throwable ex) { + log.severe("Exception in abort " + ex); } - inMessage.state = HttpMessage.State.DONE; - outMessage.state = HttpMessage.State.DONE; - sendReceiveDone = true; - error = true; - handleEndSendReceive(); } /** diff --git a/modules/tomcat-lite/java/org/apache/tomcat/lite/http/HttpConnector.java b/modules/tomcat-lite/java/org/apache/tomcat/lite/http/HttpConnector.java index 37279e3b2..e53b54431 100644 --- a/modules/tomcat-lite/java/org/apache/tomcat/lite/http/HttpConnector.java +++ b/modules/tomcat-lite/java/org/apache/tomcat/lite/http/HttpConnector.java @@ -6,11 +6,10 @@ import java.io.IOException; import java.net.URL; import java.util.ArrayList; import java.util.HashMap; +import java.util.List; import java.util.Map; -import java.util.Queue; import java.util.Set; import java.util.Timer; -import java.util.concurrent.ConcurrentLinkedQueue; import java.util.concurrent.atomic.AtomicInteger; import java.util.logging.Logger; @@ -46,12 +45,22 @@ public class HttpConnector { HttpConnectionManager conManager = new HttpConnectionManager(); private static Logger log = Logger.getLogger("HttpConnector"); - private int maxHttpPoolSize = 20; - private int maxSocketPoolSize = 100; - private int keepAliveTimeMs = 30000; + /** + * Cache HttpChannel/request/buffers + */ + private int maxHttpPoolSize = 50; + + /** + * Max number of connections to keep alive. + * Each connection holds a header buffer and the socket. + * ( we could skip the header buffer ) + */ + private int maxSocketPoolSize = 500; // 10000; + + private int keepAliveTimeMs = 300000; - private Queue httpChannelPool = new ConcurrentLinkedQueue(); + private List httpChannelPool = new ArrayList(); protected IOConnector ioConnector; @@ -76,12 +85,21 @@ public class HttpConnector { Dispatcher dispatcher; protected HttpService defaultService; int port = 8080; - + + private Timer timer; + + private static Timer defaultTimer = new Timer(true); public HttpConnector(IOConnector ioConnector) { this.ioConnector = ioConnector; dispatcher = new Dispatcher(); defaultService = dispatcher; + if (ioConnector != null) { + timer = ioConnector.getTimer(); + } else { + // tests + timer = defaultTimer; + } } protected HttpConnector() { @@ -214,10 +232,16 @@ public class HttpConnector { protected HttpChannel get(boolean server) throws IOException { HttpChannel processor = null; synchronized (httpChannelPool) { - processor = httpChannelPool.poll(); + int cnt = httpChannelPool.size(); + if (cnt > 0) { + processor = httpChannelPool.remove(cnt - 1); + } } boolean reuse = false; totalHttpChannel.incrementAndGet(); + if (!server) { + totalClientHttpChannel.incrementAndGet(); + } if (processor == null) { processor = create(); } else { @@ -225,9 +249,6 @@ public class HttpConnector { reusedChannels.incrementAndGet(); processor.release = false; } - if (!server) { - totalClientHttpChannel.incrementAndGet(); - } processor.serverMode(server); if (debug) { log.info((reuse ? "REUSE ": "Create ") + @@ -242,50 +263,35 @@ public class HttpConnector { return processor; } - - /** - * Called by HttpChannel when the HTTP request is done, i.e. all - * sending/receiving is complete. The service may still use the - * HttpChannel object. - * - * If keepOpen: clients will wait in the pool, detecting server close. - * For server: will wait for new requests. - * - * TODO: timeouts, better pool management - */ - protected void returnSocket(IOChannel ch, boolean serverMode, - boolean keepOpen) - throws IOException { - // Now handle net - note that we could have reused the async object - - - } - protected void returnToPool(HttpChannel http) throws IOException { inUse.decrementAndGet(); recycledChannels.incrementAndGet(); - if (debug) { - log.info("Return " + http.getTarget() + " obj=" + - http + " size=" + httpChannelPool.size()); - } + int size = 0; + boolean pool = false; http.recycle(); + http.setConnection(null); + http.setConnector(null); // No more data - release the object synchronized (httpChannelPool) { - http.setConnection(null); - http.setConnector(null); + size = httpChannelPool.size(); if (httpChannelPool.contains(http)) { - System.err.println("dup ? "); - } - if (httpChannelPool.size() >= maxHttpPoolSize) { - if (httpEvents != null) { - httpEvents.onDestroy(http, this); - } - } else { + log.severe("Duplicate element in pool !"); + } else if (size < maxHttpPoolSize) { httpChannelPool.add(http); + pool = true; } } + + if (!pool && httpEvents != null) { + httpEvents.onDestroy(http, this); + } + if (debug) { + log.info((pool ? "Return " : "Destroy ") + + http.getTarget() + " obj=" + + http + " size=" + size); + } } @@ -438,7 +444,7 @@ public class HttpConnector { implements DataReceivedCallback { protected HttpConnector httpConnector; - protected boolean serverMode; + protected boolean serverMode = false; protected BBuffer headRecvBuf = BBuffer.allocate(8192); @@ -543,7 +549,6 @@ public class HttpConnector { */ public Map hosts = new HashMap(); - boolean keepOpen = true; // Statistics public AtomicInteger waitingSockets = new AtomicInteger(); @@ -552,8 +557,6 @@ public class HttpConnector { public AtomicInteger hits = new AtomicInteger(); public AtomicInteger misses = new AtomicInteger(); - Timer timer; - public int getTargetCount() { return hosts.size(); } @@ -633,12 +636,6 @@ public class HttpConnector { return; } - if (!keepOpen) { - ch.close(); - return; - } - -// SocketIOChannel sdata = (SocketIOChannel) ch; if (!ch.isOpen()) { ch.close(); // make sure all closed if (debug) { diff --git a/modules/tomcat-lite/java/org/apache/tomcat/lite/http/HttpMessage.java b/modules/tomcat-lite/java/org/apache/tomcat/lite/http/HttpMessage.java index 5daddfe78..867ca2ea8 100644 --- a/modules/tomcat-lite/java/org/apache/tomcat/lite/http/HttpMessage.java +++ b/modules/tomcat-lite/java/org/apache/tomcat/lite/http/HttpMessage.java @@ -79,8 +79,11 @@ public abstract class HttpMessage { } public int addHeader() { - headerNames.add(BBuffer.wrapper()); - headerValues.add(BBuffer.wrapper()); + if (headerCount >= headerNames.size()) { + // make space for the new header. + headerNames.add(BBuffer.wrapper()); + headerValues.add(BBuffer.wrapper()); + } return headerCount++; } diff --git a/modules/tomcat-lite/java/org/apache/tomcat/lite/io/IOConnector.java b/modules/tomcat-lite/java/org/apache/tomcat/lite/io/IOConnector.java index 1505cc35a..62eb0da1b 100644 --- a/modules/tomcat-lite/java/org/apache/tomcat/lite/io/IOConnector.java +++ b/modules/tomcat-lite/java/org/apache/tomcat/lite/io/IOConnector.java @@ -3,6 +3,7 @@ package org.apache.tomcat.lite.io; import java.io.IOException; +import java.util.Timer; /** @@ -27,17 +28,25 @@ public abstract class IOConnector { public static interface DataFlushedCallback { public void handleFlushed(IOChannel ch) throws IOException; } + + protected Timer timer; + + public Timer getTimer() { + return timer; + } public abstract void acceptor(IOConnector.ConnectedCallback sc, CharSequence port, Object extra) throws IOException; - + // TODO: failures ? // TODO: use String target or url public abstract void connect(String host, int port, IOConnector.ConnectedCallback sc) throws IOException; public void stop() { - + if (timer != null) { + timer.cancel(); + } } } diff --git a/modules/tomcat-lite/java/org/apache/tomcat/lite/io/MemoryIOConnector.java b/modules/tomcat-lite/java/org/apache/tomcat/lite/io/MemoryIOConnector.java index 0b9f56b23..5f6b50c01 100644 --- a/modules/tomcat-lite/java/org/apache/tomcat/lite/io/MemoryIOConnector.java +++ b/modules/tomcat-lite/java/org/apache/tomcat/lite/io/MemoryIOConnector.java @@ -3,6 +3,7 @@ package org.apache.tomcat.lite.io; import java.io.IOException; +import java.util.Timer; public class MemoryIOConnector extends IOConnector { @@ -57,6 +58,10 @@ public class MemoryIOConnector extends IOConnector { ConnectedCallback acceptor; MemoryIOConnector server; + public MemoryIOConnector() { + timer = new Timer(true); + } + public MemoryIOConnector withServer(MemoryIOConnector server) { this.server = server; return server; diff --git a/modules/tomcat-lite/java/org/apache/tomcat/lite/io/NioThread.java b/modules/tomcat-lite/java/org/apache/tomcat/lite/io/NioThread.java index 2391246e9..d782ae3b2 100644 --- a/modules/tomcat-lite/java/org/apache/tomcat/lite/io/NioThread.java +++ b/modules/tomcat-lite/java/org/apache/tomcat/lite/io/NioThread.java @@ -229,10 +229,13 @@ public class NioThread implements Runnable { // handle events for existing req first. if (selected != 0) { sloops = 0; + int callbackCnt = 0; Set sel = selector.selectedKeys(); Iterator i = sel.iterator(); while (i.hasNext()) { + callbackCnt++; + long beforeCallback = System.currentTimeMillis(); SelectionKey sk = i.next(); i.remove(); @@ -295,11 +298,12 @@ public class NioThread implements Runnable { } long callbackTime = - System.currentTimeMillis() - lastWakeup; + System.currentTimeMillis() - beforeCallback; if (callbackTime > 250) { log.warning("Callback too long ! ops=" + ready + - " time=" + callbackTime + " ch=" + ch); + " time=" + callbackTime + " ch=" + ch + + " " + callbackCnt); } if (callbackTime > maxCallbackTime) { maxCallbackTime = callbackTime; @@ -349,6 +353,9 @@ public class NioThread implements Runnable { selector = Selector.open(); for (int i = 0; i < oldCh.size(); i++) { NioChannel selectorData = oldCh.get(i); + if (selectorData == null) { + continue; + } int interest = interests.get(i); if (selectorData.channel instanceof ServerSocketChannel) { ServerSocketChannel socketChannel = @@ -1148,4 +1155,9 @@ public class NioThread implements Runnable { return Thread.currentThread() == selectorThread; } + public static boolean isSelectorThread(IOChannel ch) { + SocketIOChannel sc = (SocketIOChannel) ch.getFirst(); + return Thread.currentThread() == sc.ch.sel.selectorThread; + } + } \ No newline at end of file diff --git a/modules/tomcat-lite/java/org/apache/tomcat/lite/io/SocketConnector.java b/modules/tomcat-lite/java/org/apache/tomcat/lite/io/SocketConnector.java index 8cd33ee2b..9b50f493b 100644 --- a/modules/tomcat-lite/java/org/apache/tomcat/lite/io/SocketConnector.java +++ b/modules/tomcat-lite/java/org/apache/tomcat/lite/io/SocketConnector.java @@ -4,6 +4,7 @@ package org.apache.tomcat.lite.io; import java.io.IOException; import java.net.InetSocketAddress; +import java.util.Timer; import java.util.concurrent.Executor; import java.util.concurrent.Executors; import java.util.logging.Logger; @@ -36,6 +37,7 @@ public class SocketConnector extends IOConnector { Executor threadPool = Executors.newCachedThreadPool(); public SocketConnector() { + timer = new Timer(true); } /** diff --git a/modules/tomcat-lite/java/org/apache/tomcat/lite/service/IOStatus.java b/modules/tomcat-lite/java/org/apache/tomcat/lite/service/IOStatus.java index fee4d188b..705df981c 100644 --- a/modules/tomcat-lite/java/org/apache/tomcat/lite/service/IOStatus.java +++ b/modules/tomcat-lite/java/org/apache/tomcat/lite/service/IOStatus.java @@ -20,10 +20,6 @@ public class IOStatus implements HttpService { private ConnectionPool pool; - public IOStatus() { - - } - public IOStatus(ConnectionPool pool) { this.pool = pool; } @@ -31,9 +27,7 @@ public class IOStatus implements HttpService { @Override public void service(HttpRequest httpReq, HttpResponse httpRes) throws IOException { - ConnectionPool sc = pool == null ? - httpReq.getHttpChannel().getConnector().cpool : - pool; + ConnectionPool sc = pool; HttpWriter out = httpRes.getBodyWriter(); httpRes.setContentType("text/plain"); diff --git a/modules/tomcat-lite/pom.xml b/modules/tomcat-lite/pom.xml index e3de60334..d44cd77d2 100644 --- a/modules/tomcat-lite/pom.xml +++ b/modules/tomcat-lite/pom.xml @@ -99,7 +99,6 @@ org/apache/coyote/servlet/** - org/apache/coyote/lite/** **/ServletApi30.java diff --git a/modules/tomcat-lite/test/org/apache/coyote/lite/ServletTests.java b/modules/tomcat-lite/test/org/apache/coyote/lite/ServletTests.java new file mode 100644 index 000000000..46814061e --- /dev/null +++ b/modules/tomcat-lite/test/org/apache/coyote/lite/ServletTests.java @@ -0,0 +1,67 @@ +/* + */ +package org.apache.coyote.lite; + +import java.io.File; +import java.io.IOException; + +import javax.servlet.ServletException; + +import org.apache.catalina.LifecycleException; +import org.apache.tomcat.lite.servlet.TomcatLite; +import org.apache.tomcat.lite.servlet.TomcatLiteWatchdog; + +import junit.framework.Test; + +public class ServletTests extends TomcatLiteWatchdog { + + public ServletTests() { + super(); + port = 7075; + exclude = new String[] { + "ServletToJSPErrorPageTest", + "ServletToJSPError502PageTest", + }; + } + + public ServletTests(String name) { + super(name); + port = 7075; + } + + protected void addConnector(TomcatLite connector) { + + } + + public void initServerWithWatchdog(String wdDir) throws ServletException, + IOException { + Tomcat tomcat = new Tomcat(); + + File f = new File(wdDir + "/build/webapps"); + tomcat.setPort(port); + tomcat.setBaseDir(f.getCanonicalPath()); + + TomcatLiteCoyoteTest.setUp(tomcat, port); + + for (String s : new String[] { + "servlet-compat", + "servlet-tests", + "jsp-tests"} ) { + tomcat.addWebapp("/" + s, f.getCanonicalPath() + "/" + s); + } + + try { + tomcat.start(); + } catch (LifecycleException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } + } + + /** + * Magic JUnit method + */ + public static Test suite() { + return new ServletTests().getSuite(); + } +} diff --git a/modules/tomcat-lite/test/org/apache/coyote/lite/Tomcat.java b/modules/tomcat-lite/test/org/apache/coyote/lite/Tomcat.java new file mode 100644 index 000000000..887c65455 --- /dev/null +++ b/modules/tomcat-lite/test/org/apache/coyote/lite/Tomcat.java @@ -0,0 +1,912 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.coyote.lite; + +import java.io.File; +import java.io.IOException; +import java.security.Principal; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.logging.Level; +import java.util.logging.Logger; + +import javax.servlet.Servlet; +import javax.servlet.ServletException; + +import org.apache.catalina.Container; +import org.apache.catalina.Context; +import org.apache.catalina.Lifecycle; +import org.apache.catalina.LifecycleEvent; +import org.apache.catalina.LifecycleException; +import org.apache.catalina.LifecycleListener; +import org.apache.catalina.Realm; +import org.apache.catalina.connector.Connector; +import org.apache.catalina.core.StandardContext; +import org.apache.catalina.core.StandardEngine; +import org.apache.catalina.core.StandardHost; +import org.apache.catalina.core.StandardServer; +import org.apache.catalina.core.StandardService; +import org.apache.catalina.core.StandardWrapper; +import org.apache.catalina.realm.GenericPrincipal; +import org.apache.catalina.realm.RealmBase; +import org.apache.catalina.session.StandardManager; +import org.apache.catalina.startup.ContextConfig; + +// TODO: lazy init for the temp dir - only when a JSP is compiled or +// get temp dir is called we need to create it. This will avoid the +// need for the baseDir + +// TODO: allow contexts without a base dir - i.e. +// only programmatic. This would disable the default servlet. + +/** + * Minimal tomcat starter for embedding/unit tests. + * + * Tomcat supports multiple styles of configuration and + * startup - the most common and stable is server.xml-based, + * implemented in org.apache.catalina.startup.Bootstrap. + * + * This class is for use in apps that embed tomcat. + * Requirements: + * + * - all tomcat classes and possibly servlets are in the classpath. + * ( for example all is in one big jar, or in eclipse CP, or in any other + * combination ) + * + * - we need one temporary directory for work files + * + * - no config file is required. This class provides methods to + * use if you have a webapp with a web.xml file, but it is + * optional - you can use your own servlets. + * + * This class provides a main() and few simple CLI arguments, + * see setters for doc. It can be used for simple tests and + * demo. + * + * @see TestTomcat for examples on how to use this + * @author Costin Manolache + */ +public class Tomcat { + // Single engine, service, server, connector - few cases need more, + // they can use server.xml + protected StandardServer server; + protected StandardService service; + protected StandardEngine engine; + protected Connector connector; // for more - customize the classes + + // To make it a bit easier to config for the common case + // ( one host, one context ). + protected StandardHost host; + + // TODO: it's easy to add support for more hosts - but is it + // really needed ? + + // TODO: allow use of in-memory connector + + protected int port = 8080; + protected String hostname = "localhost"; + protected String basedir; + + // Default in-memory realm, will be set by default on + // created contexts. Can be replaced with setRealm() on + // the context. + protected Realm defaultRealm; + private Map userPass = new HashMap(); + private Map> userRoles = + new HashMap>(); + private Map userPrincipals = new HashMap(); + + public Tomcat() { + // NOOP + } + + /** + * Tomcat needs a directory for temp files. This should be the + * first method called. + * + * By default, if this method is not called, we use: + * - system properties - catalina.base, catalina.home + * - $HOME/tomcat.$PORT + * ( /tmp doesn't seem a good choice for security ). + * + * + * TODO: better default ? Maybe current dir ? + * TODO: disable work dir if not needed ( no jsp, etc ). + */ + public void setBaseDir(String basedir) { + this.basedir = basedir; + } + + /** + * Set the port for the default connector. Must + * be called before start(). + */ + public void setPort(int port) { + this.port = port; + } + + /** + * The the hostname of the default host, default is + * 'localhost'. + */ + public void setHostname(String s) { + hostname = s; + } + + /** + * Add a webapp using normal WEB-INF/web.xml if found. + * + * @param contextPath + * @param baseDir + * @return new StandardContext + * @throws ServletException + */ + public StandardContext addWebapp(String contextPath, + String baseDir) throws ServletException { + + return addWebapp(getHost(), contextPath, baseDir); + } + + + /** + * Add a context - programmatic mode, no web.xml used. + * + * API calls equivalent with web.xml: + * + * context-param + * ctx.addParameter("name", "value"); + * + * + * error-page + * ErrorPage ep = new ErrorPage(); + * ep.setErrorCode(500); + * ep.setLocation("/error.html"); + * ctx.addErrorPage(ep); + * + * ctx.addMimeMapping("ext", "type"); + * + * Note: If you reload the Context, all your configuration will be lost. If + * you need reload support, consider using a LifecycleListener to provide + * your configuration. + * + * TODO: add the rest + * + * @param contextPath "/" for root context. + * @param baseDir base dir for the context, for static files. Must exist, + * relative to the server home + */ + public StandardContext addContext(String contextPath, + String baseDir) { + return addContext(getHost(), contextPath, baseDir); + } + + /** + * Equivalent with + * . + * + * In general it is better/faster to use the method that takes a + * Servlet as param - this one can be used if the servlet is not + * commonly used, and want to avoid loading all deps. + * ( for example: jsp servlet ) + * + * You can customize the returned servlet, ex: + * + * wrapper.addInitParameter("name", "value"); + * + * @param contextPath Context to add Servlet to + * @param servletName Servlet name (used in mappings) + * @param servletClass The class to be used for the Servlet + * @return The wrapper for the servlet + */ + public StandardWrapper addServlet(String contextPath, + String servletName, + String servletClass) { + Container ctx = getHost().findChild(contextPath); + return addServlet((StandardContext) ctx, + servletName, servletClass); + } + + /** + * Static version of {@link #addServlet(String, String, String)} + * @param ctx Context to add Servlet to + * @param servletName Servlet name (used in mappings) + * @param servletClass The class to be used for the Servlet + * @return The wrapper for the servlet + */ + public static StandardWrapper addServlet(StandardContext ctx, + String servletName, + String servletClass) { + // will do class for name and set init params + StandardWrapper sw = (StandardWrapper)ctx.createWrapper(); + sw.setServletClass(servletClass); + sw.setName(servletName); + ctx.addChild(sw); + + return sw; + } + + /** + * Add an existing Servlet to the context with no class.forName or + * initialisation. + * @param contextPath Context to add Servlet to + * @param servletName Servlet name (used in mappings) + * @param servlet The Servlet to add + * @return The wrapper for the servlet + */ + public StandardWrapper addServlet(String contextPath, + String servletName, + Servlet servlet) { + Container ctx = getHost().findChild(contextPath); + return addServlet((StandardContext) ctx, + servletName, servlet); + } + + /** + * Static version of {@link #addServlet(String, String, Servlet)}. + * @param ctx Context to add Servlet to + * @param servletName Servlet name (used in mappings) + * @param servlet The Servlet to add + * @return The wrapper for the servlet + */ + public static StandardWrapper addServlet(StandardContext ctx, + String servletName, + Servlet servlet) { + // will do class for name and set init params + StandardWrapper sw = new ExistingStandardWrapper(servlet); + sw.setName(servletName); + ctx.addChild(sw); + + return sw; + } + + + /** + * Initialize and start the server. + * @throws LifecycleException + */ + public void start() throws LifecycleException { + getServer(); + getConnector(); + server.initialize(); + server.start(); + } + + /** + * Stop the server. + * @throws LifecycleException + */ + public void stop() throws LifecycleException { + getServer().stop(); + } + + + /** + * Add a user for the in-memory realm. All created apps use this + * by default, can be replaced using setRealm(). + * + */ + public void addUser(String user, String pass) { + userPass.put(user, pass); + } + + /** + * @see #addUser(String, String) + */ + public void addRole(String user, String role) { + List roles = userRoles.get(user); + if (roles == null) { + roles = new ArrayList(); + userRoles.put(user, roles); + } + roles.add(role); + } + + // ------- Extra customization ------- + // You can tune individual tomcat objects, using internal APIs + + /** + * Get the default http connector. You can set more + * parameters - the port is already initialized. + * + * Alternatively, you can construct a Connector and set any params, + * then call addConnector(Connector) + * + * @return A connector object that can be customized + */ + public Connector getConnector() { + getServer(); + if (connector != null) { + return connector; + } + // This will load Apr connector if available, + // default to nio. I'm having strange problems with apr + // and for the use case the speed benefit wouldn't matter. + + //connector = new Connector("HTTP/1.1"); + try { + connector = new Connector("org.apache.coyote.http11.Http11Protocol"); + connector.setPort(port); + service.addConnector( connector ); + } catch (Exception e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } + return connector; + } + + public void setConnector(Connector connector) { + this.connector = connector; + } + + /** + * Get the service object. Can be used to add more + * connectors and few other global settings. + */ + public StandardService getService() { + getServer(); + return service; + } + + /** + * Sets the current host - all future webapps will + * be added to this host. When tomcat starts, the + * host will be the default host. + * + * @param host + */ + public void setHost(StandardHost host) { + this.host = host; + } + + public StandardHost getHost() { + if (host == null) { + host = new StandardHost(); + host.setName(hostname); + + getEngine().addChild( host ); + } + return host; + } + + /** + * Set a custom realm for auth. If not called, a simple + * default will be used, using an internal map. + * + * Must be called before adding a context. + */ + public void setDefaultRealm(Realm realm) { + defaultRealm = realm; + } + + + /** + * Access to the engine, for further customization. + */ + public StandardEngine getEngine() { + if(engine == null ) { + getServer(); + engine = new StandardEngine(); + engine.setName( "Tomcat" ); + engine.setDefaultHost(hostname); + service.setContainer(engine); + } + return engine; + } + + /** + * Get the server object. You can add listeners and few more + * customizations. JNDI is disabled by default. + */ + public StandardServer getServer() { + + if (server != null) { + return server; + } + + initBaseDir(); + + System.setProperty("catalina.useNaming", "false"); + + server = new StandardServer(); + server.setPort( -1 ); + + service = new StandardService(); + service.setName("Tomcat"); + server.addService( service ); + return server; + } + + public StandardContext addContext(StandardHost host, + String contextPath, + String dir) { + silence(contextPath); + StandardContext ctx = new StandardContext(); + ctx.setPath( contextPath ); + ctx.setDocBase(dir); + ctx.addLifecycleListener(new FixContextListener()); + + if (host == null) { + getHost().addChild(ctx); + } else { + host.addChild(ctx); + } + return ctx; + } + + public StandardContext addWebapp(StandardHost host, + String url, String path) { + silence(url); + + StandardContext ctx = new StandardContext(); + ctx.setPath( url ); + ctx.setDocBase(path); + if (defaultRealm == null) { + initSimpleAuth(); + } + ctx.setRealm(defaultRealm); + ctx.addLifecycleListener(new DefaultWebXmlListener()); + + ContextConfig ctxCfg = new ContextConfig(); + ctx.addLifecycleListener( ctxCfg ); + // prevent it from looking ( if it finds one - it'll have dup error ) + ctxCfg.setDefaultWebXml("org/apache/catalin/startup/NO_DEFAULT_XML"); + + if (host == null) { + getHost().addChild(ctx); + } else { + host.addChild(ctx); + } + + return ctx; + } + + + + + + // ---------- Helper methods and classes ------------------- + + /** + * Initialize an in-memory realm. You can replace it + * for contexts with a real one. + */ + protected void initSimpleAuth() { + defaultRealm = new RealmBase() { + @Override + protected String getName() { + return "Simple"; + } + + @Override + protected String getPassword(String username) { + return userPass.get(username); + } + + @Override + protected Principal getPrincipal(final String username) { + Principal p = userPrincipals.get(username); + if (p == null) { + String pass = userPass.get(username); + if (pass != null) { + p = new Principal() { + + @Override + public String getName() { + return username; + } + + }; + } + } + return p; + } + + }; + } + + protected void initBaseDir() { + if (basedir == null) { + basedir = System.getProperty("catalina.base"); + } + if (basedir == null) { + basedir = System.getProperty("catalina.home"); + } + if (basedir == null) { + // Create a temp dir. + basedir = System.getProperty("user.dir") + + "/tomcat." + port; + File home = new File(basedir); + home.mkdir(); + if (!home.isAbsolute()) { + try { + basedir = home.getCanonicalPath(); + } catch (IOException e) { + basedir = home.getAbsolutePath(); + } + } + } + System.setProperty("catalina.home", basedir); + System.setProperty("catalina.base", basedir); + } + + static String[] silences = new String[] { + "org.apache.coyote.http11.Http11Protocol", + "org.apache.catalina.core.StandardService", + "org.apache.catalina.core.StandardEngine", + "org.apache.catalina.startup.ContextConfig", + "org.apache.catalina.core.ApplicationContext", + "org.apache.catalina.core.AprLifecycleListener" + }; + + /** + * Controls if the loggers will be silenced or not. + * @param silent true sets the log level to WARN for the + * loggers that log information on Tomcat start up. This + * prevents the usual startup information being logged. + * false sets the log level to the default + * level of INFO. + */ + public void setSilent(boolean silent) { + for (String s : silences) { + if (silent) { + Logger.getLogger(s).setLevel(Level.WARNING); + } else { + Logger.getLogger(s).setLevel(Level.INFO); + } + } + } + + private void silence(String ctx) { + String base = "org.apache.catalina.core.ContainerBase.[default].["; + base += getHost().getName(); + base += "].["; + base += ctx; + base += "]"; + Logger.getLogger(base).setLevel(Level.WARNING); + } + + /** + * Enables JNDI naming which is disabled by default. + */ + public void enableNaming() { + // Make sure getServer() has been called as that is where naming is + // disabled + getServer(); + + System.setProperty("catalina.useNaming", "true"); + String value = "org.apache.naming"; + String oldValue = + System.getProperty(javax.naming.Context.URL_PKG_PREFIXES); + if (oldValue != null) { + value = value + ":" + oldValue; + } + System.setProperty(javax.naming.Context.URL_PKG_PREFIXES, value); + value = System.getProperty + (javax.naming.Context.INITIAL_CONTEXT_FACTORY); + if (value == null) { + System.setProperty + (javax.naming.Context.INITIAL_CONTEXT_FACTORY, + "org.apache.naming.java.javaURLContextFactory"); + } + } + + /** + * Provide default configuration for a context. This is the programmatic + * equivalent of the default web.xml. + * + * TODO: in normal tomcat, if default-web.xml is not found, use this + * method + * + * @param contextPath The context to set the defaults for + */ + public void initWebappDefaults(String contextPath) { + Container ctx = getHost().findChild(contextPath); + initWebappDefaults((StandardContext) ctx); + } + + /** + * Static version of {@link #initWebappDefaults(String)} + * @param ctx The context to set the defaults for + */ + public static void initWebappDefaults(StandardContext ctx) { + // Default servlet + StandardWrapper servlet = addServlet( + ctx, "default", "org.apache.catalina.servlets.DefaultServlet"); + servlet.setLoadOnStartup(1); + + // JSP servlet (by class name - to avoid loading all deps) + servlet = addServlet( + ctx, "jsp", "org.apache.jasper.servlet.JspServlet"); + servlet.addInitParameter("fork", "false"); + servlet.setLoadOnStartup(3); + + // Servlet mappings + ctx.addServletMapping("/", "default"); + ctx.addServletMapping("*.jsp", "jsp"); + ctx.addServletMapping("*.jspx", "jsp"); + + // Sessions + ctx.setManager( new StandardManager()); + ctx.setSessionTimeout(30); + + // MIME mappings + for (int i = 0; i < DEFAULT_MIME_MAPPINGS.length; ) { + ctx.addMimeMapping(DEFAULT_MIME_MAPPINGS[i++], + DEFAULT_MIME_MAPPINGS[i++]); + } + + // Welcome files + ctx.addWelcomeFile("index.html"); + ctx.addWelcomeFile("index.htm"); + ctx.addWelcomeFile("index.jsp"); + } + + + /** + * Fix startup sequence - required if you don't use web.xml. + * + * The start() method in context will set 'configured' to false - and + * expects a listener to set it back to true. + */ + public static class FixContextListener implements LifecycleListener { + + public void lifecycleEvent(LifecycleEvent event) { + try { + Context context = (Context) event.getLifecycle(); + if (event.getType().equals(Lifecycle.START_EVENT)) { + context.setConfigured(true); + } + } catch (ClassCastException e) { + return; + } + } + + } + + + /** + * Fix reload - required if reloading and using programmatic configuration. + * When a context is reloaded, any programmatic configuration is lost. This + * listener sets the equivalent of conf/web.xml when the context starts. The + * context needs to be an instance of StandardContext for this listener to + * have any effect. + */ + public static class DefaultWebXmlListener implements LifecycleListener { + public void lifecycleEvent(LifecycleEvent event) { + if (Lifecycle.BEFORE_START_EVENT.equals(event.getType()) && + event.getLifecycle() instanceof StandardContext) { + initWebappDefaults((StandardContext) event.getLifecycle()); + } + } + } + + + /** + * Helper class for wrapping existing servlets. This disables servlet + * lifecycle and normal reloading, but also reduces overhead and provide + * more direct control over the servlet. + */ + public static class ExistingStandardWrapper extends StandardWrapper { + private Servlet existing; + boolean init = false; + + public ExistingStandardWrapper( Servlet existing ) { + this.existing = existing; + } + @Override + public synchronized Servlet loadServlet() throws ServletException { + if (!init) { + existing.init(facade); + init = true; + } + return existing; + + } + @Override + public long getAvailable() { + return 0; + } + @Override + public boolean isUnavailable() { + return false; + } + } + + /** + * TODO: would a properties resource be better ? Or just parsing + * /etc/mime.types ? + * This is needed because we don't use the default web.xml, where this + * is encoded. + */ + public static final String[] DEFAULT_MIME_MAPPINGS = { + "abs", "audio/x-mpeg", + "ai", "application/postscript", + "aif", "audio/x-aiff", + "aifc", "audio/x-aiff", + "aiff", "audio/x-aiff", + "aim", "application/x-aim", + "art", "image/x-jg", + "asf", "video/x-ms-asf", + "asx", "video/x-ms-asf", + "au", "audio/basic", + "avi", "video/x-msvideo", + "avx", "video/x-rad-screenplay", + "bcpio", "application/x-bcpio", + "bin", "application/octet-stream", + "bmp", "image/bmp", + "body", "text/html", + "cdf", "application/x-cdf", + "cer", "application/x-x509-ca-cert", + "class", "application/java", + "cpio", "application/x-cpio", + "csh", "application/x-csh", + "css", "text/css", + "dib", "image/bmp", + "doc", "application/msword", + "dtd", "application/xml-dtd", + "dv", "video/x-dv", + "dvi", "application/x-dvi", + "eps", "application/postscript", + "etx", "text/x-setext", + "exe", "application/octet-stream", + "gif", "image/gif", + "gtar", "application/x-gtar", + "gz", "application/x-gzip", + "hdf", "application/x-hdf", + "hqx", "application/mac-binhex40", + "htc", "text/x-component", + "htm", "text/html", + "html", "text/html", + "hqx", "application/mac-binhex40", + "ief", "image/ief", + "jad", "text/vnd.sun.j2me.app-descriptor", + "jar", "application/java-archive", + "java", "text/plain", + "jnlp", "application/x-java-jnlp-file", + "jpe", "image/jpeg", + "jpeg", "image/jpeg", + "jpg", "image/jpeg", + "js", "text/javascript", + "jsf", "text/plain", + "jspf", "text/plain", + "kar", "audio/x-midi", + "latex", "application/x-latex", + "m3u", "audio/x-mpegurl", + "mac", "image/x-macpaint", + "man", "application/x-troff-man", + "mathml", "application/mathml+xml", + "me", "application/x-troff-me", + "mid", "audio/x-midi", + "midi", "audio/x-midi", + "mif", "application/x-mif", + "mov", "video/quicktime", + "movie", "video/x-sgi-movie", + "mp1", "audio/x-mpeg", + "mp2", "audio/x-mpeg", + "mp3", "audio/x-mpeg", + "mp4", "video/mp4", + "mpa", "audio/x-mpeg", + "mpe", "video/mpeg", + "mpeg", "video/mpeg", + "mpega", "audio/x-mpeg", + "mpg", "video/mpeg", + "mpv2", "video/mpeg2", + "ms", "application/x-wais-source", + "nc", "application/x-netcdf", + "oda", "application/oda", + "odb", "application/vnd.oasis.opendocument.database", + "odc", "application/vnd.oasis.opendocument.chart", + "odf", "application/vnd.oasis.opendocument.formula", + "odg", "application/vnd.oasis.opendocument.graphics", + "odi", "application/vnd.oasis.opendocument.image", + "odm", "application/vnd.oasis.opendocument.text-master", + "odp", "application/vnd.oasis.opendocument.presentation", + "ods", "application/vnd.oasis.opendocument.spreadsheet", + "odt", "application/vnd.oasis.opendocument.text", + "otg", "application/vnd.oasis.opendocument.graphics-template", + "oth", "application/vnd.oasis.opendocument.text-web", + "otp", "application/vnd.oasis.opendocument.presentation-template", + "ots", "application/vnd.oasis.opendocument.spreadsheet-template ", + "ott", "application/vnd.oasis.opendocument.text-template", + "ogx", "application/ogg", + "ogv", "video/ogg", + "oga", "audio/ogg", + "ogg", "audio/ogg", + "spx", "audio/ogg", + "faca", "audio/flac", + "anx", "application/annodex", + "axa", "audio/annodex", + "axv", "video/annodex", + "xspf", "application/xspf+xml", + "pbm", "image/x-portable-bitmap", + "pct", "image/pict", + "pdf", "application/pdf", + "pgm", "image/x-portable-graymap", + "pic", "image/pict", + "pict", "image/pict", + "pls", "audio/x-scpls", + "png", "image/png", + "pnm", "image/x-portable-anymap", + "pnt", "image/x-macpaint", + "ppm", "image/x-portable-pixmap", + "ppt", "application/vnd.ms-powerpoint", + "pps", "application/vnd.ms-powerpoint", + "ps", "application/postscript", + "psd", "image/x-photoshop", + "qt", "video/quicktime", + "qti", "image/x-quicktime", + "qtif", "image/x-quicktime", + "ras", "image/x-cmu-raster", + "rdf", "application/rdf+xml", + "rgb", "image/x-rgb", + "rm", "application/vnd.rn-realmedia", + "roff", "application/x-troff", + "rtf", "application/rtf", + "rtx", "text/richtext", + "sh", "application/x-sh", + "shar", "application/x-shar", + /*"shtml", "text/x-server-parsed-html",*/ + "smf", "audio/x-midi", + "sit", "application/x-stuffit", + "snd", "audio/basic", + "src", "application/x-wais-source", + "sv4cpio", "application/x-sv4cpio", + "sv4crc", "application/x-sv4crc", + "svg", "image/svg+xml", + "svgz", "image/svg+xml", + "swf", "application/x-shockwave-flash", + "t", "application/x-troff", + "tar", "application/x-tar", + "tcl", "application/x-tcl", + "tex", "application/x-tex", + "texi", "application/x-texinfo", + "texinfo", "application/x-texinfo", + "tif", "image/tiff", + "tiff", "image/tiff", + "tr", "application/x-troff", + "tsv", "text/tab-separated-values", + "txt", "text/plain", + "ulw", "audio/basic", + "ustar", "application/x-ustar", + "vxml", "application/voicexml+xml", + "xbm", "image/x-xbitmap", + "xht", "application/xhtml+xml", + "xhtml", "application/xhtml+xml", + "xls", "application/vnd.ms-excel", + "xml", "application/xml", + "xpm", "image/x-xpixmap", + "xsl", "application/xml", + "xslt", "application/xslt+xml", + "xul", "application/vnd.mozilla.xul+xml", + "xwd", "image/x-xwindowdump", + "vsd", "application/x-visio", + "wav", "audio/x-wav", + "wbmp", "image/vnd.wap.wbmp", + "wml", "text/vnd.wap.wml", + "wmlc", "application/vnd.wap.wmlc", + "wmls", "text/vnd.wap.wmlscript", + "wmlscriptc", "application/vnd.wap.wmlscriptc", + "wmv", "video/x-ms-wmv", + "wrl", "x-world/x-vrml", + "wspolicy", "application/wspolicy+xml", + "Z", "application/x-compress", + "z", "application/x-compress", + "zip", "application/zip" + }; +} diff --git a/modules/tomcat-lite/test/org/apache/coyote/lite/TomcatLiteCoyoteTest.java b/modules/tomcat-lite/test/org/apache/coyote/lite/TomcatLiteCoyoteTest.java index f79c27537..e1653837c 100644 --- a/modules/tomcat-lite/test/org/apache/coyote/lite/TomcatLiteCoyoteTest.java +++ b/modules/tomcat-lite/test/org/apache/coyote/lite/TomcatLiteCoyoteTest.java @@ -10,8 +10,6 @@ import junit.framework.TestCase; import org.apache.catalina.LifecycleException; import org.apache.catalina.connector.Connector; -import org.apache.catalina.startup.Tomcat; -import org.apache.coyote.lite.LiteProtocolHandler; import org.apache.tomcat.lite.http.DefaultHttpConnector; import org.apache.tomcat.lite.http.HttpChannel; import org.apache.tomcat.lite.http.HttpConnector; @@ -26,8 +24,9 @@ public class TomcatLiteCoyoteTest extends TestCase { if (tomcat == null) { try { tomcat = new Tomcat(); + tomcat.setPort(8885); - tomcat.setBaseDir("output/build"); + tomcat.setBaseDir("../../output/build/webapps"); tomcat.addWebapp("/examples", "examples"); tomcat.addWebapp("/", "ROOT"); @@ -36,7 +35,7 @@ public class TomcatLiteCoyoteTest extends TestCase { //tomcat.addServlet(ctx, "name", "class"); // ctx.addServletMapping("/foo/*", "name"); - litePH = setUp(tomcat); + litePH = setUp(tomcat, 8885); tomcat.start(); } catch (LifecycleException e) { @@ -49,14 +48,19 @@ public class TomcatLiteCoyoteTest extends TestCase { } } - public static LiteProtocolHandler setUp(Tomcat tomcat) { - Connector connector = new Connector(LiteProtocolHandler.class.getName()); - tomcat.getService().addConnector(connector); - connector.setPort(8885); - tomcat.setConnector(connector); - LiteProtocolHandler ph = - (LiteProtocolHandler) connector.getProtocolHandler(); - return ph; + public static LiteProtocolHandler setUp(Tomcat tomcat, int port) { + Connector connector; + try { + connector = new Connector(LiteProtocolHandler.class.getName()); + tomcat.getService().addConnector(connector); + connector.setPort(port); + tomcat.setConnector(connector); + LiteProtocolHandler ph = + (LiteProtocolHandler) connector.getProtocolHandler(); + return ph; + } catch (Exception e) { + throw new RuntimeException(e); + } } diff --git a/modules/tomcat-lite/test/org/apache/tomcat/lite/load/LiveHttpThreadedTest.java b/modules/tomcat-lite/test/org/apache/tomcat/lite/load/LiveHttpThreadedTest.java index fc3108d97..035879cf7 100644 --- a/modules/tomcat-lite/test/org/apache/tomcat/lite/load/LiveHttpThreadedTest.java +++ b/modules/tomcat-lite/test/org/apache/tomcat/lite/load/LiveHttpThreadedTest.java @@ -17,101 +17,201 @@ package org.apache.tomcat.lite.load; +import java.io.File; import java.io.IOException; +import java.lang.management.ManagementFactory; +import java.net.HttpURLConnection; +import java.net.URL; +import java.util.HashMap; +import java.util.Map; +import java.util.concurrent.atomic.AtomicInteger; + +import javax.management.InstanceNotFoundException; +import javax.management.MBeanException; +import javax.management.MBeanServer; +import javax.management.MalformedObjectNameException; +import javax.management.ObjectName; +import javax.management.ReflectionException; import junit.framework.TestCase; import org.apache.tomcat.lite.TestMain; -import org.apache.tomcat.lite.http.DefaultHttpConnector; import org.apache.tomcat.lite.http.HttpChannel; import org.apache.tomcat.lite.http.HttpConnector; import org.apache.tomcat.lite.http.HttpRequest; -import org.apache.tomcat.lite.http.HttpChannel.HttpService; import org.apache.tomcat.lite.http.HttpChannel.RequestCompleted; +import org.apache.tomcat.lite.io.BBuffer; +import org.apache.tomcat.lite.io.IOBuffer; + +/* + Notes on memory use ( from heap dumps ): + - buffers are not yet recycled ( the BBuffers used in channels ) + + - each active connection consumes at least 26k - 2 buffers + head buffer + ( 8k each ) + TODO: could 'peak' in the In buffer and move headRecv to HttpChannel + + + - HttpChannel keeps about 64K ( for the hello world ). + -- res is 25k + -- req is 32k, BufferedIOReader 16k, + + TODO: + - leak in NioThread.active - closed sockets not removed + - need to rate-limit and queue requests - OOM + - timeouts + - seems few responses missing on large async requests (URL works) + */ +/** + * Long running test - async tests are failing since rate control + * is not implemented ( too many outstanding requests - OOM ), + * it seems there is a bug as well. + */ public class LiveHttpThreadedTest extends TestCase { - HttpConnector staticMain = TestMain.initTestEnv(); + HttpConnector clientCon = TestMain.getClientAndInit(); + ThreadRunner tr; + static MBeanServer server; + AtomicInteger ok = new AtomicInteger(); + Object lock = new Object(); + int reqCnt; + + Map active = new HashMap(); - int tCount = 1; - Thread[] threads = new Thread[tCount]; - int[] ok = new int[tCount]; - private int rCount = 100; + public void xtest1000Async() throws Exception { + try { + asyncRequest(10, 100); + } finally { + dumpHeap("heapAsync.bin"); + } + + } + + public void xtest10000Async() throws Exception { + try { + asyncRequest(20, 500); + } finally { + dumpHeap("heapAsync.bin"); + } + } - public void xtestSimpleRequest() throws Exception { - long t0 = System.currentTimeMillis(); - for (int i = 0; i < tCount; i++) { - final int j = i; - threads[i] = new Thread(new Runnable() { - public void run() { - makeRequests(j, true); - } - }); - threads[i].start(); - } - - int res = 0; - for (int i = 0; i < tCount; i++) { - threads[i].join(); - res += ok[i]; - } - long t1 = System.currentTimeMillis(); - System.err.println("Time: " + (t1 - t0) + " " + res); + public void asyncRequest(int thr, int perthr) throws Exception { + reqCnt = thr * perthr; + long t0 = System.currentTimeMillis(); + tr = new ThreadRunner(thr, perthr) { + public void makeRequest(int i) throws Exception { + HttpRequest cstate = clientCon.request("localhost", 8802); + synchronized (active) { + active.put(cstate, cstate); + } + + cstate.requestURI().set("/hello"); + cstate.setCompletedCallback(reqCallback); + + // Send the request, wait response + Thread.currentThread().sleep(20); + cstate.send(); + } + }; + tr.run(); + assertEquals(0, tr.errors.get()); + synchronized (lock) { + lock.wait(reqCnt * 100); + } + assertEquals(reqCnt, ok.get()); + System.err.println(reqCnt + " Async requests: " + (System.currentTimeMillis() - t0)); + } + + public void xtestURLRequest() throws Exception { + urlRequest(10, 200); } - public void testSimpleRequestNB() throws Exception { - long t0 = System.currentTimeMillis(); - for (int i = 0; i < tCount; i++) { - final int j = i; - threads[i] = new Thread(new Runnable() { - public void run() { - makeRequests(j, false); - } - }); - threads[i].start(); - } - - int res = 0; - for (int i = 0; i < tCount; i++) { - threads[i].join(); - res += ok[i]; - } - long t1 = System.currentTimeMillis(); - System.err.println("TimeNB: " + (t1 - t0) + " " + res); + public void testURLRequest2() throws Exception { + urlRequest(40, 500); } - void makeRequests(int t, boolean b) { - for (int i = 0; i < rCount ; i++) { + public void urlRequest(int thr, int cnt) throws Exception { + + try { - //System.err.println("MakeReq " + t + " " + i); - makeRequest(t, b); - } catch (Exception e) { - e.printStackTrace(); + HttpConnector testServer = TestMain.testServer; + + tr = new ThreadRunner(thr, cnt) { + + public void makeRequest(int i) throws Exception { + try { + URL url = new URL("http://localhost:8802/hello"); + HttpURLConnection con = (HttpURLConnection) url.openConnection(); + con.setReadTimeout(4000000); + con.connect(); + if (con.getResponseCode() != 200) { + errors.incrementAndGet(); + } + BBuffer out = new IOBuffer().append(con.getInputStream()).copyAll(null); + if (!"Hello world".equals(out.toString())) { + errors.incrementAndGet(); + System.err.println("bad result " + out); + } + } catch(Throwable t) { + t.printStackTrace(); + errors.incrementAndGet(); + } + } + }; + tr.run(); + assertEquals(0, tr.errors.get()); + + //assertEquals(testServer., actual) + } finally { + dumpHeap("heapURLReq.bin"); } - } } + + // TODO: move to a servlet + private void dumpHeap(String file) throws InstanceNotFoundException, + MBeanException, ReflectionException, MalformedObjectNameException { + + if (server == null) { + server = ManagementFactory.getPlatformMBeanServer(); + + } + File f1 = new java.io.File(file); + if (f1.exists()) { + f1.delete(); + } + server.invoke(new ObjectName("com.sun.management:type=HotSpotDiagnostic"), + "dumpHeap", + new Object[] {file, Boolean.FALSE /* live */}, + new String[] {String.class.getName(), "boolean"}); + } + - static RequestCompleted reqCallback = new RequestCompleted() { + RequestCompleted reqCallback = new RequestCompleted() { @Override public void handle(HttpChannel data, Object extraData) throws IOException { - //dumpHead(cstate); - //System.err.println("DATA\n" + cstate.output.toString() + "\n----"); - //assertTrue(cstate.bodyRecvBuffer.toString().indexOf("AAA") >= 0); - + String out = data.getIn().copyAll(null).toString(); + if (200 != data.getResponse().getStatus()) { + System.err.println("Wrong status"); + tr.errors.incrementAndGet(); + } + if (!"Hello world".equals(out)) { + tr.errors.incrementAndGet(); + System.err.println("bad result " + out); + } + synchronized (active) { + active.remove(data.getRequest()); + } data.release(); + int okres = ok.incrementAndGet(); + if (okres >= reqCnt) { + synchronized (lock) { + lock.notify(); + } + } } - }; - void makeRequest(int i, boolean block) throws Exception { - HttpRequest cstate = DefaultHttpConnector.get().request("localhost", 8802); - - cstate.requestURI().set("/hello"); - cstate.setCompletedCallback(reqCallback); - - // Send the request, wait response - cstate.send(); - } } diff --git a/modules/tomcat-lite/test/org/apache/tomcat/lite/load/ThreadRunner.java b/modules/tomcat-lite/test/org/apache/tomcat/lite/load/ThreadRunner.java new file mode 100644 index 000000000..8f61eadcc --- /dev/null +++ b/modules/tomcat-lite/test/org/apache/tomcat/lite/load/ThreadRunner.java @@ -0,0 +1,65 @@ +/* + */ +package org.apache.tomcat.lite.load; + +import java.util.concurrent.atomic.AtomicInteger; + +public class ThreadRunner { + int tCount = 10; + int rCount = 100; + Thread[] threads; + int[] ok; + + int sleepTime = 0; + + long time; + protected AtomicInteger errors = new AtomicInteger(); + + public ThreadRunner(int threads, int count) { + tCount = threads; + rCount = count; + this.threads = new Thread[tCount]; + ok = new int[tCount]; + } + + public void run() { + long t0 = System.currentTimeMillis(); + for (int i = 0; i < tCount; i++) { + final int j = i; + threads[i] = new Thread(new Runnable() { + public void run() { + makeRequests(j); + } + }); + threads[i].start(); + } + + int res = 0; + for (int i = 0; i < tCount; i++) { + try { + threads[i].join(); + } catch (InterruptedException e) { + e.printStackTrace(); + } + res += ok[i]; + } + long t1 = System.currentTimeMillis(); + time = t1 - t0; + System.err.println("TimeNB: " + (t1 - t0) + " " + res); + } + + public void makeRequests(int cnt) { + for (int i = 0; i < rCount ; i++) { + try { + //System.err.println("MakeReq " + t + " " + i); + makeRequest(cnt); + } catch (Exception e) { + e.printStackTrace(); + } + } + } + + public void makeRequest(int i) throws Exception { + + } +} \ No newline at end of file diff --git a/modules/tomcat-lite/test/org/apache/tomcat/lite/servlet/TomcatLiteWatchdog.java b/modules/tomcat-lite/test/org/apache/tomcat/lite/servlet/TomcatLiteWatchdog.java index 4b758264f..a306b3c51 100644 --- a/modules/tomcat-lite/test/org/apache/tomcat/lite/servlet/TomcatLiteWatchdog.java +++ b/modules/tomcat-lite/test/org/apache/tomcat/lite/servlet/TomcatLiteWatchdog.java @@ -30,6 +30,7 @@ import org.apache.tomcat.test.watchdog.WatchdogClient; public abstract class TomcatLiteWatchdog extends WatchdogClient { public TomcatLiteWatchdog() { + super(); goldenDir = getWatchdogdir() + "/src/clients/org/apache/jcheck/servlet/client/"; testMatch = //"HttpServletResponseWrapperSetStatusMsgTest"; @@ -40,6 +41,11 @@ public abstract class TomcatLiteWatchdog extends WatchdogClient { targetMatch = "gtestservlet-test"; } + public TomcatLiteWatchdog(String s) { + this(); + super.single = s; + } + protected void beforeSuite() { // required for the tests System.setProperty("org.apache.coyote.USE_CUSTOM_STATUS_MSG_IN_HEADER", @@ -58,10 +64,10 @@ public abstract class TomcatLiteWatchdog extends WatchdogClient { protected abstract void addConnector(TomcatLite liteServer); - TomcatLite tomcatForWatchdog; - public void initServerWithWatchdog(String wdDir) throws ServletException, IOException { + TomcatLite tomcatForWatchdog; + File f = new File(wdDir + "/build/webapps"); tomcatForWatchdog = new TomcatLite(); diff --git a/modules/tomcat-lite/test/org/apache/tomcat/test/watchdog/WatchdogClient.java b/modules/tomcat-lite/test/org/apache/tomcat/test/watchdog/WatchdogClient.java index ac10f8486..8f39d3513 100644 --- a/modules/tomcat-lite/test/org/apache/tomcat/test/watchdog/WatchdogClient.java +++ b/modules/tomcat-lite/test/org/apache/tomcat/test/watchdog/WatchdogClient.java @@ -33,7 +33,7 @@ import org.w3c.dom.Element; import org.w3c.dom.NodeList; import org.xml.sax.SAXException; -public class WatchdogClient { +public class WatchdogClient implements Test { protected String goldenDir; protected String testMatch; @@ -45,8 +45,9 @@ public class WatchdogClient { }; protected String targetMatch; - - + + protected int port; + Properties props = new Properties(); protected void beforeSuite() { @@ -56,7 +57,7 @@ public class WatchdogClient { } public Test getSuite() { - return getSuite(8080); + return getSuite(port); } /** @@ -95,6 +96,9 @@ public class WatchdogClient { for (int j = 0; j < watchDogL.getLength(); j++) { Element watchE = (Element) watchDogL.item(j); String testName = watchE.getAttribute("testName"); + if (single != null && !testName.equals(single)) { + continue; + } if (testMatch != null) { if (!testName.startsWith(testMatch)) { continue; @@ -112,9 +116,13 @@ public class WatchdogClient { continue; } } - testName = testName + ";" + this.getClass().getName(); + testName = testName + "(" + this.getClass().getName() + ")"; WatchdogTestCase test = new WatchdogTestCase(watchE, props, testName); tests.addTest(test); + if (single != null) { + singleTest = test; + break; + } } } @@ -154,4 +162,24 @@ public class WatchdogClient { } } + // Support for running a single test in the suite + + protected String single; + WatchdogTestCase singleTest; + + @Override + public int countTestCases() { + return 1; + } + + @Override + public void run(TestResult result) { + getSuite(); + if (singleTest != null) { + beforeSuite(); + singleTest.run(result); + afterSuite(result); + } + } + } diff --git a/modules/tomcat-lite/test/org/apache/tomcat/test/watchdog/WatchdogHttpClient.java b/modules/tomcat-lite/test/org/apache/tomcat/test/watchdog/WatchdogHttpClient.java index 3100737ac..f4b71d014 100644 --- a/modules/tomcat-lite/test/org/apache/tomcat/test/watchdog/WatchdogHttpClient.java +++ b/modules/tomcat-lite/test/org/apache/tomcat/test/watchdog/WatchdogHttpClient.java @@ -52,7 +52,7 @@ public class WatchdogHttpClient { System.out.println( " Socket Exception: " + ex ); return; } - //socket.setSoTimeout(2000); + socket.setSoTimeout(10000); //socket obtained, rebuild the request. rebuildRequest(client, client.request, socket); diff --git a/modules/tomcat-lite/test/org/apache/tomcat/test/watchdog/WatchdogTestCase.java b/modules/tomcat-lite/test/org/apache/tomcat/test/watchdog/WatchdogTestCase.java index 3486a802a..e5cf561ba 100644 --- a/modules/tomcat-lite/test/org/apache/tomcat/test/watchdog/WatchdogTestCase.java +++ b/modules/tomcat-lite/test/org/apache/tomcat/test/watchdog/WatchdogTestCase.java @@ -7,7 +7,6 @@ import java.util.Properties; import junit.framework.AssertionFailedError; import junit.framework.Test; import junit.framework.TestResult; -import junit.framework.TestSuite; import org.apache.tomcat.integration.DynamicObject; import org.apache.tomcat.integration.simple.AntProperties; @@ -22,30 +21,12 @@ public class WatchdogTestCase implements Test { private Properties props; - private WatchdogTestCase delegate; private WatchdogClient wc; - public WatchdogTestCase(String s) throws Throwable { - String[] comp = s.split(";"); - if (comp.length < 2) { - return; - } - Class c = Class.forName(comp[1]); - wc = (WatchdogClient) c.newInstance(); - TestSuite suite = (TestSuite) wc.getSuite(); - // need to encode the base, file, etc in the test name - - System.err.println(s); - - for (int i = 0; i < suite.testCount(); i++) { - WatchdogTestCase t = (WatchdogTestCase) suite.testAt(i); - if (s.equals(t.getName())) { - delegate = t; - return; - } - } + public WatchdogTestCase() { + } - + public WatchdogTestCase(Element watchE, Properties props, String testName) { this.testName = testName; this.watchE = watchE; @@ -57,20 +38,17 @@ public class WatchdogTestCase implements Test { } public String getName() { - return testName; + return testName == null ? "WatchdogTest" : testName; } + public String toString() { + return getName(); + } + public void testDummy() { } public void run(TestResult res) { - if (delegate != null) { - // Single method run - wc.beforeSuite(); - delegate.run(res); - wc.afterSuite(res); - return; - } if (watchE == null) { res.endTest(this); return; -- 2.11.0