From 41e52221b0bdd3880c868c430eced293c8168b7a Mon Sep 17 00:00:00 2001 From: fhanik Date: Thu, 8 Oct 2009 17:08:20 +0000 Subject: [PATCH] Refactor the BIO connector to align it better for async support, this means a "poller" style for timeouts etc. First step is to allow the connector to do keep alive on more connections than we have threads. More changes to follow, good chance that NIO and BIO can share tons of code git-svn-id: https://svn.apache.org/repos/asf/tomcat/trunk@823234 13f79535-47bb-0310-9956-ffa450edef68 --- java/org/apache/coyote/ajp/AjpProtocol.java | 5 +- .../coyote/http11/AbstractHttp11Processor.java | 731 +++++++++++++++++ .../apache/coyote/http11/Http11NioProcessor.java | 584 +------------ java/org/apache/coyote/http11/Http11Processor.java | 912 +++------------------ java/org/apache/coyote/http11/Http11Protocol.java | 14 +- java/org/apache/tomcat/util/net/JIoEndpoint.java | 32 +- java/org/apache/tomcat/util/net/NioEndpoint.java | 40 +- java/org/apache/tomcat/util/net/SocketWrapper.java | 59 ++ .../tomcat/util/net/jsse/JSSESocketFactory.java | 7 +- test/org/apache/TestAll.java | 11 +- .../catalina/connector/TestKeepAliveCount.java | 131 +++ .../apache/catalina/startup/SimpleHttpClient.java | 17 +- 12 files changed, 1133 insertions(+), 1410 deletions(-) create mode 100644 java/org/apache/coyote/http11/AbstractHttp11Processor.java create mode 100644 java/org/apache/tomcat/util/net/SocketWrapper.java create mode 100644 test/org/apache/catalina/connector/TestKeepAliveCount.java diff --git a/java/org/apache/coyote/ajp/AjpProtocol.java b/java/org/apache/coyote/ajp/AjpProtocol.java index 6576ead2c..8dd956f05 100644 --- a/java/org/apache/coyote/ajp/AjpProtocol.java +++ b/java/org/apache/coyote/ajp/AjpProtocol.java @@ -39,6 +39,7 @@ import org.apache.coyote.RequestGroupInfo; import org.apache.coyote.RequestInfo; import org.apache.tomcat.util.modeler.Registry; import org.apache.tomcat.util.net.JIoEndpoint; +import org.apache.tomcat.util.net.SocketWrapper; import org.apache.tomcat.util.net.JIoEndpoint.Handler; import org.apache.tomcat.util.res.StringManager; @@ -358,7 +359,7 @@ public class AjpProtocol this.proto = proto; } - public boolean process(Socket socket) { + public boolean process(SocketWrapper socket) { AjpProcessor processor = recycledProcessors.poll(); try { @@ -370,7 +371,7 @@ public class AjpProtocol ((ActionHook) processor).action(ActionCode.ACTION_START, null); } - processor.process(socket); + processor.process(socket.getSocket()); return false; } catch(java.net.SocketException e) { diff --git a/java/org/apache/coyote/http11/AbstractHttp11Processor.java b/java/org/apache/coyote/http11/AbstractHttp11Processor.java new file mode 100644 index 000000000..bb4088f2b --- /dev/null +++ b/java/org/apache/coyote/http11/AbstractHttp11Processor.java @@ -0,0 +1,731 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.coyote.http11; + +import java.net.InetAddress; +import java.util.StringTokenizer; +import java.util.regex.Pattern; +import java.util.regex.PatternSyntaxException; + +import org.apache.coyote.Adapter; +import org.apache.coyote.Request; +import org.apache.coyote.Response; +import org.apache.tomcat.util.buf.Ascii; +import org.apache.tomcat.util.buf.ByteChunk; +import org.apache.tomcat.util.buf.HexUtils; +import org.apache.tomcat.util.buf.MessageBytes; +import org.apache.tomcat.util.res.StringManager; + +public class AbstractHttp11Processor { + + /** + * Logger. + */ + protected static org.apache.juli.logging.Log log + = org.apache.juli.logging.LogFactory.getLog(AbstractHttp11Processor.class); + + /** + * The string manager for this package. + */ + protected static StringManager sm = + StringManager.getManager(Constants.Package); + + protected static boolean isSecurityEnabled = + org.apache.coyote.Constants.IS_SECURITY_ENABLED; + + /** + * Associated adapter. + */ + protected Adapter adapter = null; + + + /** + * Request object. + */ + protected Request request = null; + + + /** + * Response object. + */ + protected Response response = null; + + + /** + * State flag. + */ + protected boolean started = false; + + + /** + * Error flag. + */ + protected boolean error = false; + + + /** + * Keep-alive. + */ + protected boolean keepAlive = true; + + + /** + * HTTP/1.1 flag. + */ + protected boolean http11 = true; + + + /** + * HTTP/0.9 flag. + */ + protected boolean http09 = false; + + + /** + * Content delimitator for the request (if false, the connection will + * be closed at the end of the request). + */ + protected boolean contentDelimitation = true; + + + /** + * Is there an expectation ? + */ + protected boolean expectation = false; + + + /** + * List of restricted user agents. + */ + protected Pattern[] restrictedUserAgents = null; + + + /** + * Maximum number of Keep-Alive requests to honor. + */ + protected int maxKeepAliveRequests = -1; + + /** + * The number of seconds Tomcat will wait for a subsequent request + * before closing the connection. + */ + protected int keepAliveTimeout = -1; + + /** + * Remote Address associated with the current connection. + */ + protected String remoteAddr = null; + + + /** + * Remote Host associated with the current connection. + */ + protected String remoteHost = null; + + + /** + * Local Host associated with the current connection. + */ + protected String localName = null; + + + + /** + * Local port to which the socket is connected + */ + protected int localPort = -1; + + + /** + * Remote port to which the socket is connected + */ + protected int remotePort = -1; + + + /** + * The local Host address. + */ + protected String localAddr = null; + + + /** + * Maximum timeout on uploads. 5 minutes as in Apache HTTPD server. + */ + protected int timeout = 300000; + + + /** + * Flag to disable setting a different time-out on uploads. + */ + protected boolean disableUploadTimeout = false; + + + /** + * Allowed compression level. + */ + protected int compressionLevel = 0; + + + /** + * Minimum contentsize to make compression. + */ + protected int compressionMinSize = 2048; + + + /** + * Socket buffering. + */ + protected int socketBuffer = -1; + + + /** + * Max saved post size. + */ + protected int maxSavePostSize = 4 * 1024; + + + /** + * List of user agents to not use gzip with + */ + protected Pattern noCompressionUserAgents[] = null; + + /** + * List of MIMES which could be gzipped + */ + protected String[] compressableMimeTypes = + { "text/html", "text/xml", "text/plain" }; + + + /** + * Host name (used to avoid useless B2C conversion on the host name). + */ + protected char[] hostNameC = new char[0]; + + + /** + * Allow a customized the server header for the tin-foil hat folks. + */ + protected String server = null; + + /** + * Set compression level. + */ + public void setCompression(String compression) { + if (compression.equals("on")) { + this.compressionLevel = 1; + } else if (compression.equals("force")) { + this.compressionLevel = 2; + } else if (compression.equals("off")) { + this.compressionLevel = 0; + } else { + try { + // Try to parse compression as an int, which would give the + // minimum compression size + compressionMinSize = Integer.parseInt(compression); + this.compressionLevel = 1; + } catch (Exception e) { + this.compressionLevel = 0; + } + } + } + + /** + * Set Minimum size to trigger compression. + */ + public void setCompressionMinSize(int compressionMinSize) { + this.compressionMinSize = compressionMinSize; + } + + + /** + * Add user-agent for which gzip compression didn't works + * The user agent String given will be exactly matched + * to the user-agent header submitted by the client. + * + * @param userAgent user-agent string + */ + public void addNoCompressionUserAgent(String userAgent) { + try { + Pattern nRule = Pattern.compile(userAgent); + noCompressionUserAgents = + addREArray(noCompressionUserAgents, nRule); + } catch (PatternSyntaxException pse) { + log.error(sm.getString("http11processor.regexp.error", userAgent), pse); + } + } + + + /** + * Set no compression user agent list (this method is best when used with + * a large number of connectors, where it would be better to have all of + * them referenced a single array). + */ + public void setNoCompressionUserAgents(Pattern[] noCompressionUserAgents) { + this.noCompressionUserAgents = noCompressionUserAgents; + } + + + /** + * Set no compression user agent list. + * List contains users agents separated by ',' : + * + * ie: "gorilla,desesplorer,tigrus" + */ + public void setNoCompressionUserAgents(String noCompressionUserAgents) { + if (noCompressionUserAgents != null) { + StringTokenizer st = new StringTokenizer(noCompressionUserAgents, ","); + + while (st.hasMoreTokens()) { + addNoCompressionUserAgent(st.nextToken().trim()); + } + } + } + + /** + * Add a mime-type which will be compressable + * The mime-type String will be exactly matched + * in the response mime-type header . + * + * @param mimeType mime-type string + */ + public void addCompressableMimeType(String mimeType) { + compressableMimeTypes = + addStringArray(compressableMimeTypes, mimeType); + } + + + /** + * Set compressable mime-type list (this method is best when used with + * a large number of connectors, where it would be better to have all of + * them referenced a single array). + */ + public void setCompressableMimeTypes(String[] compressableMimeTypes) { + this.compressableMimeTypes = compressableMimeTypes; + } + + + /** + * Set compressable mime-type list + * List contains users agents separated by ',' : + * + * ie: "text/html,text/xml,text/plain" + */ + public void setCompressableMimeTypes(String compressableMimeTypes) { + if (compressableMimeTypes != null) { + this.compressableMimeTypes = null; + StringTokenizer st = new StringTokenizer(compressableMimeTypes, ","); + + while (st.hasMoreTokens()) { + addCompressableMimeType(st.nextToken().trim()); + } + } + } + + + /** + * Return the list of restricted user agents. + */ + public String[] findCompressableMimeTypes() { + return (compressableMimeTypes); + } + + + /** + * Return compression level. + */ + public String getCompression() { + switch (compressionLevel) { + case 0: + return "off"; + case 1: + return "on"; + case 2: + return "force"; + } + return "off"; + } + + + + + + /** + * General use method + * + * @param sArray the StringArray + * @param value string + */ + private String[] addStringArray(String sArray[], String value) { + String[] result = null; + if (sArray == null) { + result = new String[1]; + result[0] = value; + } + else { + result = new String[sArray.length + 1]; + for (int i = 0; i < sArray.length; i++) + result[i] = sArray[i]; + result[sArray.length] = value; + } + return result; + } + + + /** + * General use method + * + * @param rArray the REArray + * @param value Obj + */ + private Pattern[] addREArray(Pattern rArray[], Pattern value) { + Pattern[] result = null; + if (rArray == null) { + result = new Pattern[1]; + result[0] = value; + } + else { + result = new Pattern[rArray.length + 1]; + for (int i = 0; i < rArray.length; i++) + result[i] = rArray[i]; + result[rArray.length] = value; + } + return result; + } + + + /** + * Checks if any entry in the string array starts with the specified value + * + * @param sArray the StringArray + * @param value string + */ + private boolean startsWithStringArray(String sArray[], String value) { + if (value == null) + return false; + for (int i = 0; i < sArray.length; i++) { + if (value.startsWith(sArray[i])) { + return true; + } + } + return false; + } + + + /** + * Add restricted user-agent (which will downgrade the connector + * to HTTP/1.0 mode). The user agent String given will be matched + * via regexp to the user-agent header submitted by the client. + * + * @param userAgent user-agent string + */ + public void addRestrictedUserAgent(String userAgent) { + try { + Pattern nRule = Pattern.compile(userAgent); + restrictedUserAgents = addREArray(restrictedUserAgents, nRule); + } catch (PatternSyntaxException pse) { + log.error(sm.getString("http11processor.regexp.error", userAgent), pse); + } + } + + + /** + * Set restricted user agent list (this method is best when used with + * a large number of connectors, where it would be better to have all of + * them referenced a single array). + */ + public void setRestrictedUserAgents(Pattern[] restrictedUserAgents) { + this.restrictedUserAgents = restrictedUserAgents; + } + + + /** + * Set restricted user agent list (which will downgrade the connector + * to HTTP/1.0 mode). List contains users agents separated by ',' : + * + * ie: "gorilla,desesplorer,tigrus" + */ + public void setRestrictedUserAgents(String restrictedUserAgents) { + if (restrictedUserAgents != null) { + StringTokenizer st = + new StringTokenizer(restrictedUserAgents, ","); + while (st.hasMoreTokens()) { + addRestrictedUserAgent(st.nextToken().trim()); + } + } + } + + + /** + * Return the list of restricted user agents. + */ + public String[] findRestrictedUserAgents() { + String[] sarr = new String [restrictedUserAgents.length]; + + for (int i = 0; i < restrictedUserAgents.length; i++) + sarr[i] = restrictedUserAgents[i].toString(); + + return (sarr); + } + + + /** + * Set the maximum number of Keep-Alive requests to honor. + * This is to safeguard from DoS attacks. Setting to a negative + * value disables the check. + */ + public void setMaxKeepAliveRequests(int mkar) { + maxKeepAliveRequests = mkar; + } + + + /** + * Return the number of Keep-Alive requests that we will honor. + */ + public int getMaxKeepAliveRequests() { + return maxKeepAliveRequests; + } + + /** + * Set the Keep-Alive timeout. + */ + public void setKeepAliveTimeout(int timeout) { + keepAliveTimeout = timeout; + } + + + /** + * Return the number Keep-Alive timeout. + */ + public int getKeepAliveTimeout() { + return keepAliveTimeout; + } + + + /** + * Set the maximum size of a POST which will be buffered in SSL mode. + */ + public void setMaxSavePostSize(int msps) { + maxSavePostSize = msps; + } + + + /** + * Return the maximum size of a POST which will be buffered in SSL mode. + */ + public int getMaxSavePostSize() { + return maxSavePostSize; + } + + + /** + * Set the flag to control upload time-outs. + */ + public void setDisableUploadTimeout(boolean isDisabled) { + disableUploadTimeout = isDisabled; + } + + /** + * Get the flag that controls upload time-outs. + */ + public boolean getDisableUploadTimeout() { + return disableUploadTimeout; + } + + /** + * Set the socket buffer flag. + */ + public void setSocketBuffer(int socketBuffer) { + this.socketBuffer = socketBuffer; + } + + /** + * Get the socket buffer flag. + */ + public int getSocketBuffer() { + return socketBuffer; + } + + /** + * Set the upload timeout. + */ + public void setTimeout( int timeouts ) { + timeout = timeouts ; + } + + /** + * Get the upload timeout. + */ + public int getTimeout() { + return timeout; + } + + + /** + * Set the server header name. + */ + public void setServer( String server ) { + if (server==null || server.equals("")) { + this.server = null; + } else { + this.server = server; + } + } + + /** + * Get the server header name. + */ + public String getServer() { + return server; + } + + + /** Get the request associated with this processor. + * + * @return The request + */ + public Request getRequest() { + return request; + } + + + /** + * Set the associated adapter. + * + * @param adapter the new adapter + */ + public void setAdapter(Adapter adapter) { + this.adapter = adapter; + } + + + /** + * Get the associated adapter. + * + * @return the associated adapter + */ + public Adapter getAdapter() { + return adapter; + } + + + + + /** + * Check for compression + */ + protected boolean isCompressable() { + + // Nope Compression could works in HTTP 1.0 also + // cf: mod_deflate + + // Compression only since HTTP 1.1 + // if (! http11) + // return false; + + // Check if browser support gzip encoding + MessageBytes acceptEncodingMB = + request.getMimeHeaders().getValue("accept-encoding"); + + if ((acceptEncodingMB == null) + || (acceptEncodingMB.indexOf("gzip") == -1)) + return false; + + // Check if content is not allready gzipped + MessageBytes contentEncodingMB = + response.getMimeHeaders().getValue("Content-Encoding"); + + if ((contentEncodingMB != null) + && (contentEncodingMB.indexOf("gzip") != -1)) + return false; + + // If force mode, allways compress (test purposes only) + if (compressionLevel == 2) + return true; + + // Check for incompatible Browser + if (noCompressionUserAgents != null) { + MessageBytes userAgentValueMB = + request.getMimeHeaders().getValue("user-agent"); + if(userAgentValueMB != null) { + String userAgentValue = userAgentValueMB.toString(); + + // If one Regexp rule match, disable compression + for (int i = 0; i < noCompressionUserAgents.length; i++) + if (noCompressionUserAgents[i].matcher(userAgentValue).matches()) + return false; + } + } + + // Check if suffisant len to trig the compression + long contentLength = response.getContentLengthLong(); + if ((contentLength == -1) + || (contentLength > compressionMinSize)) { + // Check for compatible MIME-TYPE + if (compressableMimeTypes != null) { + return (startsWithStringArray(compressableMimeTypes, + response.getContentType())); + } + } + + return false; + } + + /** + * Specialized utility method: find a sequence of lower case bytes inside + * a ByteChunk. + */ + protected int findBytes(ByteChunk bc, byte[] b) { + + byte first = b[0]; + byte[] buff = bc.getBuffer(); + int start = bc.getStart(); + int end = bc.getEnd(); + + // Look for first char + int srcEnd = b.length; + + for (int i = start; i <= (end - srcEnd); i++) { + if (Ascii.toLower(buff[i]) != first) continue; + // found first char, now look for a match + int myPos = i+1; + for (int srcPos = 1; srcPos < srcEnd; ) { + if (Ascii.toLower(buff[myPos++]) != b[srcPos++]) + break; + if (srcPos == srcEnd) return i - start; // found it + } + } + return -1; + + } + + /** + * Determine if we must drop the connection because of the HTTP status + * code. Use the same list of codes as Apache/httpd. + */ + protected boolean statusDropsConnection(int status) { + return status == 400 /* SC_BAD_REQUEST */ || + status == 408 /* SC_REQUEST_TIMEOUT */ || + status == 411 /* SC_LENGTH_REQUIRED */ || + status == 413 /* SC_REQUEST_ENTITY_TOO_LARGE */ || + status == 414 /* SC_REQUEST_URI_TOO_LARGE */ || + status == 500 /* SC_INTERNAL_SERVER_ERROR */ || + status == 503 /* SC_SERVICE_UNAVAILABLE */ || + status == 501 /* SC_NOT_IMPLEMENTED */; + } + + +} diff --git a/java/org/apache/coyote/http11/Http11NioProcessor.java b/java/org/apache/coyote/http11/Http11NioProcessor.java index aadcd75d0..27b520672 100644 --- a/java/org/apache/coyote/http11/Http11NioProcessor.java +++ b/java/org/apache/coyote/http11/Http11NioProcessor.java @@ -62,7 +62,7 @@ import org.apache.tomcat.util.net.NioEndpoint.KeyAttachment; * @author Remy Maucherat * @author Filip Hanik */ -public class Http11NioProcessor implements ActionHook { +public class Http11NioProcessor extends AbstractHttp11Processor implements ActionHook { /** @@ -113,26 +113,6 @@ public class Http11NioProcessor implements ActionHook { // ----------------------------------------------------- Instance Variables - - - /** - * Associated adapter. - */ - protected Adapter adapter = null; - - - /** - * Request object. - */ - protected Request request = null; - - - /** - * Response object. - */ - protected Response response = null; - - /** * Input. */ @@ -146,29 +126,6 @@ public class Http11NioProcessor implements ActionHook { /** - * Error flag. - */ - protected boolean error = false; - - - /** - * Keep-alive. - */ - protected boolean keepAlive = true; - - - /** - * HTTP/1.1 flag. - */ - protected boolean http11 = true; - - - /** - * HTTP/0.9 flag. - */ - protected boolean http09 = false; - - /** * Sendfile data. */ protected NioEndpoint.SendfileData sendfileData = null; @@ -192,30 +149,6 @@ public class Http11NioProcessor implements ActionHook { protected boolean async = false; /** - * Content delimitator for the request (if false, the connection will - * be closed at the end of the request). - */ - protected boolean contentDelimitation = true; - - - /** - * Is there an expectation ? - */ - protected boolean expectation = false; - - - /** - * List of restricted user agents. - */ - protected Pattern[] restrictedUserAgents = null; - - - /** - * Maximum number of Keep-Alive requests to honor. - */ - protected int maxKeepAliveRequests = -1; - - /** * SSL enabled ? */ protected boolean ssl = false; @@ -228,249 +161,14 @@ public class Http11NioProcessor implements ActionHook { /** - * Remote Address associated with the current connection. - */ - protected String remoteAddr = null; - - - /** - * Remote Host associated with the current connection. - */ - protected String remoteHost = null; - - - /** - * Local Host associated with the current connection. - */ - protected String localName = null; - - - - /** - * Local port to which the socket is connected - */ - protected int localPort = -1; - - - /** - * Remote port to which the socket is connected - */ - protected int remotePort = -1; - - - /** - * The local Host address. - */ - protected String localAddr = null; - - - /** - * Maximum timeout on uploads. 5 minutes as in Apache HTTPD server. - */ - protected int timeout = 300000; - - - /** - * Flag to disable setting a different time-out on uploads. - */ - protected boolean disableUploadTimeout = false; - - - /** - * Allowed compression level. - */ - protected int compressionLevel = 0; - - - /** - * Minimum contentsize to make compression. - */ - protected int compressionMinSize = 2048; - - - /** - * Socket buffering. - */ - protected int socketBuffer = -1; - - - /** - * Max save post size. - */ - protected int maxSavePostSize = 4 * 1024; - - - /** - * List of user agents to not use gzip with - */ - protected Pattern noCompressionUserAgents[] = null; - - /** - * List of MIMES which could be gzipped - */ - protected String[] compressableMimeTypes = - { "text/html", "text/xml", "text/plain" }; - - - /** - * Host name (used to avoid useless B2C conversion on the host name). - */ - protected char[] hostNameC = new char[0]; - - - /** * Associated endpoint. */ protected NioEndpoint endpoint; - - /** - * Allow a customized the server header for the tin-foil hat folks. - */ - protected String server = null; // ------------------------------------------------------------- Properties - /** - * Return compression level. - */ - public String getCompression() { - switch (compressionLevel) { - case 0: - return "off"; - case 1: - return "on"; - case 2: - return "force"; - } - return "off"; - } - - - /** - * Set compression level. - */ - public void setCompression(String compression) { - if (compression.equals("on")) { - this.compressionLevel = 1; - } else if (compression.equals("force")) { - this.compressionLevel = 2; - } else if (compression.equals("off")) { - this.compressionLevel = 0; - } else { - try { - // Try to parse compression as an int, which would give the - // minimum compression size - compressionMinSize = Integer.parseInt(compression); - this.compressionLevel = 1; - } catch (Exception e) { - this.compressionLevel = 0; - } - } - } - - /** - * Set Minimum size to trigger compression. - */ - public void setCompressionMinSize(int compressionMinSize) { - this.compressionMinSize = compressionMinSize; - } - - - /** - * Add user-agent for which gzip compression didn't works - * The user agent String given will be exactly matched - * to the user-agent header submitted by the client. - * - * @param userAgent user-agent string - */ - public void addNoCompressionUserAgent(String userAgent) { - try { - Pattern nRule = Pattern.compile(userAgent); - noCompressionUserAgents = - addREArray(noCompressionUserAgents, nRule); - } catch (PatternSyntaxException pse) { - log.error(sm.getString("http11processor.regexp.error", userAgent), pse); - } - } - - - /** - * Set no compression user agent list (this method is best when used with - * a large number of connectors, where it would be better to have all of - * them referenced a single array). - */ - public void setNoCompressionUserAgents(Pattern[] noCompressionUserAgents) { - this.noCompressionUserAgents = noCompressionUserAgents; - } - - - /** - * Set no compression user agent list. - * List contains users agents separated by ',' : - * - * ie: "gorilla,desesplorer,tigrus" - */ - public void setNoCompressionUserAgents(String noCompressionUserAgents) { - if (noCompressionUserAgents != null) { - StringTokenizer st = new StringTokenizer(noCompressionUserAgents, ","); - - while (st.hasMoreTokens()) { - addNoCompressionUserAgent(st.nextToken().trim()); - } - } - } - - /** - * Add a mime-type which will be compressable - * The mime-type String will be exactly matched - * in the response mime-type header . - * - * @param mimeType mime-type string - */ - public void addCompressableMimeType(String mimeType) { - compressableMimeTypes = - addStringArray(compressableMimeTypes, mimeType); - } - - - /** - * Set compressable mime-type list (this method is best when used with - * a large number of connectors, where it would be better to have all of - * them referenced a single array). - */ - public void setCompressableMimeTypes(String[] compressableMimeTypes) { - this.compressableMimeTypes = compressableMimeTypes; - } - - - /** - * Set compressable mime-type list - * List contains users agents separated by ',' : - * - * ie: "text/html,text/xml,text/plain" - */ - public void setCompressableMimeTypes(String compressableMimeTypes) { - if (compressableMimeTypes != null) { - this.compressableMimeTypes = null; - StringTokenizer st = new StringTokenizer(compressableMimeTypes, ","); - - while (st.hasMoreTokens()) { - addCompressableMimeType(st.nextToken().trim()); - } - } - } - - - /** - * Return the list of restricted user agents. - */ - public String[] findCompressableMimeTypes() { - return (compressableMimeTypes); - } - - // --------------------------------------------------------- Public Methods @@ -558,168 +256,6 @@ public class Http11NioProcessor implements ActionHook { return false; } - - /** - * Add restricted user-agent (which will downgrade the connector - * to HTTP/1.0 mode). The user agent String given will be matched - * via regexp to the user-agent header submitted by the client. - * - * @param userAgent user-agent string - */ - public void addRestrictedUserAgent(String userAgent) { - try { - Pattern nRule = Pattern.compile(userAgent); - restrictedUserAgents = addREArray(restrictedUserAgents, nRule); - } catch (PatternSyntaxException pse) { - log.error(sm.getString("http11processor.regexp.error", userAgent), pse); - } - } - - - /** - * Set restricted user agent list (this method is best when used with - * a large number of connectors, where it would be better to have all of - * them referenced a single array). - */ - public void setRestrictedUserAgents(Pattern[] restrictedUserAgents) { - this.restrictedUserAgents = restrictedUserAgents; - } - - - /** - * Set restricted user agent list (which will downgrade the connector - * to HTTP/1.0 mode). List contains users agents separated by ',' : - * - * ie: "gorilla,desesplorer,tigrus" - */ - public void setRestrictedUserAgents(String restrictedUserAgents) { - if (restrictedUserAgents != null) { - StringTokenizer st = - new StringTokenizer(restrictedUserAgents, ","); - while (st.hasMoreTokens()) { - addRestrictedUserAgent(st.nextToken().trim()); - } - } - } - - - /** - * Return the list of restricted user agents. - */ - public String[] findRestrictedUserAgents() { - String[] sarr = new String [restrictedUserAgents.length]; - - for (int i = 0; i < restrictedUserAgents.length; i++) - sarr[i] = restrictedUserAgents[i].toString(); - - return (sarr); - } - - - /** - * Set the maximum number of Keep-Alive requests to honor. - * This is to safeguard from DoS attacks. Setting to a negative - * value disables the check. - */ - public void setMaxKeepAliveRequests(int mkar) { - maxKeepAliveRequests = mkar; - } - - - /** - * Return the number of Keep-Alive requests that we will honor. - */ - public int getMaxKeepAliveRequests() { - return maxKeepAliveRequests; - } - - - /** - * Set the maximum size of a POST which will be buffered in SSL mode. - */ - public void setMaxSavePostSize(int msps) { - maxSavePostSize = msps; - } - - - /** - * Return the maximum size of a POST which will be buffered in SSL mode. - */ - public int getMaxSavePostSize() { - return maxSavePostSize; - } - - - /** - * Set the flag to control upload time-outs. - */ - public void setDisableUploadTimeout(boolean isDisabled) { - disableUploadTimeout = isDisabled; - } - - /** - * Get the flag that controls upload time-outs. - */ - public boolean getDisableUploadTimeout() { - return disableUploadTimeout; - } - - /** - * Set the socket buffer flag. - */ - public void setSocketBuffer(int socketBuffer) { - this.socketBuffer = socketBuffer; - } - - /** - * Get the socket buffer flag. - */ - public int getSocketBuffer() { - return socketBuffer; - } - - /** - * Set the upload timeout. - */ - public void setTimeout( int timeouts ) { - timeout = timeouts ; - } - - /** - * Get the upload timeout. - */ - public int getTimeout() { - return timeout; - } - - - /** - * Set the server header name. - */ - public void setServer( String server ) { - if (server==null || server.equals("")) { - this.server = null; - } else { - this.server = server; - } - } - - /** - * Get the server header name. - */ - public String getServer() { - return server; - } - - - /** Get the request associated with this processor. - * - * @return The request - */ - public Request getRequest() { - return request; - } - /** * Process pipelined HTTP requests using the specified input and output * streams. @@ -1336,29 +872,6 @@ public class Http11NioProcessor implements ActionHook { // ------------------------------------------------------ Connector Methods - - /** - * Set the associated adapter. - * - * @param adapter the new adapter - */ - public void setAdapter(Adapter adapter) { - this.adapter = adapter; - } - - public void setSslSupport(SSLSupport sslSupport) { - this.sslSupport = sslSupport; - } - - /** - * Get the associated adapter. - * - * @return the associated adapter - */ - public Adapter getAdapter() { - return adapter; - } - public SSLSupport getSslSupport() { return sslSupport; } @@ -1618,68 +1131,6 @@ public class Http11NioProcessor implements ActionHook { } - - /** - * Check for compression - */ - private boolean isCompressable() { - - // Nope Compression could works in HTTP 1.0 also - // cf: mod_deflate - - // Compression only since HTTP 1.1 - // if (! http11) - // return false; - - // Check if browser support gzip encoding - MessageBytes acceptEncodingMB = - request.getMimeHeaders().getValue("accept-encoding"); - - if ((acceptEncodingMB == null) - || (acceptEncodingMB.indexOf("gzip") == -1)) - return false; - - // Check if content is not allready gzipped - MessageBytes contentEncodingMB = - response.getMimeHeaders().getValue("Content-Encoding"); - - if ((contentEncodingMB != null) - && (contentEncodingMB.indexOf("gzip") != -1)) - return false; - - // If force mode, allways compress (test purposes only) - if (compressionLevel == 2) - return true; - - // Check for incompatible Browser - if (noCompressionUserAgents != null) { - MessageBytes userAgentValueMB = - request.getMimeHeaders().getValue("user-agent"); - if(userAgentValueMB != null) { - String userAgentValue = userAgentValueMB.toString(); - - // If one Regexp rule match, disable compression - for (int i = 0; i < noCompressionUserAgents.length; i++) - if (noCompressionUserAgents[i].matcher(userAgentValue).matches()) - return false; - } - } - - // Check if suffisant len to trig the compression - long contentLength = response.getContentLengthLong(); - if ((contentLength == -1) - || (contentLength > compressionMinSize)) { - // Check for compatible MIME-TYPE - if (compressableMimeTypes != null) { - return (startsWithStringArray(compressableMimeTypes, - response.getContentType())); - } - } - - return false; - } - - /** * When committing the response, we have to validate the set of headers, as * well as setup the response filters. @@ -1887,20 +1338,20 @@ public class Http11NioProcessor implements ActionHook { int start = bc.getStart(); int end = bc.getEnd(); - // Look for first char - int srcEnd = b.length; - - for (int i = start; i <= (end - srcEnd); i++) { - if (Ascii.toLower(buff[i]) != first) continue; - // found first char, now look for a match - int myPos = i+1; - for (int srcPos = 1; srcPos < srcEnd; ) { - if (Ascii.toLower(buff[myPos++]) != b[srcPos++]) - break; - if (srcPos == srcEnd) return i - start; // found it + // Look for first char + int srcEnd = b.length; + + for (int i = start; i <= (end - srcEnd); i++) { + if (Ascii.toLower(buff[i]) != first) continue; + // found first char, now look for a match + int myPos = i+1; + for (int srcPos = 1; srcPos < srcEnd; ) { + if (Ascii.toLower(buff[myPos++]) != b[srcPos++]) + break; + if (srcPos == srcEnd) return i - start; // found it + } } - } - return -1; + return -1; } @@ -1918,5 +1369,12 @@ public class Http11NioProcessor implements ActionHook { status == 503 /* SC_SERVICE_UNAVAILABLE */ || status == 501 /* SC_NOT_IMPLEMENTED */; } + + /** + * Set the SSL information for this HTTP connection. + */ + public void setSslSupport(SSLSupport sslSupport) { + this.sslSupport = sslSupport; + } } diff --git a/java/org/apache/coyote/http11/Http11Processor.java b/java/org/apache/coyote/http11/Http11Processor.java index f70f03f68..c3c4aedcb 100644 --- a/java/org/apache/coyote/http11/Http11Processor.java +++ b/java/org/apache/coyote/http11/Http11Processor.java @@ -23,13 +23,9 @@ import java.net.InetAddress; import java.net.Socket; import java.security.AccessController; import java.security.PrivilegedAction; -import java.util.StringTokenizer; -import java.util.regex.Pattern; -import java.util.regex.PatternSyntaxException; import org.apache.coyote.ActionCode; import org.apache.coyote.ActionHook; -import org.apache.coyote.Adapter; import org.apache.coyote.Request; import org.apache.coyote.RequestInfo; import org.apache.coyote.Response; @@ -42,7 +38,6 @@ import org.apache.coyote.http11.filters.IdentityOutputFilter; import org.apache.coyote.http11.filters.SavedRequestInputFilter; import org.apache.coyote.http11.filters.VoidInputFilter; import org.apache.coyote.http11.filters.VoidOutputFilter; -import org.apache.tomcat.util.buf.Ascii; import org.apache.tomcat.util.buf.ByteChunk; import org.apache.tomcat.util.buf.HexUtils; import org.apache.tomcat.util.buf.MessageBytes; @@ -50,15 +45,16 @@ import org.apache.tomcat.util.http.FastHttpDateFormat; import org.apache.tomcat.util.http.MimeHeaders; import org.apache.tomcat.util.net.JIoEndpoint; import org.apache.tomcat.util.net.SSLSupport; -import org.apache.tomcat.util.res.StringManager; +import org.apache.tomcat.util.net.SocketWrapper; /** * Processes HTTP requests. * * @author Remy Maucherat + * @author fhanik */ -public class Http11Processor implements ActionHook { +public class Http11Processor extends AbstractHttp11Processor implements ActionHook { /** @@ -67,15 +63,6 @@ public class Http11Processor implements ActionHook { protected static org.apache.juli.logging.Log log = org.apache.juli.logging.LogFactory.getLog(Http11Processor.class); - /** - * The string manager for this package. - */ - protected static StringManager sm = - StringManager.getManager(Constants.Package); - - protected static boolean isSecurityEnabled = - org.apache.coyote.Constants.IS_SECURITY_ENABLED; - // ------------------------------------------------------------ Constructor @@ -105,24 +92,6 @@ public class Http11Processor implements ActionHook { /** - * Associated adapter. - */ - protected Adapter adapter = null; - - - /** - * Request object. - */ - protected Request request = null; - - - /** - * Response object. - */ - protected Response response = null; - - - /** * Input. */ protected InternalInputBuffer inputBuffer = null; @@ -134,597 +103,46 @@ public class Http11Processor implements ActionHook { protected InternalOutputBuffer outputBuffer = null; - /** - * State flag. - */ - protected boolean started = false; - - - /** - * Error flag. - */ - protected boolean error = false; - - - /** - * Keep-alive. - */ - protected boolean keepAlive = true; - - - /** - * HTTP/1.1 flag. - */ - protected boolean http11 = true; - - - /** - * HTTP/0.9 flag. - */ - protected boolean http09 = false; - - - /** - * Content delimitator for the request (if false, the connection will - * be closed at the end of the request). - */ - protected boolean contentDelimitation = true; - - - /** - * Is there an expectation ? - */ - protected boolean expectation = false; - - - /** - * List of restricted user agents. - */ - protected Pattern[] restrictedUserAgents = null; - - - /** - * Maximum number of Keep-Alive requests to honor. - */ - protected int maxKeepAliveRequests = -1; - - /** - * The number of seconds Tomcat will wait for a subsequent request - * before closing the connection. - */ - protected int keepAliveTimeout = -1; - - - /** - * SSL information. - */ - protected SSLSupport sslSupport; - - - /** - * Socket associated with the current connection. - */ - protected Socket socket; - - - /** - * Remote Address associated with the current connection. - */ - protected String remoteAddr = null; - - - /** - * Remote Host associated with the current connection. - */ - protected String remoteHost = null; - - - /** - * Local Host associated with the current connection. - */ - protected String localName = null; - - - - /** - * Local port to which the socket is connected - */ - protected int localPort = -1; - - - /** - * Remote port to which the socket is connected - */ - protected int remotePort = -1; - - - /** - * The local Host address. - */ - protected String localAddr = null; - - - /** - * Maximum timeout on uploads. 5 minutes as in Apache HTTPD server. - */ - protected int timeout = 300000; - - - /** - * Flag to disable setting a different time-out on uploads. - */ - protected boolean disableUploadTimeout = false; - - - /** - * Allowed compression level. - */ - protected int compressionLevel = 0; - - - /** - * Minimum contentsize to make compression. - */ - protected int compressionMinSize = 2048; - - - /** - * Socket buffering. - */ - protected int socketBuffer = -1; - - - /** - * Max saved post size. - */ - protected int maxSavePostSize = 4 * 1024; - - - /** - * List of user agents to not use gzip with - */ - protected Pattern noCompressionUserAgents[] = null; - - /** - * List of MIMES which could be gzipped - */ - protected String[] compressableMimeTypes = - { "text/html", "text/xml", "text/plain" }; - - - /** - * Host name (used to avoid useless B2C conversion on the host name). - */ - protected char[] hostNameC = new char[0]; - - - /** - * Associated endpoint. - */ - protected JIoEndpoint endpoint; - - - /** - * Allow a customized the server header for the tin-foil hat folks. - */ - protected String server = null; - - - // ------------------------------------------------------------- Properties - - - /** - * Return compression level. - */ - public String getCompression() { - switch (compressionLevel) { - case 0: - return "off"; - case 1: - return "on"; - case 2: - return "force"; - } - return "off"; - } - - - /** - * Set compression level. - */ - public void setCompression(String compression) { - if (compression.equals("on")) { - this.compressionLevel = 1; - } else if (compression.equals("force")) { - this.compressionLevel = 2; - } else if (compression.equals("off")) { - this.compressionLevel = 0; - } else { - try { - // Try to parse compression as an int, which would give the - // minimum compression size - compressionMinSize = Integer.parseInt(compression); - this.compressionLevel = 1; - } catch (Exception e) { - this.compressionLevel = 0; - } - } - } - - /** - * Set Minimum size to trigger compression. - */ - public void setCompressionMinSize(int compressionMinSize) { - this.compressionMinSize = compressionMinSize; - } - - - /** - * Add user-agent for which gzip compression didn't works - * The user agent String given will be exactly matched - * to the user-agent header submitted by the client. - * - * @param userAgent user-agent string - */ - public void addNoCompressionUserAgent(String userAgent) { - try { - Pattern nRule = Pattern.compile(userAgent); - noCompressionUserAgents = - addREArray(noCompressionUserAgents, nRule); - } catch (PatternSyntaxException pse) { - log.error(sm.getString("http11processor.regexp.error", userAgent), pse); - } - } - - - /** - * Set no compression user agent list (this method is best when used with - * a large number of connectors, where it would be better to have all of - * them referenced a single array). - */ - public void setNoCompressionUserAgents(Pattern[] noCompressionUserAgents) { - this.noCompressionUserAgents = noCompressionUserAgents; - } - - - /** - * Set no compression user agent list. - * List contains users agents separated by ',' : - * - * ie: "gorilla,desesplorer,tigrus" - */ - public void setNoCompressionUserAgents(String noCompressionUserAgents) { - if (noCompressionUserAgents != null) { - StringTokenizer st = new StringTokenizer(noCompressionUserAgents, ","); - - while (st.hasMoreTokens()) { - addNoCompressionUserAgent(st.nextToken().trim()); - } - } - } - - /** - * Add a mime-type which will be compressable - * The mime-type String will be exactly matched - * in the response mime-type header . - * - * @param mimeType mime-type string - */ - public void addCompressableMimeType(String mimeType) { - compressableMimeTypes = - addStringArray(compressableMimeTypes, mimeType); - } - - - /** - * Set compressable mime-type list (this method is best when used with - * a large number of connectors, where it would be better to have all of - * them referenced a single array). - */ - public void setCompressableMimeTypes(String[] compressableMimeTypes) { - this.compressableMimeTypes = compressableMimeTypes; - } - - - /** - * Set compressable mime-type list - * List contains users agents separated by ',' : - * - * ie: "text/html,text/xml,text/plain" - */ - public void setCompressableMimeTypes(String compressableMimeTypes) { - if (compressableMimeTypes != null) { - this.compressableMimeTypes = null; - StringTokenizer st = new StringTokenizer(compressableMimeTypes, ","); - - while (st.hasMoreTokens()) { - addCompressableMimeType(st.nextToken().trim()); - } - } - } - - - /** - * Return the list of restricted user agents. - */ - public String[] findCompressableMimeTypes() { - return (compressableMimeTypes); - } - - - - // --------------------------------------------------------- Public Methods - - - /** - * Add input or output filter. - * - * @param className class name of the filter - */ - protected void addFilter(String className) { - try { - Class clazz = Class.forName(className); - Object obj = clazz.newInstance(); - if (obj instanceof InputFilter) { - inputBuffer.addFilter((InputFilter) obj); - } else if (obj instanceof OutputFilter) { - outputBuffer.addFilter((OutputFilter) obj); - } else { - log.warn(sm.getString("http11processor.filter.unknown", className)); - } - } catch (Exception e) { - log.error(sm.getString("http11processor.filter.error", className), e); - } - } - - - /** - * General use method - * - * @param sArray the StringArray - * @param value string - */ - private String[] addStringArray(String sArray[], String value) { - String[] result = null; - if (sArray == null) { - result = new String[1]; - result[0] = value; - } - else { - result = new String[sArray.length + 1]; - for (int i = 0; i < sArray.length; i++) - result[i] = sArray[i]; - result[sArray.length] = value; - } - return result; - } - - - /** - * General use method - * - * @param rArray the REArray - * @param value Obj - */ - private Pattern[] addREArray(Pattern rArray[], Pattern value) { - Pattern[] result = null; - if (rArray == null) { - result = new Pattern[1]; - result[0] = value; - } - else { - result = new Pattern[rArray.length + 1]; - for (int i = 0; i < rArray.length; i++) - result[i] = rArray[i]; - result[rArray.length] = value; - } - return result; - } - - - /** - * Checks if any entry in the string array starts with the specified value - * - * @param sArray the StringArray - * @param value string - */ - private boolean startsWithStringArray(String sArray[], String value) { - if (value == null) - return false; - for (int i = 0; i < sArray.length; i++) { - if (value.startsWith(sArray[i])) { - return true; - } - } - return false; - } - - - /** - * Add restricted user-agent (which will downgrade the connector - * to HTTP/1.0 mode). The user agent String given will be matched - * via regexp to the user-agent header submitted by the client. - * - * @param userAgent user-agent string - */ - public void addRestrictedUserAgent(String userAgent) { - try { - Pattern nRule = Pattern.compile(userAgent); - restrictedUserAgents = addREArray(restrictedUserAgents, nRule); - } catch (PatternSyntaxException pse) { - log.error(sm.getString("http11processor.regexp.error", userAgent), pse); - } - } - - - /** - * Set restricted user agent list (this method is best when used with - * a large number of connectors, where it would be better to have all of - * them referenced a single array). - */ - public void setRestrictedUserAgents(Pattern[] restrictedUserAgents) { - this.restrictedUserAgents = restrictedUserAgents; - } - - - /** - * Set restricted user agent list (which will downgrade the connector - * to HTTP/1.0 mode). List contains users agents separated by ',' : - * - * ie: "gorilla,desesplorer,tigrus" - */ - public void setRestrictedUserAgents(String restrictedUserAgents) { - if (restrictedUserAgents != null) { - StringTokenizer st = - new StringTokenizer(restrictedUserAgents, ","); - while (st.hasMoreTokens()) { - addRestrictedUserAgent(st.nextToken().trim()); - } - } - } - - - /** - * Return the list of restricted user agents. - */ - public String[] findRestrictedUserAgents() { - String[] sarr = new String [restrictedUserAgents.length]; - - for (int i = 0; i < restrictedUserAgents.length; i++) - sarr[i] = restrictedUserAgents[i].toString(); - - return (sarr); - } - - - /** - * Set the maximum number of Keep-Alive requests to honor. - * This is to safeguard from DoS attacks. Setting to a negative - * value disables the check. - */ - public void setMaxKeepAliveRequests(int mkar) { - maxKeepAliveRequests = mkar; - } - - - /** - * Return the number of Keep-Alive requests that we will honor. - */ - public int getMaxKeepAliveRequests() { - return maxKeepAliveRequests; - } - - /** - * Set the Keep-Alive timeout. - */ - public void setKeepAliveTimeout(int timeout) { - keepAliveTimeout = timeout; - } - - - /** - * Return the number Keep-Alive timeout. - */ - public int getKeepAliveTimeout() { - return keepAliveTimeout; - } - /** - * Set the maximum size of a POST which will be buffered in SSL mode. + * SSL information. */ - public void setMaxSavePostSize(int msps) { - maxSavePostSize = msps; - } + protected SSLSupport sslSupport; /** - * Return the maximum size of a POST which will be buffered in SSL mode. + * Socket associated with the current connection. */ - public int getMaxSavePostSize() { - return maxSavePostSize; - } + protected Socket socket; + /** - * Set the SSL information for this HTTP connection. + * Associated endpoint. */ - public void setSSLSupport(SSLSupport sslSupport) { - this.sslSupport = sslSupport; - } + protected JIoEndpoint endpoint; - /** - * Set the flag to control upload time-outs. - */ - public void setDisableUploadTimeout(boolean isDisabled) { - disableUploadTimeout = isDisabled; - } - /** - * Get the flag that controls upload time-outs. - */ - public boolean getDisableUploadTimeout() { - return disableUploadTimeout; - } - /** - * Set the socket buffer flag. - */ - public void setSocketBuffer(int socketBuffer) { - this.socketBuffer = socketBuffer; - outputBuffer.setSocketBuffer(socketBuffer); - } + // ------------------------------------------------------------- Properties - /** - * Get the socket buffer flag. - */ - public int getSocketBuffer() { - return socketBuffer; - } - /** - * Set the upload timeout. - */ - public void setTimeout( int timeouts ) { - timeout = timeouts ; - } - /** - * Get the upload timeout. - */ - public int getTimeout() { - return timeout; - } + + + // --------------------------------------------------------- Public Methods - /** - * Set the server header name. - */ - public void setServer( String server ) { - if (server==null || server.equals("")) { - this.server = null; - } else { - this.server = server; - } - } /** - * Get the server header name. + * Set the SSL information for this HTTP connection. */ - public String getServer() { - return server; + public void setSSLSupport(SSLSupport sslSupport) { + this.sslSupport = sslSupport; } - /** Get the request associated with this processor. - * - * @return The request - */ - public Request getRequest() { - return request; - } - /** * Process pipelined HTTP requests on the specified socket. * @@ -733,8 +151,9 @@ public class Http11Processor implements ActionHook { * * @throws IOException error during an I/O operation */ - public void process(Socket theSocket) + public boolean process(SocketWrapper socketWrapper) throws IOException { + Socket theSocket = socketWrapper.getSocket(); RequestInfo rp = request.getRequestProcessor(); rp.setStage(org.apache.coyote.Constants.STAGE_PARSE); @@ -755,7 +174,8 @@ public class Http11Processor implements ActionHook { error = false; keepAlive = true; - int keepAliveLeft = maxKeepAliveRequests; + int keepAliveLeft = maxKeepAliveRequests>0?socketWrapper.decrementKeepAlive():-1; + int soTimeout = endpoint.getSoTimeout(); int threadRatio = (endpoint.getCurrentThreadsBusy() * 100) @@ -821,7 +241,7 @@ public class Http11Processor implements ActionHook { } } - if (maxKeepAliveRequests > 0 && --keepAliveLeft == 0) + if (maxKeepAliveRequests > 0 && keepAliveLeft == 0) keepAlive = false; // Process the request in the adapter @@ -891,7 +311,9 @@ public class Http11Processor implements ActionHook { // Next request inputBuffer.nextRequest(); outputBuffer.nextRequest(); - + + //hack keep alive behavior + break; } rp.setStage(org.apache.coyote.Constants.STAGE_ENDED); @@ -902,6 +324,11 @@ public class Http11Processor implements ActionHook { this.socket = null; // Recycle ssl info sslSupport = null; + if (log.isTraceEnabled()) { + boolean returnvalue = (!error && keepAlive); + log.trace("Returning "+returnvalue+" to adjust for keep alive."); + } + return !error && keepAlive; } @@ -1120,25 +547,6 @@ public class Http11Processor implements ActionHook { // ------------------------------------------------------ Connector Methods - /** - * Set the associated adapter. - * - * @param adapter the new adapter - */ - public void setAdapter(Adapter adapter) { - this.adapter = adapter; - } - - - /** - * Get the associated adapter. - * - * @return the associated adapter - */ - public Adapter getAdapter() { - return adapter; - } - // ------------------------------------------------------ Protected Methods @@ -1328,141 +736,6 @@ public class Http11Processor implements ActionHook { } - /** - * Parse host. - */ - protected void parseHost(MessageBytes valueMB) { - - if (valueMB == null || valueMB.isNull()) { - // HTTP/1.0 - // Default is what the socket tells us. Overriden if a host is - // found/parsed - request.setServerPort(socket.getLocalPort()); - InetAddress localAddress = socket.getLocalAddress(); - // Setting the socket-related fields. The adapter doesn't know - // about socket. - request.serverName().setString(localAddress.getHostName()); - return; - } - - ByteChunk valueBC = valueMB.getByteChunk(); - byte[] valueB = valueBC.getBytes(); - int valueL = valueBC.getLength(); - int valueS = valueBC.getStart(); - int colonPos = -1; - if (hostNameC.length < valueL) { - hostNameC = new char[valueL]; - } - - boolean ipv6 = (valueB[valueS] == '['); - boolean bracketClosed = false; - for (int i = 0; i < valueL; i++) { - char b = (char) valueB[i + valueS]; - hostNameC[i] = b; - if (b == ']') { - bracketClosed = true; - } else if (b == ':') { - if (!ipv6 || bracketClosed) { - colonPos = i; - break; - } - } - } - - if (colonPos < 0) { - if (sslSupport == null) { - // 80 - Default HTTP port - request.setServerPort(80); - } else { - // 443 - Default HTTPS port - request.setServerPort(443); - } - request.serverName().setChars(hostNameC, 0, valueL); - } else { - - request.serverName().setChars(hostNameC, 0, colonPos); - - int port = 0; - int mult = 1; - for (int i = valueL - 1; i > colonPos; i--) { - int charValue = HexUtils.DEC[valueB[i + valueS]]; - if (charValue == -1) { - // Invalid character - error = true; - // 400 - Bad request - response.setStatus(400); - break; - } - port = port + (charValue * mult); - mult = 10 * mult; - } - request.setServerPort(port); - - } - - } - - - /** - * Check for compression - */ - private boolean isCompressable() { - - // Nope Compression could works in HTTP 1.0 also - // cf: mod_deflate - - // Compression only since HTTP 1.1 - // if (! http11) - // return false; - - // Check if browser support gzip encoding - MessageBytes acceptEncodingMB = - request.getMimeHeaders().getValue("accept-encoding"); - - if ((acceptEncodingMB == null) - || (acceptEncodingMB.indexOf("gzip") == -1)) - return false; - - // Check if content is not allready gzipped - MessageBytes contentEncodingMB = - response.getMimeHeaders().getValue("Content-Encoding"); - - if ((contentEncodingMB != null) - && (contentEncodingMB.indexOf("gzip") != -1)) - return false; - - // If force mode, allways compress (test purposes only) - if (compressionLevel == 2) - return true; - - // Check for incompatible Browser - if (noCompressionUserAgents != null) { - MessageBytes userAgentValueMB = - request.getMimeHeaders().getValue("user-agent"); - if(userAgentValueMB != null) { - String userAgentValue = userAgentValueMB.toString(); - - // If one Regexp rule match, disable compression - for (int i = 0; i < noCompressionUserAgents.length; i++) - if (noCompressionUserAgents[i].matcher(userAgentValue).matches()) - return false; - } - } - - // Check if suffisant len to trig the compression - long contentLength = response.getContentLengthLong(); - if ((contentLength == -1) - || (contentLength > compressionMinSize)) { - // Check for compatible MIME-TYPE - if (compressableMimeTypes != null) { - return (startsWithStringArray(compressableMimeTypes, - response.getContentType())); - } - } - - return false; - } - /** * When committing the response, we have to validate the set of headers, as @@ -1658,46 +931,107 @@ public class Http11Processor implements ActionHook { /** - * Specialized utility method: find a sequence of lower case bytes inside - * a ByteChunk. + * Add input or output filter. + * + * @param className class name of the filter */ - protected int findBytes(ByteChunk bc, byte[] b) { - - byte first = b[0]; - byte[] buff = bc.getBuffer(); - int start = bc.getStart(); - int end = bc.getEnd(); - - // Look for first char - int srcEnd = b.length; - - for (int i = start; i <= (end - srcEnd); i++) { - if (Ascii.toLower(buff[i]) != first) continue; - // found first char, now look for a match - int myPos = i+1; - for (int srcPos = 1; srcPos < srcEnd; ) { - if (Ascii.toLower(buff[myPos++]) != b[srcPos++]) - break; - if (srcPos == srcEnd) return i - start; // found it + protected void addFilter(String className) { + try { + Class clazz = Class.forName(className); + Object obj = clazz.newInstance(); + if (obj instanceof InputFilter) { + inputBuffer.addFilter((InputFilter) obj); + } else if (obj instanceof OutputFilter) { + outputBuffer.addFilter((OutputFilter) obj); + } else { + log.warn(sm.getString("http11processor.filter.unknown", className)); + } + } catch (Exception e) { + log.error(sm.getString("http11processor.filter.error", className), e); } } - return -1; + + /** + * Parse host. + */ + protected void parseHost(MessageBytes valueMB) { + + if (valueMB == null || valueMB.isNull()) { + // HTTP/1.0 + // Default is what the socket tells us. Overriden if a host is + // found/parsed + request.setServerPort(socket.getLocalPort()); + InetAddress localAddress = socket.getLocalAddress(); + // Setting the socket-related fields. The adapter doesn't know + // about socket. + request.serverName().setString(localAddress.getHostName()); + return; + } + + ByteChunk valueBC = valueMB.getByteChunk(); + byte[] valueB = valueBC.getBytes(); + int valueL = valueBC.getLength(); + int valueS = valueBC.getStart(); + int colonPos = -1; + if (hostNameC.length < valueL) { + hostNameC = new char[valueL]; + } + + boolean ipv6 = (valueB[valueS] == '['); + boolean bracketClosed = false; + for (int i = 0; i < valueL; i++) { + char b = (char) valueB[i + valueS]; + hostNameC[i] = b; + if (b == ']') { + bracketClosed = true; + } else if (b == ':') { + if (!ipv6 || bracketClosed) { + colonPos = i; + break; + } + } + } + + if (colonPos < 0) { + if (sslSupport == null) { + // 80 - Default HTTP port + request.setServerPort(80); + } else { + // 443 - Default HTTPS port + request.setServerPort(443); + } + request.serverName().setChars(hostNameC, 0, valueL); + } else { + + request.serverName().setChars(hostNameC, 0, colonPos); + + int port = 0; + int mult = 1; + for (int i = valueL - 1; i > colonPos; i--) { + int charValue = HexUtils.DEC[valueB[i + valueS]]; + if (charValue == -1) { + // Invalid character + error = true; + // 400 - Bad request + response.setStatus(400); + break; + } + port = port + (charValue * mult); + mult = 10 * mult; + } + request.setServerPort(port); + + } } /** - * Determine if we must drop the connection because of the HTTP status - * code. Use the same list of codes as Apache/httpd. + * Set the socket buffer flag. + * @Override */ - protected boolean statusDropsConnection(int status) { - return status == 400 /* SC_BAD_REQUEST */ || - status == 408 /* SC_REQUEST_TIMEOUT */ || - status == 411 /* SC_LENGTH_REQUIRED */ || - status == 413 /* SC_REQUEST_ENTITY_TOO_LARGE */ || - status == 414 /* SC_REQUEST_URI_TOO_LARGE */ || - status == 500 /* SC_INTERNAL_SERVER_ERROR */ || - status == 503 /* SC_SERVICE_UNAVAILABLE */ || - status == 501 /* SC_NOT_IMPLEMENTED */; + public void setSocketBuffer(int socketBuffer) { + super.setSocketBuffer(socketBuffer); + outputBuffer.setSocketBuffer(socketBuffer); } } diff --git a/java/org/apache/coyote/http11/Http11Protocol.java b/java/org/apache/coyote/http11/Http11Protocol.java index 286acbf8b..a6ee79635 100644 --- a/java/org/apache/coyote/http11/Http11Protocol.java +++ b/java/org/apache/coyote/http11/Http11Protocol.java @@ -40,6 +40,7 @@ import org.apache.tomcat.util.modeler.Registry; import org.apache.tomcat.util.net.JIoEndpoint; import org.apache.tomcat.util.net.SSLImplementation; import org.apache.tomcat.util.net.ServerSocketFactory; +import org.apache.tomcat.util.net.SocketWrapper; import org.apache.tomcat.util.net.JIoEndpoint.Handler; import org.apache.tomcat.util.res.StringManager; @@ -306,7 +307,10 @@ public class Http11Protocol */ protected int maxKeepAliveRequests = 100; public int getMaxKeepAliveRequests() { return maxKeepAliveRequests; } - public void setMaxKeepAliveRequests(int mkar) { maxKeepAliveRequests = mkar; } + public void setMaxKeepAliveRequests(int mkar) { + maxKeepAliveRequests = mkar; + endpoint.setMaxKeepAliveRequests(mkar); + } // HTTP /** @@ -564,7 +568,7 @@ public class Http11Protocol this.proto = proto; } - public boolean process(Socket socket) { + public boolean process(SocketWrapper socket) { Http11Processor processor = recycledProcessors.poll(); try { @@ -576,13 +580,13 @@ public class Http11Protocol if (proto.secure && (proto.sslImplementation != null)) { processor.setSSLSupport - (proto.sslImplementation.getSSLSupport(socket)); + (proto.sslImplementation.getSSLSupport(socket.getSocket())); } else { processor.setSSLSupport(null); } - processor.process(socket); - return false; + return processor.process(socket); + //return false; } catch(java.net.SocketException e) { // SocketExceptions are normal diff --git a/java/org/apache/tomcat/util/net/JIoEndpoint.java b/java/org/apache/tomcat/util/net/JIoEndpoint.java index 62e2ce7a9..984c89576 100644 --- a/java/org/apache/tomcat/util/net/JIoEndpoint.java +++ b/java/org/apache/tomcat/util/net/JIoEndpoint.java @@ -117,7 +117,7 @@ public class JIoEndpoint extends AbstractEndpoint { * thread local fields. */ public interface Handler { - public boolean process(Socket socket); + public boolean process(SocketWrapper socket); } @@ -185,26 +185,35 @@ public class JIoEndpoint extends AbstractEndpoint { */ protected class SocketProcessor implements Runnable { - protected Socket socket = null; + protected SocketWrapper socket = null; - public SocketProcessor(Socket socket) { + public SocketProcessor(SocketWrapper socket) { this.socket = socket; } public void run() { - + boolean close = false; // Process the request from this socket - if (!setSocketOptions(socket) || !handler.process(socket)) { - // Close socket + if (!setSocketOptions(socket.getSocket())) { //this does a handshake and resets socket value + close = true; + } else if (!handler.process(socket)) { + close = true; + } + if (close) { + // Close socket + if (log.isTraceEnabled()) { + log.trace("Closing socket:"+socket); + } try { - socket.close(); + socket.getSocket().close(); } catch (IOException e) { } + } else { + //keepalive connection + getExecutor().execute(new SocketProcessor(socket)); } - // Finish up this request socket = null; - } } @@ -353,12 +362,13 @@ public class JIoEndpoint extends AbstractEndpoint { */ protected boolean processSocket(Socket socket) { try { - getExecutor().execute(new SocketProcessor(socket)); + SocketWrapper wrapper = new SocketWrapper(socket); + wrapper.setKeepAliveLeft(getMaxKeepAliveRequests()); + getExecutor().execute(new SocketProcessor(wrapper)); } catch (RejectedExecutionException x) { log.warn("Socket processing request was rejected for:"+socket,x); return false; } catch (Throwable t) { - // This means we got an OOM or similar creating a thread, or that // the pool and its queue are full log.error(sm.getString("endpoint.process.fail"), t); diff --git a/java/org/apache/tomcat/util/net/NioEndpoint.java b/java/org/apache/tomcat/util/net/NioEndpoint.java index 145a824a7..09f9dbda6 100644 --- a/java/org/apache/tomcat/util/net/NioEndpoint.java +++ b/java/org/apache/tomcat/util/net/NioEndpoint.java @@ -1115,7 +1115,7 @@ public class NioEndpoint extends AbstractEndpoint { { socket.setPoller(this); KeyAttachment key = keyCache.poll(); - final KeyAttachment ka = key!=null?key:new KeyAttachment(); + final KeyAttachment ka = key!=null?key:new KeyAttachment(socket); ka.reset(this,socket,getSocketProperties().getSoTimeout()); ka.setKeepAliveLeft(NioEndpoint.this.getMaxKeepAliveRequests()); PollerEvent r = eventCache.poll(); @@ -1145,7 +1145,7 @@ public class NioEndpoint extends AbstractEndpoint { else handler.release((SocketChannel)key.channel()); if (key.isValid()) key.cancel(); if (key.channel().isOpen()) try {key.channel().close();}catch (Exception ignore){} - try {if (ka!=null) ka.channel.close(true);}catch (Exception ignore){} + try {if (ka!=null) ka.getSocket().close(true);}catch (Exception ignore){} try {if (ka!=null && ka.getSendfileData()!=null && ka.getSendfileData().fchannel!=null && ka.getSendfileData().fchannel.isOpen()) ka.getSendfileData().fchannel.close();}catch (Exception ignore){} if (ka!=null) ka.reset(); } catch (Throwable e) { @@ -1454,13 +1454,14 @@ public class NioEndpoint extends AbstractEndpoint { } // ----------------------------------------------------- Key Attachment Class - public static class KeyAttachment { + public static class KeyAttachment extends SocketWrapper { - public KeyAttachment() { - + public KeyAttachment(NioChannel channel) { + super(channel); } + public void reset(Poller poller, NioChannel channel, long soTimeout) { - this.channel = channel; + this.socket = channel; this.poller = poller; lastAccess = System.currentTimeMillis(); currentAccess = false; @@ -1484,27 +1485,16 @@ public class NioEndpoint extends AbstractEndpoint { reset(null,null,-1); } - public boolean isAsync() { return async; } - public void setAsync(boolean async) { this.async = async; } public Poller getPoller() { return poller;} public void setPoller(Poller poller){this.poller = poller;} - public long getLastAccess() { return lastAccess; } - public void access() { access(System.currentTimeMillis()); } - public void access(long access) { lastAccess = access; } public void setComet(boolean comet) { this.comet = comet; } public boolean getComet() { return comet; } public void setCometNotify(boolean notify) { this.cometNotify = notify; } public boolean getCometNotify() { return cometNotify; } public void setCometOps(int ops) { this.cometOps = ops; } public int getCometOps() { return cometOps; } - public boolean getCurrentAccess() { return currentAccess; } - public void setCurrentAccess(boolean access) { currentAccess = access; } - public void setTimeout(long timeout) {this.timeout = timeout;} - public long getTimeout() {return this.timeout;} - public boolean getError() { return error; } - public void setError(boolean error) { this.error = error; } - public NioChannel getChannel() { return channel;} - public void setChannel(NioChannel channel) { this.channel = channel;} + public NioChannel getChannel() { return getSocket();} + public void setChannel(NioChannel channel) { this.socket = channel;} protected Poller poller = null; protected int interestOps = 0; public int interestOps() { return interestOps;} @@ -1526,9 +1516,6 @@ public class NioEndpoint extends AbstractEndpoint { } public void startReadLatch(int cnt) { readLatch = startLatch(readLatch,cnt);} public void startWriteLatch(int cnt) { writeLatch = startLatch(writeLatch,cnt);} - public int getKeepAliveLeft() { return this.keepAliveLeft; } - public void setKeepAliveLeft(int keepAliveLeft) { this.keepAliveLeft = keepAliveLeft;} - public int decrementKeepAlive() { return (--keepAliveLeft);} protected void awaitLatch(CountDownLatch latch, long timeout, TimeUnit unit) throws InterruptedException { if ( latch == null ) throw new IllegalStateException("Latch cannot be null"); @@ -1543,20 +1530,13 @@ public class NioEndpoint extends AbstractEndpoint { public void setSendfileData(SendfileData sf) { this.sendfileData = sf;} public SendfileData getSendfileData() { return this.sendfileData;} - protected long lastAccess = -1; - protected boolean currentAccess = false; protected boolean comet = false; protected int cometOps = SelectionKey.OP_READ; protected boolean cometNotify = false; - protected long timeout = -1; - protected boolean error = false; - protected NioChannel channel = null; protected CountDownLatch readLatch = null; protected CountDownLatch writeLatch = null; - protected long lastRegistered = 0; protected SendfileData sendfileData = null; - protected int keepAliveLeft = 100; - protected boolean async = false; + } // ------------------------------------------------ Application Buffer Handler diff --git a/java/org/apache/tomcat/util/net/SocketWrapper.java b/java/org/apache/tomcat/util/net/SocketWrapper.java new file mode 100644 index 000000000..624701990 --- /dev/null +++ b/java/org/apache/tomcat/util/net/SocketWrapper.java @@ -0,0 +1,59 @@ +/* + * 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.util.net; + + +public class SocketWrapper { + + protected volatile E socket; + + protected long lastAccess = -1; + protected boolean currentAccess = false; + protected long timeout = -1; + protected boolean error = false; + protected long lastRegistered = 0; + protected volatile int keepAliveLeft = 100; + protected boolean async = false; + + public SocketWrapper(E socket) { + reset(socket); + } + + public E getSocket() { + return socket; + } + + public void reset(E socket) { + this.socket = socket; + } + + public boolean isAsync() { return async; } + public void setAsync(boolean async) { this.async = async; } + public long getLastAccess() { return lastAccess; } + public void access() { access(System.currentTimeMillis()); } + public void access(long access) { lastAccess = access; } + public boolean getCurrentAccess() { return currentAccess; } + public void setCurrentAccess(boolean access) { currentAccess = access; } + public void setTimeout(long timeout) {this.timeout = timeout;} + public long getTimeout() {return this.timeout;} + public boolean getError() { return error; } + public void setError(boolean error) { this.error = error; } + public int getKeepAliveLeft() { return this.keepAliveLeft; } + public void setKeepAliveLeft(int keepAliveLeft) { this.keepAliveLeft = keepAliveLeft;} + public int decrementKeepAlive() { return (--keepAliveLeft);} + +} diff --git a/java/org/apache/tomcat/util/net/jsse/JSSESocketFactory.java b/java/org/apache/tomcat/util/net/jsse/JSSESocketFactory.java index 698751c8b..f588525d2 100644 --- a/java/org/apache/tomcat/util/net/jsse/JSSESocketFactory.java +++ b/java/org/apache/tomcat/util/net/jsse/JSSESocketFactory.java @@ -49,6 +49,7 @@ import javax.net.ssl.SSLContext; import javax.net.ssl.SSLException; import javax.net.ssl.SSLServerSocket; import javax.net.ssl.SSLServerSocketFactory; +import javax.net.ssl.SSLSession; import javax.net.ssl.SSLSessionContext; import javax.net.ssl.SSLSocket; import javax.net.ssl.TrustManager; @@ -157,7 +158,11 @@ public class JSSESocketFactory } public void handshake(Socket sock) throws IOException { - ((SSLSocket)sock).startHandshake(); + //we do getSession instead of startHandshake() so we can call this multiple times + SSLSession session = ((SSLSocket)sock).getSession(); + if (session.getCipherSuite().equals("SSL_NULL_WITH_NULL_NULL")) + throw new IOException("SSL handshake failed. Ciper suite in SSL Session is SSL_NULL_WITH_NULL_NULL"); + //((SSLSocket)sock).startHandshake(); } /* diff --git a/test/org/apache/TestAll.java b/test/org/apache/TestAll.java index 8168931a7..ed0aa68fa 100644 --- a/test/org/apache/TestAll.java +++ b/test/org/apache/TestAll.java @@ -1,17 +1,17 @@ package org.apache; +import junit.framework.Test; +import junit.framework.TestSuite; + +import org.apache.catalina.connector.TestKeepAliveCount; import org.apache.catalina.connector.TestRequest; import org.apache.catalina.ha.session.TestSerializablePrincipal; import org.apache.catalina.startup.TestTomcat; -import org.apache.catalina.tribes.test.TribesTestSuite; -import org.apache.el.lang.TestELSupport; import org.apache.el.TestELEvaluation; +import org.apache.el.lang.TestELSupport; import org.apache.tomcat.util.http.TestCookies; import org.apache.tomcat.util.res.TestStringManager; -import junit.framework.Test; -import junit.framework.TestSuite; - public class TestAll { public static Test suite() { @@ -19,6 +19,7 @@ public class TestAll { // o.a.catalina // connector suite.addTestSuite(TestRequest.class); + suite.addTestSuite(TestKeepAliveCount.class); // ha.session suite.addTestSuite(TestSerializablePrincipal.class); // startup diff --git a/test/org/apache/catalina/connector/TestKeepAliveCount.java b/test/org/apache/catalina/connector/TestKeepAliveCount.java new file mode 100644 index 000000000..8dc4bc727 --- /dev/null +++ b/test/org/apache/catalina/connector/TestKeepAliveCount.java @@ -0,0 +1,131 @@ +/* + * 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.catalina.connector; + +import java.io.IOException; + +import javax.servlet.ServletException; +import javax.servlet.http.HttpServlet; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import org.apache.catalina.core.StandardContext; +import org.apache.catalina.startup.SimpleHttpClient; +import org.apache.catalina.startup.TestTomcatBase; +import org.apache.catalina.startup.Tomcat; + +public class TestKeepAliveCount extends TestTomcatBase{ + + public void testHttp10() throws Exception { + TestKeepAliveClient client = new TestKeepAliveClient(); + client.doHttp10Request(); + } + + public void testHttp11() throws Exception { + TestKeepAliveClient client = new TestKeepAliveClient(); + client.doHttp11Request(); + } + + + private class TestKeepAliveClient extends SimpleHttpClient { + + + private boolean init; + + private synchronized void init() throws Exception { + if (init) return; + + Tomcat tomcat = getTomcatInstance(); + StandardContext root = tomcat.addContext("", TEMP_DIR); + Tomcat.addServlet(root, "Simple", new SimpleServlet()); + root.addServletMapping("/test", "Simple"); + tomcat.getConnector().setProperty("maxKeepAliveRequests", "5"); + tomcat.getConnector().setProperty("soTimeout", "20000"); + tomcat.getConnector().setProperty("keepAliveTimeout", "50000"); + tomcat.getConnector().setProperty("port", "8080"); + init = true; + } + + private void doHttp10Request() throws Exception { + Tomcat tomcat = getTomcatInstance(); + init(); + tomcat.start(); + // Open connection + connect(); + + // Send request in two parts + String[] request = new String[1]; + request[0] = + "GET /test HTTP/1.0" + CRLF + CRLF; + setRequest(request); + processRequest(false); // blocks until response has been read + boolean passed = (this.readLine()==null); + // Close the connection + disconnect(); + reset(); + tomcat.stop(); + assertTrue(passed); + } + + private void doHttp11Request() throws Exception { + Tomcat tomcat = getTomcatInstance(); + init(); + tomcat.start(); + // Open connection + connect(); + + // Send request in two parts + String[] request = new String[1]; + request[0] = + "GET /test HTTP/1.1" + CRLF + + "Host: localhost" + CRLF + + "Connection: Keep-Alive" + CRLF+ + "Keep-Alive: 300"+ CRLF+ CRLF; + + setRequest(request); + + for (int i=0; i<5; i++) { + processRequest(false); // blocks until response has been read + assertTrue(getResponseLine()!=null && getResponseLine().trim().startsWith("HTTP/1.1 200")); + } + boolean passed = (this.readLine()==null); + // Close the connection + disconnect(); + reset(); + tomcat.stop(); + assertTrue(passed); + } + + @Override + public boolean isResponseBodyOK() { + return true; + } + + } + + + private static class SimpleServlet extends HttpServlet { + + @Override + protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { + resp.setContentLength(0); + resp.flushBuffer(); + } + + } + +} diff --git a/test/org/apache/catalina/startup/SimpleHttpClient.java b/test/org/apache/catalina/startup/SimpleHttpClient.java index c33911617..3a88c94ef 100644 --- a/test/org/apache/catalina/startup/SimpleHttpClient.java +++ b/test/org/apache/catalina/startup/SimpleHttpClient.java @@ -75,6 +75,9 @@ public abstract class SimpleHttpClient { } public void processRequest() throws IOException, InterruptedException { + processRequest(true); + } + public void processRequest(boolean readBody) throws IOException, InterruptedException { // Send the request boolean first = true; for (String requestPart : request) { @@ -92,17 +95,19 @@ public abstract class SimpleHttpClient { // Put the headers into the map String line = readLine(); - while (line.length() > 0) { + while (line!=null && line.length() > 0) { responseHeaders.add(line); line = readLine(); } // Read the body, if any StringBuilder builder = new StringBuilder(); - line = readLine(); - while (line != null && line.length() > 0) { - builder.append(line); + if (readBody) { line = readLine(); + while (line != null && line.length() > 0) { + builder.append(line); + line = readLine(); + } } responseBody = builder.toString(); @@ -142,6 +147,10 @@ public abstract class SimpleHttpClient { public boolean isResponse500() { return getResponseLine().startsWith(FAIL_500); } + + public Socket getSocket() { + return socket; + } public abstract boolean isResponseBodyOK(); } \ No newline at end of file -- 2.11.0