From a2636a93478a1e73e9d816a34d4d5b63fc7783a2 Mon Sep 17 00:00:00 2001 From: costin Date: Tue, 19 Jan 2010 22:17:13 +0000 Subject: [PATCH] Few more fixes after running the load tests, more monitoring. The connection pool is now a top level class, a lot of fixes to handle spdy and http mixing. Added support for header compression in SPDY requests, reduced window size. Got it to compile again on android ( the UTF8 in getBytes - will add it back, but using the char-byte convertor ) git-svn-id: https://svn.apache.org/repos/asf/tomcat/trunk@900979 13f79535-47bb-0310-9956-ffa450edef68 --- modules/tomcat-lite/.classpath | 6 +- .../apache/tomcat/integration/DynamicObject.java | 124 +++---- .../jmx/JMXProxyServlet.java | 2 +- .../integration/jmx/JmxObjectManagerSpi.java | 2 + .../jmx/UJmxHandler.java} | 95 ++--- .../integration/jmx/UJmxObjectManagerSpi.java | 76 ++++ .../apache/tomcat/lite/http/CompressFilter.java | 3 +- .../apache/tomcat/lite/http/Http11Connection.java | 19 +- .../tomcat/lite/http/HttpConnectionPool.java | 404 +++++++++++++++++++++ .../org/apache/tomcat/lite/http/HttpConnector.java | 12 + .../java/org/apache/tomcat/lite/http/MultiMap.java | 1 - .../apache/tomcat/lite/http/SpdyConnection.java | 45 +-- .../java/org/apache/tomcat/lite/io/BBuffer.java | 7 +- .../org/apache/tomcat/lite/io/SocketConnector.java | 6 +- .../org/apache/tomcat/lite/io/SocketIOChannel.java | 28 +- .../tomcat/lite/proxy/StaticContentService.java | 20 +- .../tomcat/lite/servlet/ServletConfigImpl.java | 16 + .../tomcat/servlets/jmx/JmxObjectManagerSpi.java | 41 --- .../test/org/apache/tomcat/lite/TestMain.java | 112 ++++-- .../tomcat/lite/load/LiveHttpThreadedTest.java | 48 ++- .../org/apache/tomcat/lite/proxy/ProxyTest.java | 2 +- .../test/org/apache/tomcat/lite/test.properties | 5 +- 22 files changed, 829 insertions(+), 245 deletions(-) rename modules/tomcat-lite/java/org/apache/tomcat/{servlets => integration}/jmx/JMXProxyServlet.java (99%) rename modules/tomcat-lite/java/org/apache/tomcat/{lite/service/JMXProxy.java => integration/jmx/UJmxHandler.java} (75%) create mode 100644 modules/tomcat-lite/java/org/apache/tomcat/integration/jmx/UJmxObjectManagerSpi.java create mode 100644 modules/tomcat-lite/java/org/apache/tomcat/lite/http/HttpConnectionPool.java delete mode 100644 modules/tomcat-lite/java/org/apache/tomcat/servlets/jmx/JmxObjectManagerSpi.java diff --git a/modules/tomcat-lite/.classpath b/modules/tomcat-lite/.classpath index bfb992185..9cb14db3d 100644 --- a/modules/tomcat-lite/.classpath +++ b/modules/tomcat-lite/.classpath @@ -11,7 +11,7 @@ - + @@ -19,7 +19,7 @@ - - + + diff --git a/modules/tomcat-lite/java/org/apache/tomcat/integration/DynamicObject.java b/modules/tomcat-lite/java/org/apache/tomcat/integration/DynamicObject.java index 7afcb1ffb..d7c5128a7 100644 --- a/modules/tomcat-lite/java/org/apache/tomcat/integration/DynamicObject.java +++ b/modules/tomcat-lite/java/org/apache/tomcat/integration/DynamicObject.java @@ -2,6 +2,7 @@ */ package org.apache.tomcat.integration; +import java.lang.reflect.AccessibleObject; import java.lang.reflect.Field; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; @@ -29,9 +30,9 @@ import java.util.logging.Logger; public class DynamicObject { // Based on MbeansDescriptorsIntrospectionSource - static Logger log = Logger.getLogger(DynamicObject.class.getName()); + private static Logger log = Logger.getLogger(DynamicObject.class.getName()); - static Class NO_PARAMS[] = new Class[0]; + private static Class NO_PARAMS[] = new Class[0]; private static String strArray[] = new String[0]; @@ -47,21 +48,18 @@ public class DynamicObject { private Class realClass; - private Map getAttMap; + // Method or Field + private Map getAttMap; public DynamicObject(Class beanClass) { this.realClass = beanClass; initCache(); } - public DynamicObject(Class beanClass, boolean noCache) { - this.realClass = beanClass; - } - private void initCache() { Method methods[] = null; - getAttMap = new HashMap(); + getAttMap = new HashMap(); methods = realClass.getMethods(); for (int j = 0; j < methods.length; ++j) { @@ -94,52 +92,18 @@ public class DynamicObject { getAttMap.put(name, methods[j]); } } - } - - private boolean ignorable(Method method) { - if (Modifier.isStatic(method.getModifiers())) - return true; - if (!Modifier.isPublic(method.getModifiers())) { - return true; - } - if (method.getDeclaringClass() == Object.class) - return true; - return false; - } - - public List attributeNames() { - List attributes = new ArrayList(); - Method methods[] = realClass.getMethods(); - for (int j = 0; j < methods.length; ++j) { - String name = methods[j].getName(); - if (ignorable(methods[j])) { - continue; - } - Class params[] = methods[j].getParameterTypes(); - if (name.startsWith("get") && params.length == 0) { - Class ret = methods[j].getReturnType(); - if (!supportedType(ret)) { - continue; - } - name = unCapitalize(name.substring(3)); - attributes.add(name); - } else if (name.startsWith("is") && params.length == 0) { - Class ret = methods[j].getReturnType(); - if (Boolean.TYPE != ret) { - continue; - } - name = unCapitalize(name.substring(2)); - attributes.add(name); - } else if (name.startsWith("set") && params.length == 1) { - if (!supportedType(params[0])) { - continue; - } - name = unCapitalize(name.substring(3)); - attributes.add(name); + // non-private AtomicInteger and AtomicLong - stats + Field fields[] = realClass.getFields(); + for (int j = 0; j < fields.length; ++j) { + if (fields[j].getType() == AtomicInteger.class) { + getAttMap.put(fields[j].getName(), fields[j]); } } + + } - return attributes; + public List attributeNames() { + return new ArrayList(getAttMap.keySet()); } @@ -154,28 +118,44 @@ public class DynamicObject { } // TODO - public Object invoke(String method, Object[] params) { - return null; - } - - public boolean hasHook(String method) { - return false; - } +// public Object invoke(String method, Object[] params) { +// return null; +// } public Object getAttribute(Object o, String att) { - Method m = getAttMap.get(att); - if (m == null) - return null; - try { - return m.invoke(o); - } catch (Throwable e) { - log.log(Level.INFO, "Error getting attribute " + realClass + " " - + att, e); + AccessibleObject m = getAttMap.get(att); + if (m instanceof Method) { + try { + return ((Method) m).invoke(o); + } catch (Throwable e) { + log.log(Level.INFO, "Error getting attribute " + realClass + " " + + att, e); + return null; + } + } if (m instanceof Field) { + if (((Field) m).getType() == AtomicInteger.class) { + try { + Object value = ((Field) m).get(o); + return ((AtomicInteger) value).get(); + } catch (Throwable e) { + return null; + } + } else { + return null; + } + } else { return null; } } + /** + * Set an object-type attribute. + * + * Use setProperty to use a string value and convert it to the + * specific (primitive) type. + */ public boolean setAttribute(Object proxy, String name, Object value) { + // TODO: use the cache... String methodName = "set" + capitalize(name); Method[] methods = proxy.getClass().getMethods(); for (Method m : methods) { @@ -203,6 +183,7 @@ public class DynamicObject { } public boolean setProperty(Object proxy, String name, String value) { + // TODO: use the cache... String setter = "set" + capitalize(name); try { @@ -389,5 +370,16 @@ public class DynamicObject { return new String(chars); } + private boolean ignorable(Method method) { + if (Modifier.isStatic(method.getModifiers())) + return true; + if (!Modifier.isPublic(method.getModifiers())) { + return true; + } + if (method.getDeclaringClass() == Object.class) + return true; + return false; + } + } diff --git a/modules/tomcat-lite/java/org/apache/tomcat/servlets/jmx/JMXProxyServlet.java b/modules/tomcat-lite/java/org/apache/tomcat/integration/jmx/JMXProxyServlet.java similarity index 99% rename from modules/tomcat-lite/java/org/apache/tomcat/servlets/jmx/JMXProxyServlet.java rename to modules/tomcat-lite/java/org/apache/tomcat/integration/jmx/JMXProxyServlet.java index 9eb2d8431..0e653c790 100644 --- a/modules/tomcat-lite/java/org/apache/tomcat/servlets/jmx/JMXProxyServlet.java +++ b/modules/tomcat-lite/java/org/apache/tomcat/integration/jmx/JMXProxyServlet.java @@ -16,7 +16,7 @@ */ -package org.apache.tomcat.servlets.jmx; +package org.apache.tomcat.integration.jmx; import java.io.IOException; diff --git a/modules/tomcat-lite/java/org/apache/tomcat/integration/jmx/JmxObjectManagerSpi.java b/modules/tomcat-lite/java/org/apache/tomcat/integration/jmx/JmxObjectManagerSpi.java index b70127fe7..326bb57de 100644 --- a/modules/tomcat-lite/java/org/apache/tomcat/integration/jmx/JmxObjectManagerSpi.java +++ b/modules/tomcat-lite/java/org/apache/tomcat/integration/jmx/JmxObjectManagerSpi.java @@ -17,6 +17,7 @@ package org.apache.tomcat.integration.jmx; +import java.lang.management.ManagementFactory; import java.util.logging.Logger; import org.apache.tomcat.integration.ObjectManager; @@ -33,6 +34,7 @@ public class JmxObjectManagerSpi extends ObjectManager { public JmxObjectManagerSpi() { registry = Registry.getRegistry(null, null); + registry.setServer(ManagementFactory.getPlatformMBeanServer()); } public void bind(String name, Object o) { diff --git a/modules/tomcat-lite/java/org/apache/tomcat/lite/service/JMXProxy.java b/modules/tomcat-lite/java/org/apache/tomcat/integration/jmx/UJmxHandler.java similarity index 75% rename from modules/tomcat-lite/java/org/apache/tomcat/lite/service/JMXProxy.java rename to modules/tomcat-lite/java/org/apache/tomcat/integration/jmx/UJmxHandler.java index 284ee2577..c5a2e761b 100644 --- a/modules/tomcat-lite/java/org/apache/tomcat/lite/service/JMXProxy.java +++ b/modules/tomcat-lite/java/org/apache/tomcat/integration/jmx/UJmxHandler.java @@ -16,9 +16,10 @@ */ -package org.apache.tomcat.lite.service; +package org.apache.tomcat.integration.jmx; +import java.io.IOException; import java.io.PrintWriter; import java.util.HashMap; import java.util.Iterator; @@ -28,6 +29,10 @@ import java.util.logging.Logger; import org.apache.tomcat.integration.DynamicObject; import org.apache.tomcat.integration.ObjectManager; +import org.apache.tomcat.lite.http.HttpRequest; +import org.apache.tomcat.lite.http.HttpResponse; +import org.apache.tomcat.lite.http.HttpWriter; +import org.apache.tomcat.lite.http.HttpChannel.HttpService; /** * Send all registered JMX objects and properties as JSON. @@ -40,49 +45,21 @@ import org.apache.tomcat.integration.ObjectManager; * * @author Costin Manolache */ -public class JMXProxy extends ObjectManager implements Runnable { +public class UJmxHandler implements HttpService { - static Logger log = Logger.getLogger(JMXProxy.class.getName()); + private static Logger log = Logger.getLogger(UJmxHandler.class.getName()); + private UJmxObjectManagerSpi jmx; - protected ObjectManager om; - - Map types = new HashMap(); - - Map objects = new HashMap(); - - - public void bind(String name, Object o) { - objects.put(name, o); - } - - public void unbind(String name) { - objects.remove(name); - } - - - public void setObjectManager(ObjectManager om) { - this.om = om; + public UJmxHandler(UJmxObjectManagerSpi jmx) { + this.jmx = jmx; } - - private DynamicObject getClassInfo(Class beanClass) { - if (types.get(beanClass) != null) { - return types.get(beanClass); - } - DynamicObject res = new DynamicObject(beanClass); - types.put(beanClass, res); - return res; - } - - - // --------------------------------------------------------- Public Methods - public void getAttribute(PrintWriter writer, String onameStr, String att) { try { - Object bean = objects.get(onameStr); + Object bean = jmx.objects.get(onameStr); Class beanClass = bean.getClass(); - DynamicObject ci = getClassInfo(beanClass); + DynamicObject ci = jmx.getClassInfo(beanClass); Object value = ci.getAttribute(bean, att); writer.println("OK - Attribute get '" + onameStr + "' - " + att @@ -97,9 +74,9 @@ public class JMXProxy extends ObjectManager implements Runnable { String onameStr, String att, String val ) { try { - Object bean = objects.get(onameStr); + Object bean = jmx.objects.get(onameStr); Class beanClass = bean.getClass(); - DynamicObject ci = getClassInfo(beanClass); + DynamicObject ci = jmx.getClassInfo(beanClass); ci.setProperty(bean, att, val); writer.println("OK - Attribute set"); @@ -114,19 +91,24 @@ public class JMXProxy extends ObjectManager implements Runnable { listBeansJson(writer, qry); return; } - Set names = objects.keySet(); + Set names = jmx.objects.keySet(); writer.println("OK - Number of results: " + names.size()); writer.println(); Iterator it=names.iterator(); while( it.hasNext()) { String oname=it.next(); + if (qry != null && oname.indexOf(qry) < 0) { + continue; + } writer.println( "Name: " + oname); try { - Object bean = objects.get(oname); + Object bean = jmx.objects.get(oname); + Class beanClass = bean.getClass(); - DynamicObject ci = getClassInfo(beanClass); + DynamicObject ci = jmx.getClassInfo(beanClass); + writer.println("modelerType: " + beanClass.getName()); Object value=null; @@ -155,7 +137,7 @@ public class JMXProxy extends ObjectManager implements Runnable { } private void listBeansJson(PrintWriter writer, String qry) { - Set names = objects.keySet(); + Set names = jmx.objects.keySet(); writer.println("["); Iterator it=names.iterator(); @@ -165,9 +147,9 @@ public class JMXProxy extends ObjectManager implements Runnable { json(writer, "name", oname); try { - Object bean = objects.get(oname); + Object bean = jmx.objects.get(oname); Class beanClass = bean.getClass(); - DynamicObject ci = getClassInfo(beanClass); + DynamicObject ci = jmx.getClassInfo(beanClass); json(writer, "modelerType", beanClass.getName()); Object value=null; @@ -234,7 +216,30 @@ public class JMXProxy extends ObjectManager implements Runnable { } @Override - public void run() { + public void service(HttpRequest request, HttpResponse httpRes) + throws IOException { + + httpRes.setContentType("text/plain"); + HttpWriter out = httpRes.getBodyWriter(); + PrintWriter writer = new PrintWriter(out); + String qry = request.getParameter("set"); + if( qry!= null ) { + String name=request.getParameter("att"); + String val=request.getParameter("val"); + + setAttribute( writer, qry, name, val ); + return; + } + qry=request.getParameter("get"); + if( qry!= null ) { + String name=request.getParameter("att"); + getAttribute( writer, qry, name ); + return; + } + qry=request.getParameter("qry"); + + listBeans( writer, qry, request.getParameter("json") != null); + } } diff --git a/modules/tomcat-lite/java/org/apache/tomcat/integration/jmx/UJmxObjectManagerSpi.java b/modules/tomcat-lite/java/org/apache/tomcat/integration/jmx/UJmxObjectManagerSpi.java new file mode 100644 index 000000000..3ba23e177 --- /dev/null +++ b/modules/tomcat-lite/java/org/apache/tomcat/integration/jmx/UJmxObjectManagerSpi.java @@ -0,0 +1,76 @@ +/* + * 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.tomcat.integration.jmx; + + +import java.util.HashMap; +import java.util.Map; +import java.util.logging.Logger; + +import org.apache.tomcat.integration.DynamicObject; +import org.apache.tomcat.integration.ObjectManager; + +/** + * Send all registered JMX objects and properties as JSON. + * + * Based on JMXProxy servlet, but: + * - Async handler instead of servlet - so it works with 'raw' connector + * - doesn't use JMX - integrates with the ObjectManager ( assuming OM + * provies a list of managed objects ) + * - all the reflection magic from modeler is implemented here. + * + * @author Costin Manolache + */ +public class UJmxObjectManagerSpi extends ObjectManager { + + private static Logger log = Logger.getLogger(UJmxObjectManagerSpi.class.getName()); + + private ObjectManager om; + + Map types = new HashMap(); + + Map objects = new HashMap(); + + @Override + public void bind(String name, Object o) { + if (objects.get(name) != null) { + log.warning("Duplicated name " + name); + } + objects.put(name, o); + } + + @Override + public void unbind(String name) { + objects.remove(name); + } + + // Dynamic + public void setObjectManager(ObjectManager om) { + this.om = om; + } + + DynamicObject getClassInfo(Class beanClass) { + if (types.get(beanClass) != null) { + return types.get(beanClass); + } + DynamicObject res = new DynamicObject(beanClass); + types.put(beanClass, res); + return res; + } +} diff --git a/modules/tomcat-lite/java/org/apache/tomcat/lite/http/CompressFilter.java b/modules/tomcat-lite/java/org/apache/tomcat/lite/http/CompressFilter.java index ffeb036fa..d78c55e59 100644 --- a/modules/tomcat-lite/java/org/apache/tomcat/lite/http/CompressFilter.java +++ b/modules/tomcat-lite/java/org/apache/tomcat/lite/http/CompressFilter.java @@ -57,7 +57,8 @@ public class CompressFilter { // can't call: cStream.free(); - will kill the adler, NPE cStream = new ZStream(); // BEST_COMRESSION results in 256Kb per Deflate - cStream.deflateInit(JZlib.Z_BEST_SPEED); + // 15 == default = 32k window + cStream.deflateInit(JZlib.Z_BEST_SPEED, 10); dStream = new ZStream(); dStream.inflateInit(); 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 7794fadfa..2571a85ad 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 @@ -67,6 +67,7 @@ public class Http11Connection extends HttpConnection private int requestCount = 0; + // dataReceived and endSendReceive private Object readLock = new Object(); public Http11Connection(HttpConnector httpConnector) { @@ -318,6 +319,8 @@ public class Http11Connection extends HttpConnection switchedProtocol.endSendReceive(http); return; } + chunk.recycle(); + rchunk.recycle(); boolean keepAlive = keepAlive(); if (!keepAlive) { if (debug) { @@ -329,14 +332,13 @@ public class Http11Connection extends HttpConnection } } - synchronized (readLock) { - requestCount++; - beforeRequest(); - httpConnector.cpool.afterRequest(http, this, true); - if (serverMode && keepAlive) { - handleReceived(net); // will attempt to read next req - } + requestCount++; + beforeRequest(); + httpConnector.cpool.afterRequest(http, this, true); + + if (serverMode && keepAlive) { + handleReceived(net); // will attempt to read next req } } @@ -680,7 +682,7 @@ public class Http11Connection extends HttpConnection int rc = NEED_MORE; // TODO: simplify, use readLine() while (rc == NEED_MORE) { - rc = chunk.parseChunkHeader(rawReceiveBuffers); + rc = rchunk.parseChunkHeader(rawReceiveBuffers); if (rc == ERROR) { http.abort("Chunk error"); receiveDone(http, body, true); @@ -1218,6 +1220,7 @@ public class Http11Connection extends HttpConnection // used for chunk parsing/end ChunkState chunk = new ChunkState(); + ChunkState rchunk = new ChunkState(); static final int NEED_MORE = -1; static final int ERROR = -4; static final int DONE = -5; diff --git a/modules/tomcat-lite/java/org/apache/tomcat/lite/http/HttpConnectionPool.java b/modules/tomcat-lite/java/org/apache/tomcat/lite/http/HttpConnectionPool.java new file mode 100644 index 000000000..363ac59bd --- /dev/null +++ b/modules/tomcat-lite/java/org/apache/tomcat/lite/http/HttpConnectionPool.java @@ -0,0 +1,404 @@ +/* + */ +package org.apache.tomcat.lite.http; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashMap; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; +import java.util.Queue; +import java.util.Set; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.logging.Logger; + +import org.apache.tomcat.lite.http.HttpConnector.HttpConnection; +import org.apache.tomcat.lite.io.IOChannel; +import org.apache.tomcat.lite.io.IOConnector; + +/** + * - Holds references to all active and kept-alive connections. + * - makes decisions on accepting more connections, closing old + * connections, etc + * + */ +public class HttpConnectionPool { + // TODO: add timeouts, limits per host/total, expire old entries + + public static interface HttpConnectionPoolEvents { + public void newTarget(RemoteServer host); + + public void targetRemoved(RemoteServer host); + + public void newConnection(RemoteServer host, HttpConnection con); + public void closedConnection(RemoteServer host, HttpConnection con); + } + + /** + * Connections for one remote host. + * This should't be restricted by IP:port or even hostname, + * for example if a server has multiple IPs or LB replicas - any would work. + */ + public static class RemoteServer { + // all access sync on RemoteServer + private SpdyConnection spdy; + + // all access sync on RemoteServer + private ArrayList connections + = new ArrayList(); + + Queue pending = new LinkedList(); + + + // TODO: setter, default from connector + private int maxConnections = 20; + + AtomicInteger activeRequests = new AtomicInteger(); + AtomicInteger totalRequests = new AtomicInteger(); + private volatile long lastActivity; + + public String target; + + public synchronized List getConnections() + { + return new ArrayList(connections); + } + + public synchronized Collection getActives() { + ArrayList actives = new ArrayList(); + for (Http11Connection con: connections) { + if (con.activeHttp != null) { + actives.add(con.activeHttp); + } + } + if (spdy != null) { + actives.addAll(spdy.getActives()); + } + + return actives; + } + + public synchronized void touch() { + lastActivity = System.currentTimeMillis(); + } + } + + private HttpConnectionPoolEvents poolEvents; + + private static Logger log = Logger.getLogger("HttpConnector"); + + // visible for debugging - will be made private, with accessor + /** + * Map from client names to socket pools. + */ + public Map hosts = new HashMap(); + + // Statistics + public AtomicInteger waitingSockets = new AtomicInteger(); + public AtomicInteger closedSockets = new AtomicInteger(); + + public AtomicInteger hits = new AtomicInteger(); + public AtomicInteger misses = new AtomicInteger(); + public AtomicInteger queued = new AtomicInteger(); + + public AtomicInteger activeRequests = new AtomicInteger(); + + private static boolean debug = false; + HttpConnector httpConnector; + + public HttpConnectionPool(HttpConnector httpConnector) { + this.httpConnector = httpConnector; + } + + public int getTargetCount() { + return hosts.size(); + } + + public int getSocketCount() { + return waitingSockets.get(); + } + + public int getClosedSockets() { + return closedSockets.get(); + } + + public Set getKeepAliveTargets() { + return hosts.keySet(); + } + + public List getServers() { + return new ArrayList(hosts.values()); + } + + public void setEvents(HttpConnectionPoolEvents events) { + this.poolEvents = events; + } + /** + * Stop all cached connections. + */ + public void clear() throws IOException { + synchronized (hosts) { + int active = 0; + for (RemoteServer rs: hosts.values()) { + synchronized (rs) { + int hostActive = 0; + if (rs.spdy != null) { + if (rs.spdy.channels.size() == 0) { + rs.spdy.close(); + rs.spdy = null; + } else { + hostActive += rs.spdy.channels.size(); + } + } + for (Http11Connection con: rs.connections) { + if (con.activeHttp == null) { + con.close(); + } else { + hostActive++; + } + } + if (hostActive != rs.activeRequests.get()) { + log.warning("Active missmatch " + rs.target + " " + + hostActive + " " + + rs.activeRequests.get()); + rs.activeRequests.set(hostActive); + } + active += hostActive; + } + } + if (active != this.activeRequests.get()) { + log.warning("Active missmatch " + active + " " + + activeRequests.get()); + activeRequests.set(active); + } + } + } + + /** + * Stop all active and cached connections + * @throws IOException + */ + public void abort() throws IOException { + // TODO + clear(); + hosts.clear(); + } + + /** + * @param key host:port, or some other key if multiple hosts:ips + * are connected to equivalent servers ( LB ) + * @param httpCh + * @throws IOException + */ + public void send(HttpChannel httpCh) + throws IOException { + String target = httpCh.getTarget(); + HttpConnection con = null; + // TODO: check ssl on connection - now if a second request + // is received on a ssl connection - we just send it + boolean ssl = httpCh.getRequest().isSecure(); + + HttpConnectionPool.RemoteServer remoteServer = null; + synchronized (hosts) { + remoteServer = hosts.get(target); + if (remoteServer == null) { + remoteServer = new HttpConnectionPool.RemoteServer(); + remoteServer.target = target; + hosts.put(target, remoteServer); + } + } + + // TODO: remove old servers and connections + + // Temp magic - until a better negotiation is defined + boolean forceSpdy = "SPDY/1.0".equals(httpCh.getRequest().getProtocol()); + if (forceSpdy) { + // switch back the protocol + httpCh.getRequest().setProtocol("HTTP/1.1"); + } + + activeRequests.incrementAndGet(); + remoteServer.activeRequests.incrementAndGet(); + + // if we already have a spdy connection or explicitely + // requested. + if (forceSpdy || remoteServer.spdy != null) { + synchronized (remoteServer) { + if (remoteServer.spdy == null) { + remoteServer.spdy = new SpdyConnection(httpConnector, + remoteServer); + } + con = remoteServer.spdy; + } + + // Will be queued - multiple threads may try to send + // at the same time, and we need to queue anyways. + con.sendRequest(httpCh); + } else { + synchronized (remoteServer) { + Http11Connection hcon; + for (int i = 0; i < remoteServer.connections.size(); i++) { + hcon = (Http11Connection) remoteServer.connections.get(i); + if (hcon != null && hcon.activeHttp == null) { + hcon.beforeRequest(); // recycle + + hcon.activeHttp = httpCh; + con = hcon; + break; + } + } + if (con == null) { +// if (remoteServer.connections.size() > remoteServer.maxConnections) { +// remoteServer.pending.add(httpCh); +// queued.incrementAndGet(); +// if (debug) { +// log.info("Queue: " + target + " " + remoteServer.connections.size()); +// } +// return; +// } + hcon = new Http11Connection(httpConnector); + hcon.setTarget(target); + hcon.activeHttp = httpCh; + hcon.remoteHost = remoteServer; + remoteServer.connections.add(hcon); + con = hcon; + } + } + + + + + // we got a connection - make sure we're connected + http11ConnectOrSend(httpCh, target, con, ssl); + } + } + + private void http11ConnectOrSend(HttpChannel httpCh, String target, + HttpConnection con, boolean ssl) throws IOException { + httpCh.setConnection(con); + + if (con.isOpen()) { + hits.incrementAndGet(); + if (debug) { + httpCh.trace("HTTP_CONNECT: Reuse connection " + target + " " + this); + } + con.sendRequest(httpCh); + } else { + misses.incrementAndGet(); + if (debug) { + httpCh.trace("HTTP_CONNECT: Start connection " + target + " " + this); + } + httpConnect(httpCh, target, ssl, + (Http11Connection) con); + } + } + + void httpConnect(HttpChannel httpCh, String target, + boolean ssl, IOConnector.ConnectedCallback cb) + throws IOException { + if (debug) { + httpCh.trace("HTTP_CONNECT: New connection " + target); + } + String[] hostPort = target.split(":"); + + int targetPort = ssl ? 443 : 80; + if (hostPort.length > 1) { + targetPort = Integer.parseInt(hostPort[1]); + } + + httpConnector.getIOConnector().connect(hostPort[0], targetPort, + cb); + } + + public void afterRequest(HttpChannel http, HttpConnection con, + boolean keepAlive) + throws IOException { + activeRequests.decrementAndGet(); + if (con.remoteHost != null) { + con.remoteHost.touch(); + con.remoteHost.activeRequests.decrementAndGet(); + } + if (con.serverMode) { + afterServerRequest(con, keepAlive); + } else { + afterClientRequest(con); + } + } + + private void afterClientRequest(HttpConnection con) + throws IOException { + RemoteServer remoteServer = con.remoteHost; + HttpChannel req = null; + + // If we have pending requests ( because too many active limit ), pick + // one and send it. + synchronized (remoteServer) { + // If closed - we can remove the object - or + // let a background thread do it, in case it's needed + // again. + if (remoteServer.pending.size() == 0) { + con.activeHttp = null; + if (debug) { + log.info("After request: no pending"); + } + return; + } + req = remoteServer.pending.remove(); + con.activeHttp = req; + if (debug) { + log.info("After request: send pending " + remoteServer.pending.size()); + } + } + + http11ConnectOrSend(req, con.getTarget().toString(), + con, req.getRequest().isSecure()); + } + + RemoteServer serverPool = new RemoteServer(); + + public void afterServerRequest(HttpConnection con, boolean keepAlive) + throws IOException { + con.activeHttp = null; + if (!keepAlive) { + synchronized (serverPool) { + // I could also reuse the object. + serverPool.connections.remove(con); + } + } + } + + public HttpConnection accepted(IOChannel accepted) { + Http11Connection con = new Http11Connection(httpConnector); + con.remoteHost = serverPool; + synchronized (serverPool) { + serverPool.connections.add(con); + } + return con; + } + + + // Called by handleClosed + void stopKeepAlive(IOChannel schannel) { + CharSequence target = schannel.getTarget(); + HttpConnectionPool.RemoteServer remoteServer = null; + synchronized (hosts) { + remoteServer = hosts.get(target); + if (remoteServer == null) { + return; + } + } + synchronized (remoteServer) { + if (remoteServer.connections.remove(schannel)) { + waitingSockets.decrementAndGet(); + if (remoteServer.connections.size() == 0) { + hosts.remove(target); + } + } + } + } + + +} 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 42313ab0e..63a657275 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 @@ -91,6 +91,8 @@ public class HttpConnector { private Timer timer; + boolean compression = true; + private static Timer defaultTimer = new Timer(true); public HttpConnector(IOConnector ioConnector) { @@ -125,6 +127,15 @@ public class HttpConnector { public void setDebugHttp(boolean b) { this.debugHttp = b; } + + /** + * Allow or disable compression for this connector. + * Compression is enabled by default. + */ + public HttpConnector setCompression(boolean b) { + this.compression = b; + return this; + } public void setClientKeepAlive(boolean b) { this.clientKeepAlive = b; @@ -472,4 +483,5 @@ public class HttpConnector { } } + } diff --git a/modules/tomcat-lite/java/org/apache/tomcat/lite/http/MultiMap.java b/modules/tomcat-lite/java/org/apache/tomcat/lite/http/MultiMap.java index 515c2df59..97fca7cd6 100644 --- a/modules/tomcat-lite/java/org/apache/tomcat/lite/http/MultiMap.java +++ b/modules/tomcat-lite/java/org/apache/tomcat/lite/http/MultiMap.java @@ -27,7 +27,6 @@ import java.util.Map; import org.apache.tomcat.lite.io.BBuffer; import org.apache.tomcat.lite.io.CBucket; import org.apache.tomcat.lite.io.CBuffer; -import org.apache.tomcat.util.buf.CharChunk; /** * Map used to represent headers and parameters ( could be used diff --git a/modules/tomcat-lite/java/org/apache/tomcat/lite/http/SpdyConnection.java b/modules/tomcat-lite/java/org/apache/tomcat/lite/http/SpdyConnection.java index 9cdb95f27..e4c2a77dd 100644 --- a/modules/tomcat-lite/java/org/apache/tomcat/lite/http/SpdyConnection.java +++ b/modules/tomcat-lite/java/org/apache/tomcat/lite/http/SpdyConnection.java @@ -5,10 +5,7 @@ package org.apache.tomcat.lite.http; import java.io.IOException; import java.util.Collection; import java.util.HashMap; -import java.util.LinkedList; -import java.util.List; import java.util.Map; -import java.util.Queue; import java.util.concurrent.atomic.AtomicInteger; import java.util.logging.Level; import java.util.logging.Logger; @@ -102,6 +99,19 @@ public class SpdyConnection extends HttpConnector.HttpConnection .setDictionary(SPDY_DICT, DICT_ID); IOBuffer headerCompressBuffer = new IOBuffer(); + AtomicInteger inFrames = new AtomicInteger(); + AtomicInteger inDataFrames = new AtomicInteger(); + AtomicInteger inSyncStreamFrames = new AtomicInteger(); + AtomicInteger inBytes = new AtomicInteger(); + + AtomicInteger outFrames = new AtomicInteger(); + AtomicInteger outDataFrames = new AtomicInteger(); + AtomicInteger outBytes = new AtomicInteger(); + + + volatile boolean connecting = false; + volatile boolean connected = false; + // TODO: detect if it's spdy or http based on bit 8 @Override @@ -139,16 +149,7 @@ public class SpdyConnection extends HttpConnector.HttpConnection } } - AtomicInteger inFrames = new AtomicInteger(); - AtomicInteger inDataFrames = new AtomicInteger(); - AtomicInteger inSyncStreamFrames = new AtomicInteger(); - AtomicInteger inBytes = new AtomicInteger(); - AtomicInteger outFrames = new AtomicInteger(); - AtomicInteger outDataFrames = new AtomicInteger(); - AtomicInteger outBytes = new AtomicInteger(); - - /** * Frame received. Must consume all data for the frame. @@ -367,15 +368,11 @@ public class SpdyConnection extends HttpConnector.HttpConnection return; } MultiMap mimeHeaders = http.getRequest().getMimeHeaders(); + BBuffer headBuf = BBuffer.allocate(); - SpdyConnection.appendShort(headBuf, mimeHeaders.size() + 3); - serializeMime(mimeHeaders, headBuf); - if (headerCompression) { - } - // TODO: url - with host prefix , method // optimize... SpdyConnection.appendAsciiHead(headBuf, "version"); @@ -387,7 +384,14 @@ public class SpdyConnection extends HttpConnector.HttpConnection SpdyConnection.appendAsciiHead(headBuf, "url"); // TODO: url SpdyConnection.appendAsciiHead(headBuf, http.getRequest().requestURL()); - + + if (headerCompression && httpConnector.compression) { + headerCompressBuffer.recycle(); + headCompressOut.compress(headBuf, headerCompressBuffer, false); + headBuf.recycle(); + headerCompressBuffer.copyAll(headBuf); + } + // Frame head - 8 BBuffer out = BBuffer.allocate(); // Syn-reply @@ -417,6 +421,7 @@ public class SpdyConnection extends HttpConnector.HttpConnection http.channelId = 2 * lastOutStream.incrementAndGet() + 1; } SpdyConnection.appendInt(out, http.channelId); + http.setConnection(this); synchronized (this) { @@ -722,10 +727,6 @@ public class SpdyConnection extends HttpConnector.HttpConnection // TODO: send interrupt signal } - - - volatile boolean connecting = false; - volatile boolean connected = false; private boolean checkConnection(HttpChannel http) throws IOException { diff --git a/modules/tomcat-lite/java/org/apache/tomcat/lite/io/BBuffer.java b/modules/tomcat-lite/java/org/apache/tomcat/lite/io/BBuffer.java index 3ceb5da0b..f4053a056 100644 --- a/modules/tomcat-lite/java/org/apache/tomcat/lite/io/BBuffer.java +++ b/modules/tomcat-lite/java/org/apache/tomcat/lite/io/BBuffer.java @@ -156,14 +156,14 @@ public class BBuffer implements Cloneable, Serializable, public static BBuffer allocate(String msg) { BBuffer bc = allocate(); - byte[] data = msg.getBytes(UTF8); + byte[] data = msg.getBytes(); bc.append(data, 0, data.length); return bc; } public static BBuffer wrapper(String msg) { BBuffer bc = new IOBucketWrap(); - byte[] data = msg.getBytes(UTF8); + byte[] data = msg.getBytes(); bc.setBytes(data, 0, data.length); return bc; } @@ -500,6 +500,9 @@ public class BBuffer implements Cloneable, Serializable, } public int indexOf(String src, int srcOff, int srcLen, int myOff) { + if ("".equals(src)) { + return myOff; + } char first = src.charAt(srcOff); // Look for first char 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 9b50f493b..0b9a09e78 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 @@ -40,6 +40,10 @@ public class SocketConnector extends IOConnector { timer = new Timer(true); } + public SocketConnector(int port) { + timer = new Timer(true); + } + /** * This may be blocking - involves host resolution, connect. * If the IP address is provided - it shouldn't block. @@ -109,7 +113,7 @@ public class SocketConnector extends IOConnector { static int id = 0; - synchronized NioThread getSelector() { + public synchronized NioThread getSelector() { if (selector == null) { String name = "SelectorThread-" + id++; selector = new NioThread(name, true); diff --git a/modules/tomcat-lite/java/org/apache/tomcat/lite/io/SocketIOChannel.java b/modules/tomcat-lite/java/org/apache/tomcat/lite/io/SocketIOChannel.java index ec3f5155a..6c75d4818 100644 --- a/modules/tomcat-lite/java/org/apache/tomcat/lite/io/SocketIOChannel.java +++ b/modules/tomcat-lite/java/org/apache/tomcat/lite/io/SocketIOChannel.java @@ -142,6 +142,7 @@ public class SocketIOChannel extends IOChannel implements NioChannelCallback { // we place the Buckets in the queue, as 'readable' buffers. boolean newData = false; try { + int read = 0; synchronized(in) { // data between 0 and position int total = 0; @@ -149,39 +150,36 @@ public class SocketIOChannel extends IOChannel implements NioChannelCallback { if (in.isAppendClosed()) { // someone closed me ? ch.inputClosed(); // remove read interest. // if outClosed - close completely - super.sendHandleReceivedCallback(); - return; + newData = true; + break; } ByteBuffer bb = in.getWriteBuffer(); - int read = ch.read(bb); + read = ch.read(bb); in.releaseWriteBuffer(read); - if (in == null) { - // Detached. - if (newData) { - sendHandleReceivedCallback(); - } - return; + if (in == null) { // Detached. + break; } if (read < 0) { // mark the in buffer as closed in.close(); ch.inputClosed(); - sendHandleReceivedCallback(); - return; + newData = true; + break; } if (read == 0) { - if (newData) { - super.sendHandleReceivedCallback(); - } - return; + break; } total += read; newData = true; } + } // sync + if (newData) { + super.sendHandleReceivedCallback(); } + } catch (Throwable t) { close(); if (t instanceof IOException) { diff --git a/modules/tomcat-lite/java/org/apache/tomcat/lite/proxy/StaticContentService.java b/modules/tomcat-lite/java/org/apache/tomcat/lite/proxy/StaticContentService.java index dac28eb66..c45b2110d 100644 --- a/modules/tomcat-lite/java/org/apache/tomcat/lite/proxy/StaticContentService.java +++ b/modules/tomcat-lite/java/org/apache/tomcat/lite/proxy/StaticContentService.java @@ -36,6 +36,7 @@ public class StaticContentService implements HttpService { protected BBucket mb; protected boolean chunked = false; + int code = 200; protected String contentType = "text/plain"; @@ -43,6 +44,10 @@ public class StaticContentService implements HttpService { public StaticContentService() { } + /** + * Used for testing chunked encoding. + * @return + */ public StaticContentService chunked() { chunked = true; return this; @@ -52,6 +57,11 @@ public class StaticContentService implements HttpService { mb = BBuffer.wrapper(data, 0, data.length); return this; } + + public StaticContentService setStatus(int status) { + this.code = status; + return this; + } public StaticContentService withLen(int len) { byte[] data = new byte[len]; @@ -97,19 +107,23 @@ public class StaticContentService implements HttpService { @Override public void service(HttpRequest httpReq, HttpResponse res) throws IOException { - res.setStatus(200); + res.setStatus(code); if (!chunked) { res.setContentLength(mb.remaining()); } res.setContentType(contentType); + int len = mb.remaining(); + int first = 0; + if (chunked) { + first = len / 2; res.getBody() - .queue(BBuffer.wrapper(mb, 0, mb.remaining())); + .queue(BBuffer.wrapper(mb, 0, first)); res.flush(); } - res.getBody().queue(BBuffer.wrapper(mb, 0, mb.remaining())); + res.getBody().queue(BBuffer.wrapper(mb, 0, len - first)); } } \ No newline at end of file diff --git a/modules/tomcat-lite/java/org/apache/tomcat/lite/servlet/ServletConfigImpl.java b/modules/tomcat-lite/java/org/apache/tomcat/lite/servlet/ServletConfigImpl.java index ee2b436c9..235d1e1d2 100644 --- a/modules/tomcat-lite/java/org/apache/tomcat/lite/servlet/ServletConfigImpl.java +++ b/modules/tomcat-lite/java/org/apache/tomcat/lite/servlet/ServletConfigImpl.java @@ -40,10 +40,12 @@ import javax.servlet.SingleThreadModel; import javax.servlet.UnavailableException; import javax.servlet.http.HttpServletResponse; +import org.apache.tomcat.integration.jmx.JMXProxyServlet; import org.apache.tomcat.lite.http.HttpChannel; import org.apache.tomcat.lite.http.HttpRequest; import org.apache.tomcat.lite.http.HttpResponse; import org.apache.tomcat.lite.http.MappingData; +import org.apache.tomcat.lite.io.WrappedException; import org.apache.tomcat.servlets.jsp.BaseJspLoader; import org.apache.tomcat.servlets.util.Enumerator; @@ -136,6 +138,15 @@ public class ServletConfigImpl implements ServletConfig, HttpChannel.HttpService ctx.lite.notifyAdd(this); } + public ServletConfigImpl(Servlet realServlet) throws IOException { + instance = realServlet; + try { + realServlet.init(this); + } catch (ServletException e) { + throw new WrappedException(e); + } + } + /** * Return the available date/time for this servlet, in milliseconds since * the epoch. If this date/time is Long.MAX_VALUE, it is considered to mean @@ -768,6 +779,11 @@ public class ServletConfigImpl implements ServletConfig, HttpChannel.HttpService ServletResponseImpl res = req.getResponse(); // TODO + try { + instance.service(req, res); + } catch (ServletException e) { + throw new WrappedException(e); + } } /** Coyote / mapper adapter. Result of the mapper. diff --git a/modules/tomcat-lite/java/org/apache/tomcat/servlets/jmx/JmxObjectManagerSpi.java b/modules/tomcat-lite/java/org/apache/tomcat/servlets/jmx/JmxObjectManagerSpi.java deleted file mode 100644 index 47578ccd1..000000000 --- a/modules/tomcat-lite/java/org/apache/tomcat/servlets/jmx/JmxObjectManagerSpi.java +++ /dev/null @@ -1,41 +0,0 @@ -/* - */ -package org.apache.tomcat.servlets.jmx; - -import java.util.logging.Logger; - -import org.apache.tomcat.integration.ObjectManager; -import org.apache.tomcat.util.modeler.Registry; - -/** - * Plugin for integration with JMX. - * - * All objects of interest are registered automatically. - */ -public class JmxObjectManagerSpi extends ObjectManager { - Registry registry; - Logger log = Logger.getLogger("JmxObjectManager"); - - public JmxObjectManagerSpi() { - registry = Registry.getRegistry(null, null); - } - - public void bind(String name, Object o) { - try { - registry.registerComponent(o, - ":name=\"" + name + "\"", null); - } catch (Exception e) { - log.severe("Error registering" + e); - } - } - - public void unbind(String name) { - registry.unregisterComponent(":name=\"" + name + "\""); - } - - @Override - public Object get(String key) { - return null; - } - -} diff --git a/modules/tomcat-lite/test/org/apache/tomcat/lite/TestMain.java b/modules/tomcat-lite/test/org/apache/tomcat/lite/TestMain.java index 9cd3386e0..41126d831 100644 --- a/modules/tomcat-lite/test/org/apache/tomcat/lite/TestMain.java +++ b/modules/tomcat-lite/test/org/apache/tomcat/lite/TestMain.java @@ -8,17 +8,24 @@ import java.io.InputStream; import java.net.HttpURLConnection; import java.net.URL; +import org.apache.tomcat.integration.jmx.JMXProxyServlet; +import org.apache.tomcat.integration.jmx.JmxObjectManagerSpi; +import org.apache.tomcat.integration.jmx.UJmxHandler; +import org.apache.tomcat.integration.jmx.UJmxObjectManagerSpi; import org.apache.tomcat.integration.simple.Main; import org.apache.tomcat.integration.simple.SimpleObjectManager; import org.apache.tomcat.lite.http.BaseMapper; import org.apache.tomcat.lite.http.DefaultHttpConnector; import org.apache.tomcat.lite.http.Dispatcher; import org.apache.tomcat.lite.http.HttpChannel; +import org.apache.tomcat.lite.http.HttpConnectionPool; 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.HttpChannel.HttpService; +import org.apache.tomcat.lite.http.HttpConnectionPool.RemoteServer; import org.apache.tomcat.lite.http.HttpConnector.HttpChannelEvents; +import org.apache.tomcat.lite.http.HttpConnector.HttpConnection; import org.apache.tomcat.lite.http.services.EchoCallback; import org.apache.tomcat.lite.http.services.SleepCallback; import org.apache.tomcat.lite.io.BBuffer; @@ -27,6 +34,7 @@ import org.apache.tomcat.lite.io.SslConnector; import org.apache.tomcat.lite.proxy.HttpProxyService; import org.apache.tomcat.lite.proxy.StaticContentService; import org.apache.tomcat.lite.service.IOStatus; +import org.apache.tomcat.lite.servlet.ServletConfigImpl; import org.apache.tomcat.util.buf.ByteChunk; /** @@ -53,7 +61,9 @@ public class TestMain { private HttpConnector testProxy = new HttpConnector(serverCon); private HttpProxyService proxy; - + + UJmxObjectManagerSpi jmx = new UJmxObjectManagerSpi(); + public static TestMain shared() { if (defaultServer == null) { defaultServer = new TestMain(); @@ -70,7 +80,7 @@ public class TestMain { return shared().testClient; } - public void initTestCallback(Dispatcher d) { + public void initTestCallback(Dispatcher d) throws IOException { BaseMapper.ContextMapping mCtx = d.addContext(null, "", null, null, null, null); d.addWrapper(mCtx, "/", new StaticContentService() @@ -80,6 +90,9 @@ public class TestMain { "Proxy pool
" + "")); + d.addWrapper(mCtx, "/favicon.ico", + new StaticContentService().setStatus(404).setData("Not found")); + d.addWrapper(mCtx, "/hello", new StaticContentService().setData("Hello world")); d.addWrapper(mCtx, "/2nd", new StaticContentService().setData("Hello world2")); d.addWrapper(mCtx, "/echo/*", new EchoCallback()); @@ -104,6 +117,9 @@ public class TestMain { } }); + d.addWrapper(mCtx, "/ujmx", new UJmxHandler(jmx)); + d.addWrapper(mCtx, "/jmx", + new ServletConfigImpl(new JMXProxyServlet())); } public void run() { @@ -114,9 +130,22 @@ public class TestMain { } } + public int getServerPort() { + return 8802; + } + + public int getProxyPort() { + return 8903; + } + + public int getSslServerPort() { + return 8443; + } + protected void startAll(int basePort) throws IOException { int port = basePort + 903; if (proxy == null) { + proxy = new HttpProxyService() .withHttpClient(testClient); testProxy.setPort(port); @@ -156,7 +185,10 @@ public class TestMain { // testServer.setDebug(true); // sslServer.setDebug(true); // sslServer.setDebugHttp(true); - + + // Bind the objects, make them visible in JMX + // additional settings from config + initObjectManager("org/apache/tomcat/lite/test.properties"); } Runtime.getRuntime().addShutdownHook(new Thread() { @@ -168,12 +200,63 @@ public class TestMain { } }); } + + public void bindConnector(HttpConnector con, final String base) { + om.bind("HttpConnector-" + base, con); + om.bind("HttpConnectionPool-" + base, con.cpool); + SocketConnector sc = (SocketConnector) con.getIOConnector(); + om.bind("NioThread-" + base, sc.getSelector()); + + con.cpool.setEvents(new HttpConnectionPool.HttpConnectionPoolEvents() { + @Override + public void closedConnection(RemoteServer host, HttpConnection con) { + om.unbind("HttpConnection-" + base + "-" + con.getId()); + } + + @Override + public void newConnection(RemoteServer host, HttpConnection con) { + om.bind("HttpConnection-" + base + "-" + con.getId(), con); + } + + @Override + public void newTarget(RemoteServer host) { + om.bind("AsyncHttp-" + base + "-" + host.target, host); + } + + @Override + public void targetRemoved(RemoteServer host) { + om.unbind("AsyncHttp-" + base + "-" + host.target); + } + + }); + + con.setOnCreate(new HttpChannelEvents() { + @Override + public void onCreate(HttpChannel data, HttpConnector extraData) + throws IOException { + om.bind("AsyncHttp-" + base + "-" + data.getId(), data); + } + @Override + public void onDestroy(HttpChannel data, HttpConnector extraData) + throws IOException { + om.unbind("AsyncHttp-" + base + "-" + data.getId()); + } + }); + + + } + private void initObjectManager(String cfgFile) { if (om == null) { om = new SimpleObjectManager(); } - + // All objects visible in JMX via util.registry + // ( optional dependency ) + om.register(new JmxObjectManagerSpi()); + om.register(jmx); + + om.loadResource(cfgFile); String run = (String) om.getProperty("RUN"); String[] runNames = run == null ? new String[] {} : run.split(","); @@ -185,24 +268,10 @@ public class TestMain { } } - om.bind("HttpConnector-TestServer", testServer); - om.bind("HttpConnector", testClient); - om.bind("HttpConnector-Proxy", testProxy); + bindConnector(testServer, "TestServer"); + bindConnector(testClient, "Client"); + bindConnector(testProxy, "Proxy"); - testServer.setOnCreate(new HttpChannelEvents() { - @Override - public void onCreate(HttpChannel data, HttpConnector extraData) - throws IOException { - //data.trace("BIND"); - om.bind("AsyncHttp-" + data.getId(), data); - } - @Override - public void onDestroy(HttpChannel data, HttpConnector extraData) - throws IOException { - //data.trace("UNBIND"); - om.unbind("AsyncHttp-" + data.getId()); - } - }); } @@ -251,7 +320,6 @@ public class TestMain { TestMain testMain = new TestMain(); TestMain.defaultServer = testMain; testMain.om = new SimpleObjectManager(args); - testMain.initObjectManager("org/apache/tomcat/lite/test.properties"); testMain.run(); Main.waitStop(); } 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 4487bceee..1304038b7 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 @@ -38,8 +38,8 @@ import org.apache.tomcat.lite.TestMain; 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.SpdyConnection; import org.apache.tomcat.lite.http.HttpChannel.RequestCompleted; +import org.apache.tomcat.lite.io.SocketConnector; import org.apache.tomcat.util.buf.ByteChunk; /* @@ -70,9 +70,20 @@ import org.apache.tomcat.util.buf.ByteChunk; public class LiveHttpThreadedTest extends TestCase { HttpConnector clientCon = TestMain.shared().getClient(); HttpConnector serverCon = TestMain.shared().getTestServer(); + + HttpConnector spdyClient = + new HttpConnector(new SocketConnector()).setCompression(false); + + HttpConnector spdyClientCompress = + new HttpConnector(new SocketConnector()); + + HttpConnector spdyClientCompressSsl = + new HttpConnector(new SocketConnector()); + ThreadRunner tr; static MBeanServer server; - + static boolean dumpHeap = false; + AtomicInteger ok = new AtomicInteger(); Object lock = new Object(); int reqCnt; @@ -85,7 +96,7 @@ public class LiveHttpThreadedTest extends TestCase { public void test1000Async() throws Exception { try { - asyncRequest(10, 100, false); + asyncRequest(10, 100, false, clientCon); } finally { dumpHeap("heapAsync.bin"); } @@ -94,7 +105,7 @@ public class LiveHttpThreadedTest extends TestCase { public void test10000Async() throws Exception { try { - asyncRequest(20, 500, false); + asyncRequest(20, 500, false, clientCon); } finally { dumpHeap("heapAsyncBig.bin"); } @@ -102,7 +113,7 @@ public class LiveHttpThreadedTest extends TestCase { public void test1000AsyncSpdy() throws Exception { try { - asyncRequest(10, 100, true); + asyncRequest(10, 100, true, spdyClient); } finally { dumpHeap("heapSpdy1000.bin"); } @@ -111,14 +122,31 @@ public class LiveHttpThreadedTest extends TestCase { public void test10000AsyncSpdy() throws Exception { try { - asyncRequest(20, 500, true); + asyncRequest(20, 500, true, spdyClient); + } finally { + dumpHeap("heapSpdy10000.bin"); + } + } + + public void test1000AsyncSpdyComp() throws Exception { + try { + asyncRequest(10, 100, true, spdyClientCompress); + } finally { + dumpHeap("heapSpdy1000Comp.bin"); + } + + } + + public void test10000AsyncSpdyComp() throws Exception { + try { + asyncRequest(20, 500, true, spdyClientCompress); } finally { dumpHeap("heapSpdy10000.bin"); } } public void asyncRequest(int thr, int perthr, - final boolean spdy) throws Exception { + final boolean spdy, final HttpConnector clientCon) throws Exception { reqCnt = thr * perthr; long t0 = System.currentTimeMillis(); tr = new ThreadRunner(thr, perthr) { @@ -156,7 +184,7 @@ public class LiveHttpThreadedTest extends TestCase { urlRequest(10, 100); } - public void testURLRequest10000() throws Exception { + public void xtestURLRequest10000() throws Exception { urlRequest(20, 500); } @@ -205,7 +233,9 @@ public class LiveHttpThreadedTest extends TestCase { // TODO: move to a servlet private void dumpHeap(String file) throws InstanceNotFoundException, MBeanException, ReflectionException, MalformedObjectNameException { - + if (!dumpHeap) { + return; + } if (server == null) { server = ManagementFactory.getPlatformMBeanServer(); diff --git a/modules/tomcat-lite/test/org/apache/tomcat/lite/proxy/ProxyTest.java b/modules/tomcat-lite/test/org/apache/tomcat/lite/proxy/ProxyTest.java index 1c40a2dfc..1d21a57ad 100644 --- a/modules/tomcat-lite/test/org/apache/tomcat/lite/proxy/ProxyTest.java +++ b/modules/tomcat-lite/test/org/apache/tomcat/lite/proxy/ProxyTest.java @@ -106,7 +106,7 @@ public class ProxyTest extends TestCase { String resStr = TestMain.get("http://localhost:8903/chunked/test") .toString(); - assertEquals(8, resStr.length()); + assertEquals(4, resStr.length()); assertTrue(resStr.indexOf("AAA") >= 0); } diff --git a/modules/tomcat-lite/test/org/apache/tomcat/lite/test.properties b/modules/tomcat-lite/test/org/apache/tomcat/lite/test.properties index 8c60706ba..baade733b 100644 --- a/modules/tomcat-lite/test/org/apache/tomcat/lite/test.properties +++ b/modules/tomcat-lite/test/org/apache/tomcat/lite/test.properties @@ -1,12 +1,9 @@ -RUN=Log,JMXProxy,Socks,TomcatLite +RUN=Log,Socks,TomcatLite Log.(class)=org.apache.tomcat.lite.service.LogConfig Log.debug=org.apache.tomcat.lite.http.HttpConnector Log.debug=Proxy -JMXProxy.(class)=org.apache.tomcat.lite.service.JMXProxy -JMXProxy.port=8003 - Socks.(class)=org.apache.tomcat.lite.proxy.SocksServer Socks.port=2080 Socks.idleTimeout=0 -- 2.11.0