From 6cf88dddb35b64dc922dd95c604e9278d36ec958 Mon Sep 17 00:00:00 2001 From: costin Date: Wed, 24 Feb 2010 18:03:00 +0000 Subject: [PATCH] Moved utils to connector ( the servlet part will either go away or be a separate package ). Make it compile again on android. Few sync issues found while load testing. SSL fixes. Separate build target for connector. git-svn-id: https://svn.apache.org/repos/asf/tomcat/trunk@915902 13f79535-47bb-0310-9956-ffa450edef68 --- modules/tomcat-lite/build.xml | 108 ++++++- .../integration/jmx/JmxObjectManagerSpi.java | 12 +- .../apache/tomcat/integration/jmx/UJmxHandler.java | 9 +- .../org/apache/tomcat/lite/http/ContentType.java | 2 +- .../apache/tomcat/lite/http/Http11Connection.java | 38 ++- .../tomcat/lite/http/HttpConnectionPool.java | 13 +- .../org/apache/tomcat/lite/http/HttpConnector.java | 4 + .../org/apache/tomcat/lite/http/HttpMessage.java | 67 +++- .../org/apache/tomcat/lite/http/HttpRequest.java | 27 ++ .../org/apache/tomcat/lite/http/HttpResponse.java | 344 ++++++++++++++++++++- .../apache/tomcat/lite/http/SpdyConnection.java | 89 +++--- .../org/apache/tomcat/lite/io/DumpChannel.java | 4 +- .../java/org/apache/tomcat/lite/io/IOBuffer.java | 6 +- .../java/org/apache/tomcat/lite/io/IOChannel.java | 17 +- .../org/apache/tomcat/lite/io/IOConnector.java | 10 + .../java/org/apache/tomcat/lite/io/IOWriter.java | 35 ++- .../java/org/apache/tomcat/lite/io/NioChannel.java | 1 - .../java/org/apache/tomcat/lite/io/NioThread.java | 28 +- .../org/apache/tomcat/lite/io/SocketIOChannel.java | 1 + .../java/org/apache/tomcat/lite/io/SslChannel.java | 218 +++++++++---- .../org/apache/tomcat/lite/io/SslConnector.java | 50 ++- .../apache/tomcat/lite/servlet/ServletApi30.java | 209 ++++++------- .../tomcat/lite/servlet/ServletContextImpl.java | 4 +- .../tomcat/lite/servlet/ServletRequestImpl.java | 34 +- .../tomcat/lite/servlet/WebappFilterMapper.java | 3 +- .../tomcat/{servlets => lite}/util/Base64.java | 7 +- .../util/FastHttpDateFormat.java | 2 +- .../{servlets => lite}/util/LocaleParser.java | 2 +- .../tomcat/{servlets => lite}/util/MimeMap.java | 2 +- .../tomcat/{servlets => lite}/util/Range.java | 2 +- .../org/apache/tomcat/lite/util/URLEncoder.java | 227 ++++++++++++++ .../tomcat/{servlets => lite}/util/UrlUtils.java | 2 +- .../tomcat/servlets/file/DefaultServlet.java | 83 +++-- .../apache/tomcat/servlets/file/WebdavServlet.java | 27 +- .../apache/tomcat/servlets/util/RequestUtil.java | 88 +----- .../apache/tomcat/servlets/util/URLEncoder.java | 101 ------ .../apache/coyote/lite/TomcatLiteCoyoteTest.java | 5 +- .../test/org/apache/tomcat/lite/TestMain.java | 66 ++-- .../org/apache/tomcat/lite/http/HttpsTest.java | 19 +- .../org/apache/tomcat/lite/http/LiveHttp1Test.java | 3 +- .../test/org/apache/tomcat/lite/http/SpdyTest.java | 38 ++- .../tomcat/lite/load/LiveHttpThreadedTest.java | 224 +++++++------- .../org/apache/tomcat/lite/load/MicroTest.java | 3 +- .../org/apache/tomcat/lite/load/ThreadRunner.java | 1 - .../test/org/apache/tomcat/lite/test.properties | 5 +- .../{util/buf => lite/util}/UEncoderTest.java | 8 +- 46 files changed, 1557 insertions(+), 691 deletions(-) rename modules/tomcat-lite/java/org/apache/tomcat/{servlets => lite}/util/Base64.java (98%) rename modules/tomcat-lite/java/org/apache/tomcat/{servlets => lite}/util/FastHttpDateFormat.java (99%) rename modules/tomcat-lite/java/org/apache/tomcat/{servlets => lite}/util/LocaleParser.java (99%) rename modules/tomcat-lite/java/org/apache/tomcat/{servlets => lite}/util/MimeMap.java (99%) rename modules/tomcat-lite/java/org/apache/tomcat/{servlets => lite}/util/Range.java (99%) create mode 100644 modules/tomcat-lite/java/org/apache/tomcat/lite/util/URLEncoder.java rename modules/tomcat-lite/java/org/apache/tomcat/{servlets => lite}/util/UrlUtils.java (98%) delete mode 100644 modules/tomcat-lite/java/org/apache/tomcat/servlets/util/URLEncoder.java rename modules/tomcat-lite/test/org/apache/tomcat/{util/buf => lite/util}/UEncoderTest.java (90%) diff --git a/modules/tomcat-lite/build.xml b/modules/tomcat-lite/build.xml index 36974b008..9941f37d4 100644 --- a/modules/tomcat-lite/build.xml +++ b/modules/tomcat-lite/build.xml @@ -14,9 +14,11 @@ + + @@ -31,9 +33,109 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + description="Build all classes against tomcat head."> + + encoding="ISO-8859-1"> - 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 45bec43fb..02b449073 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 @@ -28,7 +28,7 @@ import org.apache.tomcat.util.modeler.Registry; * * All objects of interest are registered automatically. */ -public class JmxObjectManagerSpi extends ObjectManager { +public class JmxObjectManagerSpi extends ObjectManager implements Runnable { Registry registry; Logger log = Logger.getLogger("JmxObjectManager"); @@ -54,5 +54,15 @@ public class JmxObjectManagerSpi extends ObjectManager { public Object get(String key) { return null; } + + ObjectManager om; + + public void setObjectManager(ObjectManager om) { + this.om = om; + } + public void run() { + om.register(this); + // TODO: register existing objects in JMX + } } diff --git a/modules/tomcat-lite/java/org/apache/tomcat/integration/jmx/UJmxHandler.java b/modules/tomcat-lite/java/org/apache/tomcat/integration/jmx/UJmxHandler.java index c5a2e761b..660131a27 100644 --- a/modules/tomcat-lite/java/org/apache/tomcat/integration/jmx/UJmxHandler.java +++ b/modules/tomcat-lite/java/org/apache/tomcat/integration/jmx/UJmxHandler.java @@ -21,14 +21,11 @@ package org.apache.tomcat.integration.jmx; import java.io.IOException; import java.io.PrintWriter; -import java.util.HashMap; import java.util.Iterator; -import java.util.Map; import java.util.Set; 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; @@ -47,16 +44,18 @@ import org.apache.tomcat.lite.http.HttpChannel.HttpService; */ public class UJmxHandler implements HttpService { - private static Logger log = Logger.getLogger(UJmxHandler.class.getName()); + protected static Logger log = Logger.getLogger(UJmxHandler.class.getName()); private UJmxObjectManagerSpi jmx; + public UJmxHandler() { + } + public UJmxHandler(UJmxObjectManagerSpi jmx) { this.jmx = jmx; } public void getAttribute(PrintWriter writer, String onameStr, String att) { try { - Object bean = jmx.objects.get(onameStr); Class beanClass = bean.getClass(); DynamicObject ci = jmx.getClassInfo(beanClass); diff --git a/modules/tomcat-lite/java/org/apache/tomcat/lite/http/ContentType.java b/modules/tomcat-lite/java/org/apache/tomcat/lite/http/ContentType.java index 993566c13..c98276cfa 100644 --- a/modules/tomcat-lite/java/org/apache/tomcat/lite/http/ContentType.java +++ b/modules/tomcat-lite/java/org/apache/tomcat/lite/http/ContentType.java @@ -72,7 +72,7 @@ public class ContentType { int index = type.indexOf(';'); while (index != -1) { index++; - while (index < len && Character.isSpace(type.charAt(index))) { + while (index < len && Character.isWhitespace(type.charAt(index))) { index++; } if (index+8 < len 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 2571a85ad..9529a1946 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 @@ -4,6 +4,8 @@ package org.apache.tomcat.lite.http; import java.io.IOException; import java.nio.ByteBuffer; +import java.util.ArrayList; +import java.util.List; import java.util.logging.Level; import java.util.logging.Logger; @@ -144,6 +146,8 @@ public class Http11Connection extends HttpConnection return closeInHead(); } } + + return true; } @@ -209,6 +213,11 @@ public class Http11Connection extends HttpConnection if (!headersReceived) { headRecvBuf.wrapTo(headW); parseMessage(activeHttp, headW); + // Part of parseMessage we can switch the protocol + if (switchedProtocol != null) { + return; + } + if (serverMode && activeHttp.httpReq.decodedUri.remaining() == 0) { abort(activeHttp, "Invalid url"); } @@ -552,6 +561,8 @@ public class Http11Connection extends HttpConnection return statusCode.remaining() > 0; } + List connectionHeaders = new ArrayList(); + private void parseHeaders(HttpChannel http, HttpMessageBytes msgBytes, BBuffer head) throws IOException { @@ -559,6 +570,9 @@ public class Http11Connection extends HttpConnection head.readLine(line); int idx = 0; + + BBuffer upgrade = null; + while(line.remaining() > 0) { // not empty.. idx = msgBytes.addHeader(); @@ -567,7 +581,23 @@ public class Http11Connection extends HttpConnection parseHeader(http, head, line, nameBuf, valBuf); // TODO: process 'interesting' headers here. + if (nameBuf.equalsIgnoreCase("connection")) { + // TODO: save and remove if not recognized + } + if (nameBuf.equalsIgnoreCase("upgrade")) { + upgrade = valBuf; + } + } + + if (upgrade != null) { + if (upgrade.equalsIgnoreCase("WebSocket")) { + + } else if (upgrade.equalsIgnoreCase("SPDY/1.0")) { + + } } + + // TODO: process connection headers } /** @@ -1406,8 +1436,10 @@ public class Http11Connection extends HttpConnection HttpChannel httpCh = activeHttp; boolean ssl = httpCh.getRequest().isSecure(); if (ssl) { - SslChannel ch1 = new SslChannel(); - ch1.setSslContext(httpConnector.sslConnector.getSSLContext()); + String[] hostPort = httpCh.getTarget().split(":"); + + SslChannel ch1 = httpConnector.sslConnector.channel( + hostPort[0], Integer.parseInt(hostPort[1])); ch1.setSink(net); net.addFilterAfter(ch1); net = ch1; @@ -1419,7 +1451,7 @@ public class Http11Connection extends HttpConnection } if (!net.isOpen()) { - httpCh.abort("Can't connect"); + httpCh.abort(net.lastException()); return; } 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 index 363ac59bd..696a47089 100644 --- a/modules/tomcat-lite/java/org/apache/tomcat/lite/http/HttpConnectionPool.java +++ b/modules/tomcat-lite/java/org/apache/tomcat/lite/http/HttpConnectionPool.java @@ -282,14 +282,14 @@ public class HttpConnectionPool { if (con.isOpen()) { hits.incrementAndGet(); - if (debug) { - httpCh.trace("HTTP_CONNECT: Reuse connection " + target + " " + this); - } +// if (debug) { +// log.info("HTTP_CONNECT: Reuse connection " + target + " " + this); +// } con.sendRequest(httpCh); } else { misses.incrementAndGet(); if (debug) { - httpCh.trace("HTTP_CONNECT: Start connection " + target + " " + this); + log.info("HTTP_CONNECT: Start connection " + target + " " + this); } httpConnect(httpCh, target, ssl, (Http11Connection) con); @@ -300,7 +300,7 @@ public class HttpConnectionPool { boolean ssl, IOConnector.ConnectedCallback cb) throws IOException { if (debug) { - httpCh.trace("HTTP_CONNECT: New connection " + target); + log.info("HTTP_CONNECT: New connection " + target); } String[] hostPort = target.split(":"); @@ -341,9 +341,6 @@ public class HttpConnectionPool { // again. if (remoteServer.pending.size() == 0) { con.activeHttp = null; - if (debug) { - log.info("After request: no pending"); - } return; } req = remoteServer.pending.remove(); 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 63a657275..cc272e99c 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 @@ -115,6 +115,10 @@ public class HttpConnector { return dispatcher; } + public HttpConnectionPool getConnectionPool() { + return cpool; + } + public HttpConnector withIOConnector(IOConnector selectors) { ioConnector = selectors; return this; 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 867ca2ea8..3bd8f0112 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 @@ -4,16 +4,23 @@ package org.apache.tomcat.lite.http; import java.io.BufferedReader; import java.io.IOException; +import java.io.InputStream; +import java.io.PrintWriter; import java.io.UnsupportedEncodingException; +import java.io.Writer; +import java.text.SimpleDateFormat; import java.util.ArrayList; import java.util.Collection; import java.util.List; +import java.util.Locale; +import java.util.TimeZone; import org.apache.tomcat.lite.http.HttpChannel.RequestCompleted; import org.apache.tomcat.lite.http.HttpConnector.HttpConnection; import org.apache.tomcat.lite.io.BBuffer; import org.apache.tomcat.lite.io.BufferedIOReader; import org.apache.tomcat.lite.io.CBuffer; +import org.apache.tomcat.lite.io.FastHttpDateFormat; import org.apache.tomcat.lite.io.IOBuffer; import org.apache.tomcat.lite.io.IOInputStream; import org.apache.tomcat.lite.io.IOOutputStream; @@ -113,7 +120,9 @@ public abstract class HttpMessage { } } } - + + protected static final TimeZone GMT_ZONE = TimeZone.getTimeZone("GMT"); + private HttpMessageBytes msgBytes = new HttpMessageBytes(); protected HttpMessage.State state = HttpMessage.State.HEAD; @@ -149,6 +158,15 @@ public abstract class HttpMessage { long contentLength = -2; boolean chunked; + /** + * The set of SimpleDateFormat formats to use in getDateHeader(). + * + * Notice that because SimpleDateFormat is not thread-safe, we can't + * declare formats[] as a static variable. + */ + protected SimpleDateFormat formats[] = null; + + BBuffer clBuffer = BBuffer.allocate(64); public HttpMessage(HttpChannel httpCh) { @@ -189,6 +207,41 @@ public abstract class HttpMessage { return headers; } + /** + * Return the value of the specified date header, if any; otherwise + * return -1. + * + * @param name Name of the requested date header + * + * @exception IllegalArgumentException if the specified header value + * cannot be converted to a date + */ + public long getDateHeader(String name) { + + String value = getHeader(name); + if (value == null) + return (-1L); + if (formats == null) { + formats = new SimpleDateFormat[] { + new SimpleDateFormat("EEE, dd MMM yyyy HH:mm:ss zzz", Locale.US), + new SimpleDateFormat("EEEEEE, dd-MMM-yy HH:mm:ss zzz", Locale.US), + new SimpleDateFormat("EEE MMMM d HH:mm:ss yyyy", Locale.US) + }; + formats[0].setTimeZone(GMT_ZONE); + formats[1].setTimeZone(GMT_ZONE); + formats[2].setTimeZone(GMT_ZONE); + } + + // Attempt to convert the date header in a variety of formats + long result = FastHttpDateFormat.parseDate(value, formats); + if (result != (-1L)) { + return result; + } + throw new IllegalArgumentException(value); + + } + + public Collection getHeaderNames() { MultiMap headers = getMimeHeaders(); @@ -373,6 +426,14 @@ public abstract class HttpMessage { return in; } + public InputStream getInputStream() { + return in; + } + + public IOOutputStream getOutputStream() { + return out; + } + public IOOutputStream getBodyOutputStream() { return out; } @@ -410,6 +471,10 @@ public abstract class HttpMessage { reader.setEncoding(getCharacterEncoding()); return bufferedReader; } + + public PrintWriter getWriter() { + return new PrintWriter(getBodyWriter()); + } public HttpWriter getBodyWriter() { conv.setEncoding(getCharacterEncoding()); diff --git a/modules/tomcat-lite/java/org/apache/tomcat/lite/http/HttpRequest.java b/modules/tomcat-lite/java/org/apache/tomcat/lite/http/HttpRequest.java index bbc670786..c43594633 100644 --- a/modules/tomcat-lite/java/org/apache/tomcat/lite/http/HttpRequest.java +++ b/modules/tomcat-lite/java/org/apache/tomcat/lite/http/HttpRequest.java @@ -175,6 +175,30 @@ public class HttpRequest extends HttpMessage { return (mappingData); } + /** + * Return the portion of the request URI used to select the Context + * of the Request. + */ + public String getContextPath() { + return (getMappingData().contextPath.toString()); + } + + public String getPathInfo() { + CBuffer pathInfo = getMappingData().pathInfo; + if (pathInfo.length() == 0) { + return null; + } + return (getMappingData().pathInfo.toString()); + } + + /** + * Return the portion of the request URI used to select the servlet + * that will process this request. + */ + public String getServletPath() { + return (getMappingData().wrapperPath.toString()); + } + /** * Parse query parameters - but not POST body. * @@ -843,6 +867,9 @@ public class HttpRequest extends HttpMessage { */ protected void processReceivedHeaders() throws IOException { BBuffer url = getMsgBytes().url(); + if (url.remaining() == 0) { + System.err.println("No input"); + } if (url.get(0) == 'h') { int firstSlash = url.indexOf('/', 0); schemeMB.appendAscii(url.array(), diff --git a/modules/tomcat-lite/java/org/apache/tomcat/lite/http/HttpResponse.java b/modules/tomcat-lite/java/org/apache/tomcat/lite/http/HttpResponse.java index fd38abf28..39f2ffa57 100644 --- a/modules/tomcat-lite/java/org/apache/tomcat/lite/http/HttpResponse.java +++ b/modules/tomcat-lite/java/org/apache/tomcat/lite/http/HttpResponse.java @@ -8,9 +8,336 @@ import java.util.HashMap; import org.apache.tomcat.lite.io.BBucket; import org.apache.tomcat.lite.io.BBuffer; import org.apache.tomcat.lite.io.CBuffer; -import org.apache.tomcat.lite.io.IOBuffer; public class HttpResponse extends HttpMessage { + + /* + * Server status codes; see RFC 2068. + */ + + /** + * Status code (100) indicating the client can continue. + */ + + public static final int SC_CONTINUE = 100; + + + /** + * Status code (101) indicating the server is switching protocols + * according to Upgrade header. + */ + + public static final int SC_SWITCHING_PROTOCOLS = 101; + + /** + * Status code (200) indicating the request succeeded normally. + */ + + public static final int SC_OK = 200; + + /** + * Status code (201) indicating the request succeeded and created + * a new resource on the server. + */ + + public static final int SC_CREATED = 201; + + /** + * Status code (202) indicating that a request was accepted for + * processing, but was not completed. + */ + + public static final int SC_ACCEPTED = 202; + + /** + * Status code (203) indicating that the meta information presented + * by the client did not originate from the server. + */ + + public static final int SC_NON_AUTHORITATIVE_INFORMATION = 203; + + /** + * Status code (204) indicating that the request succeeded but that + * there was no new information to return. + */ + + public static final int SC_NO_CONTENT = 204; + + /** + * Status code (205) indicating that the agent SHOULD reset + * the document view which caused the request to be sent. + */ + + public static final int SC_RESET_CONTENT = 205; + + /** + * Status code (206) indicating that the server has fulfilled + * the partial GET request for the resource. + */ + + public static final int SC_PARTIAL_CONTENT = 206; + + /** + * Used by Webdav. + */ + public static final int SC_MULTI_STATUS = 207; + // This one collides with HTTP 1.1 + // "207 Partial Update OK" + + /** + * Status code (300) indicating that the requested resource + * corresponds to any one of a set of representations, each with + * its own specific location. + */ + + public static final int SC_MULTIPLE_CHOICES = 300; + + /** + * Status code (301) indicating that the resource has permanently + * moved to a new location, and that future references should use a + * new URI with their requests. + */ + + public static final int SC_MOVED_PERMANENTLY = 301; + + /** + * Status code (302) indicating that the resource has temporarily + * moved to another location, but that future references should + * still use the original URI to access the resource. + * + * This definition is being retained for backwards compatibility. + * SC_FOUND is now the preferred definition. + */ + + public static final int SC_MOVED_TEMPORARILY = 302; + + /** + * Status code (302) indicating that the resource reside + * temporarily under a different URI. Since the redirection might + * be altered on occasion, the client should continue to use the + * Request-URI for future requests.(HTTP/1.1) To represent the + * status code (302), it is recommended to use this variable. + */ + + public static final int SC_FOUND = 302; + + /** + * Status code (303) indicating that the response to the request + * can be found under a different URI. + */ + + public static final int SC_SEE_OTHER = 303; + + /** + * Status code (304) indicating that a conditional GET operation + * found that the resource was available and not modified. + */ + + public static final int SC_NOT_MODIFIED = 304; + + /** + * Status code (305) indicating that the requested resource + * MUST be accessed through the proxy given by the + * Location field. + */ + + public static final int SC_USE_PROXY = 305; + + /** + * Status code (307) indicating that the requested resource + * resides temporarily under a different URI. The temporary URI + * SHOULD be given by the Location + * field in the response. + */ + + public static final int SC_TEMPORARY_REDIRECT = 307; + + /** + * Status code (400) indicating the request sent by the client was + * syntactically incorrect. + */ + + public static final int SC_BAD_REQUEST = 400; + + /** + * Status code (401) indicating that the request requires HTTP + * authentication. + */ + + public static final int SC_UNAUTHORIZED = 401; + + /** + * Status code (402) reserved for future use. + */ + + public static final int SC_PAYMENT_REQUIRED = 402; + + /** + * Status code (403) indicating the server understood the request + * but refused to fulfill it. + */ + + public static final int SC_FORBIDDEN = 403; + + /** + * Status code (404) indicating that the requested resource is not + * available. + */ + + public static final int SC_NOT_FOUND = 404; + + /** + * Status code (405) indicating that the method specified in the + * Request-Line is not allowed for the resource + * identified by the Request-URI. + */ + + public static final int SC_METHOD_NOT_ALLOWED = 405; + + /** + * Status code (406) indicating that the resource identified by the + * request is only capable of generating response entities which have + * content characteristics not acceptable according to the accept + * headers sent in the request. + */ + + public static final int SC_NOT_ACCEPTABLE = 406; + + /** + * Status code (407) indicating that the client MUST first + * authenticate itself with the proxy. + */ + + public static final int SC_PROXY_AUTHENTICATION_REQUIRED = 407; + + /** + * Status code (408) indicating that the client did not produce a + * request within the time that the server was prepared to wait. + */ + + public static final int SC_REQUEST_TIMEOUT = 408; + + /** + * Status code (409) indicating that the request could not be + * completed due to a conflict with the current state of the + * resource. + */ + + public static final int SC_CONFLICT = 409; + + /** + * Status code (410) indicating that the resource is no longer + * available at the server and no forwarding address is known. + * This condition SHOULD be considered permanent. + */ + + public static final int SC_GONE = 410; + + /** + * Status code (411) indicating that the request cannot be handled + * without a defined Content-Length. + */ + + public static final int SC_LENGTH_REQUIRED = 411; + + /** + * Status code (412) indicating that the precondition given in one + * or more of the request-header fields evaluated to false when it + * was tested on the server. + */ + + public static final int SC_PRECONDITION_FAILED = 412; + + /** + * Status code (413) indicating that the server is refusing to process + * the request because the request entity is larger than the server is + * willing or able to process. + */ + + public static final int SC_REQUEST_ENTITY_TOO_LARGE = 413; + + /** + * Status code (414) indicating that the server is refusing to service + * the request because the Request-URI is longer + * than the server is willing to interpret. + */ + + public static final int SC_REQUEST_URI_TOO_LONG = 414; + + /** + * Status code (415) indicating that the server is refusing to service + * the request because the entity of the request is in a format not + * supported by the requested resource for the requested method. + */ + + public static final int SC_UNSUPPORTED_MEDIA_TYPE = 415; + + /** + * Status code (416) indicating that the server cannot serve the + * requested byte range. + */ + + public static final int SC_REQUESTED_RANGE_NOT_SATISFIABLE = 416; + + /** + * Status code (417) indicating that the server could not meet the + * expectation given in the Expect request header. + */ + + public static final int SC_EXPECTATION_FAILED = 417; + + /** + * Status code (423) indicating the destination resource of a + * method is locked, and either the request did not contain a + * valid Lock-Info header, or the Lock-Info header identifies + * a lock held by another principal. + */ + public static final int SC_LOCKED = 423; + + /** + * Status code (500) indicating an error inside the HTTP server + * which prevented it from fulfilling the request. + */ + + public static final int SC_INTERNAL_SERVER_ERROR = 500; + + /** + * Status code (501) indicating the HTTP server does not support + * the functionality needed to fulfill the request. + */ + + public static final int SC_NOT_IMPLEMENTED = 501; + + /** + * Status code (502) indicating that the HTTP server received an + * invalid response from a server it consulted when acting as a + * proxy or gateway. + */ + + public static final int SC_BAD_GATEWAY = 502; + + /** + * Status code (503) indicating that the HTTP server is + * temporarily overloaded, and unable to handle the request. + */ + + public static final int SC_SERVICE_UNAVAILABLE = 503; + + /** + * Status code (504) indicating that the server did not receive + * a timely response from the upstream server while acting as + * a gateway or proxy. + */ + + public static final int SC_GATEWAY_TIMEOUT = 504; + + /** + * Status code (505) indicating that the server does not support + * or refuses to support the HTTP protocol version that was used + * in the request message. + */ + + public static final int SC_HTTP_VERSION_NOT_SUPPORTED = 505; // will not be recycled public Object nativeResponse; @@ -47,6 +374,14 @@ public class HttpResponse extends HttpMessage { status = i; } + public void sendError(int status) { + this.status = status; + } + + public void sendError(int status, String msg) { + message.set(msg); + } + public int getStatus() { if (status >= 0) { return status; @@ -112,7 +447,7 @@ public class HttpResponse extends HttpMessage { * Common messages are cached. * */ - BBucket getMessage( int status ) { + static BBucket getMessage( int status ) { // method from Response. // Does HTTP requires/allow international messages or @@ -134,6 +469,9 @@ public class HttpResponse extends HttpMessage { return bb; } + public static String getStatusText(int code) { + return getMessage(code).toString(); + } static BBucket st_unknown = BBuffer.wrapper("No Message"); static BBucket st_200 = BBuffer.wrapper("OK"); @@ -192,6 +530,8 @@ public class HttpResponse extends HttpMessage { addStatus(504, "Gateway Timeout"); addStatus(505, "HTTP Version Not Supported"); addStatus(507, "Insufficient Storage"); + addStatus(SC_LOCKED, "Locked"); + } 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 e4c2a77dd..ccba678a0 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 @@ -95,9 +95,12 @@ public class SpdyConnection extends HttpConnector.HttpConnection CompressFilter headCompressIn = new CompressFilter() .setDictionary(SPDY_DICT, DICT_ID); + CompressFilter headCompressOut = new CompressFilter() .setDictionary(SPDY_DICT, DICT_ID); + IOBuffer headerCompressBuffer = new IOBuffer(); + IOBuffer headerDeCompressBuffer = new IOBuffer(); AtomicInteger inFrames = new AtomicInteger(); AtomicInteger inDataFrames = new AtomicInteger(); @@ -120,32 +123,35 @@ public class SpdyConnection extends HttpConnector.HttpConnection } @Override - public synchronized void dataReceived(IOBuffer iob) throws IOException { - while (true) { - int avail = iob.available(); - if (avail == 0) { - return; - } - if (currentInFrame == null) { - if (inFrameBuffer.remaining() + avail < 8) { + public void dataReceived(IOBuffer iob) throws IOException { + // Only one thread doing receive at a time. + synchronized (inFrameBuffer) { + while (true) { + int avail = iob.available(); + if (avail == 0) { return; } - if (inFrameBuffer.remaining() < 8) { - int headRest = 8 - inFrameBuffer.remaining(); - int rd = iob.read(inFrameBuffer, headRest); + if (currentInFrame == null) { + if (inFrameBuffer.remaining() + avail < 8) { + return; + } + if (inFrameBuffer.remaining() < 8) { + int headRest = 8 - inFrameBuffer.remaining(); + int rd = iob.read(inFrameBuffer, headRest); + } + currentInFrame = new SpdyConnection.Frame(); // TODO: reuse + currentInFrame.parse(this, inFrameBuffer); } - currentInFrame = new SpdyConnection.Frame(); // TODO: reuse - currentInFrame.parse(this, inFrameBuffer); - } - if (iob.available() < currentInFrame.length) { - return; - } - // We have a full frame. Process it. - onFrame(iob); + if (iob.available() < currentInFrame.length) { + return; + } + // We have a full frame. Process it. + onFrame(iob); - // TODO: extra checks, make sure the frame is correct and - // it consumed all data. - currentInFrame = null; + // TODO: extra checks, make sure the frame is correct and + // it consumed all data. + currentInFrame = null; + } } } @@ -179,7 +185,7 @@ public class SpdyConnection extends HttpConnector.HttpConnection ch.setHttpService(this.httpConnector.defaultService); } - synchronized (this) { + synchronized (channels) { channels.put(ch.channelId, ch); } @@ -206,7 +212,7 @@ public class SpdyConnection extends HttpConnector.HttpConnection } else if (currentInFrame.type == SpdyConnection.Frame.TYPE_SYN_REPLY) { int chId = SpdyConnection.readInt(iob); HttpChannel ch; - synchronized (this) { + synchronized (channels) { ch = channels.get(chId); if (ch == null) { abort("Channel not found"); @@ -239,7 +245,7 @@ public class SpdyConnection extends HttpConnector.HttpConnection inDataFrames.incrementAndGet(); // data frame - part of an existing stream HttpChannel ch; - synchronized (this) { + synchronized (channels) { ch = channels.get(currentInFrame.streamId); } if (ch == null) { @@ -280,8 +286,10 @@ public class SpdyConnection extends HttpConnector.HttpConnection */ private void abort(String msg) throws IOException { streamErrors.incrementAndGet(); - for (HttpChannel ch : channels.values()) { - ch.abort(msg); + synchronized(channels) { + for (HttpChannel ch : channels.values()) { + ch.abort(msg); + } } close(); } @@ -299,13 +307,13 @@ public class SpdyConnection extends HttpConnector.HttpConnection if (headerCompression) { // 0x800 headers seems a bit too much - assume compressed. // I wish this was a flag... - headerCompressBuffer.recycle(); + headerDeCompressBuffer.recycle(); // stream id ( 4 ) + unused ( 2 ) // nvCount is compressed in impl - spec is different - headCompressIn.decompress(iob, headerCompressBuffer, + headCompressIn.decompress(iob, headerDeCompressBuffer, currentInFrame.length - 6); - headerCompressBuffer.copyAll(headRecvBuf); - headerCompressBuffer.recycle(); + headerDeCompressBuffer.copyAll(headRecvBuf); + headerDeCompressBuffer.recycle(); nvCount = readShort(headRecvBuf); } else { nvCount = readShort(iob); @@ -424,7 +432,7 @@ public class SpdyConnection extends HttpConnector.HttpConnection http.setConnection(this); - synchronized (this) { + synchronized (channels) { channels.put(http.channelId, http); } @@ -446,7 +454,9 @@ public class SpdyConnection extends HttpConnector.HttpConnection public synchronized Collection getActives() { - return channels.values(); + synchronized(channels) { + return channels.values(); + } } @Override @@ -711,7 +721,7 @@ public class SpdyConnection extends HttpConnector.HttpConnection @Override protected void endSendReceive(HttpChannel http) throws IOException { - synchronized (this) { + synchronized (channels) { HttpChannel doneHttp = channels.remove(http.channelId); if (doneHttp != http) { log.severe("Error removing " + doneHttp + " " + http); @@ -775,8 +785,15 @@ public class SpdyConnection extends HttpConnector.HttpConnection } secure = httpCh.getRequest().isSecure(); if (secure) { - SslChannel ch1 = new SslChannel(); - ch1.setSslContext(httpConnector.sslConnector.getSSLContext()); + if (httpConnector.debugHttp) { + IOChannel ch1 = new DumpChannel("NET-IN"); + net.addFilterAfter(ch1); + net = ch1; + } + String[] hostPort = httpCh.getTarget().split(":"); + + SslChannel ch1 = httpConnector.sslConnector.channel( + hostPort[0], Integer.parseInt(hostPort[1])); ch1.setSink(net); net.addFilterAfter(ch1); net = ch1; diff --git a/modules/tomcat-lite/java/org/apache/tomcat/lite/io/DumpChannel.java b/modules/tomcat-lite/java/org/apache/tomcat/lite/io/DumpChannel.java index 1319b271d..47ea5025b 100644 --- a/modules/tomcat-lite/java/org/apache/tomcat/lite/io/DumpChannel.java +++ b/modules/tomcat-lite/java/org/apache/tomcat/lite/io/DumpChannel.java @@ -46,7 +46,9 @@ public class DumpChannel extends IOChannel { } any = true; out("IN", first, false); - in.queue(first); + if (!in.isAppendClosed()) { + in.queue(first); + } } } diff --git a/modules/tomcat-lite/java/org/apache/tomcat/lite/io/IOBuffer.java b/modules/tomcat-lite/java/org/apache/tomcat/lite/io/IOBuffer.java index 2553d62fa..f3a2cabe4 100644 --- a/modules/tomcat-lite/java/org/apache/tomcat/lite/io/IOBuffer.java +++ b/modules/tomcat-lite/java/org/apache/tomcat/lite/io/IOBuffer.java @@ -342,8 +342,12 @@ public class IOBuffer { public int write(ByteBuffer bb) throws IOException { int len = bb.remaining(); + int pos = bb.position(); + if (len == 0) { + return 0; + } append(bb); - bb.position(bb.position() + len); + bb.position(pos + len); return len; } diff --git a/modules/tomcat-lite/java/org/apache/tomcat/lite/io/IOChannel.java b/modules/tomcat-lite/java/org/apache/tomcat/lite/io/IOChannel.java index 9893cf6cd..99dbc8812 100644 --- a/modules/tomcat-lite/java/org/apache/tomcat/lite/io/IOChannel.java +++ b/modules/tomcat-lite/java/org/apache/tomcat/lite/io/IOChannel.java @@ -21,7 +21,7 @@ import java.nio.channels.ByteChannel; * @author Costin Manolache */ public abstract class IOChannel implements ByteChannel, IOConnector.DataReceivedCallback, - IOConnector.DataFlushedCallback { //, IOConnector.ClosedCallback { + IOConnector.DataFlushedCallback { protected IOChannel net; protected IOChannel app; @@ -39,6 +39,8 @@ public abstract class IOChannel implements ByteChannel, IOConnector.DataReceived // TODO: update, etc public long ts; + protected Throwable lastException; + public void setConnectedCallback(IOConnector.ConnectedCallback connectedCallback) { this.connectedCallback = connectedCallback; } @@ -131,7 +133,18 @@ public abstract class IOChannel implements ByteChannel, IOConnector.DataReceived } } } - + + /** + * Return last IO exception. + * + * The channel is async, exceptions can happen at any time. + * The normal callback will be called ( connected, received ), it + * should check if the channel is closed and the exception. + */ + public Throwable lastException() { + return lastException; + } + public void close() throws IOException { shutdownOutput(); // Should it read the buffers ? 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 62eb0da1b..89468594b 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 @@ -21,6 +21,12 @@ public abstract class IOConnector { public void handleReceived(IOChannel ch) throws IOException; } + /** + * Callback for accept and connect. + * + * Will also be called if an error happens while connecting, in + * which case the connection will be closed. + */ public static interface ConnectedCallback { public void handleConnected(IOChannel ch) throws IOException; } @@ -35,6 +41,10 @@ public abstract class IOConnector { return timer; } + public IOConnector getNet() { + return null; + } + public abstract void acceptor(IOConnector.ConnectedCallback sc, CharSequence port, Object extra) throws IOException; diff --git a/modules/tomcat-lite/java/org/apache/tomcat/lite/io/IOWriter.java b/modules/tomcat-lite/java/org/apache/tomcat/lite/io/IOWriter.java index ad8f9520c..0c84f6841 100644 --- a/modules/tomcat-lite/java/org/apache/tomcat/lite/io/IOWriter.java +++ b/modules/tomcat-lite/java/org/apache/tomcat/lite/io/IOWriter.java @@ -10,7 +10,6 @@ import java.nio.charset.Charset; import java.nio.charset.CharsetEncoder; import java.nio.charset.CoderResult; import java.nio.charset.CodingErrorAction; -import java.util.BitSet; import java.util.HashMap; import java.util.Map; @@ -155,6 +154,40 @@ public class IOWriter extends Writer { } } + // TODO: use it for utf-8 + public static int char2utf8(byte[] ba, int off, char c, char c1) { + int i = 0; + if (c < 0x80) { + ba[off++] = (byte) (c & 0xFF); + return 1; + } else if (c < 0x800) + { + ba[off++] = (byte) (0xC0 | c >> 6); + ba[off++] = (byte) (0x80 | c & 0x3F); + return 2; + } + else if (c < 0x10000) + { + ba[off++] = (byte) ((0xE0 | c >> 12)); + ba[off++] = (byte) ((0x80 | c >> 6 & 0x3F)); + ba[off++] = (byte) ((0x80 | c & 0x3F)); + return 3; + } + else if (c < 0x200000) + { + ba[off++] = (byte) ((0xF0 | c >> 18)); + ba[off++] = (byte) ((0x80 | c >> 12 & 0x3F)); + ba[off++] = (byte) ((0x80 | c >> 6 & 0x3F)); + ba[off++] = (byte) ((0x80 | c & 0x3F)); + return 4; + } + + + return i; + } + + + /** * Just send the chars to the byte[], without flushing down. * diff --git a/modules/tomcat-lite/java/org/apache/tomcat/lite/io/NioChannel.java b/modules/tomcat-lite/java/org/apache/tomcat/lite/io/NioChannel.java index c8a136b1f..559a9e541 100644 --- a/modules/tomcat-lite/java/org/apache/tomcat/lite/io/NioChannel.java +++ b/modules/tomcat-lite/java/org/apache/tomcat/lite/io/NioChannel.java @@ -5,7 +5,6 @@ import java.net.InetAddress; import java.nio.ByteBuffer; import java.nio.channels.ByteChannel; import java.nio.channels.Channel; -import java.nio.channels.SelectionKey; /** 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 d782ae3b2..f10d37a00 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 @@ -410,23 +410,17 @@ public class NioThread implements Runnable { } readInterest(ch, true); - - if (ch.callback != null) { - ch.callback.handleConnected(ch); - } } catch (Throwable t) { - log.warning("Error in connect, closing " + t); close(ch, t); - try { - if (ch.callback != null) { - ch.callback.handleConnected(ch); - } - } catch(Throwable t1) { - log.warning("Double error in connect, callback broken too"); - t1.printStackTrace(); + } + try { + if (ch.callback != null) { + ch.callback.handleConnected(ch); } - + } catch(Throwable t1) { + log.log(Level.WARNING, "Error in connect callback", t1); } + } private void handleAccept(NioChannel ch, SelectionKey sk) @@ -729,17 +723,17 @@ public class NioThread implements Runnable { */ public int close(NioChannel selectorData, Throwable exception) throws IOException { synchronized (closeInterest) { + if (exception != null) { + selectorData.lastException = exception; + } + selectorData.readInterest = false; if (isSelectorThread()) { closeIOThread(selectorData, true); return 0; } - if (exception != null) { - selectorData.lastException = exception; - } if (!selectorData.inClosed) { closeInterest.add(selectorData); } - selectorData.readInterest = false; } selector.wakeup(); return 0; 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 6c75d4818..1fa662baa 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 @@ -239,6 +239,7 @@ public class SocketIOChannel extends IOChannel implements NioChannelCallback { @Override public void handleClosed(NioChannel ch) throws IOException { + lastException = ch.lastException; closed(); // our callback. } diff --git a/modules/tomcat-lite/java/org/apache/tomcat/lite/io/SslChannel.java b/modules/tomcat-lite/java/org/apache/tomcat/lite/io/SslChannel.java index 163228ac6..e2ae08a29 100644 --- a/modules/tomcat-lite/java/org/apache/tomcat/lite/io/SslChannel.java +++ b/modules/tomcat-lite/java/org/apache/tomcat/lite/io/SslChannel.java @@ -5,12 +5,14 @@ package org.apache.tomcat.lite.io; import java.io.IOException; import java.nio.ByteBuffer; import java.security.GeneralSecurityException; +import java.util.concurrent.TimeoutException; import java.util.logging.Level; import java.util.logging.Logger; import javax.net.ssl.SSLContext; import javax.net.ssl.SSLEngine; import javax.net.ssl.SSLEngineResult; +import javax.net.ssl.SSLException; import javax.net.ssl.SSLSession; import javax.net.ssl.SSLEngineResult.HandshakeStatus; import javax.net.ssl.SSLEngineResult.Status; @@ -30,9 +32,13 @@ public class SslChannel extends IOChannel implements Runnable { IOBuffer in = new IOBuffer(this); IOBuffer out = new IOBuffer(this); + + long handshakeTimeout = 10000; + // Used for session reuse + String host; + int port; public SslChannel() { - } ByteBuffer myAppOutData; @@ -52,13 +58,30 @@ public class SslChannel extends IOChannel implements Runnable { private boolean closeHandshake = false; - private void initSsl() throws GeneralSecurityException { + /** + * Setting the host/port enables clients to reuse SSL session - + * less traffic and encryption overhead at startup, assuming the + * server caches the session ( i.e. single server or distributed cache ). + * + * SSL ticket extension is another possibility. + */ + public SslChannel setTarget(String host, int port) { + this.host = host; + this.port = port; + return this; + } + + private synchronized void initSsl() throws GeneralSecurityException { if (sslEngine != null) { return; } if (client) { - sslEngine = sslCtx.createSSLEngine(); + if (port > 0) { + sslEngine = sslCtx.createSSLEngine(host, port); + } else { + sslEngine = sslCtx.createSSLEngine(); + } sslEngine.setUseClientMode(client); } else { sslEngine = sslCtx.createSSLEngine(); @@ -89,7 +112,7 @@ public class SslChannel extends IOChannel implements Runnable { @Override - public void setSink(IOChannel net) throws IOException { + public synchronized void setSink(IOChannel net) throws IOException { try { initSsl(); super.setSink(net); @@ -97,7 +120,7 @@ public class SslChannel extends IOChannel implements Runnable { log.log(Level.SEVERE, "Error initializing ", e); } } - + @Override public IOBuffer getIn() { return in; @@ -118,7 +141,7 @@ public class SslChannel extends IOChannel implements Runnable { */ public int processInput(IOBuffer netIn, IOBuffer appIn) throws IOException { if (log.isLoggable(Level.FINEST)) { - log.finest("JSSE: processInput " + handshakeInProgress + " " + netIn.getBufferCount()); + log.info("JSSE: processInput " + handshakeInProgress + " " + netIn.getBufferCount()); } if (!handshakeDone && !handshakeInProgress) { handshakeInProgress = true; @@ -134,6 +157,7 @@ public class SslChannel extends IOChannel implements Runnable { private synchronized int processRealInput(IOBuffer netIn, IOBuffer appIn) throws IOException { int rd = 0; boolean needsMore = true; + boolean notEnough = false; while (needsMore) { if (netIn.isClosedAndEmpty()) { @@ -142,9 +166,14 @@ public class SslChannel extends IOChannel implements Runnable { return -1; } myNetInData.compact(); - int rdNow = netIn.read(myNetInData); - myNetInData.flip(); - if (rdNow == 0) { + int rdNow; + try { + rdNow = netIn.read(myNetInData); + } finally { + myNetInData.flip(); + } + if (rdNow == 0 && (myNetInData.remaining() == 0 || + notEnough)) { return rd; } if (rdNow == -1) { @@ -153,10 +182,18 @@ public class SslChannel extends IOChannel implements Runnable { return rd; } + notEnough = true; // next read of 0 while (myNetInData.remaining() > 0) { myAppInData.compact(); - unwrapR = sslEngine.unwrap(myNetInData, myAppInData); - myAppInData.flip(); + try { + unwrapR = sslEngine.unwrap(myNetInData, myAppInData); + } catch (SSLException ex) { + log.warning("Read error: " + ex); + close(); + return -1; + } finally { + myAppInData.flip(); + } if (myAppInData.remaining() > 0) { in.write(myAppInData); // all will be written } @@ -216,50 +253,78 @@ public class SslChannel extends IOChannel implements Runnable { } public void close() throws IOException { + if (net.getOut().isAppendClosed()) { + return; + } sslEngine.closeOutbound(); // mark as closed - myNetOutData.compact(); - SSLEngineResult wrap = sslEngine.wrap(EMPTY, - myNetOutData); - myNetOutData.flip(); - if (wrap.getStatus() != Status.CLOSED) { - System.err.println("Unexpected status " + wrap); + synchronized(myNetOutData) { + myNetOutData.compact(); + + SSLEngineResult wrap; + try { + wrap = sslEngine.wrap(EMPTY, myNetOutData); + if (wrap.getStatus() != Status.CLOSED) { + log.warning("Unexpected close status " + wrap); + } + } catch (Throwable t ) { + log.info("Error wrapping " + myNetOutData); + } finally { + myNetOutData.flip(); + } + if (myNetOutData.remaining() > 0) { + net.getOut().write(myNetOutData); + } } - net.getOut().write(myNetOutData); // TODO: timer to close socket if we don't get // clean close handshake + super.close(); } - private synchronized void startRealSending() throws IOException { - while (true) { - - myAppOutData.compact(); - int rd = out.read(myAppOutData); - myAppOutData.flip(); - if (rd == 0) { - break; - } - if (rd < 0) { - close(); - break; - } + private Object sendLock = new Object(); - myNetOutData.compact(); - SSLEngineResult wrap = sslEngine.wrap(myAppOutData, - myNetOutData); - myNetOutData.flip(); - net.getOut().write(myNetOutData); - - if (wrap != null) { - switch (wrap.getStatus()) { - case BUFFER_UNDERFLOW: { + private void startRealSending() throws IOException { + // Only one thread at a time + synchronized (sendLock) { + while (true) { + + myAppOutData.compact(); + int rd; + try { + rd = out.read(myAppOutData); + } finally { + myAppOutData.flip(); + } + if (rd == 0) { break; } - case OK: { + if (rd < 0) { + close(); break; } - case BUFFER_OVERFLOW: { - throw new IOException("Overflow"); + + SSLEngineResult wrap; + synchronized(myNetOutData) { + myNetOutData.compact(); + try { + wrap = sslEngine.wrap(myAppOutData, + myNetOutData); + } finally { + myNetOutData.flip(); + } + net.getOut().write(myNetOutData); } + if (wrap != null) { + switch (wrap.getStatus()) { + case BUFFER_UNDERFLOW: { + break; + } + case OK: { + break; + } + case BUFFER_OVERFLOW: { + throw new IOException("Overflow"); + } + } } } } @@ -275,16 +340,16 @@ public class SslChannel extends IOChannel implements Runnable { // We'll need to unregister and register again from the selector. private void handleHandshking() { if (log.isLoggable(Level.FINEST)) { - log.finest("Starting handshake"); + log.info("Starting handshake"); } handshakeInProgress = true; - new Thread(this).start(); + ((SslConnector) connector).handshakeExecutor.execute(this); } private void endHandshake() throws IOException { if (log.isLoggable(Level.FINEST)) { - log.finest("Handshake done"); + log.info("Handshake done " + net.getIn().available()); } handshakeDone = true; handshakeInProgress = false; @@ -292,6 +357,10 @@ public class SslChannel extends IOChannel implements Runnable { flushing = false; startSending(); } + if (myNetInData.remaining() > 0 || net.getIn().available() > 0) { + // Last SSL packet also includes data. + handleReceived(net); + } } /** @@ -311,26 +380,41 @@ public class SslChannel extends IOChannel implements Runnable { long t0 = System.currentTimeMillis(); - while (hstatus != HandshakeStatus.FINISHED) { + while (hstatus != HandshakeStatus.NOT_HANDSHAKING + && hstatus != HandshakeStatus.FINISHED + && !net.getIn().isAppendClosed()) { + if (System.currentTimeMillis() - t0 > handshakeTimeout) { + throw new TimeoutException(); + } if (wrap != null && wrap.getStatus() == Status.CLOSED) { break; } if (log.isLoggable(Level.FINEST)) { - log.finest("-->doHandshake() loop: status = " + hstatus + " " + + log.info("-->doHandshake() loop: status = " + hstatus + " " + sslEngine.getHandshakeStatus()); } if (hstatus == HandshakeStatus.NEED_WRAP) { // || initial - for client initial = false; - myNetOutData.compact(); - - wrap = sslEngine.wrap(myAppOutData, myNetOutData); - myNetOutData.flip(); - - hstatus = wrap.getHandshakeStatus(); + synchronized(myNetOutData) { + myNetOutData.compact(); - net.getOut().write(myNetOutData); + try { + wrap = sslEngine.wrap(myAppOutData, myNetOutData); + } catch (Throwable t) { + t.printStackTrace(); + close(); + return; + } finally { + myNetOutData.flip(); + } + + hstatus = wrap.getHandshakeStatus(); + if (myNetOutData.remaining() > 0) { + net.getOut().write(myNetOutData); + } + } net.startSending(); @@ -356,11 +440,17 @@ public class SslChannel extends IOChannel implements Runnable { || wrap.getStatus() == Status.BUFFER_UNDERFLOW || (hstatus == HandshakeStatus.NEED_UNWRAP && myNetInData.remaining() == 0)) { myNetInData.compact(); - int rd = net.getIn().read(myNetInData); - myNetInData.flip(); + // non-blocking + int rd; + try { + rd = net.getIn().read(myNetInData); + } finally { + myNetInData.flip(); + } if (rd == 0) { - net.getIn().waitData(10000); - continue; + net.getIn().waitData(handshakeTimeout - + (System.currentTimeMillis() - t0)); + rd = net.getIn().read(myNetInData); } if (rd < 0) { // in closed @@ -368,7 +458,7 @@ public class SslChannel extends IOChannel implements Runnable { } } if (log.isLoggable(Level.FINEST)) { - log.finest("Unwrap chunk done " + hstatus + " " + wrap + log.info("Unwrap chunk done " + hstatus + " " + wrap + " " + sslEngine.getHandshakeStatus()); } @@ -384,7 +474,7 @@ public class SslChannel extends IOChannel implements Runnable { long t1task = System.currentTimeMillis(); hstatus = sslEngine.getHandshakeStatus(); if (log.isLoggable(Level.FINEST)) { - log.finest("Tasks done in " + (t1task - t0task) + " new status " + + log.info("Tasks done in " + (t1task - t0task) + " new status " + hstatus); } @@ -401,6 +491,7 @@ public class SslChannel extends IOChannel implements Runnable { try { close(); net.close(); + sendHandleReceivedCallback(); } catch (IOException ex) { log.log(Level.SEVERE, "Error closing", ex); } @@ -419,4 +510,9 @@ public class SslChannel extends IOChannel implements Runnable { this.sslCtx = sslCtx; return this; } + + public SslChannel setSslConnector(SslConnector con) { + this.connector = con; + return this; + } } diff --git a/modules/tomcat-lite/java/org/apache/tomcat/lite/io/SslConnector.java b/modules/tomcat-lite/java/org/apache/tomcat/lite/io/SslConnector.java index 04acbeefe..cdb4acef9 100644 --- a/modules/tomcat-lite/java/org/apache/tomcat/lite/io/SslConnector.java +++ b/modules/tomcat-lite/java/org/apache/tomcat/lite/io/SslConnector.java @@ -19,7 +19,6 @@ import java.security.KeyStoreException; import java.security.NoSuchAlgorithmException; import java.security.Principal; import java.security.PrivateKey; -import java.security.PublicKey; import java.security.UnrecoverableKeyException; import java.security.cert.Certificate; import java.security.cert.CertificateException; @@ -28,8 +27,9 @@ import java.security.cert.X509Certificate; import java.security.spec.InvalidKeySpecException; import java.security.spec.PKCS8EncodedKeySpec; import java.security.spec.RSAKeyGenParameterSpec; -import java.security.spec.X509EncodedKeySpec; import java.util.concurrent.Executor; +import java.util.concurrent.Executors; +import java.util.concurrent.atomic.AtomicInteger; import javax.net.ssl.KeyManager; import javax.net.ssl.KeyManagerFactory; @@ -42,6 +42,12 @@ import javax.net.ssl.X509TrustManager; public class SslConnector extends IOConnector { + /** + * TODO: option to require validation. + * TODO: remember cert signature. This is needed to support self-signed + * certs, like those used by the test. + * + */ public static class BasicTrustManager implements X509TrustManager { private X509Certificate[] chain; @@ -64,20 +70,21 @@ public class SslConnector extends IOConnector { public static TrustManager[] trustAllCerts = new TrustManager[] { new BasicTrustManager() }; - static final boolean debug = false; - static { - if (debug) { - System.setProperty("javax.net.debug", "ssl"); - } - } + static final boolean debug = false; + IOConnector net; private KeyManager[] keyManager; SSLContext sslCtx; boolean server; private TrustManager[] trustManagers; - Executor handshakeExecutor; + public AtomicInteger handshakeCount = new AtomicInteger(); + public AtomicInteger handshakeOk = new AtomicInteger(); + public AtomicInteger handshakeErr = new AtomicInteger(); + public AtomicInteger handshakeTime = new AtomicInteger(); + + Executor handshakeExecutor = Executors.newCachedThreadPool(); static int id = 0; public SslConnector() { @@ -92,7 +99,7 @@ public class SslConnector extends IOConnector { try { sslCtx = SSLContext.getInstance("TLS"); if (trustManagers == null) { - trustManagers = + trustManagers = new TrustManager[] {new BasicTrustManager()}; } @@ -115,6 +122,18 @@ public class SslConnector extends IOConnector { return net; } + public SslChannel channel(String host, int port) { + return new SslChannel() + .setTarget(host, port) + .setSslContext(getSSLContext()) + .setSslConnector(this); + } + + public SslChannel serverChannel() { + return new SslChannel() + .setSslContext(getSSLContext()) + .setSslConnector(this).withServer(); + } @Override public void acceptor(final ConnectedCallback sc, CharSequence port, Object extra) @@ -129,9 +148,7 @@ public class SslConnector extends IOConnector { first = dch; } - IOChannel sslch = new SslChannel() - .setSslContext(sslCtx) - .withServer(); + IOChannel sslch = serverChannel(); sslch.setSink(first); first.addFilterAfter(sslch); @@ -148,7 +165,7 @@ public class SslConnector extends IOConnector { } @Override - public void connect(String host, int port, final ConnectedCallback sc) + public void connect(final String host, final int port, final ConnectedCallback sc) throws IOException { getNet().connect(host, port, new ConnectedCallback() { @@ -161,8 +178,7 @@ public class SslConnector extends IOConnector { first = dch; } - IOChannel sslch = new SslChannel() - .setSslContext(sslCtx); + IOChannel sslch = channel(host, port); sslch.setSink(first); first.addFilterAfter(sslch); @@ -318,7 +334,7 @@ public class SslConnector extends IOConnector { public static void fixUrlConnection() { try { - SSLContext sc = SSLContext.getInstance("SSL"); + SSLContext sc = SSLContext.getInstance("TLS"); sc.init(null, SslConnector.trustAllCerts, null); javax.net.ssl.HttpsURLConnection.setDefaultSSLSocketFactory( sc.getSocketFactory()); diff --git a/modules/tomcat-lite/java/org/apache/tomcat/lite/servlet/ServletApi30.java b/modules/tomcat-lite/java/org/apache/tomcat/lite/servlet/ServletApi30.java index 684b85c7e..d641cb66b 100644 --- a/modules/tomcat-lite/java/org/apache/tomcat/lite/servlet/ServletApi30.java +++ b/modules/tomcat-lite/java/org/apache/tomcat/lite/servlet/ServletApi30.java @@ -33,147 +33,33 @@ import org.apache.tomcat.lite.http.HttpRequest; public class ServletApi30 extends ServletApi { public ServletContextImpl newContext() { - return new ServletContextImpl() { - protected void initEngineDefaults() throws ServletException { - super.initEngineDefaults(); - setAttribute(InstanceManager.class.getName(), - new LiteInstanceManager(getObjectManager())); - } - @Override - public Dynamic addFilter(String filterName, String className) { - FilterConfigImpl fc = new FilterConfigImpl(this); - fc.setData(filterName, null, new HashMap()); - fc.setData(filterName, className, new HashMap()); - filters.put(filterName, fc); - return new DynamicFilterRegistration(fc); - } - - @Override - public Dynamic addFilter(String filterName, Filter filter) { - FilterConfigImpl fc = new FilterConfigImpl(this); - fc.setData(filterName, null, new HashMap()); - fc.setFilter(filter); - filters.put(filterName, fc); - return new DynamicFilterRegistration(fc); - } - - @Override - public Dynamic addFilter(String filterName, - Class filterClass) { - FilterConfigImpl fc = new FilterConfigImpl(this); - fc.setData(filterName, null, new HashMap()); - fc.setFilterClass(filterClass); - filters.put(filterName, fc); - return new DynamicFilterRegistration(fc); - } - - @Override - public javax.servlet.ServletRegistration.Dynamic addServlet( - String servletName, String className) { - return null; - } - - @Override - public javax.servlet.ServletRegistration.Dynamic addServlet( - String servletName, Servlet servlet) { - return null; - } - - @Override - public javax.servlet.ServletRegistration.Dynamic addServlet( - String servletName, Class servletClass) { - return null; - } - - - @Override - public Set getDefaultSessionTrackingModes() { - return null; - } - - - @Override - public Set getEffectiveSessionTrackingModes() { - return null; - } - - @Override - public FilterRegistration getFilterRegistration(String filterName) { - return null; - } - - @Override - public Map getFilterRegistrations() { - return null; - } - - @Override - public JspConfigDescriptor getJspConfigDescriptor() { - return null; - } - - @Override - public ServletRegistration getServletRegistration(String servletName) { - return null; - } - - @Override - public Map getServletRegistrations() { - return null; - } - - @Override - public SessionCookieConfig getSessionCookieConfig() { - return null; - } - - @Override - public void setSessionTrackingModes( - EnumSet sessionTrackingModes) - throws IllegalStateException, IllegalArgumentException { - } - - public int getMajorVersion() { - return 3; - } - - public int getMinorVersion() { - return 0; - } - - }; + return new Servlet30ContextImpl(); } public ServletRequestImpl newRequest(HttpRequest req) { return new ServletRequestImpl(req) { - @Override public Part getPart(String name) { return null; } - @Override public Collection getParts() throws IOException, ServletException { return null; } - @Override public AsyncContext getAsyncContext() { return null; } - @Override public DispatcherType getDispatcherType() { return null; } - @Override public AsyncContext startAsync() { return null; } - @Override public AsyncContext startAsync(ServletRequest servletRequest, ServletResponse servletResponse) { return null; @@ -182,6 +68,99 @@ public class ServletApi30 extends ServletApi { }; } + public final class Servlet30ContextImpl extends ServletContextImpl { + protected void initEngineDefaults() throws ServletException { + super.initEngineDefaults(); + setAttribute(InstanceManager.class.getName(), + new LiteInstanceManager(getObjectManager())); + } + + public Dynamic addFilter(String filterName, String className) { + FilterConfigImpl fc = new FilterConfigImpl(this); + fc.setData(filterName, null, new HashMap()); + fc.setData(filterName, className, new HashMap()); + filters.put(filterName, fc); + return new DynamicFilterRegistration(fc); + } + + public Dynamic addFilter(String filterName, Filter filter) { + FilterConfigImpl fc = new FilterConfigImpl(this); + fc.setData(filterName, null, new HashMap()); + fc.setFilter(filter); + filters.put(filterName, fc); + return new DynamicFilterRegistration(fc); + } + + public Dynamic addFilter(String filterName, + Class filterClass) { + FilterConfigImpl fc = new FilterConfigImpl(this); + fc.setData(filterName, null, new HashMap()); + fc.setFilterClass(filterClass); + filters.put(filterName, fc); + return new DynamicFilterRegistration(fc); + } + + public javax.servlet.ServletRegistration.Dynamic addServlet( + String servletName, String className) { + return null; + } + + public javax.servlet.ServletRegistration.Dynamic addServlet( + String servletName, Servlet servlet) { + return null; + } + + public javax.servlet.ServletRegistration.Dynamic addServlet( + String servletName, Class servletClass) { + return null; + } + + public Set getDefaultSessionTrackingModes() { + return null; + } + + public Set getEffectiveSessionTrackingModes() { + return null; + } + + public FilterRegistration getFilterRegistration(String filterName) { + return null; + } + + public Map getFilterRegistrations() { + return null; + } + + public JspConfigDescriptor getJspConfigDescriptor() { + return null; + } + + public ServletRegistration getServletRegistration(String servletName) { + return null; + } + + public Map getServletRegistrations() { + return null; + } + + public SessionCookieConfig getSessionCookieConfig() { + return null; + } + + public int getMajorVersion() { + return 3; + } + + public int getMinorVersion() { + return 0; + } + + public void setSessionTrackingModes( + Set sessionTrackingModes) + throws IllegalStateException, IllegalArgumentException { + } + } + private final class LiteInstanceManager implements InstanceManager { private ObjectManager om; diff --git a/modules/tomcat-lite/java/org/apache/tomcat/lite/servlet/ServletContextImpl.java b/modules/tomcat-lite/java/org/apache/tomcat/lite/servlet/ServletContextImpl.java index 78e96cce0..002b19281 100644 --- a/modules/tomcat-lite/java/org/apache/tomcat/lite/servlet/ServletContextImpl.java +++ b/modules/tomcat-lite/java/org/apache/tomcat/lite/servlet/ServletContextImpl.java @@ -53,6 +53,8 @@ import javax.servlet.ServletException; import org.apache.tomcat.integration.ObjectManager; import org.apache.tomcat.lite.http.BaseMapper; import org.apache.tomcat.lite.io.FileConnectorJavaIo; +import org.apache.tomcat.lite.util.MimeMap; +import org.apache.tomcat.lite.util.UrlUtils; import org.apache.tomcat.servlets.config.ConfigLoader; import org.apache.tomcat.servlets.config.ServletContextConfig; import org.apache.tomcat.servlets.config.ServletContextConfig.FilterData; @@ -60,9 +62,7 @@ import org.apache.tomcat.servlets.config.ServletContextConfig.FilterMappingData; import org.apache.tomcat.servlets.config.ServletContextConfig.ServletData; import org.apache.tomcat.servlets.session.UserSessionManager; import org.apache.tomcat.servlets.util.Enumerator; -import org.apache.tomcat.servlets.util.MimeMap; import org.apache.tomcat.servlets.util.RequestUtil; -import org.apache.tomcat.servlets.util.UrlUtils; /** diff --git a/modules/tomcat-lite/java/org/apache/tomcat/lite/servlet/ServletRequestImpl.java b/modules/tomcat-lite/java/org/apache/tomcat/lite/servlet/ServletRequestImpl.java index a2e430c21..8b9baa769 100644 --- a/modules/tomcat-lite/java/org/apache/tomcat/lite/servlet/ServletRequestImpl.java +++ b/modules/tomcat-lite/java/org/apache/tomcat/lite/servlet/ServletRequestImpl.java @@ -54,9 +54,9 @@ import org.apache.tomcat.lite.http.MultiMap.Entry; import org.apache.tomcat.lite.io.BBuffer; import org.apache.tomcat.lite.io.CBuffer; import org.apache.tomcat.lite.io.FastHttpDateFormat; +import org.apache.tomcat.lite.util.LocaleParser; import org.apache.tomcat.servlets.session.UserSessionManager; import org.apache.tomcat.servlets.util.Enumerator; -import org.apache.tomcat.servlets.util.LocaleParser; import org.apache.tomcat.servlets.util.RequestUtil; @@ -206,8 +206,6 @@ public abstract class ServletRequestImpl implements HttpServletRequest { public static final String WORK_DIR_ATTR = "javax.servlet.context.tempdir"; - protected static final TimeZone GMT_ZONE = TimeZone.getTimeZone("GMT"); - /** * The default Locale if none are specified. @@ -228,13 +226,6 @@ public abstract class ServletRequestImpl implements HttpServletRequest { protected Cookie[] cookies = null; - /** - * The set of SimpleDateFormat formats to use in getDateHeader(). - * - * Notice that because SimpleDateFormat is not thread-safe, we can't - * declare formats[] as a static variable. - */ - protected SimpleDateFormat formats[] = null; /** @@ -683,28 +674,7 @@ public abstract class ServletRequestImpl implements HttpServletRequest { * cannot be converted to a date */ public long getDateHeader(String name) { - - String value = getHeader(name); - if (value == null) - return (-1L); - if (formats == null) { - formats = new SimpleDateFormat[] { - new SimpleDateFormat("EEE, dd MMM yyyy HH:mm:ss zzz", Locale.US), - new SimpleDateFormat("EEEEEE, dd-MMM-yy HH:mm:ss zzz", Locale.US), - new SimpleDateFormat("EEE MMMM d HH:mm:ss yyyy", Locale.US) - }; - formats[0].setTimeZone(GMT_ZONE); - formats[1].setTimeZone(GMT_ZONE); - formats[2].setTimeZone(GMT_ZONE); - } - - // Attempt to convert the date header in a variety of formats - long result = FastHttpDateFormat.parseDate(value, formats); - if (result != (-1L)) { - return result; - } - throw new IllegalArgumentException(value); - + return httpRequest.getDateHeader(name); } /** diff --git a/modules/tomcat-lite/java/org/apache/tomcat/lite/servlet/WebappFilterMapper.java b/modules/tomcat-lite/java/org/apache/tomcat/lite/servlet/WebappFilterMapper.java index 791759c40..9fcfc7314 100644 --- a/modules/tomcat-lite/java/org/apache/tomcat/lite/servlet/WebappFilterMapper.java +++ b/modules/tomcat-lite/java/org/apache/tomcat/lite/servlet/WebappFilterMapper.java @@ -32,6 +32,7 @@ import javax.servlet.ServletRequest; import javax.servlet.ServletResponse; import javax.servlet.http.HttpServletRequest; +import org.apache.tomcat.lite.util.URLEncoder; import org.apache.tomcat.servlets.util.RequestUtil; /** @@ -430,7 +431,7 @@ public class WebappFilterMapper implements Filter { } public void setURLPattern(String urlPattern) { - this.urlPattern = RequestUtil.URLDecode(urlPattern); + this.urlPattern = URLEncoder.URLDecode(urlPattern); } /** diff --git a/modules/tomcat-lite/java/org/apache/tomcat/servlets/util/Base64.java b/modules/tomcat-lite/java/org/apache/tomcat/lite/util/Base64.java similarity index 98% rename from modules/tomcat-lite/java/org/apache/tomcat/servlets/util/Base64.java rename to modules/tomcat-lite/java/org/apache/tomcat/lite/util/Base64.java index 1f4228644..848c99d3e 100644 --- a/modules/tomcat-lite/java/org/apache/tomcat/servlets/util/Base64.java +++ b/modules/tomcat-lite/java/org/apache/tomcat/lite/util/Base64.java @@ -16,7 +16,7 @@ */ -package org.apache.tomcat.servlets.util; +package org.apache.tomcat.lite.util; import java.util.logging.Level; import java.util.logging.Logger; @@ -165,7 +165,10 @@ public final class Base64 { return encodedData; } - + public byte[] decode(String enc) { + return decode(enc.getBytes()); + } + /** * Decodes Base64 data into octects * diff --git a/modules/tomcat-lite/java/org/apache/tomcat/servlets/util/FastHttpDateFormat.java b/modules/tomcat-lite/java/org/apache/tomcat/lite/util/FastHttpDateFormat.java similarity index 99% rename from modules/tomcat-lite/java/org/apache/tomcat/servlets/util/FastHttpDateFormat.java rename to modules/tomcat-lite/java/org/apache/tomcat/lite/util/FastHttpDateFormat.java index 7c2f905d6..fe3c69a40 100644 --- a/modules/tomcat-lite/java/org/apache/tomcat/servlets/util/FastHttpDateFormat.java +++ b/modules/tomcat-lite/java/org/apache/tomcat/lite/util/FastHttpDateFormat.java @@ -15,7 +15,7 @@ * limitations under the License. */ -package org.apache.tomcat.servlets.util; +package org.apache.tomcat.lite.util; import java.text.DateFormat; import java.text.ParseException; diff --git a/modules/tomcat-lite/java/org/apache/tomcat/servlets/util/LocaleParser.java b/modules/tomcat-lite/java/org/apache/tomcat/lite/util/LocaleParser.java similarity index 99% rename from modules/tomcat-lite/java/org/apache/tomcat/servlets/util/LocaleParser.java rename to modules/tomcat-lite/java/org/apache/tomcat/lite/util/LocaleParser.java index bfe3e42e4..fafdbffb8 100644 --- a/modules/tomcat-lite/java/org/apache/tomcat/servlets/util/LocaleParser.java +++ b/modules/tomcat-lite/java/org/apache/tomcat/lite/util/LocaleParser.java @@ -16,7 +16,7 @@ */ -package org.apache.tomcat.servlets.util; +package org.apache.tomcat.lite.util; import java.util.ArrayList; import java.util.Locale; diff --git a/modules/tomcat-lite/java/org/apache/tomcat/servlets/util/MimeMap.java b/modules/tomcat-lite/java/org/apache/tomcat/lite/util/MimeMap.java similarity index 99% rename from modules/tomcat-lite/java/org/apache/tomcat/servlets/util/MimeMap.java rename to modules/tomcat-lite/java/org/apache/tomcat/lite/util/MimeMap.java index a1731955c..bedb0ff0e 100644 --- a/modules/tomcat-lite/java/org/apache/tomcat/servlets/util/MimeMap.java +++ b/modules/tomcat-lite/java/org/apache/tomcat/lite/util/MimeMap.java @@ -15,7 +15,7 @@ * limitations under the License. */ -package org.apache.tomcat.servlets.util; +package org.apache.tomcat.lite.util; import java.net.FileNameMap; import java.util.Enumeration; diff --git a/modules/tomcat-lite/java/org/apache/tomcat/servlets/util/Range.java b/modules/tomcat-lite/java/org/apache/tomcat/lite/util/Range.java similarity index 99% rename from modules/tomcat-lite/java/org/apache/tomcat/servlets/util/Range.java rename to modules/tomcat-lite/java/org/apache/tomcat/lite/util/Range.java index 31d6b2718..6bc1f5f96 100644 --- a/modules/tomcat-lite/java/org/apache/tomcat/servlets/util/Range.java +++ b/modules/tomcat-lite/java/org/apache/tomcat/lite/util/Range.java @@ -15,7 +15,7 @@ * limitations under the License. */ -package org.apache.tomcat.servlets.util; +package org.apache.tomcat.lite.util; import java.io.IOException; import java.util.ArrayList; diff --git a/modules/tomcat-lite/java/org/apache/tomcat/lite/util/URLEncoder.java b/modules/tomcat-lite/java/org/apache/tomcat/lite/util/URLEncoder.java new file mode 100644 index 000000000..4364aea86 --- /dev/null +++ b/modules/tomcat-lite/java/org/apache/tomcat/lite/util/URLEncoder.java @@ -0,0 +1,227 @@ +/* + * 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.lite.util; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.OutputStreamWriter; +import java.io.UnsupportedEncodingException; +import java.util.BitSet; + +/** + * + * This class is very similar to the java.net.URLEncoder class. + * + * Unfortunately, with java.net.URLEncoder there is no way to specify to the + * java.net.URLEncoder which characters should NOT be encoded. + * + * This code was moved from DefaultServlet.java + * + * @author Craig R. McClanahan + * @author Remy Maucherat + */ +public class URLEncoder { + protected static final char[] hexadecimal = + {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', + 'A', 'B', 'C', 'D', 'E', 'F'}; + + //Array containing the safe characters set. + protected BitSet safeChars = new BitSet(128); + + public URLEncoder() { + for (char i = 'a'; i <= 'z'; i++) { + addSafeCharacter(i); + } + for (char i = 'A'; i <= 'Z'; i++) { + addSafeCharacter(i); + } + for (char i = '0'; i <= '9'; i++) { + addSafeCharacter(i); + } + //safe + safeChars.set('$'); + safeChars.set('-'); + safeChars.set('_'); + safeChars.set('.'); + + // Dangerous: someone may treat this as " " + // RFC1738 does allow it, it's not reserved + // safeChars.set('+'); + //extra + safeChars.set('!'); + safeChars.set('*'); + safeChars.set('\''); + safeChars.set('('); + safeChars.set(')'); + safeChars.set(','); + } + + public void addSafeCharacter( char c ) { + safeChars.set( c ); + } + + public String encodeURL(String path) { + return encodeURL(path, "UTF-8", true); + } + + public String encodeURL(String path, String enc, boolean allowSlash) { + int maxBytesPerChar = 10; + + StringBuffer rewrittenPath = new StringBuffer(path.length()); + ByteArrayOutputStream buf = new ByteArrayOutputStream(maxBytesPerChar); + OutputStreamWriter writer = null; + try { + writer = new OutputStreamWriter(buf, enc); + } catch (UnsupportedEncodingException e1) { + // shouldn't happen. + } + + for (int i = 0; i < path.length(); i++) { + int c = (int) path.charAt(i); + if (c < 128 && safeChars.get(c) || allowSlash && c == '/') { + rewrittenPath.append((char)c); + } else { + // convert to external encoding before hex conversion + try { + writer.write((char)c); + if (c >= 0xD800 && c <= 0xDBFF) { + if ( (i+1) < path.length()) { + int d = path.charAt(i+1); + if (d >= 0xDC00 && d <= 0xDFFF) { + writer.write((char) d); + i++; + } + } + } + writer.flush(); + } catch(IOException e) { + buf.reset(); + continue; + } + byte[] ba = buf.toByteArray(); + for (int j = 0; j < ba.length; j++) { + // Converting each byte in the buffer + byte toEncode = ba[j]; + rewrittenPath.append('%'); + int low = (int) (toEncode & 0x0f); + int high = (int) ((toEncode & 0xf0) >> 4); + rewrittenPath.append(hexadecimal[high]); + rewrittenPath.append(hexadecimal[low]); + } + buf.reset(); + } + } + return rewrittenPath.toString(); + } + + /** + * Decode and return the specified URL-encoded String. + * + * @param str The url-encoded string + * @param enc The encoding to use; if null, the default encoding is used + * @exception IllegalArgumentException if a '%' character is not followed + * by a valid 2-digit hexadecimal number + */ + public static String URLDecode(String str, String enc) { + + if (str == null) + return (null); + + // use the specified encoding to extract bytes out of the + // given string so that the encoding is not lost. If an + // encoding is not specified, let it use platform default + byte[] bytes = null; + try { + if (enc == null) { + bytes = str.getBytes(); + } else { + bytes = str.getBytes(enc); + } + } catch (UnsupportedEncodingException uee) {} + + return URLDecode(bytes, enc); + + } + + + /** + * Decode and return the specified URL-encoded String. + * When the byte array is converted to a string, the system default + * character encoding is used... This may be different than some other + * servers. + * + * @param str The url-encoded string + * + * @exception IllegalArgumentException if a '%' character is not followed + * by a valid 2-digit hexadecimal number + */ + public static String URLDecode(String str) { + + return URLDecode(str, null); + + } + + /** + * Decode and return the specified URL-encoded byte array. + * + * @param bytes The url-encoded byte array + * @param enc The encoding to use; if null, the default encoding is used + * @exception IllegalArgumentException if a '%' character is not followed + * by a valid 2-digit hexadecimal number + */ + private static String URLDecode(byte[] bytes, String enc) { + + if (bytes == null) + return (null); + + int len = bytes.length; + int ix = 0; + int ox = 0; + while (ix < len) { + byte b = bytes[ix++]; // Get byte to test + if (b == '+') { + b = (byte)' '; + } else if (b == '%') { + b = (byte) ((convertHexDigit(bytes[ix++]) << 4) + + convertHexDigit(bytes[ix++])); + } + bytes[ox++] = b; + } + if (enc != null) { + try { + return new String(bytes, 0, ox, enc); + } catch (Exception e) { + e.printStackTrace(); + } + } + return new String(bytes, 0, ox); + + } + + /** + * Convert a byte character value to hexidecimal digit value. + * + * @param b the character value byte + */ + private static byte convertHexDigit( byte b ) { + if ((b >= '0') && (b <= '9')) return (byte)(b - '0'); + if ((b >= 'a') && (b <= 'f')) return (byte)(b - 'a' + 10); + if ((b >= 'A') && (b <= 'F')) return (byte)(b - 'A' + 10); + return 0; + } + +} diff --git a/modules/tomcat-lite/java/org/apache/tomcat/servlets/util/UrlUtils.java b/modules/tomcat-lite/java/org/apache/tomcat/lite/util/UrlUtils.java similarity index 98% rename from modules/tomcat-lite/java/org/apache/tomcat/servlets/util/UrlUtils.java rename to modules/tomcat-lite/java/org/apache/tomcat/lite/util/UrlUtils.java index f968fb876..c0d9df0d9 100644 --- a/modules/tomcat-lite/java/org/apache/tomcat/servlets/util/UrlUtils.java +++ b/modules/tomcat-lite/java/org/apache/tomcat/lite/util/UrlUtils.java @@ -15,7 +15,7 @@ * limitations under the License. */ -package org.apache.tomcat.servlets.util; +package org.apache.tomcat.lite.util; public class UrlUtils { diff --git a/modules/tomcat-lite/java/org/apache/tomcat/servlets/file/DefaultServlet.java b/modules/tomcat-lite/java/org/apache/tomcat/servlets/file/DefaultServlet.java index 50ea4e64b..4015bf76e 100644 --- a/modules/tomcat-lite/java/org/apache/tomcat/servlets/file/DefaultServlet.java +++ b/modules/tomcat-lite/java/org/apache/tomcat/servlets/file/DefaultServlet.java @@ -41,8 +41,11 @@ import javax.servlet.ServletOutputStream; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; -import org.apache.tomcat.servlets.util.Range; -import org.apache.tomcat.servlets.util.URLEncoder; + +import org.apache.tomcat.lite.util.CopyUtils; +import org.apache.tomcat.lite.util.Dir2Html; +import org.apache.tomcat.lite.util.Range; +import org.apache.tomcat.lite.util.URLEncoder; /** * The default resource-serving servlet for most web applications, @@ -124,8 +127,6 @@ public class DefaultServlet extends HttpServlet { // --------------------------------------------------------- Public Methods - protected Filesystem fs; - /** * Finalize this servlet. */ @@ -136,10 +137,6 @@ public class DefaultServlet extends HttpServlet { * Initialize this servlet. */ public void init() throws ServletException { - if (fs == null) { - // R/O - no write - fs = new Filesystem(); - } String realPath = getServletContext().getRealPath("/"); basePath = new File(realPath); @@ -148,7 +145,7 @@ public class DefaultServlet extends HttpServlet { } catch (IOException e) { basePathName = basePath.getAbsolutePath(); } - log("Init fs " + fs + " base: " + basePathName); + log("Init default serviet, base: " + basePathName); // Set our properties from the initialization parameters String value = null; @@ -183,14 +180,6 @@ public class DefaultServlet extends HttpServlet { output = 256; } - public void setFilesystem(Filesystem fs) { - this.fs = fs; - } - - public Filesystem getFilesystem() { - return fs; - } - public void setBasePath(String s) { this.basePathName = s; this.basePath = new File(s); @@ -391,6 +380,64 @@ public class DefaultServlet extends HttpServlet { // } } + public void renderDir(HttpServletRequest request, + HttpServletResponse response, + File resFile, + String fileEncoding, + boolean content, + String relativePath) throws IOException { + + String contentType = "text/html;charset=" + fileEncoding; + + ServletOutputStream ostream = null; + PrintWriter writer = null; + + if (content) { + // Trying to retrieve the servlet output stream + try { + ostream = response.getOutputStream(); + } catch (IllegalStateException e) { + // If it fails, we try to get a Writer instead if we're + // trying to serve a text file + if ( (contentType == null) + || (contentType.startsWith("text")) ) { + writer = response.getWriter(); + } else { + throw e; + } + } + + } + + // Set the appropriate output headers + response.setContentType(contentType); + + InputStream renderResult = null; + + if (content) { + // Serve the directory browser + renderResult = + dir2Html.render(request.getContextPath(), resFile, relativePath); + } + + + // Copy the input stream to our output stream (if requested) + if (content) { + try { + response.setBufferSize(output); + } catch (IllegalStateException e) { + // Silent catch + } + if (ostream != null) { + CopyUtils.copy(renderResult, ostream); + } else { + CopyUtils.copy(renderResult, writer, fileEncoding); + } + } + + + } + /** * Serve the specified resource, optionally including the data content. @@ -430,7 +477,7 @@ public class DefaultServlet extends HttpServlet { request.getRequestURI()); return; } - dir2Html.renderDir(request, response, resFile, fileEncoding, content, + renderDir(request, response, resFile, fileEncoding, content, path); return; diff --git a/modules/tomcat-lite/java/org/apache/tomcat/servlets/file/WebdavServlet.java b/modules/tomcat-lite/java/org/apache/tomcat/servlets/file/WebdavServlet.java index 19a54f177..92c61d66f 100644 --- a/modules/tomcat-lite/java/org/apache/tomcat/servlets/file/WebdavServlet.java +++ b/modules/tomcat-lite/java/org/apache/tomcat/servlets/file/WebdavServlet.java @@ -18,6 +18,9 @@ package org.apache.tomcat.servlets.file; import java.io.File; +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; @@ -37,10 +40,13 @@ import javax.xml.parsers.DocumentBuilder; import javax.xml.parsers.DocumentBuilderFactory; import javax.xml.parsers.ParserConfigurationException; -import org.apache.tomcat.servlets.util.FastHttpDateFormat; -import org.apache.tomcat.servlets.util.Range; +import org.apache.tomcat.lite.util.CopyUtils; +import org.apache.tomcat.lite.util.FastHttpDateFormat; +import org.apache.tomcat.lite.util.Range; +import org.apache.tomcat.lite.util.URLEncoder; +import org.apache.tomcat.lite.util.UrlUtils; +import org.apache.tomcat.lite.util.XMLWriter; import org.apache.tomcat.servlets.util.RequestUtil; -import org.apache.tomcat.servlets.util.UrlUtils; import org.w3c.dom.Document; import org.w3c.dom.Element; @@ -420,7 +426,7 @@ public class WebdavServlet extends DefaultServlet { * @param path Path which has to be rewiten */ protected String rewriteUrl(String path) { - return urlEncoder.encode( path ); + return urlEncoder.encodeURL( path ); } @@ -841,7 +847,7 @@ public class WebdavServlet extends DefaultServlet { try { // will override - OutputStream fos = getFilesystem().getOutputStream(resFile.getPath()); + OutputStream fos = getOut(resFile.getPath()); CopyUtils.copy(resourceInputStream, fos); } catch(IOException e) { result = false; @@ -866,6 +872,7 @@ public class WebdavServlet extends DefaultServlet { * Handle a partial PUT. New content specified in request is appended to * existing content in oldRevisionContent (if present). This code does * not support simultaneous partial updates to the same resource. + * @throws FileNotFoundException */ // protected File executePartialPut(HttpServletRequest req, Range range, // String path) @@ -929,6 +936,10 @@ public class WebdavServlet extends DefaultServlet { // } + private OutputStream getOut(String path) throws FileNotFoundException { + return new FileOutputStream(path); + } + /** * COPY Method. */ @@ -1044,7 +1055,7 @@ public class WebdavServlet extends DefaultServlet { } // Remove url encoding from destination - destinationPath = RequestUtil.URLDecode(destinationPath, "UTF8"); + destinationPath = URLEncoder.URLDecode(destinationPath, "UTF8"); destinationPath = removeDestinationPrefix(req, destinationPath); @@ -1222,8 +1233,8 @@ public class WebdavServlet extends DefaultServlet { } else { try { - CopyUtils.copy(getFilesystem().getInputStream(object.getPath()), - getFilesystem().getOutputStream(dest)); + CopyUtils.copy(new FileInputStream(object.getPath()), + getOut(dest)); } catch(IOException ex ) { errorList.put (source, diff --git a/modules/tomcat-lite/java/org/apache/tomcat/servlets/util/RequestUtil.java b/modules/tomcat-lite/java/org/apache/tomcat/servlets/util/RequestUtil.java index 5135b0044..aefc629ed 100644 --- a/modules/tomcat-lite/java/org/apache/tomcat/servlets/util/RequestUtil.java +++ b/modules/tomcat-lite/java/org/apache/tomcat/servlets/util/RequestUtil.java @@ -304,102 +304,22 @@ public final class RequestUtil { } - /** - * Decode and return the specified URL-encoded String. - * When the byte array is converted to a string, the system default - * character encoding is used... This may be different than some other - * servers. - * - * @param str The url-encoded string - * - * @exception IllegalArgumentException if a '%' character is not followed - * by a valid 2-digit hexadecimal number - */ - public static String URLDecode(String str) { - - return URLDecode(str, null); - - } - - - /** - * Decode and return the specified URL-encoded String. - * - * @param str The url-encoded string - * @param enc The encoding to use; if null, the default encoding is used - * @exception IllegalArgumentException if a '%' character is not followed - * by a valid 2-digit hexadecimal number - */ - public static String URLDecode(String str, String enc) { - - if (str == null) - return (null); - - // use the specified encoding to extract bytes out of the - // given string so that the encoding is not lost. If an - // encoding is not specified, let it use platform default - byte[] bytes = null; - try { - if (enc == null) { - bytes = str.getBytes(); - } else { - bytes = str.getBytes(enc); - } - } catch (UnsupportedEncodingException uee) {} - return URLDecode(bytes, enc); - - } - - - /** - * Decode and return the specified URL-encoded byte array. - * - * @param bytes The url-encoded byte array - * @exception IllegalArgumentException if a '%' character is not followed - * by a valid 2-digit hexadecimal number - */ - public static String URLDecode(byte[] bytes) { - return URLDecode(bytes, null); - } + /** * Decode and return the specified URL-encoded byte array. * * @param bytes The url-encoded byte array - * @param enc The encoding to use; if null, the default encoding is used * @exception IllegalArgumentException if a '%' character is not followed * by a valid 2-digit hexadecimal number */ - public static String URLDecode(byte[] bytes, String enc) { +// public static String URLDecode(byte[] bytes) { +// return URLDecode(bytes, null); +// } - if (bytes == null) - return (null); - - int len = bytes.length; - int ix = 0; - int ox = 0; - while (ix < len) { - byte b = bytes[ix++]; // Get byte to test - if (b == '+') { - b = (byte)' '; - } else if (b == '%') { - b = (byte) ((convertHexDigit(bytes[ix++]) << 4) - + convertHexDigit(bytes[ix++])); - } - bytes[ox++] = b; - } - if (enc != null) { - try { - return new String(bytes, 0, ox, enc); - } catch (Exception e) { - e.printStackTrace(); - } - } - return new String(bytes, 0, ox); - } /** diff --git a/modules/tomcat-lite/java/org/apache/tomcat/servlets/util/URLEncoder.java b/modules/tomcat-lite/java/org/apache/tomcat/servlets/util/URLEncoder.java deleted file mode 100644 index bf15cd9b0..000000000 --- a/modules/tomcat-lite/java/org/apache/tomcat/servlets/util/URLEncoder.java +++ /dev/null @@ -1,101 +0,0 @@ -/* - * 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.servlets.util; - -import java.io.ByteArrayOutputStream; -import java.io.IOException; -import java.io.OutputStreamWriter; -import java.util.BitSet; - -/** - * - * This class is very similar to the java.net.URLEncoder class. - * - * Unfortunately, with java.net.URLEncoder there is no way to specify to the - * java.net.URLEncoder which characters should NOT be encoded. - * - * This code was moved from DefaultServlet.java - * - * @author Craig R. McClanahan - * @author Remy Maucherat - */ -public class URLEncoder { - protected static final char[] hexadecimal = - {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', - 'A', 'B', 'C', 'D', 'E', 'F'}; - - //Array containing the safe characters set. - protected BitSet safeCharacters = new BitSet(256); - - public URLEncoder() { - for (char i = 'a'; i <= 'z'; i++) { - addSafeCharacter(i); - } - for (char i = 'A'; i <= 'Z'; i++) { - addSafeCharacter(i); - } - for (char i = '0'; i <= '9'; i++) { - addSafeCharacter(i); - } - } - - public void addSafeCharacter( char c ) { - safeCharacters.set( c ); - } - - public String encode( String path ) { - int maxBytesPerChar = 10; - int caseDiff = ('a' - 'A'); - StringBuffer rewrittenPath = new StringBuffer(path.length()); - ByteArrayOutputStream buf = new ByteArrayOutputStream(maxBytesPerChar); - OutputStreamWriter writer = null; - try { - writer = new OutputStreamWriter(buf, "UTF8"); - } catch (Exception e) { - e.printStackTrace(); - writer = new OutputStreamWriter(buf); - } - - for (int i = 0; i < path.length(); i++) { - int c = (int) path.charAt(i); - if (safeCharacters.get(c)) { - rewrittenPath.append((char)c); - } else { - // convert to external encoding before hex conversion - try { - writer.write((char)c); - writer.flush(); - } catch(IOException e) { - buf.reset(); - continue; - } - byte[] ba = buf.toByteArray(); - for (int j = 0; j < ba.length; j++) { - // Converting each byte in the buffer - byte toEncode = ba[j]; - rewrittenPath.append('%'); - int low = (int) (toEncode & 0x0f); - int high = (int) ((toEncode & 0xf0) >> 4); - rewrittenPath.append(hexadecimal[high]); - rewrittenPath.append(hexadecimal[low]); - } - buf.reset(); - } - } - return rewrittenPath.toString(); - } -} 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 e1653837c..4eefbf702 100644 --- a/modules/tomcat-lite/test/org/apache/coyote/lite/TomcatLiteCoyoteTest.java +++ b/modules/tomcat-lite/test/org/apache/coyote/lite/TomcatLiteCoyoteTest.java @@ -28,7 +28,6 @@ public class TomcatLiteCoyoteTest extends TestCase { tomcat.setPort(8885); tomcat.setBaseDir("../../output/build/webapps"); - tomcat.addWebapp("/examples", "examples"); tomcat.addWebapp("/", "ROOT"); @@ -67,11 +66,11 @@ public class TomcatLiteCoyoteTest extends TestCase { public void testSimple() throws IOException { HttpConnector clientCon = DefaultHttpConnector.get(); HttpChannel ch = clientCon.get("localhost", 8885); - ch.getRequest().setRequestURI("/examples/servlets/servlet/HelloWorldExample"); + ch.getRequest().setRequestURI("/index.html"); ch.getRequest().send(); BBuffer res = ch.readAll(null, 0); - assertTrue(res.toString().indexOf("Hello World!") >= 0); + assertTrue(res.toString(), res.toString().indexOf("Apache Tomcat") >= 0); } 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 41126d831..005f249f6 100644 --- a/modules/tomcat-lite/test/org/apache/tomcat/lite/TestMain.java +++ b/modules/tomcat-lite/test/org/apache/tomcat/lite/TestMain.java @@ -7,9 +7,9 @@ import java.io.IOException; import java.io.InputStream; import java.net.HttpURLConnection; import java.net.URL; +import java.util.logging.Level; +import java.util.logging.Logger; -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; @@ -29,13 +29,12 @@ 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; +import org.apache.tomcat.lite.io.IOConnector; import org.apache.tomcat.lite.io.SocketConnector; 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; /** * Server with lost of test servlets. @@ -54,15 +53,20 @@ public class TestMain { private SimpleObjectManager om; + private boolean init = false; + private SocketConnector serverCon = new SocketConnector(); private HttpConnector testClient = DefaultHttpConnector.get(); private HttpConnector testServer = new HttpConnector(serverCon); private HttpConnector testProxy = new HttpConnector(serverCon); - + private HttpConnector sslServer; + private HttpProxyService proxy; UJmxObjectManagerSpi jmx = new UJmxObjectManagerSpi(); + + private IOConnector sslCon; public static TestMain shared() { if (defaultServer == null) { @@ -118,8 +122,6 @@ public class TestMain { }); d.addWrapper(mCtx, "/ujmx", new UJmxHandler(jmx)); - d.addWrapper(mCtx, "/jmx", - new ServletConfigImpl(new JMXProxyServlet())); } public void run() { @@ -142,10 +144,11 @@ public class TestMain { return 8443; } - protected void startAll(int basePort) throws IOException { + protected synchronized void startAll(int basePort) throws IOException { int port = basePort + 903; - if (proxy == null) { - + if (!init) { + init = true; + proxy = new HttpProxyService() .withHttpClient(testClient); testProxy.setPort(port); @@ -169,18 +172,21 @@ public class TestMain { e.printStackTrace(); } - SslConnector sslCon = new SslConnector() + sslCon = new SslConnector() .setKeysResource("org/apache/tomcat/lite/http/test.keystore", "changeit"); - HttpConnector sslServer = new HttpConnector(sslCon); + sslServer = new HttpConnector(sslCon); initTestCallback(sslServer.getDispatcher()); sslServer.setPort(basePort + 443); sslServer.start(); + +// System.setProperty("javax.net.debug", "ssl"); -// testProxy.setDebugHttp(true); -// testProxy.setDebug(true); -// testClient.setDebug(true); -// testClient.setDebugHttp(true); +// Logger.getLogger("SSL").setLevel(Level.FINEST); +// testProxy.setDebugHttp(true); +// testProxy.setDebug(true); +// testClient.setDebug(true); +// testClient.setDebugHttp(true); // testServer.setDebugHttp(true); // testServer.setDebug(true); // sslServer.setDebug(true); @@ -204,9 +210,17 @@ 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()); - + IOConnector io = con.getIOConnector(); + int ioLevel = 0; + while (io != null) { + om.bind("IOConnector-" + (ioLevel++) + "-" + base, io); + if (io instanceof SocketConnector) { + om.bind("NioThread-" + base, + ((SocketConnector) io).getSelector()); + + } + io = io.getNet(); + } con.cpool.setEvents(new HttpConnectionPool.HttpConnectionPoolEvents() { @Override @@ -251,13 +265,12 @@ public class TestMain { if (om == null) { om = new SimpleObjectManager(); } - // All objects visible in JMX via util.registry - // ( optional dependency ) - om.register(new JmxObjectManagerSpi()); om.register(jmx); - + // Additional settings, via spring-like config file om.loadResource(cfgFile); + + // initialization - using runnables String run = (String) om.getProperty("RUN"); String[] runNames = run == null ? new String[] {} : run.split(","); for (String name: runNames) { @@ -271,6 +284,7 @@ public class TestMain { bindConnector(testServer, "TestServer"); bindConnector(testClient, "Client"); bindConnector(testProxy, "Proxy"); + bindConnector(sslServer, "Https"); } @@ -291,14 +305,14 @@ public class TestMain { return out; } - public static ByteChunk getUrl(String path) throws IOException { - ByteChunk out = new ByteChunk(); + public static BBuffer getUrl(String path) throws IOException { + BBuffer out = BBuffer.allocate(); getUrl(path, out); return out; } public static HttpURLConnection getUrl(String path, - ByteChunk out) throws IOException { + BBuffer out) throws IOException { URL url = new URL(path); HttpURLConnection connection = (HttpURLConnection) url.openConnection(); diff --git a/modules/tomcat-lite/test/org/apache/tomcat/lite/http/HttpsTest.java b/modules/tomcat-lite/test/org/apache/tomcat/lite/http/HttpsTest.java index ed87256e6..25060c160 100644 --- a/modules/tomcat-lite/test/org/apache/tomcat/lite/http/HttpsTest.java +++ b/modules/tomcat-lite/test/org/apache/tomcat/lite/http/HttpsTest.java @@ -17,33 +17,24 @@ package org.apache.tomcat.lite.http; -import java.util.logging.Level; -import java.util.logging.Logger; - import junit.framework.TestCase; -import org.apache.commons.codec.binary.Base64; import org.apache.tomcat.lite.TestMain; import org.apache.tomcat.lite.io.BBuffer; import org.apache.tomcat.lite.io.SslConnector; -import org.apache.tomcat.util.buf.ByteChunk; +import org.apache.tomcat.lite.util.Base64; public class HttpsTest extends TestCase { static int port = 8443; final HttpConnector httpClient = TestMain.shared().getClient(); - public void setUp() { - Logger.getLogger("SSL").setLevel(Level.FINEST); - } - public void testSimpleClient() throws Exception { checkResponse(httpClient); } - public void testSimpleServer() throws Exception { - ByteChunk res = TestMain.getUrl("https://localhost:8443/hello"); + BBuffer res = TestMain.getUrl("https://localhost:8443/hello"); assertTrue(res.toString().indexOf("Hello") >= 0); } @@ -52,7 +43,7 @@ public class HttpsTest extends TestCase { HttpRequest ch = httpCon.request("localhost", port).setSecure(true); ch.setRequestURI("/hello"); - ch.setProtocol("HTTP/1.0"); + ch.setProtocol("HTTP/1.0"); // to force close ch.send(); BBuffer res = ch.readAll(); @@ -60,7 +51,7 @@ public class HttpsTest extends TestCase { } public void testSimpleClient20() throws Exception { - for (int i = 0; i < 20; i++) { + for (int i = 0; i < 10; i++) { checkResponse(httpClient); } } @@ -98,7 +89,7 @@ public class HttpsTest extends TestCase { TestMain.shared().initTestCallback(con.getDispatcher()); con.start(); - ByteChunk res = TestMain.getUrl("https://localhost:8444" + + BBuffer res = TestMain.getUrl("https://localhost:8444" + "/hello"); assertTrue(res.toString().indexOf("Hello") >= 0); diff --git a/modules/tomcat-lite/test/org/apache/tomcat/lite/http/LiveHttp1Test.java b/modules/tomcat-lite/test/org/apache/tomcat/lite/http/LiveHttp1Test.java index 923a6ae20..380f8055d 100644 --- a/modules/tomcat-lite/test/org/apache/tomcat/lite/http/LiveHttp1Test.java +++ b/modules/tomcat-lite/test/org/apache/tomcat/lite/http/LiveHttp1Test.java @@ -39,7 +39,8 @@ public class LiveHttp1Test extends TestCase { // DefaultHttpConnector.get().setDebugHttp(true); TestMain.getTestServer(); - httpClient = DefaultHttpConnector.get().request("localhost", clientPort); + httpClient = DefaultHttpConnector.get().request("localhost", + clientPort); bodyRecvBuffer.recycle(); } diff --git a/modules/tomcat-lite/test/org/apache/tomcat/lite/http/SpdyTest.java b/modules/tomcat-lite/test/org/apache/tomcat/lite/http/SpdyTest.java index 8c51fb879..1ee5e9b9d 100644 --- a/modules/tomcat-lite/test/org/apache/tomcat/lite/http/SpdyTest.java +++ b/modules/tomcat-lite/test/org/apache/tomcat/lite/http/SpdyTest.java @@ -5,17 +5,24 @@ package org.apache.tomcat.lite.http; import java.io.BufferedReader; import java.io.IOException; import java.io.InputStream; +import java.util.logging.Level; +import java.util.logging.Logger; import junit.framework.TestCase; import org.apache.tomcat.lite.TestMain; import org.apache.tomcat.lite.http.HttpConnectionPool.RemoteServer; import org.apache.tomcat.lite.io.IOBuffer; +import org.apache.tomcat.lite.io.SocketConnector; public class SpdyTest extends TestCase { HttpConnector http11Con = TestMain.shared().getClient(); - static HttpConnector spdyCon = DefaultHttpConnector.get(); + static HttpConnector spdyCon = + new HttpConnector(new SocketConnector()); + + static HttpConnector spdyConSsl = + new HttpConnector(new SocketConnector()); HttpConnector memSpdyCon = new HttpConnector(null); @@ -23,6 +30,30 @@ public class SpdyTest extends TestCase { HttpRequest req = spdyCon.request("http://localhost:8802/echo/test1"); + // Force SPDY - no negotiation + req.setProtocol("SPDY/1.0"); + + HttpResponse res = req.waitResponse(); + + assertEquals(200, res.getStatus()); + //assertEquals("", res.getHeader("")); + + BufferedReader reader = res.getReader(); + String line1 = reader.readLine(); + //assertEquals("", line1); + } + + public void testSslClient() throws IOException { + + HttpRequest req = + spdyConSsl.request("http://localhost:8443/echo/test1"); + // Enable SSL for the connection. + // TODO: this must be done on the first request, all will be + // encrypted. + req.setSecure(true); + // Force SPDY - no negotiation + req.setProtocol("SPDY/1.0"); + HttpResponse res = req.waitResponse(); assertEquals(200, res.getStatus()); @@ -32,11 +63,12 @@ public class SpdyTest extends TestCase { String line1 = reader.readLine(); //assertEquals("", line1); } + // Initial frame generated by Chrome public void testParse() throws IOException { InputStream is = - getClass().getClassLoader().getResourceAsStream("org/apache/tomcat/lite/http/spdyreq0"); + getClass().getClassLoader().getResourceAsStream("org/apache/tomcat/lite/http/spdyreq0.bin"); IOBuffer iob = new IOBuffer(); iob.append(is); @@ -65,7 +97,7 @@ public class SpdyTest extends TestCase { // Initial frame generated by Chrome public void testParseCompressed() throws IOException { InputStream is = - getClass().getClassLoader().getResourceAsStream("org/apache/tomcat/lite/http/spdyreqCompressed"); + getClass().getClassLoader().getResourceAsStream("org/apache/tomcat/lite/http/spdyreqCompressed.bin"); IOBuffer iob = new IOBuffer(); iob.append(is); 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 1304038b7..a2bd28d7c 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,21 +17,12 @@ 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.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; @@ -39,8 +30,8 @@ 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.RequestCompleted; +import org.apache.tomcat.lite.io.BBuffer; import org.apache.tomcat.lite.io.SocketConnector; -import org.apache.tomcat.util.buf.ByteChunk; /* Notes on memory use ( from heap dumps ): @@ -81,11 +72,9 @@ public class LiveHttpThreadedTest extends TestCase { new HttpConnector(new SocketConnector()); ThreadRunner tr; - static MBeanServer server; - static boolean dumpHeap = false; + static boolean dumpHeap = true; AtomicInteger ok = new AtomicInteger(); - Object lock = new Object(); int reqCnt; Map active = new HashMap(); @@ -95,63 +84,69 @@ public class LiveHttpThreadedTest extends TestCase { } public void test1000Async() throws Exception { - try { - asyncRequest(10, 100, false, clientCon); - } finally { - dumpHeap("heapAsync.bin"); - } +// try { + asyncRequest(10, 100, false, false, clientCon, "AsyncHttp"); +// } finally { +// dumpHeap("heapAsync.bin"); +// } } public void test10000Async() throws Exception { - try { - asyncRequest(20, 500, false, clientCon); - } finally { - dumpHeap("heapAsyncBig.bin"); - } + asyncRequest(20, 500, false, false, clientCon, "AsyncHttp"); } - public void test1000AsyncSpdy() throws Exception { - try { - asyncRequest(10, 100, true, spdyClient); - } finally { - dumpHeap("heapSpdy1000.bin"); - } + public void test1000AsyncSsl() throws Exception { + asyncRequest(20, 50, false, true, clientCon, "AsyncHttpSsl"); + } + + public void test10000AsyncSsl() throws Exception { + asyncRequest(20, 500, false, true, clientCon, "AsyncHttpSsl"); + } + + public void test1000AsyncSpdy() throws Exception { + asyncRequest(10, 100, true, false, spdyClient, "AsyncSpdy"); } public void test10000AsyncSpdy() throws Exception { - try { - asyncRequest(20, 500, true, spdyClient); - } finally { - dumpHeap("heapSpdy10000.bin"); - } + asyncRequest(20, 500, true, false, spdyClient, "AsyncSpdy"); } public void test1000AsyncSpdyComp() throws Exception { - try { - asyncRequest(10, 100, true, spdyClientCompress); - } finally { - dumpHeap("heapSpdy1000Comp.bin"); - } - + asyncRequest(10, 100, true, false, spdyClientCompress, "AsyncSpdyComp"); } public void test10000AsyncSpdyComp() throws Exception { - try { - asyncRequest(20, 500, true, spdyClientCompress); - } finally { - dumpHeap("heapSpdy10000.bin"); - } + asyncRequest(20, 500, true, false, spdyClientCompress, "AsyncSpdyComp"); } - public void asyncRequest(int thr, int perthr, - final boolean spdy, final HttpConnector clientCon) throws Exception { + public void test1000AsyncSpdySsl() throws Exception { + asyncRequest(10, 100, true, true, spdyClient, "AsyncSpdySsl"); + } + + public void test1000AsyncSpdyCompSsl() throws Exception { + asyncRequest(10, 100, true, true, spdyClientCompress, "AsyncSpdyCompSsl"); + } + + public void test10000AsyncSpdyCompSsl() throws Exception { + asyncRequest(20, 500, true, true, spdyClientCompress, "AsyncSpdyCompSsl"); + } + + Object thrlock = new Object(); + Object lock = new Object(); + + public void asyncRequest(final int thr, int perthr, + final boolean spdy, final boolean ssl, + final HttpConnector clientCon, String test) throws Exception { + clientCon.getConnectionPool().clear(); 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); + HttpRequest cstate = clientCon.request("localhost", + ssl ? 8443 : 8802); synchronized (active) { active.put(cstate, cstate); } @@ -160,39 +155,96 @@ public class LiveHttpThreadedTest extends TestCase { // a negotiation. cstate.setProtocol("SPDY/1.0"); } + if (ssl) { + cstate.setSecure(true); + } cstate.requestURI().set("/hello"); cstate.setCompletedCallback(reqCallback); // no body cstate.getBody().close(); - // Send the request, wait response - Thread.currentThread().sleep(20); + cstate.send(); + + while (active.size() >= thr) { + synchronized(thrlock) { + thrlock.wait(); + } + } } }; tr.run(); - assertEquals(0, tr.errors.get()); synchronized (lock) { if (ok.get() < reqCnt) { lock.wait(reqCnt * 100); } } + long time = (System.currentTimeMillis() - t0); + + System.err.println("====== " + test + + " threads: " + thr + ", req: " + + reqCnt + ", sendTime" + tr.time + + ", time: " + time + + ", connections: " + clientCon.getConnectionPool().getSocketCount() + + ", avg: " + (time / reqCnt)); + assertEquals(reqCnt, ok.get()); - System.err.println(reqCnt + " Async requests: " + (System.currentTimeMillis() - t0)); + assertEquals(0, tr.errors.get()); } + + RequestCompleted reqCallback = new RequestCompleted() { + @Override + public void handle(HttpChannel data, Object extraData) + throws IOException { + String out = data.getIn().copyAll(null).toString(); + if (200 != data.getResponse().getStatus()) { + System.err.println("Wrong status"); + tr.errors.incrementAndGet(); + } else if (!"Hello world".equals(out)) { + tr.errors.incrementAndGet(); + System.err.println("bad result " + out); + } + synchronized (active) { + active.remove(data.getRequest()); + } + synchronized (thrlock) { + thrlock.notify(); + } + data.release(); + int okres = ok.incrementAndGet(); + if (okres >= reqCnt) { + synchronized (lock) { + lock.notify(); + } + } + } + }; + + public void testURLRequest1000() throws Exception { - urlRequest(10, 100); + urlRequest(10, 100, false, "HttpURLConnection"); } public void xtestURLRequest10000() throws Exception { - urlRequest(20, 500); + urlRequest(20, 500, false, "HttpURLConnection"); + + } + + // I can't seem to get 1000 requests to all complete... + public void xtestURLRequestSsl100() throws Exception { + urlRequest(10, 10, true, "HttpURLConnectionSSL"); + } + + public void xtestURLRequestSsl10000() throws Exception { + urlRequest(20, 500, true, "HttpURLConnectionSSL"); } /** * HttpURLConnection client against lite.http server. */ - public void urlRequest(int thr, int cnt) throws Exception { + public void urlRequest(int thr, int cnt, final boolean ssl, String test) + throws Exception { long t0 = System.currentTimeMillis(); @@ -203,8 +255,11 @@ public class LiveHttpThreadedTest extends TestCase { public void makeRequest(int i) throws Exception { try { - ByteChunk out = new ByteChunk(); - HttpURLConnection con = TestMain.getUrl("http://localhost:8802/hello", out); + BBuffer out = BBuffer.allocate(); + String url = ssl ? "https://localhost:8443/hello" : + "http://localhost:8802/hello"; + HttpURLConnection con = + TestMain.getUrl(url, out); if (con.getResponseCode() != 200) { errors.incrementAndGet(); } @@ -220,62 +275,17 @@ public class LiveHttpThreadedTest extends TestCase { }; tr.run(); assertEquals(0, tr.errors.get()); + long time = (System.currentTimeMillis() - t0); - System.err.println(thr + " threads, " + (thr * cnt) + " total blocking URL requests: " + - (System.currentTimeMillis() - t0)); - - //assertEquals(testServer., actual) + System.err.println("====== " + test + " threads: " + thr + ", req: " + + (thr * cnt) + ", time: " + time + ", avg: " + + (time / (thr * cnt))); } finally { - dumpHeap("heapURLReq.bin"); + //dumpHeap("heapURLReq.bin"); } } // TODO: move to a servlet - private void dumpHeap(String file) throws InstanceNotFoundException, - MBeanException, ReflectionException, MalformedObjectNameException { - if (!dumpHeap) { - return; - } - 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"}); - } - - - RequestCompleted reqCallback = new RequestCompleted() { - @Override - public void handle(HttpChannel data, Object extraData) - throws IOException { - 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(); - } - } - } - }; } diff --git a/modules/tomcat-lite/test/org/apache/tomcat/lite/load/MicroTest.java b/modules/tomcat-lite/test/org/apache/tomcat/lite/load/MicroTest.java index bbc6a4781..4539312f8 100644 --- a/modules/tomcat-lite/test/org/apache/tomcat/lite/load/MicroTest.java +++ b/modules/tomcat-lite/test/org/apache/tomcat/lite/load/MicroTest.java @@ -44,6 +44,7 @@ public class MicroTest extends TestCase { mappingData.recycle(); mapper.map(host, uri, mappingData); } - System.out.println("Elapsed:" + (System.currentTimeMillis() - time)); + // TODO: asserts + //System.out.println("Elapsed:" + (System.currentTimeMillis() - time)); } } 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 index 8f61eadcc..96fea90fe 100644 --- a/modules/tomcat-lite/test/org/apache/tomcat/lite/load/ThreadRunner.java +++ b/modules/tomcat-lite/test/org/apache/tomcat/lite/load/ThreadRunner.java @@ -45,7 +45,6 @@ public class ThreadRunner { } long t1 = System.currentTimeMillis(); time = t1 - t0; - System.err.println("TimeNB: " + (t1 - t0) + " " + res); } public void makeRequests(int cnt) { 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 baade733b..184b583d3 100644 --- a/modules/tomcat-lite/test/org/apache/tomcat/lite/test.properties +++ b/modules/tomcat-lite/test/org/apache/tomcat/lite/test.properties @@ -1,4 +1,7 @@ -RUN=Log,Socks,TomcatLite +RUN=JMX,Log,Socks,TomcatLite,JMXHandler + +JMX.(class)=org.apache.tomcat.integration.jmx.JmxObjectManagerSpi +JMXHandler.(class)=org.apache.tomcat.integration.jmx.JmxHandler Log.(class)=org.apache.tomcat.lite.service.LogConfig Log.debug=org.apache.tomcat.lite.http.HttpConnector diff --git a/modules/tomcat-lite/test/org/apache/tomcat/util/buf/UEncoderTest.java b/modules/tomcat-lite/test/org/apache/tomcat/lite/util/UEncoderTest.java similarity index 90% rename from modules/tomcat-lite/test/org/apache/tomcat/util/buf/UEncoderTest.java rename to modules/tomcat-lite/test/org/apache/tomcat/lite/util/UEncoderTest.java index f3f8f40e2..5048e2875 100644 --- a/modules/tomcat-lite/test/org/apache/tomcat/util/buf/UEncoderTest.java +++ b/modules/tomcat-lite/test/org/apache/tomcat/lite/util/UEncoderTest.java @@ -15,12 +15,12 @@ * limitations under the License. */ -package org.apache.tomcat.util.buf; +package org.apache.tomcat.lite.util; import junit.framework.TestCase; public class UEncoderTest extends TestCase { - UEncoder enc=new UEncoder(); + URLEncoder enc=new URLEncoder(); /* * @@ -33,7 +33,7 @@ public class UEncoderTest extends TestCase { assertEquals("test", eurl1); eurl1=enc.encodeURL("/test"); - assertEquals("%2ftest", eurl1); + assertEquals("/test", eurl1); // safe ranges eurl1=enc.encodeURL("test$-_."); @@ -43,7 +43,7 @@ public class UEncoderTest extends TestCase { assertEquals("test$-_.!*'(),", eurl1); eurl1=enc.encodeURL("//test"); - assertEquals("%2f%2ftest", eurl1); + assertEquals("//test", eurl1); } -- 2.11.0