Author: remy.maucherat(a)jboss.com
Date: 2012-09-26 09:54:12 -0400 (Wed, 26 Sep 2012)
New Revision: 2087
Added:
branches/7.2.x/src/main/java/org/apache/coyote/ajp/AjpProcessor.java
branches/7.2.x/src/main/java/org/apache/coyote/ajp/AjpProtocol.java
branches/7.2.x/src/main/java/org/apache/coyote/http11/Http11Processor.java
branches/7.2.x/src/main/java/org/apache/coyote/http11/Http11Protocol.java
branches/7.2.x/src/main/java/org/apache/coyote/http11/InternalInputBuffer.java
branches/7.2.x/src/main/java/org/apache/coyote/http11/InternalOutputBuffer.java
branches/7.2.x/src/main/java/org/apache/tomcat/util/net/DefaultServerSocketFactory.java
branches/7.2.x/src/main/java/org/apache/tomcat/util/net/JIoEndpoint.java
branches/7.2.x/src/main/java/org/apache/tomcat/util/net/ServerSocketFactory.java
branches/7.2.x/src/main/java/org/apache/tomcat/util/net/jsse/JSSEFactory.java
branches/7.2.x/src/main/java/org/apache/tomcat/util/net/jsse/JSSEImplementation.java
branches/7.2.x/src/main/java/org/apache/tomcat/util/net/jsse/JSSESocketFactory.java
branches/7.2.x/src/main/java/org/apache/tomcat/util/net/jsse/JSSESupport.java
Modified:
branches/7.2.x/pom.xml
branches/7.2.x/src/main/java/org/apache/catalina/connector/Connector.java
branches/7.2.x/src/main/java/org/apache/catalina/util/ConcurrentMessageDigest.java
branches/7.2.x/src/main/java/org/apache/coyote/ajp/AjpAprProtocol.java
branches/7.2.x/src/main/java/org/apache/coyote/ajp/Constants.java
branches/7.2.x/src/main/java/org/apache/coyote/http11/Http11NioProtocol.java
branches/7.2.x/src/main/java/org/apache/tomcat/util/net/NioEndpoint.java
branches/7.2.x/src/main/java/org/apache/tomcat/util/net/SSLImplementation.java
branches/7.2.x/src/main/java/org/apache/tomcat/util/net/jsse/NioJSSESocketChannelFactory.java
branches/7.2.x/src/main/java/org/jboss/web/CatalinaLogger.java
branches/7.2.x/webapps/docs/changelog.xml
Log:
- Add back java.io AJP and HTTP, with i18n.
- Compile for Java 6.
- Try to run NIO 2 HTTP if possible.
- Done ?
Modified: branches/7.2.x/pom.xml
===================================================================
(Binary files differ)
Modified: branches/7.2.x/src/main/java/org/apache/catalina/connector/Connector.java
===================================================================
--- branches/7.2.x/src/main/java/org/apache/catalina/connector/Connector.java 2012-09-25
12:49:25 UTC (rev 2086)
+++ branches/7.2.x/src/main/java/org/apache/catalina/connector/Connector.java 2012-09-26
13:54:12 UTC (rev 2087)
@@ -549,6 +549,8 @@
if ("org.apache.coyote.http11.Http11Protocol".equals
(getProtocolHandlerClassName())
|| "org.apache.coyote.http11.Http11AprProtocol".equals
+ (getProtocolHandlerClassName())
+ || "org.apache.coyote.http11.Http11NioProtocol".equals
(getProtocolHandlerClassName())) {
return "HTTP/1.1";
} else if ("org.apache.coyote.ajp.AjpProtocol".equals
@@ -583,8 +585,19 @@
}
} else {
if ("HTTP/1.1".equals(protocol) ||
"http".equals(protocol)) {
+ try {
+ Class.forName("java.nio.channels.CompletionHandler");
+ setProtocolHandlerClassName
+ ("org.apache.coyote.http11.Http11NioProtocol");
+ } catch (Exception e) {
+ // NIO 2 is not available
+ setProtocolHandlerClassName
+ ("org.apache.coyote.http11.Http11Protocol");
+ CatalinaLogger.CONNECTOR_LOGGER.usingJavaIoConnector();
+ }
+ } else if ("AJP/1.3".equals(protocol) ||
"ajp".equals(protocol)) {
setProtocolHandlerClassName
- ("org.apache.coyote.http11.Http11NioProtocol");
+ ("org.apache.coyote.ajp.AjpProtocol");
} else if (protocol != null) {
setProtocolHandlerClassName(protocol);
}
Modified:
branches/7.2.x/src/main/java/org/apache/catalina/util/ConcurrentMessageDigest.java
===================================================================
---
branches/7.2.x/src/main/java/org/apache/catalina/util/ConcurrentMessageDigest.java 2012-09-25
12:49:25 UTC (rev 2086)
+++
branches/7.2.x/src/main/java/org/apache/catalina/util/ConcurrentMessageDigest.java 2012-09-26
13:54:12 UTC (rev 2087)
@@ -33,7 +33,7 @@
private static final String MD5 = "MD5";
private static final Map<String,Queue<MessageDigest>> queues =
- new HashMap<>();
+ new HashMap<String,Queue<MessageDigest>>();
private ConcurrentMessageDigest() {
@@ -95,7 +95,7 @@
synchronized (queues) {
if (!queues.containsKey(algorithm)) {
MessageDigest md = MessageDigest.getInstance(algorithm);
- Queue<MessageDigest> queue = new ConcurrentLinkedQueue<>();
+ Queue<MessageDigest> queue = new
ConcurrentLinkedQueue<MessageDigest>();
queue.add(md);
queues.put(algorithm, queue);
}
Modified: branches/7.2.x/src/main/java/org/apache/coyote/ajp/AjpAprProtocol.java
===================================================================
--- branches/7.2.x/src/main/java/org/apache/coyote/ajp/AjpAprProtocol.java 2012-09-25
12:49:25 UTC (rev 2086)
+++ branches/7.2.x/src/main/java/org/apache/coyote/ajp/AjpAprProtocol.java 2012-09-26
13:54:12 UTC (rev 2087)
@@ -321,7 +321,7 @@
* Should authentication be done in the native webserver layer,
* or in the Servlet container ?
*/
- protected boolean tomcatAuthentication = true;
+ protected boolean tomcatAuthentication = Constants.DEFAULT_TOMCAT_AUTHENTICATION;
public boolean getTomcatAuthentication() { return tomcatAuthentication; }
public void setTomcatAuthentication(boolean tomcatAuthentication) {
this.tomcatAuthentication = tomcatAuthentication; }
Added: branches/7.2.x/src/main/java/org/apache/coyote/ajp/AjpProcessor.java
===================================================================
--- branches/7.2.x/src/main/java/org/apache/coyote/ajp/AjpProcessor.java
(rev 0)
+++ branches/7.2.x/src/main/java/org/apache/coyote/ajp/AjpProcessor.java 2012-09-26
13:54:12 UTC (rev 2087)
@@ -0,0 +1,1337 @@
+/*
+ * JBoss, Home of Professional Open Source
+ * Copyright 2009, JBoss Inc., and individual contributors as indicated
+ * by the @authors tag. See the copyright.txt in the distribution for a
+ * full listing of individual contributors.
+ *
+ * This is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as
+ * published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this software; if not, write to the Free
+ * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+ * 02110-1301 USA, or see the FSF site:
http://www.fsf.org.
+ */
+
+package org.apache.coyote.ajp;
+
+import static org.jboss.web.CoyoteMessages.MESSAGES;
+
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InterruptedIOException;
+import java.io.OutputStream;
+import java.net.InetAddress;
+import java.net.Socket;
+import java.security.cert.CertificateFactory;
+import java.security.cert.X509Certificate;
+
+import org.apache.coyote.ActionCode;
+import org.apache.coyote.ActionHook;
+import org.apache.coyote.Adapter;
+import org.apache.coyote.InputBuffer;
+import org.apache.coyote.OutputBuffer;
+import org.apache.coyote.Request;
+import org.apache.coyote.RequestInfo;
+import org.apache.coyote.Response;
+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.http.HttpMessages;
+import org.apache.tomcat.util.http.MimeHeaders;
+import org.apache.tomcat.util.net.JIoEndpoint;
+import org.apache.tomcat.util.net.SocketStatus;
+import org.apache.tomcat.util.net.JIoEndpoint.Handler.SocketState;
+import org.jboss.web.CoyoteLogger;
+
+
+/**
+ * Processes HTTP requests.
+ *
+ * @author Remy Maucherat
+ * @author Henri Gomez
+ * @author Dan Milstein
+ * @author Keith Wannamaker
+ * @author Kevin Seguin
+ * @author Costin Manolache
+ * @author Bill Barker
+ */
+public class AjpProcessor implements ActionHook {
+
+
+ // ----------------------------------------------------------- Constructors
+
+
+ public AjpProcessor(int packetSize, JIoEndpoint endpoint) {
+
+ this.endpoint = endpoint;
+
+ request = new Request();
+ request.setInputBuffer(new SocketInputBuffer());
+
+ response = new Response();
+ response.setHook(this);
+ response.setOutputBuffer(new SocketOutputBuffer());
+ request.setResponse(response);
+
+ requestHeaderMessage = new AjpMessage(packetSize);
+ responseHeaderMessage = new AjpMessage(packetSize);
+ bodyMessage = new AjpMessage(packetSize);
+
+ // Set the get body message buffer
+ AjpMessage getBodyMessage = new AjpMessage(16);
+ getBodyMessage.reset();
+ getBodyMessage.appendByte(Constants.JK_AJP13_GET_BODY_CHUNK);
+ getBodyMessage.appendInt(packetSize - Constants.READ_HEAD_LEN);
+ getBodyMessage.end();
+ getBodyMessageArray = new byte[getBodyMessage.getLen()];
+ System.arraycopy(getBodyMessage.getBuffer(), 0, getBodyMessageArray,
+ 0, getBodyMessage.getLen());
+
+ // Cause loading of HexUtils
+ int foo = HexUtils.DEC[0];
+
+ // Cause loading of HttpMessages
+ HttpMessages.getMessage(200);
+
+ }
+
+
+ // ----------------------------------------------------- Instance Variables
+
+
+ /**
+ * Associated adapter.
+ */
+ protected Adapter adapter = null;
+
+
+ /**
+ * Request object.
+ */
+ protected Request request = null;
+
+
+ /**
+ * Response object.
+ */
+ protected Response response = null;
+
+
+ /**
+ * Header message. Note that this header is merely the one used during the
+ * processing of the first message of a "request", so it might not be a
request
+ * header. It will stay unchanged during the processing of the whole request.
+ */
+ protected AjpMessage requestHeaderMessage = null;
+
+
+ /**
+ * Message used for response header composition.
+ */
+ protected AjpMessage responseHeaderMessage = null;
+
+
+ /**
+ * Body message.
+ */
+ protected AjpMessage bodyMessage = null;
+
+
+ /**
+ * Body message.
+ */
+ protected MessageBytes bodyBytes = MessageBytes.newInstance();
+
+
+ /**
+ * Error flag.
+ */
+ protected boolean error = false;
+
+
+ /**
+ * Socket associated with the current connection.
+ */
+ protected Socket socket;
+
+
+ /**
+ * Input stream.
+ */
+ protected InputStream input;
+
+
+ /**
+ * Output stream.
+ */
+ protected OutputStream output;
+
+
+ /**
+ * Host name (used to avoid useless B2C conversion on the host name).
+ */
+ protected char[] hostNameC = new char[0];
+
+
+ /**
+ * Associated endpoint.
+ */
+ protected JIoEndpoint endpoint;
+
+
+ /**
+ * The socket timeout used when reading the first block of the request
+ * header.
+ */
+ protected long readTimeout;
+
+
+ /**
+ * Temp message bytes used for processing.
+ */
+ protected MessageBytes tmpMB = MessageBytes.newInstance();
+
+
+ /**
+ * Byte chunk for certs.
+ */
+ protected MessageBytes certificates = MessageBytes.newInstance();
+
+
+ /**
+ * End of stream flag.
+ */
+ protected boolean endOfStream = false;
+
+
+ /**
+ * Body empty flag.
+ */
+ protected boolean empty = true;
+
+
+ /**
+ * First read.
+ */
+ protected boolean first = true;
+
+
+ /**
+ * Replay read.
+ */
+ protected boolean replay = false;
+
+
+ /**
+ * Finished response.
+ */
+ protected boolean finished = false;
+
+
+ /**
+ * Direct buffer used for sending right away a get body message.
+ */
+ protected final byte[] getBodyMessageArray;
+
+
+ /**
+ * Direct buffer used for sending right away a pong message.
+ */
+ protected static final byte[] pongMessageArray;
+
+
+ /**
+ * End message array.
+ */
+ protected static final byte[] endMessageArray;
+
+ /**
+ * Flush message array.
+ */
+ protected static final byte[] flushMessageArray;
+
+
+ /**
+ * Event used.
+ */
+ protected boolean event = false;
+
+
+ /**
+ * Event processing.
+ */
+ protected boolean eventProcessing = true;
+ public void startProcessing() { eventProcessing = true; }
+ public void endProcessing() { eventProcessing = false; }
+
+
+ // ----------------------------------------------------- Static Initializer
+
+
+ static {
+
+ // Set the read body message buffer
+ AjpMessage pongMessage = new AjpMessage(16);
+ pongMessage.reset();
+ pongMessage.appendByte(Constants.JK_AJP13_CPONG_REPLY);
+ pongMessage.end();
+ pongMessageArray = new byte[pongMessage.getLen()];
+ System.arraycopy(pongMessage.getBuffer(), 0, pongMessageArray,
+ 0, pongMessage.getLen());
+
+ // Allocate the end message array
+ AjpMessage endMessage = new AjpMessage(16);
+ endMessage.reset();
+ endMessage.appendByte(Constants.JK_AJP13_END_RESPONSE);
+ endMessage.appendByte(1);
+ endMessage.end();
+ endMessageArray = new byte[endMessage.getLen()];
+ System.arraycopy(endMessage.getBuffer(), 0, endMessageArray, 0,
+ endMessage.getLen());
+
+ // Allocate the flush message array
+ AjpMessage flushMessage = new AjpMessage(16);
+ flushMessage.reset();
+ flushMessage.appendByte(Constants.JK_AJP13_SEND_BODY_CHUNK);
+ flushMessage.appendInt(0);
+ flushMessage.appendByte(0);
+ flushMessage.end();
+ flushMessageArray = new byte[flushMessage.getLen()];
+ System.arraycopy(flushMessage.getBuffer(), 0, flushMessageArray, 0,
+ flushMessage.getLen());
+
+ }
+
+
+ // ------------------------------------------------------------- Properties
+
+
+ /**
+ * Use Tomcat authentication ?
+ */
+ protected boolean tomcatAuthentication = true;
+ public boolean getTomcatAuthentication() { return tomcatAuthentication; }
+ public void setTomcatAuthentication(boolean tomcatAuthentication) {
this.tomcatAuthentication = tomcatAuthentication; }
+
+
+ /**
+ * Required secret.
+ */
+ protected String requiredSecret = null;
+ public void setRequiredSecret(String requiredSecret) { this.requiredSecret =
requiredSecret; }
+
+
+ /**
+ * The number of milliseconds Tomcat will wait for a subsequent request
+ * before closing the connection. The default is the same as for
+ * Apache HTTP Server (15 000 milliseconds).
+ */
+ protected int keepAliveTimeout = -1;
+ public int getKeepAliveTimeout() { return keepAliveTimeout; }
+ public void setKeepAliveTimeout(int timeout) { keepAliveTimeout = timeout; }
+
+
+ /**
+ * Timeout.
+ */
+ protected int timeout = -1;
+ public void setTimeout(int timeout) { this.timeout = timeout; }
+ public int getTimeout() { return timeout; }
+
+
+ /**
+ * A resume has been requested.
+ */
+ protected boolean resumeNotification = false;
+ public boolean getResumeNotification() { return resumeNotification; }
+
+
+ // --------------------------------------------------------- Public Methods
+
+
+ /** Get the request associated with this processor.
+ *
+ * @return The request
+ */
+ public Request getRequest() {
+ return request;
+ }
+
+
+ public SocketState event(SocketStatus status)
+ throws IOException {
+
+ RequestInfo rp = request.getRequestProcessor();
+ try {
+ if (status == SocketStatus.OPEN_CALLBACK) {
+ // The resume notification is now done
+ resumeNotification = false;
+ } else if (status == SocketStatus.ERROR) {
+ // Set error flag right away
+ error = true;
+ }
+ rp.setStage(org.apache.coyote.Constants.STAGE_SERVICE);
+ error = !adapter.event(request, response, status);
+ } catch (InterruptedIOException e) {
+ error = true;
+ } catch (Throwable t) {
+ CoyoteLogger.AJP_LOGGER.errorProcessingRequest(t);
+ // 500 - Internal Server Error
+ response.setStatus(500);
+ error = true;
+ }
+
+ rp.setStage(org.apache.coyote.Constants.STAGE_ENDED);
+
+ if (error) {
+ recycle();
+ return SocketState.CLOSED;
+ } else if (!event) {
+ finish();
+ recycle();
+ return SocketState.OPEN;
+ } else {
+ return SocketState.LONG;
+ }
+ }
+
+
+ /**
+ * Process pipelined HTTP requests using the specified input and output
+ * streams.
+ *
+ * @throws IOException error during an I/O operation
+ */
+ public SocketState process(Socket socket)
+ throws IOException {
+ RequestInfo rp = request.getRequestProcessor();
+ rp.setStage(org.apache.coyote.Constants.STAGE_PARSE);
+
+ // Setting up the socket
+ this.socket = socket;
+ input = socket.getInputStream();
+ output = socket.getOutputStream();
+ int soTimeout = -1;
+ if (keepAliveTimeout > 0) {
+ soTimeout = socket.getSoTimeout();
+ }
+
+ // Error flag
+ error = false;
+
+ while (!error && !event) {
+
+ // Parsing the request header
+ try {
+ // Set keep alive timeout if enabled
+ if (keepAliveTimeout > 0) {
+ socket.setSoTimeout(keepAliveTimeout);
+ }
+ // Get first message of the request
+ if (!readMessage(requestHeaderMessage)) {
+ // This means a connection timeout
+ rp.setStage(org.apache.coyote.Constants.STAGE_ENDED);
+ break;
+ }
+ // Set back timeout if keep alive timeout is enabled
+ if (keepAliveTimeout > 0) {
+ socket.setSoTimeout(soTimeout);
+ }
+ // Check message type, process right away and break if
+ // not regular request processing
+ int type = requestHeaderMessage.getByte();
+ if (type == Constants.JK_AJP13_CPING_REQUEST) {
+ try {
+ output.write(pongMessageArray);
+ } catch (IOException e) {
+ error = true;
+ }
+ continue;
+ } else if(type != Constants.JK_AJP13_FORWARD_REQUEST) {
+ // Usually the servlet didn't read the previous request body
+ CoyoteLogger.AJP_LOGGER.unexpectedAjpMessage(type);
+ error = true;
+ break;
+ }
+
+ request.setStartTime(System.currentTimeMillis());
+ } catch (IOException e) {
+ error = true;
+ break;
+ } catch (Throwable t) {
+ CoyoteLogger.AJP_LOGGER.errorParsingAjpHeaderMessage(t);
+ // 400 - Bad Request
+ response.setStatus(400);
+ error = true;
+ }
+
+ // Setting up filters, and parse some request headers
+ rp.setStage(org.apache.coyote.Constants.STAGE_PREPARE);
+ try {
+ prepareRequest();
+ } catch (Throwable t) {
+ CoyoteLogger.AJP_LOGGER.errorPreparingAjpRequest(t);
+ // 400 - Internal Server Error
+ response.setStatus(400);
+ error = true;
+ }
+
+ // Process the request in the adapter
+ if (!error) {
+ try {
+ rp.setStage(org.apache.coyote.Constants.STAGE_SERVICE);
+ adapter.service(request, response);
+ } catch (InterruptedIOException e) {
+ error = true;
+ } catch (Throwable t) {
+ CoyoteLogger.AJP_LOGGER.errorProcessingRequest(t);
+ // 500 - Internal Server Error
+ response.setStatus(500);
+ error = true;
+ }
+ }
+
+ // Finish the response if not done yet
+ if (!event && !finished) {
+ try {
+ finish();
+ } catch (Throwable t) {
+ error = true;
+ }
+ }
+
+ // If there was an error, make sure the request is counted as
+ // and error, and update the statistics counter
+ if (error) {
+ response.setStatus(500);
+ }
+ request.updateCounters();
+
+ if (!event) {
+ recycle();
+ }
+
+ rp.setStage(org.apache.coyote.Constants.STAGE_KEEPALIVE);
+
+ }
+
+ rp.setStage(org.apache.coyote.Constants.STAGE_ENDED);
+
+ if (event) {
+ if (error) {
+ input = null;
+ output = null;
+ recycle();
+ return SocketState.CLOSED;
+ } else {
+ eventProcessing = false;
+ return SocketState.LONG;
+ }
+ } else {
+ input = null;
+ output = null;
+ recycle();
+ return SocketState.CLOSED;
+ }
+ }
+
+
+ // ----------------------------------------------------- ActionHook Methods
+
+
+ /**
+ * Send an action to the connector.
+ *
+ * @param actionCode Type of the action
+ * @param param Action parameter
+ */
+ public void action(ActionCode actionCode, Object param) {
+
+ if (actionCode == ActionCode.ACTION_COMMIT) {
+
+ if (response.isCommitted())
+ return;
+
+ // Validate and write response headers
+ try {
+ prepareResponse();
+ } catch (IOException e) {
+ // Set error flag
+ error = true;
+ }
+
+ } else if (actionCode == ActionCode.ACTION_CLIENT_FLUSH) {
+
+ if (!response.isCommitted()) {
+ // Validate and write response headers
+ try {
+ prepareResponse();
+ } catch (IOException e) {
+ // Set error flag
+ error = true;
+ return;
+ }
+ }
+
+ try {
+ flush();
+ } catch (IOException e) {
+ // Set error flag
+ error = true;
+ }
+
+ } else if (actionCode == ActionCode.ACTION_CLOSE) {
+ // Close
+
+ // End the processing of the current request, and stop any further
+ // transactions with the client
+
+ try {
+ finish();
+ } catch (IOException e) {
+ // Set error flag
+ error = true;
+ }
+
+ } else if (actionCode == ActionCode.ACTION_REQ_SSL_ATTRIBUTE ) {
+
+ if (!certificates.isNull()) {
+ ByteChunk certData = certificates.getByteChunk();
+ X509Certificate jsseCerts[] = null;
+ ByteArrayInputStream bais =
+ new ByteArrayInputStream(certData.getBytes(),
+ certData.getStart(),
+ certData.getLength());
+ // Fill the elements.
+ try {
+ CertificateFactory cf =
+ CertificateFactory.getInstance("X.509");
+ while(bais.available() > 0) {
+ X509Certificate cert = (X509Certificate)
+ cf.generateCertificate(bais);
+ if(jsseCerts == null) {
+ jsseCerts = new X509Certificate[1];
+ jsseCerts[0] = cert;
+ } else {
+ X509Certificate [] temp = new
X509Certificate[jsseCerts.length+1];
+ System.arraycopy(jsseCerts,0,temp,0,jsseCerts.length);
+ temp[jsseCerts.length] = cert;
+ jsseCerts = temp;
+ }
+ }
+ } catch (java.security.cert.CertificateException e) {
+ CoyoteLogger.AJP_LOGGER.errorProcessingCertificates(e);
+ return;
+ }
+
request.setAttribute(org.apache.tomcat.util.net.Constants.CERTIFICATE_KEY, jsseCerts);
+ }
+
+ } else if (actionCode == ActionCode.ACTION_REQ_HOST_ATTRIBUTE) {
+
+ // Get remote host name using a DNS resolution
+ if (request.remoteHost().isNull()) {
+ try {
+ request.remoteHost().setString(InetAddress.getByName
+ (request.remoteAddr().toString()).getHostName());
+ } catch (IOException iex) {
+ // Ignore
+ }
+ }
+
+ } else if (actionCode == ActionCode.ACTION_REQ_LOCAL_ADDR_ATTRIBUTE) {
+
+ // Copy from local name for now, which should simply be an address
+ request.localAddr().setString(request.localName().toString());
+
+ } else if (actionCode == ActionCode.ACTION_REQ_SET_BODY_REPLAY) {
+
+ // Set the given bytes as the content
+ ByteChunk bc = (ByteChunk) param;
+ int length = bc.getLength();
+ bodyBytes.setBytes(bc.getBytes(), bc.getStart(), length);
+ request.setContentLength(length);
+ first = false;
+ empty = false;
+ replay = true;
+ endOfStream = false;
+
+ } else if (actionCode == ActionCode.ACTION_EVENT_BEGIN) {
+ event = true;
+ } else if (actionCode == ActionCode.ACTION_EVENT_END) {
+ event = false;
+ } else if (actionCode == ActionCode.ACTION_EVENT_SUSPEND) {
+ // No action needed
+ } else if (actionCode == ActionCode.ACTION_EVENT_RESUME) {
+ // An event is being processed already: adding for resume will be done
+ // when the socket gets back to the poller
+ if (!eventProcessing && !resumeNotification) {
+ endpoint.getEventPoller().add(socket, timeout, true, true);
+ }
+ resumeNotification = true;
+ } else if (actionCode == ActionCode.ACTION_EVENT_TIMEOUT) {
+ timeout = ((Integer) param).intValue();
+ }
+
+
+ }
+
+
+ // ------------------------------------------------------ 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
+
+
+ /**
+ * After reading the request headers, we have to setup the request filters.
+ */
+ protected void prepareRequest() {
+
+ // Translate the HTTP method code to a String.
+ byte methodCode = requestHeaderMessage.getByte();
+ if (methodCode != Constants.SC_M_JK_STORED) {
+ String methodName = Constants.methodTransArray[(int)methodCode - 1];
+ request.method().setString(methodName);
+ }
+
+ requestHeaderMessage.getBytes(request.protocol());
+ requestHeaderMessage.getBytes(request.requestURI());
+
+ requestHeaderMessage.getBytes(request.remoteAddr());
+ requestHeaderMessage.getBytes(request.remoteHost());
+ requestHeaderMessage.getBytes(request.localName());
+ request.setLocalPort(requestHeaderMessage.getInt());
+
+ boolean isSSL = requestHeaderMessage.getByte() != 0;
+ if (isSSL) {
+ request.scheme().setString("https");
+ }
+
+ // Decode headers
+ MimeHeaders headers = request.getMimeHeaders();
+
+ int hCount = requestHeaderMessage.getInt();
+ for(int i = 0 ; i < hCount ; i++) {
+ String hName = null;
+
+ // Header names are encoded as either an integer code starting
+ // with 0xA0, or as a normal string (in which case the first
+ // two bytes are the length).
+ int isc = requestHeaderMessage.peekInt();
+ int hId = isc & 0xFF;
+
+ MessageBytes vMB = null;
+ isc &= 0xFF00;
+ if(0xA000 == isc) {
+ requestHeaderMessage.getInt(); // To advance the read position
+ hName = Constants.headerTransArray[hId - 1];
+ vMB = headers.addValue(hName);
+ } else {
+ // reset hId -- if the header currently being read
+ // happens to be 7 or 8 bytes long, the code below
+ // will think it's the content-type header or the
+ // content-length header - SC_REQ_CONTENT_TYPE=7,
+ // SC_REQ_CONTENT_LENGTH=8 - leading to unexpected
+ // behaviour. see bug 5861 for more information.
+ hId = -1;
+ requestHeaderMessage.getBytes(tmpMB);
+ ByteChunk bc = tmpMB.getByteChunk();
+ vMB = headers.addValue(bc.getBuffer(),
+ bc.getStart(), bc.getLength());
+ }
+
+ requestHeaderMessage.getBytes(vMB);
+
+ if (hId == Constants.SC_REQ_CONTENT_LENGTH ||
+ (hId == -1 &&
tmpMB.equalsIgnoreCase("Content-Length"))) {
+ // just read the content-length header, so set it
+ request.setContentLength( vMB.getInt() );
+ } else if (hId == Constants.SC_REQ_CONTENT_TYPE ||
+ (hId == -1 &&
tmpMB.equalsIgnoreCase("Content-Type"))) {
+ // just read the content-type header, so set it
+ ByteChunk bchunk = vMB.getByteChunk();
+ request.contentType().setBytes(bchunk.getBytes(),
+ bchunk.getOffset(),
+ bchunk.getLength());
+ }
+ }
+
+ // Decode extra attributes
+ boolean secret = false;
+ byte attributeCode;
+ while ((attributeCode = requestHeaderMessage.getByte())
+ != Constants.SC_A_ARE_DONE) {
+
+ switch (attributeCode) {
+
+ case Constants.SC_A_REQ_ATTRIBUTE :
+ requestHeaderMessage.getBytes(tmpMB);
+ String n = tmpMB.toString();
+ requestHeaderMessage.getBytes(tmpMB);
+ String v = tmpMB.toString();
+ request.setAttribute(n, v);
+ break;
+
+ case Constants.SC_A_CONTEXT :
+ requestHeaderMessage.getBytes(tmpMB);
+ // nothing
+ break;
+
+ case Constants.SC_A_SERVLET_PATH :
+ requestHeaderMessage.getBytes(tmpMB);
+ // nothing
+ break;
+
+ case Constants.SC_A_REMOTE_USER :
+ if (tomcatAuthentication) {
+ // ignore server
+ requestHeaderMessage.getBytes(tmpMB);
+ } else {
+ requestHeaderMessage.getBytes(request.getRemoteUser());
+ }
+ break;
+
+ case Constants.SC_A_AUTH_TYPE :
+ if (tomcatAuthentication) {
+ // ignore server
+ requestHeaderMessage.getBytes(tmpMB);
+ } else {
+ requestHeaderMessage.getBytes(request.getAuthType());
+ }
+ break;
+
+ case Constants.SC_A_QUERY_STRING :
+ requestHeaderMessage.getBytes(request.queryString());
+ break;
+
+ case Constants.SC_A_JVM_ROUTE :
+ requestHeaderMessage.getBytes(request.instanceId());
+ break;
+
+ case Constants.SC_A_SSL_CERT :
+ request.scheme().setString("https");
+ // SSL certificate extraction is lazy, moved to JkCoyoteHandler
+ requestHeaderMessage.getBytes(certificates);
+ break;
+
+ case Constants.SC_A_SSL_CIPHER :
+ request.scheme().setString("https");
+ requestHeaderMessage.getBytes(tmpMB);
+
request.setAttribute(org.apache.tomcat.util.net.Constants.CIPHER_SUITE_KEY,
+ tmpMB.toString());
+ break;
+
+ case Constants.SC_A_SSL_SESSION :
+ request.scheme().setString("https");
+ requestHeaderMessage.getBytes(tmpMB);
+
request.setAttribute(org.apache.tomcat.util.net.Constants.SESSION_ID_KEY,
+ tmpMB.toString());
+ break;
+
+ case Constants.SC_A_SSL_KEY_SIZE :
+ request.setAttribute(org.apache.tomcat.util.net.Constants.KEY_SIZE_KEY,
+ new Integer(requestHeaderMessage.getInt()));
+ break;
+
+ case Constants.SC_A_STORED_METHOD:
+ requestHeaderMessage.getBytes(request.method());
+ break;
+
+ case Constants.SC_A_SECRET:
+ requestHeaderMessage.getBytes(tmpMB);
+ if (requiredSecret != null) {
+ secret = true;
+ if (!tmpMB.equals(requiredSecret)) {
+ response.setStatus(403);
+ error = true;
+ }
+ }
+ break;
+
+ default:
+ // Ignore unknown attribute for backward compatibility
+ break;
+
+ }
+
+ }
+
+ // Check if secret was submitted if required
+ if ((requiredSecret != null) && !secret) {
+ response.setStatus(403);
+ error = true;
+ }
+
+ // Check for a full URI (including protocol://host:port/)
+ ByteChunk uriBC = request.requestURI().getByteChunk();
+ if (uriBC.startsWithIgnoreCase("http", 0)) {
+
+ int pos = uriBC.indexOf("://", 0, 3, 4);
+ int uriBCStart = uriBC.getStart();
+ int slashPos = -1;
+ if (pos != -1) {
+ byte[] uriB = uriBC.getBytes();
+ slashPos = uriBC.indexOf('/', pos + 3);
+ if (slashPos == -1) {
+ slashPos = uriBC.getLength();
+ // Set URI as "/"
+ request.requestURI().setBytes
+ (uriB, uriBCStart + pos + 1, 1);
+ } else {
+ request.requestURI().setBytes
+ (uriB, uriBCStart + slashPos,
+ uriBC.getLength() - slashPos);
+ }
+ MessageBytes hostMB = headers.setValue("host");
+ hostMB.setBytes(uriB, uriBCStart + pos + 3,
+ slashPos - pos - 3);
+ }
+
+ }
+
+ MessageBytes valueMB = request.getMimeHeaders().getValue("host");
+ parseHost(valueMB);
+
+ }
+
+
+ /**
+ * Parse host.
+ */
+ public void parseHost(MessageBytes valueMB) {
+
+ if (valueMB == null || (valueMB != null && valueMB.isNull()) ) {
+ // HTTP/1.0
+ request.setServerPort(request.getLocalPort());
+ try {
+ request.serverName().duplicate(request.localName());
+ } catch (IOException e) {
+ response.setStatus(400);
+ error = true;
+ }
+ 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 (request.scheme().equalsIgnoreCase("https")) {
+ // 443 - Default HTTPS port
+ request.setServerPort(443);
+ } else {
+ // 80 - Default HTTTP port
+ request.setServerPort(80);
+ }
+ 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] & 0xff];
+ if (charValue == -1) {
+ // Invalid character
+ error = true;
+ // 400 - Bad request
+ response.setStatus(400);
+ break;
+ }
+ port = port + (charValue * mult);
+ mult = 10 * mult;
+ }
+ request.setServerPort(port);
+
+ }
+
+ }
+
+
+ /**
+ * When committing the response, we have to validate the set of headers, as
+ * well as setup the response filters.
+ */
+ protected void prepareResponse()
+ throws IOException {
+
+ response.setCommitted(true);
+
+ responseHeaderMessage.reset();
+ responseHeaderMessage.appendByte(Constants.JK_AJP13_SEND_HEADERS);
+
+ // HTTP header contents
+ responseHeaderMessage.appendInt(response.getStatus());
+ String message = null;
+ if (org.apache.coyote.Constants.USE_CUSTOM_STATUS_MSG_IN_HEADER) {
+ message = response.getMessage();
+ }
+ if (message == null){
+ message = HttpMessages.getMessage(response.getStatus());
+ } else {
+ message = message.replace('\n', ' ').replace('\r',
' ');
+ }
+ if (message == null) {
+ // Many httpd 2.x wants a non empty status message
+ message = Integer.toString(response.getStatus());
+ }
+ tmpMB.setString(message);
+ responseHeaderMessage.appendBytes(tmpMB);
+
+ // Special headers
+ MimeHeaders headers = response.getMimeHeaders();
+ String contentType = response.getContentType();
+ if (contentType != null) {
+ headers.setValue("Content-Type").setString(contentType);
+ }
+ String contentLanguage = response.getContentLanguage();
+ if (contentLanguage != null) {
+ headers.setValue("Content-Language").setString(contentLanguage);
+ }
+ int contentLength = response.getContentLength();
+ if (contentLength >= 0) {
+ headers.setValue("Content-Length").setInt(contentLength);
+ }
+
+ // Other headers
+ int numHeaders = headers.size();
+ responseHeaderMessage.appendInt(numHeaders);
+ for (int i = 0; i < numHeaders; i++) {
+ MessageBytes hN = headers.getName(i);
+ MessageBytes hV=headers.getValue(i);
+ if (hN.getLength() > 0 && !hV.isNull()) {
+ int hC = Constants.getResponseAjpIndex(hN.toString());
+ if (hC > 0) {
+ responseHeaderMessage.appendInt(hC);
+ }
+ else {
+ responseHeaderMessage.appendBytes(hN);
+ }
+ responseHeaderMessage.appendBytes(hV);
+ }
+ }
+
+ // Write to buffer
+ responseHeaderMessage.end();
+ output.write(responseHeaderMessage.getBuffer(), 0,
responseHeaderMessage.getLen());
+
+ }
+
+
+ /**
+ * Finish AJP response.
+ */
+ protected void finish()
+ throws IOException {
+
+ if (!response.isCommitted()) {
+ // Validate and write response headers
+ try {
+ prepareResponse();
+ } catch (IOException e) {
+ // Set error flag
+ error = true;
+ }
+ }
+
+ if (finished)
+ return;
+
+ finished = true;
+
+ // Add the end message
+ output.write(endMessageArray);
+
+
+ // read remaining data from the special first-body-chunk
+ if (first && request.getContentLengthLong() > 0) {
+ try {
+ receive();
+ } catch (IOException e) {
+ }
+ }
+
+ }
+
+
+ /**
+ * Read at least the specified amount of bytes, and place them
+ * in the input buffer.
+ */
+ protected boolean read(byte[] buf, int pos, int n)
+ throws IOException {
+
+ int read = 0;
+ int res = 0;
+ while (read < n) {
+ res = input.read(buf, read + pos, n - read);
+ if (res > 0) {
+ read += res;
+ } else {
+ throw new IOException(MESSAGES.failedRead());
+ }
+ }
+
+ return true;
+
+ }
+
+
+ /** Receive a chunk of data. Called to implement the
+ * 'special' packet in ajp13 and to receive the data
+ * after we send a GET_BODY packet
+ */
+ public boolean receive() throws IOException {
+
+ first = false;
+ bodyMessage.reset();
+ readMessage(bodyMessage);
+
+ // No data received.
+ if (bodyMessage.getLen() == 0) {
+ // just the header
+ // Don't mark 'end of stream' for the first chunk.
+ return false;
+ }
+ int blen = bodyMessage.peekInt();
+ if (blen == 0) {
+ return false;
+ }
+
+ bodyMessage.getBytes(bodyBytes);
+ empty = false;
+ return true;
+ }
+
+ /**
+ * Get more request body data from the web server and store it in the
+ * internal buffer.
+ *
+ * @return true if there is more data, false if not.
+ */
+ private boolean refillReadBuffer() throws IOException {
+ // If the server returns an empty packet, assume that that end of
+ // the stream has been reached (yuck -- fix protocol??).
+ // FORM support
+ if (replay) {
+ endOfStream = true; // we've read everything there is
+ }
+ if (endOfStream) {
+ return false;
+ }
+ if (finished) {
+ return false;
+ }
+
+ // Request more data immediately
+ output.write(getBodyMessageArray);
+
+ boolean moreData = receive();
+ if( !moreData ) {
+ endOfStream = true;
+ }
+ return moreData;
+ }
+
+
+ /**
+ * Read an AJP message.
+ *
+ * @return true if the message has been read, false if the short read
+ * didn't return anything
+ * @throws IOException any other failure, including incomplete reads
+ */
+ protected boolean readMessage(AjpMessage message)
+ throws IOException {
+
+ byte[] buf = message.getBuffer();
+
+ read(buf, 0, message.getHeaderLength());
+
+ if (message.processHeader() < 0) {
+ throw new IOException(MESSAGES.invalidAjpMessage());
+ }
+ read(buf, message.getHeaderLength(), message.getLen());
+
+ return true;
+
+ }
+
+
+ /**
+ * Recycle the processor.
+ */
+ public void recycle() {
+
+ // Recycle Request object
+ first = true;
+ endOfStream = false;
+ empty = true;
+ replay = false;
+ finished = false;
+ timeout = -1;
+ resumeNotification = false;
+ eventProcessing = true;
+ request.recycle();
+ response.recycle();
+ certificates.recycle();
+
+ }
+
+
+ /**
+ * Callback to write data from the buffer.
+ */
+ protected void flush()
+ throws IOException {
+ // Send the flush message
+ output.write(flushMessageArray);
+ }
+
+
+ // ------------------------------------- InputStreamInputBuffer Inner Class
+
+
+ /**
+ * This class is an input buffer which will read its data from an input
+ * stream.
+ */
+ protected class SocketInputBuffer
+ implements InputBuffer {
+
+
+ /**
+ * Read bytes into the specified chunk.
+ */
+ public int doRead(ByteChunk chunk, Request req )
+ throws IOException {
+
+ if (endOfStream) {
+ return -1;
+ }
+ if (first && req.getContentLength() > 0) {
+ // Handle special first-body-chunk
+ if (!receive()) {
+ return 0;
+ }
+ } else if (empty) {
+ if (!refillReadBuffer()) {
+ return -1;
+ }
+ }
+ ByteChunk bc = bodyBytes.getByteChunk();
+ chunk.setBytes(bc.getBuffer(), bc.getStart(), bc.getLength());
+ empty = true;
+ return chunk.getLength();
+
+ }
+
+ }
+
+
+ // ----------------------------------- OutputStreamOutputBuffer Inner Class
+
+
+ /**
+ * This class is an output buffer which will write data to an output
+ * stream.
+ */
+ protected class SocketOutputBuffer
+ implements OutputBuffer {
+
+
+ /**
+ * Write chunk.
+ */
+ public int doWrite(ByteChunk chunk, Response res)
+ throws IOException {
+
+ if (!response.isCommitted()) {
+ // Validate and write response headers
+ try {
+ prepareResponse();
+ } catch (IOException e) {
+ // Set error flag
+ error = true;
+ }
+ }
+
+ int len = chunk.getLength();
+ // 4 - hardcoded, byte[] marshalling overhead
+ int chunkSize = Constants.MAX_SEND_SIZE;
+ int off = 0;
+ while (len > 0) {
+ int thisTime = len;
+ if (thisTime > chunkSize) {
+ thisTime = chunkSize;
+ }
+ len -= thisTime;
+ responseHeaderMessage.reset();
+ responseHeaderMessage.appendByte(Constants.JK_AJP13_SEND_BODY_CHUNK);
+ responseHeaderMessage.appendBytes(chunk.getBytes(), chunk.getOffset() +
off, thisTime);
+ responseHeaderMessage.end();
+ output.write(responseHeaderMessage.getBuffer(), 0,
responseHeaderMessage.getLen());
+
+ off += thisTime;
+ }
+
+ return chunk.getLength();
+
+ }
+
+
+ }
+
+
+}
Added: branches/7.2.x/src/main/java/org/apache/coyote/ajp/AjpProtocol.java
===================================================================
--- branches/7.2.x/src/main/java/org/apache/coyote/ajp/AjpProtocol.java
(rev 0)
+++ branches/7.2.x/src/main/java/org/apache/coyote/ajp/AjpProtocol.java 2012-09-26
13:54:12 UTC (rev 2087)
@@ -0,0 +1,538 @@
+/*
+ * JBoss, Home of Professional Open Source
+ * Copyright 2009, JBoss Inc., and individual contributors as indicated
+ * by the @authors tag. See the copyright.txt in the distribution for a
+ * full listing of individual contributors.
+ *
+ * This is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as
+ * published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this software; if not, write to the Free
+ * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+ * 02110-1301 USA, or see the FSF site:
http://www.fsf.org.
+ */
+
+package org.apache.coyote.ajp;
+
+import java.net.InetAddress;
+import java.net.Socket;
+import java.net.URLEncoder;
+import java.util.Hashtable;
+import java.util.Iterator;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ConcurrentLinkedQueue;
+import java.util.concurrent.Executor;
+import java.util.concurrent.atomic.AtomicInteger;
+import java.util.concurrent.atomic.AtomicLong;
+
+import javax.management.MBeanRegistration;
+import javax.management.MBeanServer;
+import javax.management.ObjectName;
+
+import org.apache.coyote.Adapter;
+import org.apache.coyote.ProtocolHandler;
+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.SocketStatus;
+import org.apache.tomcat.util.net.JIoEndpoint.Handler;
+import org.jboss.web.CoyoteLogger;
+
+
+/**
+ * Abstract the protocol implementation, including threading, etc.
+ * Processor is single threaded and specific to stream-based protocols,
+ * will not fit Jk protocols like JNI.
+ *
+ * @author Remy Maucherat
+ * @author Costin Manolache
+ */
+public class AjpProtocol
+ implements ProtocolHandler, MBeanRegistration {
+
+
+ // ------------------------------------------------------------ Constructor
+
+
+ public AjpProtocol() {
+ cHandler = new AjpConnectionHandler(this);
+ setSoLinger(Constants.DEFAULT_CONNECTION_LINGER);
+ setSoTimeout(Constants.DEFAULT_CONNECTION_TIMEOUT);
+ //setServerSoTimeout(Constants.DEFAULT_SERVER_SOCKET_TIMEOUT);
+ setTcpNoDelay(Constants.DEFAULT_TCP_NO_DELAY);
+ }
+
+
+ // ----------------------------------------------------- Instance Variables
+
+
+ protected ObjectName tpOname;
+
+
+ protected ObjectName rgOname;
+
+
+ /**
+ * Associated java.io endpoint.
+ */
+ protected JIoEndpoint endpoint = new JIoEndpoint();
+
+
+ /**
+ * Configuration attributes.
+ */
+ protected Hashtable attributes = new Hashtable();
+
+
+ /**
+ * Adapter which will process the requests recieved by this endpoint.
+ */
+ private Adapter adapter;
+
+
+ /**
+ * Connection handler for AJP.
+ */
+ private AjpConnectionHandler cHandler;
+
+
+ // --------------------------------------------------------- Public Methods
+
+
+ /**
+ * Pass config info
+ */
+ public void setAttribute(String name, Object value) {
+ attributes.put(name, value);
+ }
+
+ public Object getAttribute(String key) {
+ return attributes.get(key);
+ }
+
+
+ public Iterator getAttributeNames() {
+ return attributes.keySet().iterator();
+ }
+
+
+ /**
+ * The adapter, used to call the connector
+ */
+ public void setAdapter(Adapter adapter) {
+ this.adapter = adapter;
+ }
+
+
+ public Adapter getAdapter() {
+ return adapter;
+ }
+
+
+ public boolean hasIoEvents() {
+ return false;
+ }
+
+ public RequestGroupInfo getRequestGroupInfo() {
+ return cHandler.global;
+ }
+
+
+ /** Start the protocol
+ */
+ public void init() throws Exception {
+ endpoint.setName(getName());
+ endpoint.setHandler(cHandler);
+
+ try {
+ endpoint.init();
+ } catch (Exception ex) {
+ CoyoteLogger.AJP_LOGGER.errorInitializingEndpoint(ex);
+ throw ex;
+ }
+ }
+
+
+ public void start() throws Exception {
+ if (org.apache.tomcat.util.Constants.ENABLE_MODELER) {
+ if (this.domain != null ) {
+ try {
+ tpOname = new ObjectName
+ (domain + ":" + "type=ThreadPool,name=" +
getJmxName());
+ Registry.getRegistry(null, null)
+ .registerComponent(endpoint, tpOname, null );
+ } catch (Exception e) {
+ CoyoteLogger.AJP_LOGGER.errorRegisteringPool(e);
+ }
+ rgOname = new ObjectName
+ (domain + ":type=GlobalRequestProcessor,name=" +
getJmxName());
+ Registry.getRegistry(null, null).registerComponent
+ (cHandler.global, rgOname, null);
+ }
+ }
+ try {
+ endpoint.start();
+ } catch (Exception ex) {
+ CoyoteLogger.AJP_LOGGER.errorStartingEndpoint(ex);
+ throw ex;
+ }
+ CoyoteLogger.AJP_LOGGER.startingAjpProtocol(getName());
+ }
+
+ public void pause() throws Exception {
+ try {
+ endpoint.pause();
+ } catch (Exception ex) {
+ CoyoteLogger.AJP_LOGGER.errorPausingEndpoint(ex);
+ throw ex;
+ }
+ // Wait for a while until all the processors are idle
+ RequestInfo[] states = cHandler.global.getRequestProcessors();
+ int retry = 0;
+ boolean done = false;
+ while (!done && retry < org.apache.coyote.Constants.MAX_PAUSE_WAIT) {
+ retry++;
+ done = true;
+ for (int i = 0; i < states.length; i++) {
+ if (states[i].getStage() == org.apache.coyote.Constants.STAGE_SERVICE) {
+ try {
+ Thread.sleep(1000);
+ } catch (InterruptedException e) {
+ ;
+ }
+ done = false;
+ break;
+ }
+ }
+ }
+ CoyoteLogger.AJP_LOGGER.pausingAjpProtocol(getName());
+ }
+
+ public void resume() throws Exception {
+ try {
+ endpoint.resume();
+ } catch (Exception ex) {
+ CoyoteLogger.AJP_LOGGER.errorResumingEndpoint(ex);
+ throw ex;
+ }
+ CoyoteLogger.AJP_LOGGER.resumingAjpProtocol(getName());
+ }
+
+ public void destroy() throws Exception {
+ CoyoteLogger.AJP_LOGGER.stoppingAjpProtocol(getName());
+ endpoint.destroy();
+ if (org.apache.tomcat.util.Constants.ENABLE_MODELER) {
+ if (tpOname!=null)
+ Registry.getRegistry(null, null).unregisterComponent(tpOname);
+ if (rgOname != null)
+ Registry.getRegistry(null, null).unregisterComponent(rgOname);
+ }
+ }
+
+ public String getJmxName() {
+ String encodedAddr = "";
+ if (getAddress() != null) {
+ encodedAddr = "" + getAddress();
+ encodedAddr = URLEncoder.encode(encodedAddr.replace('/',
'-').replace(':', '_').replace('%', '-')) +
"-";
+ }
+ return ("ajp-" + encodedAddr + endpoint.getPort());
+ }
+
+ public String getName() {
+ String encodedAddr = "";
+ if (getAddress() != null) {
+ encodedAddr = getAddress() + ":";
+ }
+ return ("ajp-" + encodedAddr + endpoint.getPort());
+ }
+
+ /**
+ * Processor cache.
+ */
+ protected int processorCache = -1;
+ public int getProcessorCache() { return this.processorCache; }
+ public void setProcessorCache(int processorCache) { this.processorCache =
processorCache; }
+
+ public Executor getExecutor() { return endpoint.getExecutor(); }
+ public void setExecutor(Executor executor) { endpoint.setExecutor(executor); }
+
+ public int getMaxThreads() { return endpoint.getMaxThreads(); }
+ public void setMaxThreads(int maxThreads) { endpoint.setMaxThreads(maxThreads); }
+
+ public int getThreadPriority() { return endpoint.getThreadPriority(); }
+ public void setThreadPriority(int threadPriority) {
endpoint.setThreadPriority(threadPriority); }
+
+ public int getBacklog() { return endpoint.getBacklog(); }
+ public void setBacklog(int backlog) { endpoint.setBacklog(backlog); }
+
+ public int getPort() { return endpoint.getPort(); }
+ public void setPort(int port) { endpoint.setPort(port); }
+
+ public InetAddress getAddress() { return endpoint.getAddress(); }
+ public void setAddress(InetAddress ia) { endpoint.setAddress(ia); }
+
+ public boolean getTcpNoDelay() { return endpoint.getTcpNoDelay(); }
+ public void setTcpNoDelay(boolean tcpNoDelay) { endpoint.setTcpNoDelay(tcpNoDelay);
}
+
+ public int getSoLinger() { return endpoint.getSoLinger(); }
+ public void setSoLinger(int soLinger) { endpoint.setSoLinger(soLinger); }
+
+ public int getSoTimeout() { return endpoint.getSoTimeout(); }
+ public void setSoTimeout(int soTimeout) { endpoint.setSoTimeout(soTimeout); }
+
+ public void setPollerSize(int pollerSize) { endpoint.setPollerSize(pollerSize); }
+ public int getPollerSize() { return endpoint.getPollerSize(); }
+
+ /**
+ * Should authentication be done in the native webserver layer,
+ * or in the Servlet container ?
+ */
+ protected boolean tomcatAuthentication = Constants.DEFAULT_TOMCAT_AUTHENTICATION;
+ public boolean getTomcatAuthentication() { return tomcatAuthentication; }
+ public void setTomcatAuthentication(boolean tomcatAuthentication) {
this.tomcatAuthentication = tomcatAuthentication; }
+
+ /**
+ * Required secret.
+ */
+ protected String requiredSecret = null;
+ public void setRequiredSecret(String requiredSecret) { this.requiredSecret =
requiredSecret; }
+
+ /**
+ * AJP packet size.
+ */
+ protected int packetSize = Constants.MAX_PACKET_SIZE;
+ public int getPacketSize() { return packetSize; }
+ public void setPacketSize(int packetSize) { this.packetSize = packetSize; }
+
+
+ /**
+ * The number of seconds Tomcat will wait for a subsequent request
+ * before closing the connection.
+ */
+ protected int keepAliveTimeout = -1;
+ public int getKeepAliveTimeout() { return keepAliveTimeout; }
+ public void setKeepAliveTimeout(int timeout) { keepAliveTimeout = timeout; }
+
+
+ // -------------------------------------- AjpConnectionHandler Inner Class
+
+
+ protected static class AjpConnectionHandler implements Handler {
+
+ protected AjpProtocol proto;
+ protected AtomicLong registerCount = new AtomicLong(0);
+ protected RequestGroupInfo global = new RequestGroupInfo();
+
+ protected ConcurrentHashMap<Socket, AjpProcessor> connections =
+ new ConcurrentHashMap<Socket, AjpProcessor>();
+ protected ConcurrentLinkedQueue<AjpProcessor> recycledProcessors =
+ new ConcurrentLinkedQueue<AjpProcessor>() {
+ protected AtomicInteger size = new AtomicInteger(0);
+ public boolean offer(AjpProcessor processor) {
+ boolean offer = (proto.processorCache == -1) ? true : (size.get() <
proto.processorCache);
+ //avoid over growing our cache or add after we have stopped
+ boolean result = false;
+ if ( offer ) {
+ result = super.offer(processor);
+ if ( result ) {
+ size.incrementAndGet();
+ }
+ }
+ if (!result) unregister(processor);
+ return result;
+ }
+
+ public AjpProcessor poll() {
+ AjpProcessor result = super.poll();
+ if ( result != null ) {
+ size.decrementAndGet();
+ }
+ return result;
+ }
+
+ public void clear() {
+ AjpProcessor next = poll();
+ while ( next != null ) {
+ unregister(next);
+ next = poll();
+ }
+ super.clear();
+ size.set(0);
+ }
+ };
+
+ public AjpConnectionHandler(AjpProtocol proto) {
+ this.proto = proto;
+ }
+
+ public SocketState event(Socket socket, SocketStatus status) {
+ AjpProcessor result = connections.get(socket);
+ SocketState state = SocketState.CLOSED;
+ if (result != null) {
+ result.startProcessing();
+ // Call the appropriate event
+ try {
+ state = result.event(status);
+ } catch (java.net.SocketException e) {
+ // SocketExceptions are normal
+ CoyoteLogger.AJP_LOGGER.socketException(e);
+ } catch (java.io.IOException e) {
+ // IOExceptions are normal
+ CoyoteLogger.AJP_LOGGER.socketException(e);
+ }
+ // Future developers: if you discover any other
+ // rare-but-nonfatal exceptions, catch them here, and log as
+ // above.
+ catch (Throwable e) {
+ // any other exception or error is odd. Here we log it
+ // with "ERROR" level, so it will show up even on
+ // less-than-verbose logs.
+ CoyoteLogger.AJP_LOGGER.socketError(e);
+ } finally {
+ if (state != SocketState.LONG) {
+ connections.remove(socket);
+ recycledProcessors.offer(result);
+ } else {
+ if (proto.endpoint.isRunning()) {
+ proto.endpoint.getEventPoller().add(socket,
result.getTimeout(),
+ result.getResumeNotification(), false);
+ }
+ }
+ result.endProcessing();
+ }
+ }
+ return state;
+ }
+
+ public SocketState process(Socket socket) {
+ AjpProcessor processor = recycledProcessors.poll();
+ try {
+
+ if (processor == null) {
+ processor = createProcessor();
+ }
+
+ SocketState state = processor.process(socket);
+ if (state == SocketState.LONG) {
+ // Associate the connection with the processor. The next request
+ // processed by this thread will use either a new or a recycled
+ // processor.
+ connections.put(socket, processor);
+ proto.endpoint.getEventPoller().add(socket, processor.getTimeout(),
+ processor.getResumeNotification(), false);
+ } else {
+ recycledProcessors.offer(processor);
+ }
+ return state;
+
+ } catch(java.net.SocketException e) {
+ // SocketExceptions are normal
+ CoyoteLogger.AJP_LOGGER.socketException(e);
+ } catch (java.io.IOException e) {
+ // IOExceptions are normal
+ CoyoteLogger.AJP_LOGGER.socketException(e);
+ }
+ // Future developers: if you discover any other
+ // rare-but-nonfatal exceptions, catch them here, and log as
+ // above.
+ catch (Throwable e) {
+ // any other exception or error is odd. Here we log it
+ // with "ERROR" level, so it will show up even on
+ // less-than-verbose logs.
+ CoyoteLogger.AJP_LOGGER.socketError(e);
+ }
+ recycledProcessors.offer(processor);
+ return SocketState.CLOSED;
+ }
+
+ protected AjpProcessor createProcessor() {
+ AjpProcessor processor = new AjpProcessor(proto.packetSize, proto.endpoint);
+ processor.setAdapter(proto.adapter);
+ processor.setTomcatAuthentication(proto.tomcatAuthentication);
+ processor.setRequiredSecret(proto.requiredSecret);
+ processor.setKeepAliveTimeout(proto.keepAliveTimeout);
+ register(processor);
+ return processor;
+ }
+
+ protected void register(AjpProcessor processor) {
+ RequestInfo rp = processor.getRequest().getRequestProcessor();
+ rp.setGlobalProcessor(global);
+ if (org.apache.tomcat.util.Constants.ENABLE_MODELER &&
proto.getDomain() != null) {
+ synchronized (this) {
+ try {
+ long count = registerCount.incrementAndGet();
+ ObjectName rpName = new ObjectName
+ (proto.getDomain() +
":type=RequestProcessor,worker="
+ + proto.getJmxName() + ",name=AjpRequest" +
count);
+ Registry.getRegistry(null, null).registerComponent(rp, rpName,
null);
+ rp.setRpName(rpName);
+ } catch (Exception e) {
+ CoyoteLogger.AJP_LOGGER.errorRegisteringRequest(e);
+ }
+ }
+ }
+ }
+
+ protected void unregister(AjpProcessor processor) {
+ RequestInfo rp = processor.getRequest().getRequestProcessor();
+ rp.setGlobalProcessor(null);
+ if (org.apache.tomcat.util.Constants.ENABLE_MODELER &&
proto.getDomain() != null) {
+ synchronized (this) {
+ try {
+ ObjectName rpName = rp.getRpName();
+ Registry.getRegistry(null, null).unregisterComponent(rpName);
+ rp.setRpName(null);
+ } catch (Exception e) {
+ CoyoteLogger.AJP_LOGGER.errorUnregisteringRequest(e);
+ }
+ }
+ }
+ }
+
+ }
+
+
+ // -------------------- Various implementation classes --------------------
+
+
+ protected String domain;
+ protected ObjectName oname;
+ protected MBeanServer mserver;
+
+ public ObjectName getObjectName() {
+ return oname;
+ }
+
+ public String getDomain() {
+ return domain;
+ }
+
+ public ObjectName preRegister(MBeanServer server,
+ ObjectName name) throws Exception {
+ oname=name;
+ mserver=server;
+ domain=name.getDomain();
+ return name;
+ }
+
+ public void postRegister(Boolean registrationDone) {
+ }
+
+ public void preDeregister() throws Exception {
+ }
+
+ public void postDeregister() {
+ }
+
+
+}
Modified: branches/7.2.x/src/main/java/org/apache/coyote/ajp/Constants.java
===================================================================
--- branches/7.2.x/src/main/java/org/apache/coyote/ajp/Constants.java 2012-09-25 12:49:25
UTC (rev 2086)
+++ branches/7.2.x/src/main/java/org/apache/coyote/ajp/Constants.java 2012-09-26 13:54:12
UTC (rev 2087)
@@ -44,6 +44,8 @@
public static final int DEFAULT_CONNECTION_UPLOAD_TIMEOUT = 300000;
public static final int DEFAULT_SERVER_SOCKET_TIMEOUT = 0;
public static final boolean DEFAULT_TCP_NO_DELAY = true;
+ public static final boolean DEFAULT_TOMCAT_AUTHENTICATION =
+
Boolean.valueOf(System.getProperty("org.apache.coyote.ajp.DEFAULT_TOMCAT_AUTHENTICATION",
"true")).booleanValue();
// Prefix codes for message types from server to container
public static final byte JK_AJP13_FORWARD_REQUEST = 2;
Modified: branches/7.2.x/src/main/java/org/apache/coyote/http11/Http11NioProtocol.java
===================================================================
---
branches/7.2.x/src/main/java/org/apache/coyote/http11/Http11NioProtocol.java 2012-09-25
12:49:25 UTC (rev 2086)
+++
branches/7.2.x/src/main/java/org/apache/coyote/http11/Http11NioProtocol.java 2012-09-26
13:54:12 UTC (rev 2087)
@@ -41,7 +41,6 @@
import org.apache.tomcat.util.modeler.Registry;
import org.apache.tomcat.util.net.NioChannel;
import org.apache.tomcat.util.net.NioEndpoint;
-import org.apache.tomcat.util.net.SSLImplementation;
import org.apache.tomcat.util.net.SocketStatus;
import org.apache.tomcat.util.net.jsse.NioJSSEImplementation;
import org.apache.tomcat.util.net.jsse.NioJSSESocketChannelFactory;
@@ -92,9 +91,9 @@
// Verify the validity of the configured socket factory
try {
if (isSSLEnabled()) {
- sslImplementation = SSLImplementation.getInstance(NioJSSEImplementation.class
- .getName());
- socketFactory = sslImplementation.getServerSocketChannelFactory();
+ sslImplementation = new NioJSSEImplementation();
+ //Possible pluggability ?
SSLImplementation.getInstance(NioJSSEImplementation.class.getName());
+ socketFactory = ((NioJSSEImplementation)
sslImplementation).getServerSocketChannelFactory();
endpoint.setServerSocketChannelFactory(socketFactory);
}
} catch (Exception ex) {
@@ -896,7 +895,7 @@
}
if (proto.secure && (proto.sslImplementation != null)) {
- processor.setSSLSupport(proto.sslImplementation.getSSLSupport(channel));
+ processor.setSSLSupport(((NioJSSEImplementation)
proto.sslImplementation).getSSLSupport(channel));
} else {
processor.setSSLSupport(null);
}
Added: branches/7.2.x/src/main/java/org/apache/coyote/http11/Http11Processor.java
===================================================================
--- branches/7.2.x/src/main/java/org/apache/coyote/http11/Http11Processor.java
(rev 0)
+++ branches/7.2.x/src/main/java/org/apache/coyote/http11/Http11Processor.java 2012-09-26
13:54:12 UTC (rev 2087)
@@ -0,0 +1,1721 @@
+/*
+ * JBoss, Home of Professional Open Source
+ * Copyright 2009, JBoss Inc., and individual contributors as indicated
+ * by the @authors tag. See the copyright.txt in the distribution for a
+ * full listing of individual contributors.
+ *
+ * This is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as
+ * published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this software; if not, write to the Free
+ * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+ * 02110-1301 USA, or see the FSF site:
http://www.fsf.org.
+ */
+
+package org.apache.coyote.http11;
+
+import java.io.IOException;
+import java.io.InterruptedIOException;
+import java.net.InetAddress;
+import java.net.Socket;
+import java.util.Locale;
+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;
+import org.apache.coyote.http11.filters.BufferedInputFilter;
+import org.apache.coyote.http11.filters.ChunkedInputFilter;
+import org.apache.coyote.http11.filters.ChunkedOutputFilter;
+import org.apache.coyote.http11.filters.GzipOutputFilter;
+import org.apache.coyote.http11.filters.IdentityInputFilter;
+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;
+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.net.SocketStatus;
+import org.apache.tomcat.util.net.JIoEndpoint.Handler.SocketState;
+import org.jboss.web.CoyoteLogger;
+
+
+/**
+ * Processes HTTP requests.
+ *
+ * @author Remy Maucherat
+ */
+public class Http11Processor implements ActionHook {
+
+
+ protected static final boolean CHUNK_ON_CLOSE =
+
Boolean.valueOf(System.getProperty("org.apache.coyote.http11.Http11Processor.CHUNK_ON_CLOSE",
"false")).booleanValue();
+
+
+ // ------------------------------------------------------------ Constructor
+
+
+ public Http11Processor(int headerBufferSize, JIoEndpoint endpoint) {
+
+ this.endpoint = endpoint;
+
+ request = new Request();
+ inputBuffer = new InternalInputBuffer(request, headerBufferSize);
+ request.setInputBuffer(inputBuffer);
+
+ response = new Response();
+ response.setHook(this);
+ outputBuffer = new InternalOutputBuffer(response, headerBufferSize);
+ response.setOutputBuffer(outputBuffer);
+ request.setResponse(response);
+
+ initializeFilters();
+
+ // Cause loading of HexUtils
+ int foo = HexUtils.DEC[0];
+
+ // Cause loading of FastHttpDateFormat
+ FastHttpDateFormat.getCurrentDate();
+
+ }
+
+
+ // ----------------------------------------------------- Instance Variables
+
+
+ /**
+ * Associated adapter.
+ */
+ protected Adapter adapter = null;
+
+
+ /**
+ * Request object.
+ */
+ protected Request request = null;
+
+
+ /**
+ * Response object.
+ */
+ protected Response response = null;
+
+
+ /**
+ * Input.
+ */
+ protected InternalInputBuffer inputBuffer = null;
+
+
+ /**
+ * Output.
+ */
+ protected InternalOutputBuffer outputBuffer = null;
+
+
+ /**
+ * 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;
+
+
+ /**
+ * 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;
+
+
+ /**
+ * Event used.
+ */
+ protected boolean event = false;
+
+
+ /**
+ * True if a resume has been requested.
+ */
+ protected boolean resumeNotification = false;
+
+
+ /**
+ * Event processing.
+ */
+ protected boolean eventProcessing = true;
+ public void startProcessing() { eventProcessing = true; }
+ public void endProcessing() { eventProcessing = false; }
+
+
+ // ------------------------------------------------------------- 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) {
+ CoyoteLogger.HTTP_LOGGER.errorParsingRegexp(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);
+ }
+
+
+ /**
+ * Timeout.
+ */
+ protected int timeout = -1;
+ public void setTimeout(int timeout) { this.timeout = timeout; }
+ public int getTimeout() { return timeout; }
+
+
+ // --------------------------------------------------------- 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 {
+ CoyoteLogger.HTTP_LOGGER.unknownFilter(className);
+ }
+ } catch (Exception e) {
+ CoyoteLogger.HTTP_LOGGER.errorInitializingFilter(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) {
+ CoyoteLogger.HTTP_LOGGER.errorParsingRegexp(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 SSL information for this HTTP connection.
+ */
+ public void setSSLSupport(SSLSupport sslSupport) {
+ this.sslSupport = sslSupport;
+ }
+
+
+ /**
+ * 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;
+ }
+
+ public boolean getResumeNotification() {
+ return resumeNotification;
+ }
+
+
+ /**
+ * Set the socket buffer flag.
+ */
+ public void setSocketBuffer(int socketBuffer) {
+ this.socketBuffer = socketBuffer;
+ outputBuffer.setSocketBuffer(socketBuffer);
+ }
+
+ /**
+ * Get the socket buffer flag.
+ */
+ public int getSocketBuffer() {
+ return socketBuffer;
+ }
+
+
+ /**
+ * 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;
+ }
+
+ public SocketState event(SocketStatus status)
+ throws IOException {
+
+ RequestInfo rp = request.getRequestProcessor();
+ try {
+ if (status == SocketStatus.OPEN_CALLBACK) {
+ // The resume notification is now done
+ resumeNotification = false;
+ } else if (status == SocketStatus.ERROR) {
+ // Set error flag right away
+ error = true;
+ }
+ rp.setStage(org.apache.coyote.Constants.STAGE_SERVICE);
+ error = !adapter.event(request, response, status);
+ } catch (InterruptedIOException e) {
+ error = true;
+ } catch (Throwable t) {
+ CoyoteLogger.HTTP_LOGGER.errorProcessingRequest(t);
+ // 500 - Internal Server Error
+ response.setStatus(500);
+ error = true;
+ }
+
+ rp.setStage(org.apache.coyote.Constants.STAGE_ENDED);
+
+ if (error) {
+ inputBuffer.nextRequest();
+ outputBuffer.nextRequest();
+ recycle();
+ return SocketState.CLOSED;
+ } else if (!event) {
+ endRequest();
+ boolean pipelined = inputBuffer.nextRequest();
+ outputBuffer.nextRequest();
+ recycle();
+ return (pipelined || !keepAlive) ? SocketState.CLOSED : SocketState.OPEN;
+ } else {
+ return SocketState.LONG;
+ }
+ }
+
+ /**
+ * Process pipelined HTTP requests on the specified socket.
+ *
+ * @param socket Socket from which the HTTP requests will be read
+ * and the HTTP responses will be written.
+ *
+ * @throws IOException error during an I/O operation
+ */
+ public SocketState process(Socket socket)
+ throws IOException {
+ RequestInfo rp = request.getRequestProcessor();
+ rp.setStage(org.apache.coyote.Constants.STAGE_PARSE);
+
+ // Set the remote address
+ remoteAddr = null;
+ remoteHost = null;
+ localAddr = null;
+ localName = null;
+ remotePort = -1;
+ localPort = -1;
+
+ // Setting up the I/O
+ this.socket = socket;
+ inputBuffer.setInputStream(socket.getInputStream());
+ outputBuffer.setOutputStream(socket.getOutputStream());
+
+ // Error flag
+ error = false;
+ keepAlive = true;
+
+ int keepAliveLeft = maxKeepAliveRequests;
+ int soTimeout = socket.getSoTimeout();
+
+ int threadRatio = (endpoint.getCurrentThreadsBusy() * 100)
+ / endpoint.getMaxThreads();
+ if (threadRatio > 75) {
+ keepAliveLeft = 1;
+ }
+
+ boolean keptAlive = false;
+
+ while (!error && keepAlive && !event) {
+
+ // Parsing the request header
+ try {
+ if (!disableUploadTimeout && keptAlive) {
+ if (keepAliveTimeout > 0) {
+ socket.setSoTimeout(keepAliveTimeout);
+ }
+ else if (soTimeout > 0) {
+ socket.setSoTimeout(soTimeout);
+ }
+ }
+ inputBuffer.parseRequestLine();
+ request.setStartTime(System.currentTimeMillis());
+ keptAlive = true;
+ if (!disableUploadTimeout) {
+ socket.setSoTimeout(timeout);
+ }
+ inputBuffer.parseHeaders();
+ } catch (IOException e) {
+ error = true;
+ break;
+ } catch (Throwable t) {
+ CoyoteLogger.HTTP_LOGGER.errorParsingHeader(t);
+ // 400 - Bad Request
+ response.setStatus(400);
+ error = true;
+ }
+
+ // Setting up filters, and parse some request headers
+ rp.setStage(org.apache.coyote.Constants.STAGE_PREPARE);
+ try {
+ prepareRequest();
+ } catch (Throwable t) {
+ CoyoteLogger.HTTP_LOGGER.errorPreparingRequest(t);
+ // 400 - Internal Server Error
+ response.setStatus(400);
+ error = true;
+ }
+
+ if (maxKeepAliveRequests > 0 && --keepAliveLeft == 0)
+ keepAlive = false;
+
+ // Process the request in the adapter
+ if (!error) {
+ try {
+ rp.setStage(org.apache.coyote.Constants.STAGE_SERVICE);
+ adapter.service(request, response);
+ // Handle when the response was committed before a serious
+ // error occurred. Throwing a ServletException should both
+ // set the status to 500 and set the errorException.
+ // If we fail here, then the response is likely already
+ // committed, so we can't try and set headers.
+ if(keepAlive && !error) { // Avoid checking twice.
+ error = response.getErrorException() != null ||
+ statusDropsConnection(response.getStatus());
+ }
+
+ } catch (InterruptedIOException e) {
+ error = true;
+ } catch (Throwable t) {
+ CoyoteLogger.HTTP_LOGGER.errorProcessingRequest(t);
+ // 500 - Internal Server Error
+ response.setStatus(500);
+ error = true;
+ }
+ }
+
+ // Finish the handling of the request
+ if (error) {
+ // If there is an unspecified error, the connection will be closed
+ inputBuffer.setSwallowInput(false);
+ }
+ if (!event) {
+ endRequest();
+ }
+
+ // If there was an error, make sure the request is counted as
+ // and error, and update the statistics counter
+ if (error) {
+ response.setStatus(500);
+ }
+ request.updateCounters();
+
+ if (!event) {
+ // Next request
+ inputBuffer.nextRequest();
+ outputBuffer.nextRequest();
+ }
+
+ rp.setStage(org.apache.coyote.Constants.STAGE_KEEPALIVE);
+
+ }
+
+ rp.setStage(org.apache.coyote.Constants.STAGE_ENDED);
+
+ if (event) {
+ if (error) {
+ inputBuffer.nextRequest();
+ outputBuffer.nextRequest();
+ recycle();
+ return SocketState.CLOSED;
+ } else {
+ eventProcessing = false;
+ return SocketState.LONG;
+ }
+ } else {
+ recycle();
+ return SocketState.CLOSED;
+ }
+
+ }
+
+
+ public void endRequest() {
+
+ // Finish the handling of the request
+ try {
+ inputBuffer.endRequest();
+ } catch (IOException e) {
+ error = true;
+ } catch (Throwable t) {
+ CoyoteLogger.HTTP_LOGGER.errorFinishingRequest(t);
+ // 500 - Internal Server Error
+ response.setStatus(500);
+ error = true;
+ }
+ try {
+ outputBuffer.endRequest();
+ } catch (IOException e) {
+ error = true;
+ } catch (Throwable t) {
+ CoyoteLogger.HTTP_LOGGER.errorFinishingResponse(t);
+ error = true;
+ }
+
+ }
+
+
+ public void recycle() {
+ inputBuffer.recycle();
+ outputBuffer.recycle();
+ this.socket = null;
+ sslSupport = null;
+ timeout = -1;
+ resumeNotification = false;
+ eventProcessing = true;
+ }
+
+
+ // ----------------------------------------------------- ActionHook Methods
+
+
+ /**
+ * Send an action to the connector.
+ *
+ * @param actionCode Type of the action
+ * @param param Action parameter
+ */
+ public void action(ActionCode actionCode, Object param) {
+
+ if (actionCode == ActionCode.ACTION_COMMIT) {
+ // Commit current response
+
+ if (response.isCommitted())
+ return;
+
+ // Validate and write response headers
+ prepareResponse();
+ try {
+ outputBuffer.commit();
+ } catch (IOException e) {
+ // Set error flag
+ error = true;
+ }
+
+ } else if (actionCode == ActionCode.ACTION_ACK) {
+
+ // Acknowlege request
+
+ // Send a 100 status back if it makes sense (response not committed
+ // yet, and client specified an expectation for 100-continue)
+
+ if ((response.isCommitted()) || !expectation)
+ return;
+
+ inputBuffer.setSwallowInput(true);
+ try {
+ outputBuffer.sendAck();
+ } catch (IOException e) {
+ // Set error flag
+ error = true;
+ }
+
+ } else if (actionCode == ActionCode.ACTION_CLIENT_FLUSH) {
+
+ try {
+ outputBuffer.flush();
+ } catch (IOException e) {
+ // Set error flag
+ error = true;
+ response.setErrorException(e);
+ }
+
+ } else if (actionCode == ActionCode.ACTION_CLOSE) {
+ // Close
+
+ // End the processing of the current request, and stop any further
+ // transactions with the client
+
+ try {
+ outputBuffer.endRequest();
+ } catch (IOException e) {
+ // Set error flag
+ error = true;
+ }
+
+ } else if (actionCode == ActionCode.ACTION_CUSTOM) {
+
+ // Do nothing
+
+ } else if (actionCode == ActionCode.ACTION_REQ_SSL_ATTRIBUTE ) {
+
+ try {
+ if (sslSupport != null) {
+ Object sslO = sslSupport.getCipherSuite();
+ if (sslO != null)
+ request.setAttribute
+ (org.apache.tomcat.util.net.Constants.CIPHER_SUITE_KEY,
sslO);
+ sslO = sslSupport.getPeerCertificateChain(false);
+ if (sslO != null)
+ request.setAttribute
+ (org.apache.tomcat.util.net.Constants.CERTIFICATE_KEY,
sslO);
+ sslO = sslSupport.getKeySize();
+ if (sslO != null)
+ request.setAttribute
+ (org.apache.tomcat.util.net.Constants.KEY_SIZE_KEY, sslO);
+ sslO = sslSupport.getSessionId();
+ if (sslO != null)
+ request.setAttribute
+ (org.apache.tomcat.util.net.Constants.SESSION_ID_KEY, sslO);
+ }
+ } catch (Exception e) {
+ CoyoteLogger.HTTP_LOGGER.errorGettingSslAttributes(e);
+ }
+
+ } else if (actionCode == ActionCode.ACTION_REQ_HOST_ADDR_ATTRIBUTE) {
+
+ if ((remoteAddr == null) && (socket != null)) {
+ InetAddress inetAddr = socket.getInetAddress();
+ if (inetAddr != null) {
+ remoteAddr = inetAddr.getHostAddress();
+ }
+ }
+ request.remoteAddr().setString(remoteAddr);
+
+ } else if (actionCode == ActionCode.ACTION_REQ_LOCAL_NAME_ATTRIBUTE) {
+
+ if ((localName == null) && (socket != null)) {
+ InetAddress inetAddr = socket.getLocalAddress();
+ if (inetAddr != null) {
+ localName = inetAddr.getHostName();
+ }
+ }
+ request.localName().setString(localName);
+
+ } else if (actionCode == ActionCode.ACTION_REQ_HOST_ATTRIBUTE) {
+
+ if ((remoteHost == null) && (socket != null)) {
+ InetAddress inetAddr = socket.getInetAddress();
+ if (inetAddr != null) {
+ remoteHost = inetAddr.getHostName();
+ }
+ if(remoteHost == null) {
+ if(remoteAddr != null) {
+ remoteHost = remoteAddr;
+ } else { // all we can do is punt
+ request.remoteHost().recycle();
+ }
+ }
+ }
+ request.remoteHost().setString(remoteHost);
+
+ } else if (actionCode == ActionCode.ACTION_REQ_LOCAL_ADDR_ATTRIBUTE) {
+
+ if (localAddr == null)
+ localAddr = socket.getLocalAddress().getHostAddress();
+
+ request.localAddr().setString(localAddr);
+
+ } else if (actionCode == ActionCode.ACTION_REQ_REMOTEPORT_ATTRIBUTE) {
+
+ if ((remotePort == -1 ) && (socket !=null)) {
+ remotePort = socket.getPort();
+ }
+ request.setRemotePort(remotePort);
+
+ } else if (actionCode == ActionCode.ACTION_REQ_LOCALPORT_ATTRIBUTE) {
+
+ if ((localPort == -1 ) && (socket !=null)) {
+ localPort = socket.getLocalPort();
+ }
+ request.setLocalPort(localPort);
+
+ } else if (actionCode == ActionCode.ACTION_REQ_SSL_CERTIFICATE) {
+ if( sslSupport != null) {
+ // Consume and buffer the request body, so that it does not
+ // interfere with the client's handshake messages
+ if (maxSavePostSize != 0) {
+ BufferedInputFilter buffredInputFilter = new BufferedInputFilter();
+ buffredInputFilter.setLimit(maxSavePostSize);
+ inputBuffer.addActiveFilter(buffredInputFilter);
+ }
+ try {
+ Object sslO = sslSupport.getPeerCertificateChain(true);
+ if( sslO != null) {
+ request.setAttribute
+ (org.apache.tomcat.util.net.Constants.CERTIFICATE_KEY,
sslO);
+ }
+ } catch (Exception e) {
+ CoyoteLogger.HTTP_LOGGER.errorGettingSslAttributes(e);
+ }
+ }
+ } else if (actionCode == ActionCode.ACTION_REQ_SET_BODY_REPLAY) {
+ ByteChunk body = (ByteChunk) param;
+
+ InputFilter savedBody = new SavedRequestInputFilter(body);
+ savedBody.setRequest(request);
+
+ InternalInputBuffer internalBuffer = (InternalInputBuffer)
+ request.getInputBuffer();
+ internalBuffer.addActiveFilter(savedBody);
+ } else if (actionCode == ActionCode.ACTION_EVENT_BEGIN) {
+ event = true;
+ } else if (actionCode == ActionCode.ACTION_EVENT_END) {
+ event = false;
+ } else if (actionCode == ActionCode.ACTION_EVENT_SUSPEND) {
+ // No action needed
+ } else if (actionCode == ActionCode.ACTION_EVENT_RESUME) {
+ // An event is being processed already: adding for resume will be done
+ // when the socket gets back to the poller
+ if (!eventProcessing && !resumeNotification) {
+ endpoint.getEventPoller().add(socket, timeout, true, true);
+ }
+ resumeNotification = true;
+ } else if (actionCode == ActionCode.ACTION_EVENT_TIMEOUT) {
+ timeout = ((Integer) param).intValue();
+ }
+
+ }
+
+
+ // ------------------------------------------------------ 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
+
+
+ /**
+ * After reading the request headers, we have to setup the request filters.
+ */
+ protected void prepareRequest() {
+
+ http11 = true;
+ http09 = false;
+ contentDelimitation = false;
+ expectation = false;
+ if (sslSupport != null) {
+ request.scheme().setString("https");
+ }
+ MessageBytes protocolMB = request.protocol();
+ if (protocolMB.equals(Constants.HTTP_11)) {
+ http11 = true;
+ protocolMB.setString(Constants.HTTP_11);
+ } else if (protocolMB.equals(Constants.HTTP_10)) {
+ http11 = false;
+ keepAlive = false;
+ protocolMB.setString(Constants.HTTP_10);
+ } else if (protocolMB.equals("")) {
+ // HTTP/0.9
+ http09 = true;
+ http11 = false;
+ keepAlive = false;
+ } else {
+ // Unsupported protocol
+ http11 = false;
+ error = true;
+ // Send 505; Unsupported HTTP version
+ response.setStatus(505);
+ }
+
+ MessageBytes methodMB = request.method();
+ if (methodMB.equals(Constants.GET)) {
+ methodMB.setString(Constants.GET);
+ } else if (methodMB.equals(Constants.POST)) {
+ methodMB.setString(Constants.POST);
+ }
+
+ MimeHeaders headers = request.getMimeHeaders();
+
+ // Check connection header
+ MessageBytes connectionValueMB = headers.getValue("connection");
+ if (connectionValueMB != null) {
+ ByteChunk connectionValueBC = connectionValueMB.getByteChunk();
+ if (findBytes(connectionValueBC, Constants.CLOSE_BYTES) != -1) {
+ keepAlive = false;
+ } else if (findBytes(connectionValueBC,
+ Constants.KEEPALIVE_BYTES) != -1) {
+ keepAlive = true;
+ }
+ }
+
+ MessageBytes expectMB = null;
+ if (http11)
+ expectMB = headers.getValue("expect");
+ if ((expectMB != null)
+ && (expectMB.indexOfIgnoreCase("100-continue", 0) != -1))
{
+ inputBuffer.setSwallowInput(false);
+ expectation = true;
+ }
+
+ // Check user-agent header
+ if ((restrictedUserAgents != null) && ((http11) || (keepAlive))) {
+ MessageBytes userAgentValueMB = headers.getValue("user-agent");
+ // Check in the restricted list, and adjust the http11
+ // and keepAlive flags accordingly
+ if(userAgentValueMB != null) {
+ String userAgentValue = userAgentValueMB.toString();
+ for (int i = 0; i < restrictedUserAgents.length; i++) {
+ if (restrictedUserAgents[i].matcher(userAgentValue).matches()) {
+ http11 = false;
+ keepAlive = false;
+ break;
+ }
+ }
+ }
+ }
+
+ // Check for a full URI (including protocol://host:port/)
+ ByteChunk uriBC = request.requestURI().getByteChunk();
+ if (uriBC.startsWithIgnoreCase("http", 0)) {
+
+ int pos = uriBC.indexOf("://", 0, 3, 4);
+ int uriBCStart = uriBC.getStart();
+ int slashPos = -1;
+ if (pos != -1) {
+ byte[] uriB = uriBC.getBytes();
+ slashPos = uriBC.indexOf('/', pos + 3);
+ if (slashPos == -1) {
+ slashPos = uriBC.getLength();
+ // Set URI as "/"
+ request.requestURI().setBytes
+ (uriB, uriBCStart + pos + 1, 1);
+ } else {
+ request.requestURI().setBytes
+ (uriB, uriBCStart + slashPos,
+ uriBC.getLength() - slashPos);
+ }
+ MessageBytes hostMB = headers.setValue("host");
+ hostMB.setBytes(uriB, uriBCStart + pos + 3,
+ slashPos - pos - 3);
+ }
+
+ }
+
+ // Input filter setup
+ InputFilter[] inputFilters = inputBuffer.getFilters();
+
+ // Parse transfer-encoding header
+ MessageBytes transferEncodingValueMB = null;
+ if (http11)
+ transferEncodingValueMB = headers.getValue("transfer-encoding");
+ if (transferEncodingValueMB != null) {
+ String transferEncodingValue = transferEncodingValueMB.toString();
+ // Parse the comma separated list. "identity" codings are ignored
+ int startPos = 0;
+ int commaPos = transferEncodingValue.indexOf(',');
+ String encodingName = null;
+ while (commaPos != -1) {
+ encodingName = transferEncodingValue.substring
+ (startPos, commaPos).toLowerCase(Locale.ENGLISH).trim();
+ if (!addInputFilter(inputFilters, encodingName)) {
+ // Unsupported transfer encoding
+ error = true;
+ // 501 - Unimplemented
+ response.setStatus(501);
+ }
+ startPos = commaPos + 1;
+ commaPos = transferEncodingValue.indexOf(',', startPos);
+ }
+ encodingName = transferEncodingValue.substring(startPos)
+ .toLowerCase(Locale.ENGLISH).trim();
+ if (!addInputFilter(inputFilters, encodingName)) {
+ // Unsupported transfer encoding
+ error = true;
+ // 501 - Unimplemented
+ response.setStatus(501);
+ }
+ }
+
+ // Parse content-length header
+ long contentLength = request.getContentLengthLong();
+ if (contentLength >= 0 && !contentDelimitation) {
+ inputBuffer.addActiveFilter
+ (inputFilters[Constants.IDENTITY_FILTER]);
+ contentDelimitation = true;
+ }
+
+ MessageBytes valueMB = headers.getValue("host");
+
+ // Check host header
+ if (http11 && (valueMB == null)) {
+ error = true;
+ // 400 - Bad request
+ response.setStatus(400);
+ }
+
+ parseHost(valueMB);
+
+ if (!contentDelimitation) {
+ // If there's no content length
+ // (broken HTTP/1.0 or HTTP/1.1), assume
+ // the client is not broken and didn't send a body
+ inputBuffer.addActiveFilter
+ (inputFilters[Constants.VOID_FILTER]);
+ contentDelimitation = true;
+ }
+
+ }
+
+
+ /**
+ * 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] & 0xff];
+ 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
+ * well as setup the response filters.
+ */
+ protected void prepareResponse() {
+
+ boolean entityBody = true;
+ contentDelimitation = false;
+
+ OutputFilter[] outputFilters = outputBuffer.getFilters();
+
+ if (http09 == true) {
+ // HTTP/0.9
+ outputBuffer.addActiveFilter
+ (outputFilters[Constants.IDENTITY_FILTER]);
+ return;
+ }
+
+ int statusCode = response.getStatus();
+ if ((statusCode == 204) || (statusCode == 205)
+ || (statusCode == 304)) {
+ // No entity body
+ outputBuffer.addActiveFilter
+ (outputFilters[Constants.VOID_FILTER]);
+ entityBody = false;
+ contentDelimitation = true;
+ }
+
+ MessageBytes methodMB = request.method();
+ if (methodMB.equals("HEAD")) {
+ // No entity body
+ outputBuffer.addActiveFilter
+ (outputFilters[Constants.VOID_FILTER]);
+ contentDelimitation = true;
+ }
+
+ // Check for compression
+ boolean useCompression = false;
+ if (entityBody && (compressionLevel > 0)) {
+ useCompression = isCompressable();
+
+ // Change content-length to -1 to force chunking
+ if (useCompression) {
+ response.setContentLength(-1);
+ }
+ }
+
+ MimeHeaders headers = response.getMimeHeaders();
+ if (!entityBody) {
+ response.setContentLength(-1);
+ } else {
+ String contentType = response.getContentType();
+ if (contentType != null) {
+ headers.setValue("Content-Type").setString(contentType);
+ }
+ String contentLanguage = response.getContentLanguage();
+ if (contentLanguage != null) {
+ headers.setValue("Content-Language")
+ .setString(contentLanguage);
+ }
+ }
+
+ long contentLength = response.getContentLengthLong();
+ if (contentLength != -1) {
+ headers.setValue("Content-Length").setLong(contentLength);
+ outputBuffer.addActiveFilter
+ (outputFilters[Constants.IDENTITY_FILTER]);
+ contentDelimitation = true;
+ } else {
+ if (entityBody && http11 && (keepAlive || CHUNK_ON_CLOSE)) {
+ outputBuffer.addActiveFilter
+ (outputFilters[Constants.CHUNKED_FILTER]);
+ contentDelimitation = true;
+
headers.addValue(Constants.TRANSFERENCODING).setString(Constants.CHUNKED);
+ } else {
+ outputBuffer.addActiveFilter
+ (outputFilters[Constants.IDENTITY_FILTER]);
+ }
+ }
+
+ if (useCompression) {
+ outputBuffer.addActiveFilter(outputFilters[Constants.GZIP_FILTER]);
+ headers.setValue("Content-Encoding").setString("gzip");
+ // Make Proxies happy via Vary (from mod_deflate)
+ headers.addValue("Vary").setString("Accept-Encoding");
+ }
+
+ // Add date header
+
headers.setValue("Date").setString(FastHttpDateFormat.getCurrentDate());
+
+ // FIXME: Add transfer encoding header
+
+ if ((entityBody) && (!contentDelimitation)) {
+ // Mark as close the connection after the request, and add the
+ // connection: close header
+ keepAlive = false;
+ }
+
+ // If we know that the request is bad this early, add the
+ // Connection: close header.
+ keepAlive = keepAlive && !statusDropsConnection(statusCode);
+ if (!keepAlive) {
+ headers.addValue(Constants.CONNECTION).setString(Constants.CLOSE);
+ } else if (!http11 && !error) {
+ headers.addValue(Constants.CONNECTION).setString(Constants.KEEPALIVE);
+ }
+
+ // Build the response header
+ outputBuffer.sendStatus();
+
+ // Add server header
+ if (server != null) {
+ headers.setValue("Server").setString(server);
+ } else {
+ outputBuffer.write(Constants.SERVER_BYTES);
+ }
+
+ int size = headers.size();
+ for (int i = 0; i < size; i++) {
+ outputBuffer.sendHeader(headers.getName(i), headers.getValue(i));
+ }
+ outputBuffer.endHeaders();
+
+ }
+
+
+ /**
+ * Initialize standard input and output filters.
+ */
+ protected void initializeFilters() {
+
+ // Create and add the identity filters.
+ inputBuffer.addFilter(new IdentityInputFilter());
+ outputBuffer.addFilter(new IdentityOutputFilter());
+
+ // Create and add the chunked filters.
+ inputBuffer.addFilter(new ChunkedInputFilter());
+ outputBuffer.addFilter(new ChunkedOutputFilter());
+
+ // Create and add the void filters.
+ inputBuffer.addFilter(new VoidInputFilter());
+ outputBuffer.addFilter(new VoidOutputFilter());
+
+ // Create and add the chunked filters.
+ //inputBuffer.addFilter(new GzipInputFilter());
+ outputBuffer.addFilter(new GzipOutputFilter());
+
+ }
+
+
+ /**
+ * Add an input filter to the current request.
+ *
+ * @return false if the encoding was not found (which would mean it is
+ * unsupported)
+ */
+ protected boolean addInputFilter(InputFilter[] inputFilters,
+ String encodingName) {
+ if (encodingName.equals("identity")) {
+ // Skip
+ } else if (encodingName.equals("chunked")) {
+ inputBuffer.addActiveFilter
+ (inputFilters[Constants.CHUNKED_FILTER]);
+ contentDelimitation = true;
+ } else {
+ for (int i = 2; i < inputFilters.length; i++) {
+ if (inputFilters[i].getEncodingName()
+ .toString().equals(encodingName)) {
+ inputBuffer.addActiveFilter(inputFilters[i]);
+ return true;
+ }
+ }
+ return false;
+ }
+ return true;
+ }
+
+
+ /**
+ * 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 */;
+ }
+
+}
Added: branches/7.2.x/src/main/java/org/apache/coyote/http11/Http11Protocol.java
===================================================================
--- branches/7.2.x/src/main/java/org/apache/coyote/http11/Http11Protocol.java
(rev 0)
+++ branches/7.2.x/src/main/java/org/apache/coyote/http11/Http11Protocol.java 2012-09-26
13:54:12 UTC (rev 2087)
@@ -0,0 +1,778 @@
+/*
+ * JBoss, Home of Professional Open Source
+ * Copyright 2009, JBoss Inc., and individual contributors as indicated
+ * by the @authors tag. See the copyright.txt in the distribution for a
+ * full listing of individual contributors.
+ *
+ * This is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as
+ * published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this software; if not, write to the Free
+ * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+ * 02110-1301 USA, or see the FSF site:
http://www.fsf.org.
+ */
+
+package org.apache.coyote.http11;
+
+import java.net.InetAddress;
+import java.net.Socket;
+import java.net.URLEncoder;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ConcurrentLinkedQueue;
+import java.util.concurrent.Executor;
+import java.util.concurrent.atomic.AtomicInteger;
+import java.util.concurrent.atomic.AtomicLong;
+
+import javax.management.MBeanRegistration;
+import javax.management.MBeanServer;
+import javax.management.ObjectName;
+import javax.net.ssl.SSLContext;
+
+import org.apache.coyote.Adapter;
+import org.apache.coyote.ProtocolHandler;
+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.SSLImplementation;
+import org.apache.tomcat.util.net.ServerSocketFactory;
+import org.apache.tomcat.util.net.SocketStatus;
+import org.apache.tomcat.util.net.JIoEndpoint.Handler;
+import org.apache.tomcat.util.net.jsse.JSSEImplementation;
+import org.jboss.web.CoyoteLogger;
+
+
+/**
+ * Abstract the protocol implementation, including threading, etc.
+ * Processor is single threaded and specific to stream-based protocols,
+ * will not fit Jk protocols like JNI.
+ *
+ * @author Remy Maucherat
+ * @author Costin Manolache
+ */
+public class Http11Protocol
+ implements ProtocolHandler, MBeanRegistration {
+
+
+ // ------------------------------------------------------------ Constructor
+
+
+ public Http11Protocol() {
+ setSoLinger(Constants.DEFAULT_CONNECTION_LINGER);
+ setSoTimeout(Constants.DEFAULT_CONNECTION_TIMEOUT);
+ //setServerSoTimeout(Constants.DEFAULT_SERVER_SOCKET_TIMEOUT);
+ setTcpNoDelay(Constants.DEFAULT_TCP_NO_DELAY);
+ }
+
+
+ // ----------------------------------------------------------------- Fields
+
+
+ protected Http11ConnectionHandler cHandler = new Http11ConnectionHandler(this);
+ protected JIoEndpoint endpoint = new JIoEndpoint();
+
+
+ // *
+ protected ObjectName tpOname = null;
+ // *
+ protected ObjectName rgOname = null;
+
+
+ protected ServerSocketFactory socketFactory = null;
+ protected JSSEImplementation sslImplementation = null;
+
+
+ // ----------------------------------------- ProtocolHandler Implementation
+ // *
+
+
+ protected HashMap<String, Object> attributes = new HashMap<String,
Object>();
+
+
+ /**
+ * Pass config info
+ */
+ public void setAttribute(String name, Object value) {
+ attributes.put(name, value);
+ }
+
+ public Object getAttribute(String key) {
+ return attributes.get(key);
+ }
+
+ public Iterator getAttributeNames() {
+ return attributes.keySet().iterator();
+ }
+
+ /**
+ * Set a property.
+ */
+ public void setProperty(String name, String value) {
+ setAttribute(name, value);
+ }
+
+ /**
+ * Get a property
+ */
+ public String getProperty(String name) {
+ return (String)getAttribute(name);
+ }
+
+ /**
+ * The adapter, used to call the connector.
+ */
+ protected Adapter adapter;
+ public void setAdapter(Adapter adapter) { this.adapter = adapter; }
+ public Adapter getAdapter() { return adapter; }
+
+
+ public boolean hasIoEvents() {
+ return false;
+ }
+
+ public RequestGroupInfo getRequestGroupInfo() {
+ return cHandler.global;
+ }
+
+ public void init() throws Exception {
+ endpoint.setName(getName());
+ endpoint.setHandler(cHandler);
+
+ // Verify the validity of the configured socket factory
+ try {
+ if (isSSLEnabled()) {
+ sslImplementation =
+ (JSSEImplementation)
SSLImplementation.getInstance(sslImplementationName);
+ socketFactory = sslImplementation.getServerSocketFactory();
+ endpoint.setServerSocketFactory(socketFactory);
+ } else if (socketFactoryName != null) {
+ socketFactory = (ServerSocketFactory)
Class.forName(socketFactoryName).newInstance();
+ endpoint.setServerSocketFactory(socketFactory);
+ }
+ } catch (Exception ex) {
+ CoyoteLogger.HTTP_LOGGER.errorInitializingSocketFactory(ex);
+ throw ex;
+ }
+
+ if (socketFactory!=null) {
+ Iterator<String> attE = attributes.keySet().iterator();
+ while( attE.hasNext() ) {
+ String key = attE.next();
+ Object v=attributes.get(key);
+ socketFactory.setAttribute(key, v);
+ }
+ }
+
+ try {
+ endpoint.init();
+ } catch (Exception ex) {
+ CoyoteLogger.HTTP_LOGGER.errorInitializingEndpoint(ex);
+ throw ex;
+ }
+ CoyoteLogger.HTTP_LOGGER.initHttpConnector(getName());
+
+ }
+
+ public void start() throws Exception {
+ if (org.apache.tomcat.util.Constants.ENABLE_MODELER) {
+ if (this.domain != null) {
+ try {
+ tpOname = new ObjectName
+ (domain + ":" + "type=ThreadPool,name=" +
getJmxName());
+ Registry.getRegistry(null, null)
+ .registerComponent(endpoint, tpOname, null );
+ } catch (Exception e) {
+ CoyoteLogger.HTTP_LOGGER.errorRegisteringPool(e);
+ }
+ rgOname=new ObjectName
+ (domain + ":type=GlobalRequestProcessor,name=" +
getJmxName());
+ Registry.getRegistry(null, null).registerComponent
+ ( cHandler.global, rgOname, null );
+ }
+ }
+ try {
+ endpoint.start();
+ } catch (Exception ex) {
+ CoyoteLogger.HTTP_LOGGER.errorStartingEndpoint(ex);
+ throw ex;
+ }
+ CoyoteLogger.HTTP_LOGGER.startHttpConnector(getName());
+ }
+
+ public void pause() throws Exception {
+ try {
+ endpoint.pause();
+ } catch (Exception ex) {
+ CoyoteLogger.HTTP_LOGGER.errorPausingEndpoint(ex);
+ throw ex;
+ }
+ // Wait for a while until all the processors are no longer processing requests
+ RequestInfo[] states = cHandler.global.getRequestProcessors();
+ int retry = 0;
+ boolean done = false;
+ while (!done && retry < org.apache.coyote.Constants.MAX_PAUSE_WAIT) {
+ retry++;
+ done = true;
+ for (int i = 0; i < states.length; i++) {
+ if (states[i].getStage() == org.apache.coyote.Constants.STAGE_SERVICE) {
+ try {
+ Thread.sleep(1000);
+ } catch (InterruptedException e) {
+ ;
+ }
+ done = false;
+ break;
+ }
+ }
+ }
+ CoyoteLogger.HTTP_LOGGER.pauseHttpConnector(getName());
+ }
+
+ public void resume() throws Exception {
+ try {
+ endpoint.resume();
+ } catch (Exception ex) {
+ CoyoteLogger.HTTP_LOGGER.errorResumingEndpoint(ex);
+ throw ex;
+ }
+ CoyoteLogger.HTTP_LOGGER.resumeHttpConnector(getName());
+ }
+
+ public void destroy() throws Exception {
+ CoyoteLogger.HTTP_LOGGER.stopHttpConnector(getName());
+ endpoint.destroy();
+ if (org.apache.tomcat.util.Constants.ENABLE_MODELER) {
+ if (tpOname!=null)
+ Registry.getRegistry(null, null).unregisterComponent(tpOname);
+ if (rgOname != null)
+ Registry.getRegistry(null, null).unregisterComponent(rgOname);
+ }
+ }
+
+ public String getJmxName() {
+ String encodedAddr = "";
+ if (getAddress() != null) {
+ encodedAddr = "" + getAddress();
+ encodedAddr = URLEncoder.encode(encodedAddr.replace('/',
'-').replace(':', '_').replace('%', '-')) +
"-";
+ }
+ return ("http-" + encodedAddr + endpoint.getPort());
+ }
+
+ public String getName() {
+ String encodedAddr = "";
+ if (getAddress() != null) {
+ encodedAddr = getAddress() + ":";
+ }
+ return ("http-" + encodedAddr + endpoint.getPort());
+ }
+
+ // ------------------------------------------------------------- Properties
+
+
+ /**
+ * Processor cache.
+ */
+ protected int processorCache = -1;
+ public int getProcessorCache() { return this.processorCache; }
+ public void setProcessorCache(int processorCache) { this.processorCache =
processorCache; }
+
+ protected int socketBuffer = 9000;
+ public int getSocketBuffer() { return socketBuffer; }
+ public void setSocketBuffer(int socketBuffer) { this.socketBuffer = socketBuffer; }
+
+ /**
+ * This field indicates if the protocol is secure from the perspective of
+ * the client (= https is used).
+ */
+ protected boolean secure;
+ public boolean getSecure() { return secure; }
+ public void setSecure(boolean b) { secure = b; }
+
+ protected boolean SSLEnabled = false;
+ public boolean isSSLEnabled() { return SSLEnabled;}
+ public void setSSLEnabled(boolean SSLEnabled) {this.SSLEnabled = SSLEnabled;}
+
+ /**
+ * Name of the socket factory.
+ */
+ protected String socketFactoryName = null;
+ public String getSocketFactory() { return socketFactoryName; }
+ public void setSocketFactory(String valueS) { socketFactoryName = valueS; }
+
+ /**
+ * Name of the SSL implementation.
+ */
+ protected String sslImplementationName=null;
+ public String getSSLImplementation() { return sslImplementationName; }
+ public void setSSLImplementation( String valueS) {
+ sslImplementationName = valueS;
+ setSecure(true);
+ }
+
+
+ // HTTP
+ /**
+ * Maximum number of requests which can be performed over a keepalive
+ * connection. The default is the same as for Apache HTTP Server.
+ */
+ protected int maxKeepAliveRequests = (org.apache.tomcat.util.Constants.LOW_MEMORY) ?
1 :
+
Integer.valueOf(System.getProperty("org.apache.coyote.http11.Http11Protocol.MAX_KEEP_ALIVE_REQUESTS",
"-1")).intValue();
+ public int getMaxKeepAliveRequests() { return maxKeepAliveRequests; }
+ public void setMaxKeepAliveRequests(int mkar) { maxKeepAliveRequests = mkar; }
+
+ // HTTP
+ /**
+ * The number of seconds Tomcat will wait for a subsequent request
+ * before closing the connection. The default is the same as for
+ * Apache HTTP Server (15 000 milliseconds).
+ */
+ protected int keepAliveTimeout = -1;
+ public int getKeepAliveTimeout() { return keepAliveTimeout; }
+ public void setKeepAliveTimeout(int timeout) { keepAliveTimeout = timeout; }
+
+ // HTTP
+ /**
+ * This timeout represents the socket timeout which will be used while
+ * the adapter execution is in progress, unless disableUploadTimeout
+ * is set to true. The default is the same as for Apache HTTP Server
+ * (300 000 milliseconds).
+ */
+ protected int timeout = 300000;
+ public int getTimeout() { return timeout; }
+ public void setTimeout(int timeout) { this.timeout = timeout; }
+
+
+ // *
+ /**
+ * Maximum size of the post which will be saved when processing certain
+ * requests, such as a POST.
+ */
+ protected int maxSavePostSize = 4 * 1024;
+ public int getMaxSavePostSize() { return maxSavePostSize; }
+ public void setMaxSavePostSize(int valueI) { maxSavePostSize = valueI; }
+
+
+ // HTTP
+ /**
+ * Maximum size of the HTTP message header.
+ */
+ protected int maxHttpHeaderSize =
Integer.valueOf(System.getProperty("org.apache.coyote.http11.Http11Protocol.MAX_HEADER_SIZE",
"8192")).intValue();
+ public int getMaxHttpHeaderSize() { return maxHttpHeaderSize; }
+ public void setMaxHttpHeaderSize(int valueI) { maxHttpHeaderSize = valueI; }
+
+
+ // HTTP
+ /**
+ * If true, the regular socket timeout will be used for the full duration
+ * of the connection.
+ */
+ protected boolean disableUploadTimeout = true;
+ public boolean getDisableUploadTimeout() { return disableUploadTimeout; }
+ public void setDisableUploadTimeout(boolean isDisabled) { disableUploadTimeout =
isDisabled; }
+
+
+ // HTTP
+ /**
+ * Integrated compression support.
+ */
+ protected String compression =
System.getProperty("org.apache.coyote.http11.Http11Protocol.COMPRESSION",
"off");
+ public String getCompression() { return compression; }
+ public void setCompression(String valueS) { compression = valueS; }
+
+
+ // HTTP
+ protected String noCompressionUserAgents =
System.getProperty("org.apache.coyote.http11.Http11Protocol.COMPRESSION_RESTRICTED_UA");
+ public String getNoCompressionUserAgents() { return noCompressionUserAgents; }
+ public void setNoCompressionUserAgents(String valueS) { noCompressionUserAgents =
valueS; }
+
+
+ // HTTP
+ protected String compressableMimeTypes =
System.getProperty("org.apache.coyote.http11.Http11Protocol.COMPRESSION_MIME_TYPES",
"text/html,text/xml,text/plain");
+ public String getCompressableMimeType() { return compressableMimeTypes; }
+ public void setCompressableMimeType(String valueS) { compressableMimeTypes = valueS;
}
+
+
+ // HTTP
+ protected int compressionMinSize =
Integer.valueOf(System.getProperty("org.apache.coyote.http11.Http11Protocol.COMPRESSION_MIN_SIZE",
"2048")).intValue();
+ public int getCompressionMinSize() { return compressionMinSize; }
+ public void setCompressionMinSize(int valueI) { compressionMinSize = valueI; }
+
+
+ // HTTP
+ /**
+ * User agents regular expressions which should be restricted to HTTP/1.0 support.
+ */
+ protected String restrictedUserAgents = null;
+ public String getRestrictedUserAgents() { return restrictedUserAgents; }
+ public void setRestrictedUserAgents(String valueS) { restrictedUserAgents = valueS;
}
+
+ // HTTP
+ /**
+ * Server header.
+ */
+ protected String server =
System.getProperty("org.apache.coyote.http11.Http11Protocol.SERVER");
+ public void setServer( String server ) { this.server = server; }
+ public String getServer() { return server; }
+
+ public Executor getExecutor() { return endpoint.getExecutor(); }
+ public void setExecutor(Executor executor) { endpoint.setExecutor(executor); }
+
+ public int getMaxThreads() { return endpoint.getMaxThreads(); }
+ public void setMaxThreads(int maxThreads) { endpoint.setMaxThreads(maxThreads); }
+
+ public int getThreadPriority() { return endpoint.getThreadPriority(); }
+ public void setThreadPriority(int threadPriority) {
endpoint.setThreadPriority(threadPriority); }
+
+ public int getBacklog() { return endpoint.getBacklog(); }
+ public void setBacklog(int backlog) { endpoint.setBacklog(backlog); }
+
+ public int getPort() { return endpoint.getPort(); }
+ public void setPort(int port) { endpoint.setPort(port); }
+
+ public InetAddress getAddress() { return endpoint.getAddress(); }
+ public void setAddress(InetAddress ia) { endpoint.setAddress(ia); }
+
+ public boolean getTcpNoDelay() { return endpoint.getTcpNoDelay(); }
+ public void setTcpNoDelay(boolean tcpNoDelay) { endpoint.setTcpNoDelay(tcpNoDelay);
}
+
+ public int getSoLinger() { return endpoint.getSoLinger(); }
+ public void setSoLinger(int soLinger) { endpoint.setSoLinger(soLinger); }
+
+ public int getSoTimeout() { return endpoint.getSoTimeout(); }
+ public void setSoTimeout(int soTimeout) { endpoint.setSoTimeout(soTimeout); }
+
+ public void setPollerSize(int pollerSize) { endpoint.setPollerSize(pollerSize); }
+ public int getPollerSize() { return endpoint.getPollerSize(); }
+
+ // HTTP
+ /**
+ * Return the Keep-Alive policy for the connection.
+ */
+ public boolean getKeepAlive() {
+ return ((maxKeepAliveRequests != 0) && (maxKeepAliveRequests != 1));
+ }
+
+ // HTTP
+ /**
+ * Set the keep-alive policy for this connection.
+ */
+ public void setKeepAlive(boolean keepAlive) {
+ if (!keepAlive) {
+ setMaxKeepAliveRequests(1);
+ }
+ }
+
+ /*
+ * Note: All the following are JSSE/java.io specific attributes.
+ */
+
+ public String getKeystore() {
+ return (String) getAttribute("keystore");
+ }
+
+ public void setKeystore( String k ) {
+ setAttribute("keystore", k);
+ }
+
+ public String getKeypass() {
+ return (String) getAttribute("keypass");
+ }
+
+ public void setKeypass( String k ) {
+ attributes.put("keypass", k);
+ //setAttribute("keypass", k);
+ }
+
+ public String getKeytype() {
+ return (String) getAttribute("keystoreType");
+ }
+
+ public void setKeytype( String k ) {
+ setAttribute("keystoreType", k);
+ }
+
+ public String getClientauth() {
+ return (String) getAttribute("clientauth");
+ }
+
+ public void setClientauth( String k ) {
+ setAttribute("clientauth", k);
+ }
+
+ public String getProtocols() {
+ return (String) getAttribute("protocols");
+ }
+
+ public void setProtocols(String k) {
+ setAttribute("protocols", k);
+ }
+
+ public String getAlgorithm() {
+ return (String) getAttribute("algorithm");
+ }
+
+ public void setAlgorithm( String k ) {
+ setAttribute("algorithm", k);
+ }
+
+ public String getCiphers() {
+ return (String) getAttribute("ciphers");
+ }
+
+ public void setCiphers(String ciphers) {
+ setAttribute("ciphers", ciphers);
+ }
+
+ public String getKeyAlias() {
+ return (String) getAttribute("keyAlias");
+ }
+
+ public void setKeyAlias(String keyAlias) {
+ setAttribute("keyAlias", keyAlias);
+ }
+
+ public SSLContext getSSLContext() {
+ return (SSLContext) getAttribute("SSLContext");
+ }
+
+ public void setSSLContext(SSLContext sslContext) {
+ setAttribute("SSLContext", sslContext);
+ }
+
+ // ----------------------------------- Http11ConnectionHandler Inner Class
+
+ protected static class Http11ConnectionHandler implements Handler {
+
+ protected Http11Protocol proto;
+ protected AtomicLong registerCount = new AtomicLong(0);
+ protected RequestGroupInfo global = new RequestGroupInfo();
+
+ protected ConcurrentHashMap<Socket, Http11Processor> connections =
+ new ConcurrentHashMap<Socket, Http11Processor>();
+ protected ConcurrentLinkedQueue<Http11Processor> recycledProcessors =
+ new ConcurrentLinkedQueue<Http11Processor>() {
+ protected AtomicInteger size = new AtomicInteger(0);
+ public boolean offer(Http11Processor processor) {
+ boolean offer = (proto.processorCache == -1) ? true : (size.get() <
proto.processorCache);
+ //avoid over growing our cache or add after we have stopped
+ boolean result = false;
+ if ( offer ) {
+ result = super.offer(processor);
+ if ( result ) {
+ size.incrementAndGet();
+ }
+ }
+ if (!result) unregister(processor);
+ return result;
+ }
+
+ public Http11Processor poll() {
+ Http11Processor result = super.poll();
+ if ( result != null ) {
+ size.decrementAndGet();
+ }
+ return result;
+ }
+
+ public void clear() {
+ Http11Processor next = poll();
+ while ( next != null ) {
+ unregister(next);
+ next = poll();
+ }
+ super.clear();
+ size.set(0);
+ }
+ };
+
+ Http11ConnectionHandler(Http11Protocol proto) {
+ this.proto = proto;
+ }
+
+ public SocketState event(Socket socket, SocketStatus status) {
+ Http11Processor result = connections.get(socket);
+ SocketState state = SocketState.CLOSED;
+ if (result != null) {
+ result.startProcessing();
+ // Call the appropriate event
+ try {
+ state = result.event(status);
+ } catch (java.net.SocketException e) {
+ // SocketExceptions are normal
+ CoyoteLogger.HTTP_LOGGER.socketException(e);
+ } catch (java.io.IOException e) {
+ // IOExceptions are normal
+ CoyoteLogger.HTTP_LOGGER.socketException(e);
+ }
+ // Future developers: if you discover any other
+ // rare-but-nonfatal exceptions, catch them here, and log as
+ // above.
+ catch (Throwable e) {
+ // any other exception or error is odd. Here we log it
+ // with "ERROR" level, so it will show up even on
+ // less-than-verbose logs.
+ CoyoteLogger.HTTP_LOGGER.socketError(e);
+ } finally {
+ if (state != SocketState.LONG) {
+ connections.remove(socket);
+ recycledProcessors.offer(result);
+ } else {
+ if (proto.endpoint.isRunning()) {
+ proto.endpoint.getEventPoller().add(socket,
result.getTimeout(),
+ result.getResumeNotification(), false);
+ }
+ }
+ result.endProcessing();
+ }
+ }
+ return state;
+ }
+
+ public SocketState process(Socket socket) {
+ Http11Processor processor = recycledProcessors.poll();
+ try {
+
+ if (processor == null) {
+ processor = createProcessor();
+ }
+
+ if (proto.secure && (proto.sslImplementation != null)) {
+ processor.setSSLSupport
+ (proto.sslImplementation.getSSLSupport(socket));
+ } else {
+ processor.setSSLSupport(null);
+ }
+
+ SocketState state = processor.process(socket);
+ if (state == SocketState.LONG) {
+ // Associate the connection with the processor. The next request
+ // processed by this thread will use either a new or a recycled
+ // processor.
+ connections.put(socket, processor);
+ proto.endpoint.getEventPoller().add(socket, processor.getTimeout(),
+ processor.getResumeNotification(), false);
+ } else {
+ recycledProcessors.offer(processor);
+ }
+ return state;
+
+ } catch(java.net.SocketException e) {
+ // SocketExceptions are normal
+ CoyoteLogger.HTTP_LOGGER.socketException(e);
+ } catch (java.io.IOException e) {
+ // IOExceptions are normal
+ CoyoteLogger.HTTP_LOGGER.socketException(e);
+ }
+ // Future developers: if you discover any other
+ // rare-but-nonfatal exceptions, catch them here, and log as
+ // above.
+ catch (Throwable e) {
+ // any other exception or error is odd. Here we log it
+ // with "ERROR" level, so it will show up even on
+ // less-than-verbose logs.
+ CoyoteLogger.HTTP_LOGGER.socketError(e);
+ }
+ recycledProcessors.offer(processor);
+ return SocketState.CLOSED;
+ }
+
+ protected Http11Processor createProcessor() {
+ Http11Processor processor =
+ new Http11Processor(proto.maxHttpHeaderSize, proto.endpoint);
+ processor.setAdapter(proto.adapter);
+ processor.setMaxKeepAliveRequests(proto.maxKeepAliveRequests);
+ processor.setKeepAliveTimeout(proto.keepAliveTimeout);
+ processor.setTimeout(proto.timeout);
+ processor.setDisableUploadTimeout(proto.disableUploadTimeout);
+ processor.setCompressionMinSize(proto.compressionMinSize);
+ processor.setCompression(proto.compression);
+ processor.setNoCompressionUserAgents(proto.noCompressionUserAgents);
+ processor.setCompressableMimeTypes(proto.compressableMimeTypes);
+ processor.setRestrictedUserAgents(proto.restrictedUserAgents);
+ processor.setSocketBuffer(proto.socketBuffer);
+ processor.setMaxSavePostSize(proto.maxSavePostSize);
+ processor.setServer(proto.server);
+ register(processor);
+ return processor;
+ }
+
+ protected void register(Http11Processor processor) {
+ RequestInfo rp = processor.getRequest().getRequestProcessor();
+ rp.setGlobalProcessor(global);
+ if (org.apache.tomcat.util.Constants.ENABLE_MODELER &&
proto.getDomain() != null) {
+ synchronized (this) {
+ try {
+ long count = registerCount.incrementAndGet();
+ ObjectName rpName = new ObjectName
+ (proto.getDomain() +
":type=RequestProcessor,worker="
+ + proto.getJmxName() + ",name=HttpRequest" +
count);
+ Registry.getRegistry(null, null).registerComponent(rp, rpName,
null);
+ rp.setRpName(rpName);
+ } catch (Exception e) {
+ CoyoteLogger.HTTP_LOGGER.errorRegisteringRequest(e);
+ }
+ }
+ }
+ }
+
+ protected void unregister(Http11Processor processor) {
+ RequestInfo rp = processor.getRequest().getRequestProcessor();
+ rp.setGlobalProcessor(null);
+ if (org.apache.tomcat.util.Constants.ENABLE_MODELER &&
proto.getDomain() != null) {
+ synchronized (this) {
+ try {
+ ObjectName rpName = rp.getRpName();
+ Registry.getRegistry(null, null).unregisterComponent(rpName);
+ rp.setRpName(null);
+ } catch (Exception e) {
+ CoyoteLogger.HTTP_LOGGER.errorUnregisteringRequest(e);
+ }
+ }
+ }
+ }
+
+ }
+
+
+ // -------------------- JMX related methods --------------------
+
+ // *
+ protected String domain;
+ protected ObjectName oname;
+ protected MBeanServer mserver;
+
+ public ObjectName getObjectName() {
+ return oname;
+ }
+
+ public String getDomain() {
+ return domain;
+ }
+
+ public ObjectName preRegister(MBeanServer server,
+ ObjectName name) throws Exception {
+ oname=name;
+ mserver=server;
+ domain=name.getDomain();
+ return name;
+ }
+
+ public void postRegister(Boolean registrationDone) {
+ }
+
+ public void preDeregister() throws Exception {
+ }
+
+ public void postDeregister() {
+ }
+}
Added: branches/7.2.x/src/main/java/org/apache/coyote/http11/InternalInputBuffer.java
===================================================================
--- branches/7.2.x/src/main/java/org/apache/coyote/http11/InternalInputBuffer.java
(rev 0)
+++
branches/7.2.x/src/main/java/org/apache/coyote/http11/InternalInputBuffer.java 2012-09-26
13:54:12 UTC (rev 2087)
@@ -0,0 +1,783 @@
+/*
+ * 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 static org.jboss.web.CoyoteMessages.MESSAGES;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.EOFException;
+
+import org.apache.tomcat.util.buf.ByteChunk;
+import org.apache.tomcat.util.buf.MessageBytes;
+import org.apache.tomcat.util.http.MimeHeaders;
+
+import org.apache.coyote.InputBuffer;
+import org.apache.coyote.Request;
+
+/**
+ * Implementation of InputBuffer which provides HTTP request header parsing as
+ * well as transfer decoding.
+ *
+ * @author <a href="mailto:remm@apache.org">Remy Maucherat</a>
+ */
+public class InternalInputBuffer implements InputBuffer {
+
+
+ // -------------------------------------------------------------- Constants
+
+
+ // ----------------------------------------------------------- Constructors
+
+
+ /**
+ * Default constructor.
+ */
+ public InternalInputBuffer(Request request) {
+ this(request, Constants.DEFAULT_HTTP_HEADER_BUFFER_SIZE);
+ }
+
+
+ /**
+ * Alternate constructor.
+ */
+ public InternalInputBuffer(Request request, int headerBufferSize) {
+
+ this.request = request;
+ headers = request.getMimeHeaders();
+
+ buf = new byte[headerBufferSize];
+
+ inputStreamInputBuffer = new InputStreamInputBuffer();
+
+ filterLibrary = new InputFilter[0];
+ activeFilters = new InputFilter[0];
+ lastActiveFilter = -1;
+
+ parsingHeader = true;
+ swallowInput = true;
+
+ }
+
+
+ // ----------------------------------------------------- Instance Variables
+
+
+ /**
+ * Associated Coyote request.
+ */
+ protected Request request;
+
+
+ /**
+ * Headers of the associated request.
+ */
+ protected MimeHeaders headers;
+
+
+ /**
+ * State.
+ */
+ protected boolean parsingHeader;
+
+
+ /**
+ * Swallow input ? (in the case of an expectation)
+ */
+ protected boolean swallowInput;
+
+
+ /**
+ * Pointer to the current read buffer.
+ */
+ protected byte[] buf;
+
+
+ /**
+ * Last valid byte.
+ */
+ protected int lastValid;
+
+
+ /**
+ * Position in the buffer.
+ */
+ protected int pos;
+
+
+ /**
+ * Pos of the end of the header in the buffer, which is also the
+ * start of the body.
+ */
+ protected int end;
+
+
+ /**
+ * Underlying input stream.
+ */
+ protected InputStream inputStream;
+
+
+ /**
+ * Underlying input buffer.
+ */
+ protected InputBuffer inputStreamInputBuffer;
+
+
+ /**
+ * Filter library.
+ * Note: Filter[0] is always the "chunked" filter.
+ */
+ protected InputFilter[] filterLibrary;
+
+
+ /**
+ * Active filters (in order).
+ */
+ protected InputFilter[] activeFilters;
+
+
+ /**
+ * Index of the last active filter.
+ */
+ protected int lastActiveFilter;
+
+
+ // ------------------------------------------------------------- Properties
+
+
+ /**
+ * Set the underlying socket input stream.
+ */
+ public void setInputStream(InputStream inputStream) {
+
+ // FIXME: Check for null ?
+
+ this.inputStream = inputStream;
+
+ }
+
+
+ /**
+ * Get the underlying socket input stream.
+ */
+ public InputStream getInputStream() {
+
+ return inputStream;
+
+ }
+
+
+ /**
+ * Add an input filter to the filter library.
+ */
+ public void addFilter(InputFilter filter) {
+
+ // FIXME: Check for null ?
+
+ InputFilter[] newFilterLibrary =
+ new InputFilter[filterLibrary.length + 1];
+ for (int i = 0; i < filterLibrary.length; i++) {
+ newFilterLibrary[i] = filterLibrary[i];
+ }
+ newFilterLibrary[filterLibrary.length] = filter;
+ filterLibrary = newFilterLibrary;
+
+ activeFilters = new InputFilter[filterLibrary.length];
+
+ }
+
+
+ /**
+ * Get filters.
+ */
+ public InputFilter[] getFilters() {
+
+ return filterLibrary;
+
+ }
+
+
+ /**
+ * Clear filters.
+ */
+ public void clearFilters() {
+
+ filterLibrary = new InputFilter[0];
+ lastActiveFilter = -1;
+
+ }
+
+
+ /**
+ * Add an input filter to the filter library.
+ */
+ public void addActiveFilter(InputFilter filter) {
+
+ if (lastActiveFilter == -1) {
+ filter.setBuffer(inputStreamInputBuffer);
+ } else {
+ for (int i = 0; i <= lastActiveFilter; i++) {
+ if (activeFilters[i] == filter)
+ return;
+ }
+ filter.setBuffer(activeFilters[lastActiveFilter]);
+ }
+
+ activeFilters[++lastActiveFilter] = filter;
+
+ filter.setRequest(request);
+
+ }
+
+
+ /**
+ * Set the swallow input flag.
+ */
+ public void setSwallowInput(boolean swallowInput) {
+ this.swallowInput = swallowInput;
+ }
+
+
+ // --------------------------------------------------------- Public Methods
+
+
+ /**
+ * Recycle the input buffer. This should be called when closing the
+ * connection.
+ */
+ public void recycle() {
+
+ // Recycle Request object
+ request.recycle();
+
+ inputStream = null;
+ lastValid = 0;
+ pos = 0;
+ lastActiveFilter = -1;
+ parsingHeader = true;
+ swallowInput = true;
+
+ }
+
+
+ /**
+ * End processing of current HTTP request.
+ * Note: All bytes of the current request should have been already
+ * consumed. This method only resets all the pointers so that we are ready
+ * to parse the next HTTP request.
+ */
+ public boolean nextRequest() {
+
+ // Recycle Request object
+ request.recycle();
+
+ // Copy leftover bytes to the beginning of the buffer
+ if (lastValid - pos > 0) {
+ int npos = 0;
+ int opos = pos;
+ while (lastValid - opos > opos - npos) {
+ System.arraycopy(buf, opos, buf, npos, opos - npos);
+ npos += pos;
+ opos += pos;
+ }
+ System.arraycopy(buf, opos, buf, npos, lastValid - opos);
+ }
+
+ // Recycle filters
+ for (int i = 0; i <= lastActiveFilter; i++) {
+ activeFilters[i].recycle();
+ }
+
+ // Reset pointers
+ lastValid = lastValid - pos;
+ pos = 0;
+ lastActiveFilter = -1;
+ parsingHeader = true;
+ swallowInput = true;
+
+ return (lastValid > 0);
+
+ }
+
+
+ /**
+ * End request (consumes leftover bytes).
+ *
+ * @throws IOException an undelying I/O error occured
+ */
+ public void endRequest()
+ throws IOException {
+
+ if (swallowInput && (lastActiveFilter != -1)) {
+ int extraBytes = (int) activeFilters[lastActiveFilter].end();
+ pos = pos - extraBytes;
+ }
+
+ }
+
+
+ /**
+ * Read the request line. This function is meant to be used during the
+ * HTTP request header parsing. Do NOT attempt to read the request body
+ * using it.
+ *
+ * @throws IOException If an exception occurs during the underlying socket
+ * read operations, or if the given buffer is not big enough to accomodate
+ * the whole line.
+ */
+ public void parseRequestLine()
+ throws IOException {
+
+ int start = 0;
+
+ //
+ // Skipping blank lines
+ //
+
+ byte chr = 0;
+ do {
+
+ // Read new bytes if needed
+ if (pos >= lastValid) {
+ if (!fill())
+ throw new EOFException(MESSAGES.eofError());
+ }
+
+ chr = buf[pos++];
+
+ } while ((chr == Constants.CR) || (chr == Constants.LF));
+
+ pos--;
+
+ // Mark the current buffer position
+ start = pos;
+
+ //
+ // Reading the method name
+ // Method name is always US-ASCII
+ //
+
+ boolean space = false;
+
+ while (!space) {
+
+ // Read new bytes if needed
+ if (pos >= lastValid) {
+ if (!fill())
+ throw new EOFException(MESSAGES.eofError());
+ }
+
+ // Spec says single SP but it also says be tolerant of HT
+ if (buf[pos] == Constants.SP || buf[pos] == Constants.HT) {
+ space = true;
+ request.method().setBytes(buf, start, pos - start);
+ }
+
+ pos++;
+
+ }
+
+ // Spec says single SP but also says be tolerant of multiple and/or HT
+ while (space) {
+ // Read new bytes if needed
+ if (pos >= lastValid) {
+ if (!fill())
+ throw new EOFException(MESSAGES.eofError());
+ }
+ if (buf[pos] == Constants.SP || buf[pos] == Constants.HT) {
+ pos++;
+ } else {
+ space = false;
+ }
+ }
+
+ // Mark the current buffer position
+ start = pos;
+ int end = 0;
+ int questionPos = -1;
+
+ //
+ // Reading the URI
+ //
+
+ boolean eol = false;
+
+ while (!space) {
+
+ // Read new bytes if needed
+ if (pos >= lastValid) {
+ if (!fill())
+ throw new EOFException(MESSAGES.eofError());
+ }
+
+ // Spec says single SP but it also says be tolerant of HT
+ if (buf[pos] == Constants.SP || buf[pos] == Constants.HT) {
+ space = true;
+ end = pos;
+ } else if ((buf[pos] == Constants.CR)
+ || (buf[pos] == Constants.LF)) {
+ // HTTP/0.9 style request
+ eol = true;
+ space = true;
+ end = pos;
+ } else if ((buf[pos] == Constants.QUESTION)
+ && (questionPos == -1)) {
+ questionPos = pos;
+ }
+
+ pos++;
+
+ }
+
+ request.unparsedURI().setBytes(buf, start, end - start);
+ if (questionPos >= 0) {
+ request.queryString().setBytes(buf, questionPos + 1,
+ end - questionPos - 1);
+ request.requestURI().setBytes(buf, start, questionPos - start);
+ } else {
+ request.requestURI().setBytes(buf, start, end - start);
+ }
+
+ // Spec says single SP but also says be tolerant of multiple and/or HT
+ while (space) {
+ // Read new bytes if needed
+ if (pos >= lastValid) {
+ if (!fill())
+ throw new EOFException(MESSAGES.eofError());
+ }
+ if (buf[pos] == Constants.SP || buf[pos] == Constants.HT) {
+ pos++;
+ } else {
+ space = false;
+ }
+ }
+
+ // Mark the current buffer position
+ start = pos;
+ end = 0;
+
+ //
+ // Reading the protocol
+ // Protocol is always US-ASCII
+ //
+
+ while (!eol) {
+
+ // Read new bytes if needed
+ if (pos >= lastValid) {
+ if (!fill())
+ throw new EOFException(MESSAGES.eofError());
+ }
+
+ if (buf[pos] == Constants.CR) {
+ end = pos;
+ } else if (buf[pos] == Constants.LF) {
+ if (end == 0)
+ end = pos;
+ eol = true;
+ }
+
+ pos++;
+
+ }
+
+ if ((end - start) > 0) {
+ request.protocol().setBytes(buf, start, end - start);
+ } else {
+ request.protocol().setString("");
+ }
+
+ }
+
+
+ /**
+ * Parse the HTTP headers.
+ */
+ public void parseHeaders()
+ throws IOException {
+
+ while (parseHeader()) {
+ }
+
+ parsingHeader = false;
+ end = pos;
+
+ }
+
+
+ /**
+ * Parse an HTTP header.
+ *
+ * @return false after reading a blank line (which indicates that the
+ * HTTP header parsing is done
+ */
+ public boolean parseHeader()
+ throws IOException {
+
+ //
+ // Check for blank line
+ //
+
+ byte chr = 0;
+ while (true) {
+
+ // Read new bytes if needed
+ if (pos >= lastValid) {
+ if (!fill())
+ throw new EOFException(MESSAGES.eofError());
+ }
+
+ chr = buf[pos];
+
+ if ((chr == Constants.CR) || (chr == Constants.LF)) {
+ if (chr == Constants.LF) {
+ pos++;
+ return false;
+ }
+ } else {
+ break;
+ }
+
+ pos++;
+
+ }
+
+ // Mark the current buffer position
+ int start = pos;
+
+ //
+ // Reading the header name
+ // Header name is always US-ASCII
+ //
+
+ boolean colon = false;
+ MessageBytes headerValue = null;
+
+ while (!colon) {
+
+ // Read new bytes if needed
+ if (pos >= lastValid) {
+ if (!fill())
+ throw new EOFException(MESSAGES.eofError());
+ }
+
+ if (buf[pos] == Constants.COLON) {
+ colon = true;
+ headerValue = headers.addValue(buf, start, pos - start);
+ }
+ chr = buf[pos];
+ if ((chr >= Constants.A) && (chr <= Constants.Z)) {
+ buf[pos] = (byte) (chr - Constants.LC_OFFSET);
+ }
+
+ pos++;
+
+ }
+
+ // Mark the current buffer position
+ start = pos;
+ int realPos = pos;
+
+ //
+ // Reading the header value (which can be spanned over multiple lines)
+ //
+
+ boolean eol = false;
+ boolean validLine = true;
+
+ while (validLine) {
+
+ boolean space = true;
+
+ // Skipping spaces
+ while (space) {
+
+ // Read new bytes if needed
+ if (pos >= lastValid) {
+ if (!fill())
+ throw new EOFException(MESSAGES.eofError());
+ }
+
+ if ((buf[pos] == Constants.SP) || (buf[pos] == Constants.HT)) {
+ pos++;
+ } else {
+ space = false;
+ }
+
+ }
+
+ int lastSignificantChar = realPos;
+
+ // Reading bytes until the end of the line
+ while (!eol) {
+
+ // Read new bytes if needed
+ if (pos >= lastValid) {
+ if (!fill())
+ throw new EOFException(MESSAGES.eofError());
+ }
+
+ if (buf[pos] == Constants.CR) {
+ } else if (buf[pos] == Constants.LF) {
+ eol = true;
+ } else if (buf[pos] == Constants.SP) {
+ buf[realPos] = buf[pos];
+ realPos++;
+ } else {
+ buf[realPos] = buf[pos];
+ realPos++;
+ lastSignificantChar = realPos;
+ }
+
+ pos++;
+
+ }
+
+ realPos = lastSignificantChar;
+
+ // Checking the first character of the new line. If the character
+ // is a LWS, then it's a multiline header
+
+ // Read new bytes if needed
+ if (pos >= lastValid) {
+ if (!fill())
+ throw new EOFException(MESSAGES.eofError());
+ }
+
+ chr = buf[pos];
+ if ((chr != Constants.SP) && (chr != Constants.HT)) {
+ validLine = false;
+ } else {
+ eol = false;
+ // Copying one extra space in the buffer (since there must
+ // be at least one space inserted between the lines)
+ buf[realPos] = chr;
+ realPos++;
+ }
+
+ }
+
+ // Set the header value
+ headerValue.setBytes(buf, start, realPos - start);
+
+ return true;
+
+ }
+
+
+ // ---------------------------------------------------- InputBuffer Methods
+
+
+ /**
+ * Read some bytes.
+ */
+ public int doRead(ByteChunk chunk, Request req)
+ throws IOException {
+
+ if (lastActiveFilter == -1)
+ return inputStreamInputBuffer.doRead(chunk, req);
+ else
+ return activeFilters[lastActiveFilter].doRead(chunk,req);
+
+ }
+
+
+ // ------------------------------------------------------ Protected Methods
+
+
+ /**
+ * Fill the internal buffer using data from the undelying input stream.
+ *
+ * @return false if at end of stream
+ */
+ protected boolean fill()
+ throws IOException {
+
+ int nRead = 0;
+
+ if (parsingHeader) {
+
+ if (lastValid == buf.length) {
+ throw MESSAGES.requestHeaderTooLarge();
+ }
+
+ nRead = inputStream.read(buf, pos, buf.length - lastValid);
+ if (nRead > 0) {
+ lastValid = pos + nRead;
+ }
+
+ } else {
+
+ if (buf.length - end < 4500) {
+ // In this case, the request header was really large, so we allocate a
+ // brand new one; the old one will get GCed when subsequent requests
+ // clear all references
+ buf = new byte[buf.length];
+ end = 0;
+ }
+ pos = end;
+ lastValid = pos;
+ nRead = inputStream.read(buf, pos, buf.length - lastValid);
+ if (nRead > 0) {
+ lastValid = pos + nRead;
+ }
+
+ }
+
+ return (nRead > 0);
+
+ }
+
+
+ // ------------------------------------- InputStreamInputBuffer Inner Class
+
+
+ /**
+ * This class is an input buffer which will read its data from an input
+ * stream.
+ */
+ protected class InputStreamInputBuffer
+ implements InputBuffer {
+
+
+ /**
+ * Read bytes into the specified chunk.
+ */
+ public int doRead(ByteChunk chunk, Request req )
+ throws IOException {
+
+ if (pos >= lastValid) {
+ if (!fill())
+ return -1;
+ }
+
+ int length = lastValid - pos;
+ chunk.setBytes(buf, pos, length);
+ pos = lastValid;
+
+ return (length);
+
+ }
+
+
+ }
+
+
+}
Added: branches/7.2.x/src/main/java/org/apache/coyote/http11/InternalOutputBuffer.java
===================================================================
--- branches/7.2.x/src/main/java/org/apache/coyote/http11/InternalOutputBuffer.java
(rev 0)
+++
branches/7.2.x/src/main/java/org/apache/coyote/http11/InternalOutputBuffer.java 2012-09-26
13:54:12 UTC (rev 2087)
@@ -0,0 +1,749 @@
+/*
+ * 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.io.IOException;
+import java.io.OutputStream;
+import java.security.AccessController;
+import java.security.PrivilegedAction;
+
+import org.apache.tomcat.util.buf.ByteChunk;
+import org.apache.tomcat.util.buf.CharChunk;
+import org.apache.tomcat.util.buf.MessageBytes;
+import org.apache.tomcat.util.http.HttpMessages;
+import org.apache.tomcat.util.http.MimeHeaders;
+
+import org.apache.coyote.ActionCode;
+import org.apache.coyote.OutputBuffer;
+import org.apache.coyote.Response;
+
+/**
+ * Output buffer.
+ *
+ * @author <a href="mailto:remm@apache.org">Remy Maucherat</a>
+ */
+public class InternalOutputBuffer
+ implements OutputBuffer, ByteChunk.ByteOutputChannel {
+
+ // -------------------------------------------------------------- Constants
+
+
+ // ----------------------------------------------------------- Constructors
+
+
+ /**
+ * Default constructor.
+ */
+ public InternalOutputBuffer(Response response) {
+ this(response, Constants.DEFAULT_HTTP_HEADER_BUFFER_SIZE);
+ }
+
+
+ /**
+ * Alternate constructor.
+ */
+ public InternalOutputBuffer(Response response, int headerBufferSize) {
+
+ this.response = response;
+
+ headers = response.getMimeHeaders();
+
+ buf = new byte[headerBufferSize];
+
+ outputStreamOutputBuffer = new OutputStreamOutputBuffer();
+
+ filterLibrary = new OutputFilter[0];
+ activeFilters = new OutputFilter[0];
+ lastActiveFilter = -1;
+
+ socketBuffer = new ByteChunk();
+ socketBuffer.setByteOutputChannel(this);
+
+ committed = false;
+ finished = false;
+
+ }
+
+
+ // ----------------------------------------------------- Instance Variables
+
+
+ /**
+ * Associated Coyote response.
+ */
+ protected Response response;
+
+
+ /**
+ * Headers of the associated request.
+ */
+ protected MimeHeaders headers;
+
+
+ /**
+ * Committed flag.
+ */
+ protected boolean committed;
+
+
+ /**
+ * Finished flag.
+ */
+ protected boolean finished;
+
+
+ /**
+ * The buffer used for header composition.
+ */
+ protected byte[] buf;
+
+
+ /**
+ * Position in the buffer.
+ */
+ protected int pos;
+
+
+ /**
+ * Underlying output stream.
+ */
+ protected OutputStream outputStream;
+
+
+ /**
+ * Underlying output buffer.
+ */
+ protected OutputBuffer outputStreamOutputBuffer;
+
+
+ /**
+ * Filter library.
+ * Note: Filter[0] is always the "chunked" filter.
+ */
+ protected OutputFilter[] filterLibrary;
+
+
+ /**
+ * Active filter (which is actually the top of the pipeline).
+ */
+ protected OutputFilter[] activeFilters;
+
+
+ /**
+ * Index of the last active filter.
+ */
+ protected int lastActiveFilter;
+
+
+ /**
+ * Socket buffer.
+ */
+ protected ByteChunk socketBuffer;
+
+
+ /**
+ * Socket buffer (extra buffering to reduce number of packets sent).
+ */
+ protected boolean useSocketBuffer = false;
+
+
+ // ------------------------------------------------------------- Properties
+
+
+ /**
+ * Set the underlying socket output stream.
+ */
+ public void setOutputStream(OutputStream outputStream) {
+
+ // FIXME: Check for null ?
+
+ this.outputStream = outputStream;
+
+ }
+
+
+ /**
+ * Get the underlying socket output stream.
+ */
+ public OutputStream getOutputStream() {
+
+ return outputStream;
+
+ }
+
+
+ /**
+ * Set the socket buffer size.
+ */
+ public void setSocketBuffer(int socketBufferSize) {
+
+ if (socketBufferSize > 500) {
+ useSocketBuffer = true;
+ socketBuffer.allocate(socketBufferSize, socketBufferSize);
+ } else {
+ useSocketBuffer = false;
+ }
+
+ }
+
+
+ /**
+ * Add an output filter to the filter library.
+ */
+ public void addFilter(OutputFilter filter) {
+
+ OutputFilter[] newFilterLibrary =
+ new OutputFilter[filterLibrary.length + 1];
+ for (int i = 0; i < filterLibrary.length; i++) {
+ newFilterLibrary[i] = filterLibrary[i];
+ }
+ newFilterLibrary[filterLibrary.length] = filter;
+ filterLibrary = newFilterLibrary;
+
+ activeFilters = new OutputFilter[filterLibrary.length];
+
+ }
+
+
+ /**
+ * Get filters.
+ */
+ public OutputFilter[] getFilters() {
+
+ return filterLibrary;
+
+ }
+
+
+ /**
+ * Clear filters.
+ */
+ public void clearFilters() {
+
+ filterLibrary = new OutputFilter[0];
+ lastActiveFilter = -1;
+
+ }
+
+
+ /**
+ * Add an output filter to the filter library.
+ */
+ public void addActiveFilter(OutputFilter filter) {
+
+ if (lastActiveFilter == -1) {
+ filter.setBuffer(outputStreamOutputBuffer);
+ } else {
+ for (int i = 0; i <= lastActiveFilter; i++) {
+ if (activeFilters[i] == filter)
+ return;
+ }
+ filter.setBuffer(activeFilters[lastActiveFilter]);
+ }
+
+ activeFilters[++lastActiveFilter] = filter;
+
+ filter.setResponse(response);
+
+ }
+
+
+ // --------------------------------------------------------- Public Methods
+
+
+ /**
+ * Flush the response.
+ *
+ * @throws IOException an undelying I/O error occured
+ */
+ public void flush()
+ throws IOException {
+
+ if (!committed) {
+
+ // Send the connector a request for commit. The connector should
+ // then validate the headers, send them (using sendHeader) and
+ // set the filters accordingly.
+ response.action(ActionCode.ACTION_COMMIT, null);
+
+ }
+
+ // Flush the current buffer
+ if (useSocketBuffer) {
+ socketBuffer.flushBuffer();
+ }
+
+ }
+
+
+ /**
+ * Recycle the output buffer. This should be called when closing the
+ * connection.
+ */
+ public void recycle() {
+
+ // Recycle Request object
+ response.recycle();
+ socketBuffer.recycle();
+
+ outputStream = null;
+ pos = 0;
+ lastActiveFilter = -1;
+ committed = false;
+ finished = false;
+
+ }
+
+
+ /**
+ * End processing of current HTTP request.
+ * Note: All bytes of the current request should have been already
+ * consumed. This method only resets all the pointers so that we are ready
+ * to parse the next HTTP request.
+ */
+ public void nextRequest() {
+
+ // Recycle Request object
+ response.recycle();
+ socketBuffer.recycle();
+
+ // Recycle filters
+ for (int i = 0; i <= lastActiveFilter; i++) {
+ activeFilters[i].recycle();
+ }
+
+ // Reset pointers
+ pos = 0;
+ lastActiveFilter = -1;
+ committed = false;
+ finished = false;
+
+ }
+
+
+ /**
+ * End request.
+ *
+ * @throws IOException an undelying I/O error occured
+ */
+ public void endRequest()
+ throws IOException {
+
+ if (!committed) {
+
+ // Send the connector a request for commit. The connector should
+ // then validate the headers, send them (using sendHeader) and
+ // set the filters accordingly.
+ response.action(ActionCode.ACTION_COMMIT, null);
+
+ }
+
+ if (finished)
+ return;
+
+ if (lastActiveFilter != -1)
+ activeFilters[lastActiveFilter].end();
+
+ if (useSocketBuffer) {
+ socketBuffer.flushBuffer();
+ }
+
+ finished = true;
+
+ }
+
+
+ // ------------------------------------------------ HTTP/1.1 Output Methods
+
+
+ /**
+ * Send an acknoledgement.
+ */
+ public void sendAck()
+ throws IOException {
+
+ if (!committed)
+ outputStream.write(Constants.ACK_BYTES);
+
+ }
+
+
+ /**
+ * Send the response status line.
+ */
+ public void sendStatus() {
+
+ // Write protocol name
+ write(Constants.HTTP_11_BYTES);
+ buf[pos++] = Constants.SP;
+
+ // Write status code
+ int status = response.getStatus();
+ switch (status) {
+ case 200:
+ write(Constants._200_BYTES);
+ break;
+ case 400:
+ write(Constants._400_BYTES);
+ break;
+ case 404:
+ write(Constants._404_BYTES);
+ break;
+ default:
+ write(status);
+ }
+
+ buf[pos++] = Constants.SP;
+
+ // Write message
+ String message = null;
+ if (org.apache.coyote.Constants.USE_CUSTOM_STATUS_MSG_IN_HEADER) {
+ message = response.getMessage();
+ }
+ if (message == null) {
+ write(getMessage(status));
+ } else {
+ write(message.replace('\n', ' ').replace('\r', '
'));
+ }
+
+ // End the response status line
+ if (org.apache.coyote.Constants.IS_SECURITY_ENABLED){
+ AccessController.doPrivileged(
+ new PrivilegedAction(){
+ public Object run(){
+ buf[pos++] = Constants.CR;
+ buf[pos++] = Constants.LF;
+ return null;
+ }
+ }
+ );
+ } else {
+ buf[pos++] = Constants.CR;
+ buf[pos++] = Constants.LF;
+ }
+
+ }
+
+ private String getMessage(final int message){
+ if (org.apache.coyote.Constants.IS_SECURITY_ENABLED){
+ return (String)AccessController.doPrivileged(
+ new PrivilegedAction(){
+ public Object run(){
+ return HttpMessages.getMessage(message);
+ }
+ }
+ );
+ } else {
+ return HttpMessages.getMessage(message);
+ }
+ }
+
+ /**
+ * Send a header.
+ *
+ * @param name Header name
+ * @param value Header value
+ */
+ public void sendHeader(MessageBytes name, MessageBytes value) {
+
+ if (name.getLength() > 0 && !value.isNull()) {
+ write(name);
+ buf[pos++] = Constants.COLON;
+ buf[pos++] = Constants.SP;
+ write(value);
+ buf[pos++] = Constants.CR;
+ buf[pos++] = Constants.LF;
+ }
+
+ }
+
+
+ /**
+ * Send a header.
+ *
+ * @param name Header name
+ * @param value Header value
+ */
+ public void sendHeader(ByteChunk name, ByteChunk value) {
+
+ write(name);
+ buf[pos++] = Constants.COLON;
+ buf[pos++] = Constants.SP;
+ write(value);
+ buf[pos++] = Constants.CR;
+ buf[pos++] = Constants.LF;
+
+ }
+
+
+ /**
+ * Send a header.
+ *
+ * @param name Header name
+ * @param value Header value
+ */
+ public void sendHeader(String name, String value) {
+
+ write(name);
+ buf[pos++] = Constants.COLON;
+ buf[pos++] = Constants.SP;
+ write(value);
+ buf[pos++] = Constants.CR;
+ buf[pos++] = Constants.LF;
+
+ }
+
+
+ /**
+ * End the header block.
+ */
+ public void endHeaders() {
+
+ buf[pos++] = Constants.CR;
+ buf[pos++] = Constants.LF;
+
+ }
+
+
+ // --------------------------------------------------- OutputBuffer Methods
+
+
+ /**
+ * Write the contents of a byte chunk.
+ *
+ * @param chunk byte chunk
+ * @return number of bytes written
+ * @throws IOException an undelying I/O error occured
+ */
+ public int doWrite(ByteChunk chunk, Response res)
+ throws IOException {
+
+ if (!committed) {
+
+ // Send the connector a request for commit. The connector should
+ // then validate the headers, send them (using sendHeaders) and
+ // set the filters accordingly.
+ response.action(ActionCode.ACTION_COMMIT, null);
+
+ }
+
+ if (lastActiveFilter == -1)
+ return outputStreamOutputBuffer.doWrite(chunk, res);
+ else
+ return activeFilters[lastActiveFilter].doWrite(chunk, res);
+
+ }
+
+
+ // ------------------------------------------------------ Protected Methods
+
+
+ /**
+ * Commit the response.
+ *
+ * @throws IOException an undelying I/O error occured
+ */
+ protected void commit()
+ throws IOException {
+
+ // The response is now committed
+ committed = true;
+ response.setCommitted(true);
+
+ if (pos > 0) {
+ // Sending the response header buffer
+ if (useSocketBuffer) {
+ socketBuffer.append(buf, 0, pos);
+ } else {
+ outputStream.write(buf, 0, pos);
+ }
+ }
+
+ }
+
+
+ /**
+ * This method will write the contents of the specyfied message bytes
+ * buffer to the output stream, without filtering. This method is meant to
+ * be used to write the response header.
+ *
+ * @param mb data to be written
+ */
+ protected void write(MessageBytes mb) {
+
+ if (mb.getType() == MessageBytes.T_BYTES) {
+ ByteChunk bc = mb.getByteChunk();
+ write(bc);
+ } else if (mb.getType() == MessageBytes.T_CHARS) {
+ CharChunk cc = mb.getCharChunk();
+ write(cc);
+ } else {
+ write(mb.toString());
+ }
+
+ }
+
+
+ /**
+ * This method will write the contents of the specyfied message bytes
+ * buffer to the output stream, without filtering. This method is meant to
+ * be used to write the response header.
+ *
+ * @param bc data to be written
+ */
+ protected void write(ByteChunk bc) {
+
+ // Writing the byte chunk to the output buffer
+ int length = bc.getLength();
+ System.arraycopy(bc.getBytes(), bc.getStart(), buf, pos, length);
+ pos = pos + length;
+
+ }
+
+
+ /**
+ * This method will write the contents of the specyfied char
+ * buffer to the output stream, without filtering. This method is meant to
+ * be used to write the response header.
+ *
+ * @param cc data to be written
+ */
+ protected void write(CharChunk cc) {
+
+ int start = cc.getStart();
+ int end = cc.getEnd();
+ char[] cbuf = cc.getBuffer();
+ for (int i = start; i < end; i++) {
+ char c = cbuf[i];
+ // Note: This is clearly incorrect for many strings,
+ // but is the only consistent approach within the current
+ // servlet framework. It must suffice until servlet output
+ // streams properly encode their output.
+ if (((c <= 31) && (c != 9)) || c == 127 || c > 255) {
+ c = ' ';
+ }
+ buf[pos++] = (byte) c;
+ }
+
+ }
+
+
+ /**
+ * This method will write the contents of the specyfied byte
+ * buffer to the output stream, without filtering. This method is meant to
+ * be used to write the response header.
+ *
+ * @param b data to be written
+ */
+ public void write(byte[] b) {
+
+ // Writing the byte chunk to the output buffer
+ System.arraycopy(b, 0, buf, pos, b.length);
+ pos = pos + b.length;
+
+ }
+
+
+ /**
+ * This method will write the contents of the specyfied String to the
+ * output stream, without filtering. This method is meant to be used to
+ * write the response header.
+ *
+ * @param s data to be written
+ */
+ protected void write(String s) {
+
+ if (s == null)
+ return;
+
+ // From the Tomcat 3.3 HTTP/1.0 connector
+ int len = s.length();
+ for (int i = 0; i < len; i++) {
+ char c = s.charAt (i);
+ // Note: This is clearly incorrect for many strings,
+ // but is the only consistent approach within the current
+ // servlet framework. It must suffice until servlet output
+ // streams properly encode their output.
+ if (((c <= 31) && (c != 9)) || c == 127 || c > 255) {
+ c = ' ';
+ }
+ buf[pos++] = (byte) c;
+ }
+
+ }
+
+
+ /**
+ * This method will print the specified integer to the output stream,
+ * without filtering. This method is meant to be used to write the
+ * response header.
+ *
+ * @param i data to be written
+ */
+ protected void write(int i) {
+
+ write(String.valueOf(i));
+
+ }
+
+
+ /**
+ * Callback to write data from the buffer.
+ */
+ public void realWriteBytes(byte cbuf[], int off, int len)
+ throws IOException {
+ if (len > 0) {
+ outputStream.write(cbuf, off, len);
+ }
+ }
+
+
+ // ----------------------------------- OutputStreamOutputBuffer Inner Class
+
+
+ /**
+ * This class is an output buffer which will write data to an output
+ * stream.
+ */
+ protected class OutputStreamOutputBuffer
+ implements OutputBuffer {
+
+
+ /**
+ * Write chunk.
+ */
+ public int doWrite(ByteChunk chunk, Response res)
+ throws IOException {
+
+ int length = chunk.getLength();
+ if (useSocketBuffer) {
+ socketBuffer.append(chunk.getBuffer(), chunk.getStart(),
+ length);
+ } else {
+ outputStream.write(chunk.getBuffer(), chunk.getStart(),
+ length);
+ }
+ return length;
+
+ }
+
+
+ }
+
+
+}
Added:
branches/7.2.x/src/main/java/org/apache/tomcat/util/net/DefaultServerSocketFactory.java
===================================================================
---
branches/7.2.x/src/main/java/org/apache/tomcat/util/net/DefaultServerSocketFactory.java
(rev 0)
+++
branches/7.2.x/src/main/java/org/apache/tomcat/util/net/DefaultServerSocketFactory.java 2012-09-26
13:54:12 UTC (rev 2087)
@@ -0,0 +1,70 @@
+/*
+ * 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;
+
+import java.io.*;
+import java.net.*;
+
+/**
+ * Default server socket factory. Doesn't do much except give us
+ * plain ol' server sockets.
+ *
+ * @author db(a)eng.sun.com
+ * @author Harish Prabandham
+ */
+
+// Default implementation of server sockets.
+
+//
+// WARNING: Some of the APIs in this class are used by J2EE.
+// Please talk to harishp(a)eng.sun.com before making any changes.
+//
+class DefaultServerSocketFactory extends ServerSocketFactory {
+
+ DefaultServerSocketFactory () {
+ /* NOTHING */
+ }
+
+ public ServerSocket createSocket (int port)
+ throws IOException {
+ return new ServerSocket (port);
+ }
+
+ public ServerSocket createSocket (int port, int backlog)
+ throws IOException {
+ return new ServerSocket (port, backlog);
+ }
+
+ public ServerSocket createSocket (int port, int backlog,
+ InetAddress ifAddress)
+ throws IOException {
+ return new ServerSocket (port, backlog, ifAddress);
+ }
+
+ public Socket acceptSocket(ServerSocket socket)
+ throws IOException {
+ return socket.accept();
+ }
+
+ public void handshake(Socket sock)
+ throws IOException {
+ ; // NOOP
+ }
+
+
+ }
Added: branches/7.2.x/src/main/java/org/apache/tomcat/util/net/JIoEndpoint.java
===================================================================
--- branches/7.2.x/src/main/java/org/apache/tomcat/util/net/JIoEndpoint.java
(rev 0)
+++ branches/7.2.x/src/main/java/org/apache/tomcat/util/net/JIoEndpoint.java 2012-09-26
13:54:12 UTC (rev 2087)
@@ -0,0 +1,1315 @@
+/*
+ * JBoss, Home of Professional Open Source
+ * Copyright 2009, JBoss Inc., and individual contributors as indicated
+ * by the @authors tag. See the copyright.txt in the distribution for a
+ * full listing of individual contributors.
+ *
+ * This is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as
+ * published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this software; if not, write to the Free
+ * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+ * 02110-1301 USA, or see the FSF site:
http://www.fsf.org.
+ */
+
+package
org.apache.tomcat.util.net;
+
+import java.io.IOException;
+import java.net.BindException;
+import java.net.Inet6Address;
+import java.net.InetAddress;
+import java.net.ServerSocket;
+import java.net.Socket;
+import java.util.concurrent.Executor;
+
+import org.jboss.web.CoyoteLogger;
+
+/**
+ * Handle incoming TCP connections.
+ *
+ * This class implement a simple server model: one listener thread accepts on a socket
and
+ * creates a new worker thread for each incoming connection.
+ *
+ * More advanced Endpoints will reuse the threads, use queues, etc.
+ *
+ * @author James Duncan Davidson
+ * @author Jason Hunter
+ * @author James Todd
+ * @author Costin Manolache
+ * @author Gal Shachor
+ * @author Yoav Shapira
+ * @author Remy Maucherat
+ */
+public class JIoEndpoint {
+
+
+ // ----------------------------------------------------------------- Fields
+
+
+ /**
+ * Available workers.
+ */
+ protected WorkerStack workers = null;
+
+
+ /**
+ * Running state of the endpoint.
+ */
+ protected volatile boolean running = false;
+
+
+ /**
+ * Will be set to true whenever the endpoint is paused.
+ */
+ protected volatile boolean paused = false;
+
+
+ /**
+ * Track the initialization state of the endpoint.
+ */
+ protected boolean initialized = false;
+
+
+ /**
+ * Current worker threads busy count.
+ */
+ protected int curThreadsBusy = 0;
+
+
+ /**
+ * Current worker threads count.
+ */
+ protected int curThreads = 0;
+
+
+ /**
+ * Sequence number used to generate thread names.
+ */
+ protected int sequence = 0;
+
+
+ /**
+ * Associated server socket.
+ */
+ protected ServerSocket serverSocket = null;
+
+
+ // ------------------------------------------------------------- Properties
+
+
+ /**
+ * Acceptor thread count.
+ */
+ protected int acceptorThreadCount = 0;
+ public void setAcceptorThreadCount(int acceptorThreadCount) {
this.acceptorThreadCount = acceptorThreadCount; }
+ public int getAcceptorThreadCount() { return acceptorThreadCount; }
+
+
+ /**
+ * External Executor based thread pool.
+ */
+ protected Executor executor = null;
+ public void setExecutor(Executor executor) { this.executor = executor; }
+ public Executor getExecutor() { return executor; }
+
+
+ /**
+ * Maximum amount of worker threads.
+ */
+ protected int maxThreads = (org.apache.tomcat.util.Constants.LOW_MEMORY) ? 64 :
((Constants.MAX_THREADS == -1) ? 512 * Runtime.getRuntime().availableProcessors() :
Constants.MAX_THREADS);
+ public void setMaxThreads(int maxThreads) { this.maxThreads = maxThreads; }
+ public int getMaxThreads() { return maxThreads; }
+
+
+ /**
+ * Priority of the acceptor and poller threads.
+ */
+ protected int threadPriority = Thread.NORM_PRIORITY;
+ public void setThreadPriority(int threadPriority) { this.threadPriority =
threadPriority; }
+ public int getThreadPriority() { return threadPriority; }
+
+
+ /**
+ * Size of the socket poller.
+ */
+ protected int pollerSize = (org.apache.tomcat.util.Constants.LOW_MEMORY) ? 128 : (32
* 1024);
+ public void setPollerSize(int pollerSize) { this.pollerSize = pollerSize; }
+ public int getPollerSize() { return pollerSize; }
+
+
+ /**
+ * Keep-Alive timeout.
+ */
+ protected int keepAliveTimeout = -1;
+ public int getKeepAliveTimeout() { return keepAliveTimeout; }
+ public void setKeepAliveTimeout(int keepAliveTimeout) { this.keepAliveTimeout =
keepAliveTimeout; }
+
+
+ /**
+ * Server socket port.
+ */
+ protected int port;
+ public int getPort() { return port; }
+ public void setPort(int port ) { this.port=port; }
+
+
+ /**
+ * Address for the server socket.
+ */
+ protected InetAddress address;
+ public InetAddress getAddress() { return address; }
+ public void setAddress(InetAddress address) { this.address = address; }
+
+
+ /**
+ * Handling of accepted sockets.
+ */
+ protected Handler handler = null;
+ public void setHandler(Handler handler ) { this.handler = handler; }
+ public Handler getHandler() { return handler; }
+
+
+ /**
+ * Allows the server developer to specify the backlog that
+ * should be used for server sockets. By default, this value
+ * is 100.
+ */
+ protected int backlog = 100;
+ public void setBacklog(int backlog) { if (backlog > 0) this.backlog = backlog; }
+ public int getBacklog() { return backlog; }
+
+
+ /**
+ * Socket TCP no delay.
+ */
+ protected boolean tcpNoDelay = false;
+ public boolean getTcpNoDelay() { return tcpNoDelay; }
+ public void setTcpNoDelay(boolean tcpNoDelay) { this.tcpNoDelay = tcpNoDelay; }
+
+
+ /**
+ * Socket linger.
+ */
+ protected int soLinger = 100;
+ public int getSoLinger() { return soLinger; }
+ public void setSoLinger(int soLinger) { this.soLinger = soLinger; }
+
+
+ /**
+ * Socket timeout.
+ */
+ protected int soTimeout = -1;
+ public int getSoTimeout() { return soTimeout; }
+ public void setSoTimeout(int soTimeout) { this.soTimeout = soTimeout; }
+
+
+ /**
+ * The default is true - the created threads will be
+ * in daemon mode. If set to false, the control thread
+ * will not be daemon - and will keep the process alive.
+ */
+ protected boolean daemon = true;
+ public void setDaemon(boolean b) { daemon = b; }
+ public boolean getDaemon() { return daemon; }
+
+
+ /**
+ * Name of the thread pool, which will be used for naming child threads.
+ */
+ protected String name = "TP";
+ public void setName(String name) { this.name = name; }
+ public String getName() { return name; }
+
+
+ /**
+ * Server socket factory.
+ */
+ protected ServerSocketFactory serverSocketFactory = null;
+ public void setServerSocketFactory(ServerSocketFactory factory) {
this.serverSocketFactory = factory; }
+ public ServerSocketFactory getServerSocketFactory() { return serverSocketFactory; }
+
+
+ /**
+ * The socket poller used for event support.
+ */
+ protected Poller eventPoller = null;
+ public Poller getEventPoller() {
+ return eventPoller;
+ }
+
+
+ public boolean isRunning() {
+ return running;
+ }
+
+ public boolean isPaused() {
+ return paused;
+ }
+
+ public int getCurrentThreadCount() {
+ return curThreads;
+ }
+
+ public int getCurrentThreadsBusy() {
+ return workers!=null?curThreads - workers.size():0;
+ }
+
+
+ // ------------------------------------------------ Handler Inner Interface
+
+
+ /**
+ * Bare bones interface used for socket processing. Per thread data is to be
+ * stored in the ThreadWithAttributes extra folders, or alternately in
+ * thread local fields.
+ */
+ public interface Handler {
+ public enum SocketState {
+ OPEN, CLOSED, LONG
+ }
+ public SocketState process(Socket socket);
+ public SocketState event(Socket socket, SocketStatus status);
+ }
+
+
+ // --------------------------------------------------- Acceptor Inner Class
+
+
+ /**
+ * Server socket acceptor thread.
+ */
+ protected class Acceptor implements Runnable {
+
+
+ /**
+ * The background thread that listens for incoming TCP/IP connections and
+ * hands them off to an appropriate processor.
+ */
+ public void run() {
+
+ // Loop until we receive a shutdown command
+ while (running) {
+
+ // Loop if endpoint is paused
+ while (paused) {
+ try {
+ Thread.sleep(1000);
+ } catch (InterruptedException e) {
+ // Ignore
+ }
+ }
+
+ // Accept the next incoming connection from the server socket
+ try {
+ Socket socket = serverSocketFactory.acceptSocket(serverSocket);
+ serverSocketFactory.initSocket(socket);
+ // Hand this socket off to an appropriate processor
+ if (!processSocket(socket)) {
+ // Close socket right away
+ try { socket.close(); } catch (IOException e) { }
+ }
+ } catch (IOException x) {
+ if (running) {
+ CoyoteLogger.UTIL_LOGGER.errorAcceptingSocket(x);
+ }
+ } catch (Throwable t) {
+ CoyoteLogger.UTIL_LOGGER.errorAcceptingSocket(t);
+ }
+
+ // The processor will recycle itself when it finishes
+
+ }
+
+ }
+
+ }
+
+
+ // ------------------------------------------------- SocketInfo Inner Class
+
+
+ /**
+ * Socket list class, used to avoid using a possibly large amount of objects
+ * with very little actual use.
+ */
+ public static class SocketInfo {
+ public static final int RESUME = 4;
+ public static final int WAKEUP = 8;
+ public Socket socket;
+ public int timeout;
+ public int flags;
+ public boolean resume() {
+ return (flags & RESUME) == RESUME;
+ }
+ public boolean wakeup() {
+ return (flags & WAKEUP) == WAKEUP;
+ }
+ public static int merge(int flag1, int flag2) {
+ return ((flag1 & RESUME) | (flag2 & RESUME))
+ | ((flag1 & WAKEUP) & (flag2 & WAKEUP));
+ }
+ }
+
+
+ // --------------------------------------------- SocketTimeouts Inner Class
+
+
+ /**
+ * Socket list class, used to avoid using a possibly large amount of objects
+ * with very little actual use.
+ */
+ public class SocketTimeouts {
+ protected int size;
+
+ protected Socket[] sockets;
+ protected long[] timeouts;
+ protected int pos = 0;
+
+ public SocketTimeouts(int size) {
+ this.size = 0;
+ sockets = new Socket[size];
+ timeouts = new long[size];
+ }
+
+ public void add(Socket socket, long timeout) {
+ sockets[size] = socket;
+ timeouts[size] = timeout;
+ size++;
+ }
+
+ public boolean remove(Socket socket) {
+ for (int i = 0; i < size; i++) {
+ if (sockets[i] == socket) {
+ sockets[i] = sockets[size - 1];
+ timeouts[i] = timeouts[size - 1];
+ size--;
+ return true;
+ }
+ }
+ return false;
+ }
+
+ public Socket check(long date) {
+ while (pos < size) {
+ if (date >= timeouts[pos]) {
+ Socket result = sockets[pos];
+ sockets[pos] = sockets[size - 1];
+ timeouts[pos] = timeouts[size - 1];
+ size--;
+ return result;
+ }
+ pos++;
+ }
+ pos = 0;
+ return null;
+ }
+
+ }
+
+
+ // ------------------------------------------------- SocketList Inner Class
+
+
+ /**
+ * Socket list class, used to avoid using a possibly large amount of objects
+ * with very little actual use.
+ */
+ public class SocketList {
+ protected int size;
+ protected int pos;
+
+ protected Socket[] sockets;
+ protected int[] timeouts;
+ protected int[] flags;
+
+ protected SocketInfo info = new SocketInfo();
+
+ public SocketList(int size) {
+ this.size = 0;
+ pos = 0;
+ sockets = new Socket[size];
+ timeouts = new int[size];
+ flags = new int[size];
+ }
+
+ public int size() {
+ return this.size;
+ }
+
+ public SocketInfo get() {
+ if (pos == size) {
+ return null;
+ } else {
+ info.socket = sockets[pos];
+ info.timeout = timeouts[pos];
+ info.flags = flags[pos];
+ pos++;
+ return info;
+ }
+ }
+
+ public void clear() {
+ size = 0;
+ pos = 0;
+ }
+
+ public boolean add(Socket socket, int timeout, int flag) {
+ if (size == sockets.length) {
+ return false;
+ } else {
+ for (int i = 0; i < size; i++) {
+ if (sockets[i] == socket) {
+ flags[i] = SocketInfo.merge(flags[i], flag);
+ return true;
+ }
+ }
+ sockets[size] = socket;
+ timeouts[size] = timeout;
+ flags[size] = flag;
+ size++;
+ return true;
+ }
+ }
+
+ public void duplicate(SocketList copy) {
+ copy.size = size;
+ copy.pos = pos;
+ System.arraycopy(sockets, 0, copy.sockets, 0, size);
+ System.arraycopy(timeouts, 0, copy.timeouts, 0, size);
+ System.arraycopy(flags, 0, copy.flags, 0, size);
+ }
+
+ }
+
+
+ // ------------------------------------------- SocketProcessor Inner Class
+
+
+ /**
+ * This class is the equivalent of the Worker, but will simply use in an
+ * external Executor thread pool.
+ */
+ protected class SocketProcessor implements Runnable {
+
+ protected Socket socket = null;
+
+ public SocketProcessor(Socket socket) {
+ this.socket = socket;
+ }
+
+ public void run() {
+
+ // Process the request from this socket
+ if (!setSocketOptions(socket) || (handler.process(socket) ==
Handler.SocketState.CLOSED)) {
+ // Close socket
+ try { socket.close(); } catch (IOException e) { }
+ }
+
+ // Finish up this request
+ socket = null;
+
+ }
+
+ }
+
+
+ // --------------------------------------- SocketEventProcessor Inner Class
+
+
+ /**
+ * This class is the equivalent of the Worker, but will simply use in an
+ * external Executor thread pool.
+ */
+ protected class SocketEventProcessor implements Runnable {
+
+ protected Socket socket = null;
+ protected SocketStatus status = null;
+
+ public SocketEventProcessor(Socket socket, SocketStatus status) {
+ this.socket = socket;
+ this.status = status;
+ }
+
+ public void run() {
+
+ Handler.SocketState socketState = handler.event(socket, status);
+ if (socketState == Handler.SocketState.CLOSED) {
+ // Close socket
+ try { socket.close(); } catch (IOException e) { }
+ } else if (socketState == Handler.SocketState.OPEN) {
+ // Process the keepalive after the event processing
+ // This is the main behavior difference with endpoint with pollers,
which
+ // will add the socket to the poller
+ if (handler.process(socket) == Handler.SocketState.CLOSED) {
+ // Close socket
+ try { socket.close(); } catch (IOException e) { }
+ }
+ }
+ socket = null;
+
+ }
+
+ }
+
+
+ // ----------------------------------------------------- Poller Inner Class
+
+
+ /**
+ * Poller class.
+ */
+ public class Poller implements Runnable {
+
+ /**
+ * List of sockets to be added to the poller.
+ */
+ protected SocketList addList = null;
+
+ /**
+ * List of sockets to be added to the poller.
+ */
+ protected SocketList localAddList = null;
+
+ /**
+ * Structure used for storing timeouts.
+ */
+ protected SocketTimeouts timeouts = null;
+
+
+ /**
+ * Last run of maintain. Maintain will run usually every 5s.
+ */
+ protected long lastMaintain = System.currentTimeMillis();
+
+
+ /**
+ * Amount of connections inside this poller.
+ */
+ protected int connectionCount = 0;
+ public int getConnectionCount() { return connectionCount; }
+
+ public Poller() {
+ }
+
+ /**
+ * Create the poller. The java.io poller only deals with timeouts.
+ */
+ protected void init() {
+
+ timeouts = new SocketTimeouts(pollerSize);
+
+ connectionCount = 0;
+ addList = new SocketList(pollerSize);
+ localAddList = new SocketList(pollerSize);
+
+ }
+
+ /**
+ * Destroy the poller.
+ */
+ protected void destroy() {
+ // Wait for pollerTime before doing anything, so that the poller threads
+ // exit, otherwise parallel destruction of sockets which are still
+ // in the poller can cause problems
+ try {
+ synchronized (this) {
+ this.wait(2);
+ }
+ } catch (InterruptedException e) {
+ // Ignore
+ }
+ // Close all sockets in the add queue
+ SocketInfo info = addList.get();
+ while (info != null) {
+ if (!processSocket(info.socket, SocketStatus.STOP)) {
+ try { info.socket.close(); } catch (IOException e) { }
+ }
+ info = addList.get();
+ }
+ addList.clear();
+ // Close all sockets still in the poller
+ long future = System.currentTimeMillis() + Integer.MAX_VALUE;
+ Socket socket = timeouts.check(future);
+ while (socket != null) {
+ if (!processSocket(socket, SocketStatus.TIMEOUT)) {
+ try { socket.close(); } catch (IOException e) { }
+ }
+ socket = timeouts.check(future);
+ }
+ connectionCount = 0;
+ }
+
+ /**
+ * Add specified socket and associated pool to the poller. The socket will
+ * be added to a temporary array, and polled first after a maximum amount
+ * of time equal to pollTime (in most cases, latency will be much lower,
+ * however).
+ *
+ * @param socket to add to the poller
+ */
+ public void add(Socket socket, int timeout, boolean resume, boolean wakeup) {
+ if (timeout < 0) {
+ timeout = keepAliveTimeout;
+ }
+ if (timeout < 0) {
+ timeout = soTimeout;
+ }
+ if (timeout <= 0) {
+ // Always put a timeout in
+ timeout = Integer.MAX_VALUE;
+ }
+ boolean ok = false;
+ synchronized (this) {
+ // Add socket to the list. Newly added sockets will wait
+ // at most for pollTime before being polled
+ if (addList.add(socket, timeout, (resume ? SocketInfo.RESUME : 0)
+ | (wakeup ? SocketInfo.WAKEUP : 0))) {
+ ok = true;
+ this.notify();
+ }
+ }
+ if (!ok) {
+ // Can't do anything: close the socket right away
+ if (!processSocket(socket, SocketStatus.ERROR)) {
+ try { socket.close(); } catch (IOException e) { }
+ }
+ }
+ }
+
+ /**
+ * Timeout checks.
+ */
+ protected void maintain() {
+
+ long date = System.currentTimeMillis();
+ // Maintain runs at most once every 5s, although it will likely get called
more
+ if ((date - lastMaintain) < 5000L) {
+ return;
+ } else {
+ lastMaintain = date;
+ }
+ Socket socket = timeouts.check(date);
+ while (socket != null) {
+ if (!processSocket(socket, SocketStatus.TIMEOUT)) {
+ try { socket.close(); } catch (IOException e) { }
+ }
+ socket = timeouts.check(date);
+ }
+
+ }
+
+ /**
+ * The background thread that listens for incoming TCP/IP connections and
+ * hands them off to an appropriate processor.
+ */
+ public void run() {
+
+ int maintain = 0;
+ // Loop until we receive a shutdown command
+ while (running) {
+
+ // Loop if endpoint is paused
+ while (paused) {
+ try {
+ Thread.sleep(1000);
+ } catch (InterruptedException e) {
+ // Ignore
+ }
+ }
+ // Check timeouts for suspended connections if the poller is empty
+ while (connectionCount < 1 && addList.size() < 1) {
+ // Reset maintain time.
+ try {
+ if (soTimeout > 0 && running) {
+ maintain();
+ }
+ synchronized (this) {
+ this.wait(10000);
+ }
+ } catch (InterruptedException e) {
+ // Ignore
+ } catch (Throwable t) {
+ CoyoteLogger.UTIL_LOGGER.errorProcessingSocketTimeout(t);
+ }
+ }
+
+ try {
+
+ // Add sockets which are waiting to the poller
+ if (addList.size() > 0) {
+ synchronized (this) {
+ // Duplicate to another list, so that the syncing is minimal
+ addList.duplicate(localAddList);
+ addList.clear();
+ }
+ SocketInfo info = localAddList.get();
+ while (info != null) {
+ if (info.wakeup()) {
+ // Resume event if socket is present in the poller
+ if (timeouts.remove(info.socket)) {
+ if (info.resume()) {
+ if (!processSocket(info.socket,
SocketStatus.OPEN_CALLBACK)) {
+ try { info.socket.close(); } catch
(IOException e) { }
+ }
+ } else {
+ timeouts.add(info.socket,
System.currentTimeMillis() + info.timeout);
+ }
+ }
+ } else {
+ if (info.resume()) {
+ timeouts.remove(info.socket);
+ if (!processSocket(info.socket,
SocketStatus.OPEN_CALLBACK)) {
+ try { info.socket.close(); } catch (IOException
e) { }
+ }
+ } else {
+ timeouts.add(info.socket, System.currentTimeMillis()
+ info.timeout);
+ }
+ }
+ info = localAddList.get();
+ }
+ }
+
+ try {
+ Thread.sleep(2);
+ } catch (InterruptedException e) {
+ // Ignore
+ }
+
+ // Process socket timeouts
+ if (soTimeout > 0 && maintain++ > 1000 &&
running) {
+ maintain = 0;
+ maintain();
+ }
+
+ } catch (Throwable t) {
+ if (maintain == 0) {
+ CoyoteLogger.UTIL_LOGGER.errorProcessingSocketTimeout(t);
+ } else {
+ CoyoteLogger.UTIL_LOGGER.errorPollingSocketWithException(t);
+ }
+ }
+
+ }
+
+ synchronized (this) {
+ this.notifyAll();
+ }
+
+ }
+
+ }
+
+
+ // ----------------------------------------------------- Worker Inner Class
+
+
+ protected class Worker implements Runnable {
+
+ protected Thread thread = null;
+ protected boolean available = false;
+ protected Socket socket = null;
+ protected SocketStatus status = null;
+
+
+ /**
+ * Process an incoming TCP/IP connection on the specified socket. Any
+ * exception that occurs during processing must be logged and swallowed.
+ * <b>NOTE</b>: This method is called from our Connector's
thread. We
+ * must assign it to our own thread so that multiple simultaneous
+ * requests can be handled.
+ *
+ * @param socket TCP socket to process
+ */
+ protected synchronized void assign(Socket socket) {
+
+ // Wait for the Processor to get the previous Socket
+ while (available) {
+ try {
+ wait();
+ } catch (InterruptedException e) {
+ }
+ }
+
+ // Store the newly available Socket and notify our thread
+ this.socket = socket;
+ this.status = null;
+ available = true;
+ notifyAll();
+
+ }
+
+
+ protected synchronized void assign(Socket socket, SocketStatus status) {
+
+ // Wait for the Processor to get the previous Socket
+ while (available) {
+ try {
+ wait();
+ } catch (InterruptedException e) {
+ }
+ }
+
+ // Store the newly available Socket and notify our thread
+ this.socket = socket;
+ this.status = status;
+ available = true;
+ notifyAll();
+
+ }
+
+
+ /**
+ * Await a newly assigned Socket from our Connector, or
<code>null</code>
+ * if we are supposed to shut down.
+ */
+ private synchronized Socket await() {
+
+ // Wait for the Connector to provide a new Socket
+ while (!available) {
+ try {
+ wait();
+ } catch (InterruptedException e) {
+ }
+ }
+
+ // Notify the Connector that we have received this Socket
+ Socket socket = this.socket;
+ available = false;
+ notifyAll();
+
+ return (socket);
+
+ }
+
+
+
+ /**
+ * The background thread that listens for incoming TCP/IP connections and
+ * hands them off to an appropriate processor.
+ */
+ public void run() {
+
+ // Process requests until we receive a shutdown signal
+ while (running) {
+
+ // Wait for the next socket to be assigned
+ Socket socket = await();
+ if (socket == null)
+ continue;
+
+ // Process the request from this socket
+ if (status != null){
+ Handler.SocketState socketState = handler.event(socket, status);
+ if (socketState == Handler.SocketState.CLOSED) {
+ // Close socket
+ try { socket.close(); } catch (IOException e) { }
+ } else if (socketState == Handler.SocketState.OPEN) {
+ // Process the keepalive after the event processing
+ // This is the main behavior difference with endpoint with
pollers, which
+ // will add the socket to the poller
+ if (handler.process(socket) == Handler.SocketState.CLOSED) {
+ // Close socket
+ try { socket.close(); } catch (IOException e) { }
+ }
+ }
+ } else if ((status == null) && (!setSocketOptions(socket) ||
(handler.process(socket) == Handler.SocketState.CLOSED))) {
+ // Close socket
+ try { socket.close(); } catch (IOException e) { }
+ }
+
+ // Finish up this request
+ recycleWorkerThread(this);
+
+ }
+
+ }
+
+
+ /**
+ * Start the background processing thread.
+ */
+ public void start() {
+ thread = new Thread(this);
+ thread.setName(getName() + "-" + (++curThreads));
+ thread.setDaemon(true);
+ thread.start();
+ }
+
+
+ }
+
+
+ // -------------------- Public methods --------------------
+
+ public void init()
+ throws Exception {
+
+ if (initialized)
+ return;
+
+ // Initialize thread count defaults for acceptor
+ if (acceptorThreadCount == 0) {
+ acceptorThreadCount = 1;
+ }
+ if (serverSocketFactory == null) {
+ serverSocketFactory = ServerSocketFactory.getDefault();
+ }
+ if (serverSocket == null) {
+ try {
+ if (address == null) {
+ serverSocket = serverSocketFactory.createSocket(port, backlog);
+ } else {
+ serverSocket = serverSocketFactory.createSocket(port, backlog,
address);
+ }
+ } catch (BindException be) {
+ if (address == null) {
+ throw new BindException(be.getMessage() + "<null>:" +
port);
+ } else {
+ throw new BindException(be.getMessage() + " " +
+ address.toString() + ":" + port);
+ }
+ }
+ }
+ //if( serverTimeout >= 0 )
+ // serverSocket.setSoTimeout( serverTimeout );
+
+ initialized = true;
+
+ }
+
+ public void start()
+ throws Exception {
+ // Initialize socket if not done before
+ if (!initialized) {
+ init();
+ }
+ if (!running) {
+ running = true;
+ paused = false;
+
+ // Create worker collection
+ if (executor == null) {
+ workers = new WorkerStack(maxThreads);
+ }
+
+ // Start event poller thread
+ eventPoller = new Poller();
+ eventPoller.init();
+ Thread pollerThread = new Thread(eventPoller, getName() +
"-Poller");
+ pollerThread.setPriority(threadPriority);
+ pollerThread.setDaemon(true);
+ pollerThread.start();
+
+ // Start acceptor threads
+ for (int i = 0; i < acceptorThreadCount; i++) {
+ Thread acceptorThread = new Thread(new Acceptor(), getName() +
"-Acceptor-" + i);
+ acceptorThread.setPriority(threadPriority);
+ acceptorThread.setDaemon(daemon);
+ acceptorThread.start();
+ }
+ }
+ }
+
+ public void pause() {
+ if (running && !paused) {
+ paused = true;
+ unlockAccept();
+ }
+ }
+
+ public void resume() {
+ if (running) {
+ paused = false;
+ }
+ }
+
+ public void stop() {
+ if (running) {
+ running = false;
+ unlockAccept();
+ eventPoller.destroy();
+ eventPoller = null;
+ }
+ }
+
+ /**
+ * Deallocate APR memory pools, and close server socket.
+ */
+ public void destroy() throws Exception {
+ if (running) {
+ stop();
+ }
+ if (serverSocket != null) {
+ try {
+ if (serverSocket != null)
+ serverSocket.close();
+ } catch (Exception e) {
+ CoyoteLogger.UTIL_LOGGER.errorClosingSocket(e);
+ }
+ serverSocket = null;
+ }
+ initialized = false ;
+ }
+
+
+ /**
+ * Unlock the accept by using a local connection.
+ */
+ protected void unlockAccept() {
+ Socket s = null;
+ try {
+ // Need to create a connection to unlock the accept();
+ if (address == null) {
+ s = new Socket("localhost", port);
+ } else {
+ s = new Socket(address, port);
+ // setting soLinger to a small value will help shutdown the
+ // connection quicker
+ s.setSoLinger(true, 0);
+ }
+ } catch (Exception e) {
+ // Ignore
+ } finally {
+ if (s != null) {
+ try {
+ s.close();
+ } catch (Exception e) {
+ // Ignore
+ }
+ }
+ }
+ }
+
+
+ /**
+ * Set the options for the current socket.
+ */
+ protected boolean setSocketOptions(Socket socket) {
+ // Process the connection
+ int step = 1;
+ try {
+
+ // 1: Set socket options: timeout, linger, etc
+ if (soLinger >= 0) {
+ socket.setSoLinger(true, soLinger);
+ }
+ if (tcpNoDelay) {
+ socket.setTcpNoDelay(tcpNoDelay);
+ }
+ if (soTimeout > 0) {
+ socket.setSoTimeout(soTimeout);
+ }
+
+ // 2: SSL handshake
+ step = 2;
+ serverSocketFactory.handshake(socket);
+
+ } catch (Throwable t) {
+ if (step == 2) {
+ CoyoteLogger.UTIL_LOGGER.handshakeFailed(t);
+ } else {
+ CoyoteLogger.UTIL_LOGGER.unexpectedError(t);
+ }
+ // Tell to close the socket
+ return false;
+ }
+ return true;
+ }
+
+
+ /**
+ * Create (or allocate) and return an available processor for use in
+ * processing a specific HTTP request, if possible. If the maximum
+ * allowed processors have already been created and are in use, return
+ * <code>null</code> instead.
+ */
+ protected Worker createWorkerThread() {
+
+ synchronized (workers) {
+ if (workers.size() > 0) {
+ curThreadsBusy++;
+ return workers.pop();
+ }
+ if ((maxThreads > 0) && (curThreads < maxThreads)) {
+ curThreadsBusy++;
+ if (curThreadsBusy == maxThreads) {
+ CoyoteLogger.UTIL_LOGGER.maxThreadsReached(maxThreads, address,
port);
+ }
+ return (newWorkerThread());
+ } else {
+ if (maxThreads < 0) {
+ curThreadsBusy++;
+ return (newWorkerThread());
+ } else {
+ return (null);
+ }
+ }
+ }
+
+ }
+
+
+ /**
+ * Create and return a new processor suitable for processing HTTP
+ * requests and returning the corresponding responses.
+ */
+ protected Worker newWorkerThread() {
+
+ Worker workerThread = new Worker();
+ workerThread.start();
+ return (workerThread);
+
+ }
+
+
+ /**
+ * Return a new worker thread, and block while to worker is available.
+ */
+ protected Worker getWorkerThread() {
+ // Allocate a new worker thread
+ Worker workerThread = createWorkerThread();
+ if (org.apache.tomcat.util.net.Constants.WAIT_FOR_THREAD
+ || org.apache.tomcat.util.Constants.LOW_MEMORY) {
+ while (workerThread == null) {
+ try {
+ synchronized (workers) {
+ workers.wait();
+ }
+ } catch (InterruptedException e) {
+ // Ignore
+ }
+ workerThread = createWorkerThread();
+ }
+ }
+ return workerThread;
+ }
+
+
+ /**
+ * Recycle the specified Processor so that it can be used again.
+ *
+ * @param workerThread The processor to be recycled
+ */
+ protected void recycleWorkerThread(Worker workerThread) {
+ synchronized (workers) {
+ workers.push(workerThread);
+ curThreadsBusy--;
+ workers.notify();
+ }
+ }
+
+
+ /**
+ * Process given socket.
+ */
+ protected boolean processSocket(Socket socket) {
+ try {
+ if (executor == null) {
+ Worker worker = getWorkerThread();
+ if (worker != null) {
+ worker.assign(socket);
+ } else {
+ return false;
+ }
+ } else {
+ executor.execute(new SocketProcessor(socket));
+ }
+ } catch (Throwable t) {
+ // This means we got an OOM or similar creating a thread, or that
+ // the pool and its queue are full
+ CoyoteLogger.UTIL_LOGGER.errorProcessingSocket(t);
+ return false;
+ }
+ return true;
+ }
+
+
+ /**
+ * Process given socket for an event.
+ */
+ protected boolean processSocket(Socket socket, SocketStatus status) {
+ try {
+ if (executor == null) {
+ Worker worker = getWorkerThread();
+ if (worker != null) {
+ worker.assign(socket, status);
+ } else {
+ return false;
+ }
+ } else {
+ executor.execute(new SocketEventProcessor(socket, status));
+ }
+ } catch (Throwable t) {
+ // This means we got an OOM or similar creating a thread, or that
+ // the pool and its queue are full
+ CoyoteLogger.UTIL_LOGGER.errorProcessingSocket(t);
+ return false;
+ }
+ return true;
+ }
+
+
+ // ------------------------------------------------- WorkerStack Inner Class
+
+
+ public class WorkerStack {
+
+ protected Worker[] workers = null;
+ protected int end = 0;
+
+ public WorkerStack(int size) {
+ workers = new Worker[size];
+ }
+
+ /**
+ * Put the object into the queue.
+ *
+ * @param object the object to be appended to the queue (first element).
+ */
+ public void push(Worker worker) {
+ workers[end++] = worker;
+ }
+
+ /**
+ * Get the first object out of the queue. Return null if the queue
+ * is empty.
+ */
+ public Worker pop() {
+ if (end > 0) {
+ return workers[--end];
+ }
+ return null;
+ }
+
+ /**
+ * Get the first object out of the queue, Return null if the queue
+ * is empty.
+ */
+ public Worker peek() {
+ return workers[end];
+ }
+
+ /**
+ * Is the queue empty?
+ */
+ public boolean isEmpty() {
+ return (end == 0);
+ }
+
+ /**
+ * How many elements are there in this queue?
+ */
+ public int size() {
+ return (end);
+ }
+ }
+
+}
Modified: branches/7.2.x/src/main/java/org/apache/tomcat/util/net/NioEndpoint.java
===================================================================
--- branches/7.2.x/src/main/java/org/apache/tomcat/util/net/NioEndpoint.java 2012-09-25
12:49:25 UTC (rev 2086)
+++ branches/7.2.x/src/main/java/org/apache/tomcat/util/net/NioEndpoint.java 2012-09-26
13:54:12 UTC (rev 2087)
@@ -187,15 +187,15 @@
}
if (this.connections == null) {
- this.connections = new ConcurrentHashMap<>();
+ this.connections = new ConcurrentHashMap<Long, NioChannel>();
}
if (this.recycledChannelProcessors == null) {
- this.recycledChannelProcessors = new ConcurrentLinkedQueue<>();
+ this.recycledChannelProcessors = new ConcurrentLinkedQueue<ChannelProcessor>();
}
if (this.recycledHandshakeProcessors == null) {
- this.recycledHandshakeProcessors = new ConcurrentLinkedQueue<>();
+ this.recycledHandshakeProcessors = new
ConcurrentLinkedQueue<HandshakeHandler>();
}
// If the executor is not set, create it with a fixed thread pool
@@ -1199,9 +1199,9 @@
*/
public void init() {
this.mutex = new Object();
- this.channelList = new ConcurrentHashMap<>(this.size);
- this.recycledChannelList = new ConcurrentLinkedQueue<>();
- this.recycledCompletionHandlers = new ConcurrentLinkedQueue<>();
+ this.channelList = new ConcurrentHashMap<Long, ChannelInfo>(this.size);
+ this.recycledChannelList = new ConcurrentLinkedQueue<ChannelInfo>();
+ this.recycledCompletionHandlers = new
ConcurrentLinkedQueue<CompletionHandler<Integer, NioChannel>>();
}
/**
@@ -1646,8 +1646,8 @@
this.size = maxThreads;
this.mutex = new Object();
this.counter = new AtomicInteger(0);
- this.fileDatas = new ConcurrentLinkedQueue<>();
- this.recycledFileDatas = new ConcurrentLinkedQueue<>();
+ this.fileDatas = new ConcurrentLinkedQueue<SendfileData>();
+ this.recycledFileDatas = new ConcurrentLinkedQueue<SendfileData>();
}
/**
Modified: branches/7.2.x/src/main/java/org/apache/tomcat/util/net/SSLImplementation.java
===================================================================
---
branches/7.2.x/src/main/java/org/apache/tomcat/util/net/SSLImplementation.java 2012-09-25
12:49:25 UTC (rev 2086)
+++
branches/7.2.x/src/main/java/org/apache/tomcat/util/net/SSLImplementation.java 2012-09-26
13:54:12 UTC (rev 2087)
@@ -80,26 +80,6 @@
}
}
- /**
- * @return the implementation name
- */
abstract public String getImplementationName();
-
- /**
- *
- * @return a new instance of {@link NioJSSESocketChannelFactory}
- */
- public abstract NioJSSESocketChannelFactory getServerSocketChannelFactory();
-
- /**
- * @param channel
- * @return the {@link SSLSupport} attached to this channel
- */
- public abstract SSLSupport getSSLSupport(NioChannel channel);
-
- /**
- * @param session
- * @return the {@link SSLSupport} attached to this session
- */
abstract public SSLSupport getSSLSupport(SSLSession session);
}
\ No newline at end of file
Added: branches/7.2.x/src/main/java/org/apache/tomcat/util/net/ServerSocketFactory.java
===================================================================
--- branches/7.2.x/src/main/java/org/apache/tomcat/util/net/ServerSocketFactory.java
(rev 0)
+++
branches/7.2.x/src/main/java/org/apache/tomcat/util/net/ServerSocketFactory.java 2012-09-26
13:54:12 UTC (rev 2087)
@@ -0,0 +1,173 @@
+/*
+ * 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;
+
+import java.io.IOException;
+import java.net.InetAddress;
+import java.net.ServerSocket;
+import java.net.Socket;
+import java.util.Hashtable;
+
+/**
+ * This class creates server sockets. It may be subclassed by other
+ * factories, which create particular types of server sockets. This
+ * provides a general framework for the addition of public socket-level
+ * functionality. It it is the server side analogue of a socket factory,
+ * and similarly provides a way to capture a variety of policies related
+ * to the sockets being constructed.
+ *
+ * <P> Like socket factories, Server Socket factory instances have two
+ * categories of methods. First are methods used to create sockets.
+ * Second are methods which set properties used in the production of
+ * sockets, such as networking options. There is also an environment
+ * specific default server socket factory; frameworks will often use
+ * their own customized factory.
+ *
+ * <P><hr><em> It may be desirable to move this interface into the
+ * <b>java.net</b> package, so that is not an extension but the preferred
+ * interface. Should this be serializable, making it a JavaBean which can
+ * be saved along with its networking configuration?
+ * </em>
+ *
+ * @author db(a)eng.sun.com
+ * @author Harish Prabandham
+ */
+public abstract class ServerSocketFactory implements Cloneable {
+
+ //
+ // NOTE: JDK 1.1 bug in class GC, this can get collected
+ // even though it's always accessible via getDefault().
+ //
+
+ private static ServerSocketFactory theFactory;
+ protected Hashtable attributes=new Hashtable();
+
+ /**
+ * Constructor is used only by subclasses.
+ */
+
+ protected ServerSocketFactory () {
+ /* NOTHING */
+ }
+
+ /** General mechanism to pass attributes from the
+ * ServerConnector to the socket factory.
+ *
+ * Note that the "prefered" mechanism is to
+ * use bean setters and explicit methods, but
+ * this allows easy configuration via server.xml
+ * or simple Properties
+ */
+ public void setAttribute( String name, Object value ) {
+ if( name!=null && value !=null)
+ attributes.put( name, value );
+ }
+
+ /**
+ * Returns a copy of the environment's default socket factory.
+ */
+ public static synchronized ServerSocketFactory getDefault () {
+ //
+ // optimize typical case: no synch needed
+ //
+
+ if (theFactory == null) {
+ //
+ // Different implementations of this method could
+ // work rather differently. For example, driving
+ // this from a system property, or using a different
+ // implementation than JavaSoft's.
+ //
+
+ theFactory = new DefaultServerSocketFactory ();
+ }
+
+ try {
+ return (ServerSocketFactory) theFactory.clone ();
+ } catch (CloneNotSupportedException e) {
+ throw new RuntimeException (e.getMessage ());
+ }
+ }
+
+ /**
+ * Returns a server socket which uses all network interfaces on
+ * the host, and is bound to a the specified port. The socket is
+ * configured with the socket options (such as accept timeout)
+ * given to this factory.
+ *
+ * @param port the port to listen to
+ * @exception IOException for networking errors
+ * @exception InstantiationException for construction errors
+ */
+ public abstract ServerSocket createSocket (int port)
+ throws IOException, InstantiationException;
+
+ /**
+ * Returns a server socket which uses all network interfaces on
+ * the host, is bound to a the specified port, and uses the
+ * specified connection backlog. The socket is configured with
+ * the socket options (such as accept timeout) given to this factory.
+ *
+ * @param port the port to listen to
+ * @param backlog how many connections are queued
+ * @exception IOException for networking errors
+ * @exception InstantiationException for construction errors
+ */
+
+ public abstract ServerSocket createSocket (int port, int backlog)
+ throws IOException, InstantiationException;
+
+ /**
+ * Returns a server socket which uses only the specified network
+ * interface on the local host, is bound to a the specified port,
+ * and uses the specified connection backlog. The socket is configured
+ * with the socket options (such as accept timeout) given to this factory.
+ *
+ * @param port the port to listen to
+ * @param backlog how many connections are queued
+ * @param ifAddress the network interface address to use
+ * @exception IOException for networking errors
+ * @exception InstantiationException for construction errors
+ */
+
+ public abstract ServerSocket createSocket (int port,
+ int backlog, InetAddress ifAddress)
+ throws IOException, InstantiationException;
+
+ public void initSocket( Socket s ) {
+ }
+
+ /**
+ Wrapper function for accept(). This allows us to trap and
+ translate exceptions if necessary
+
+ @exception IOException;
+ */
+ public abstract Socket acceptSocket(ServerSocket socket)
+ throws IOException;
+
+ /**
+ Extra function to initiate the handshake. Sometimes necessary
+ for SSL
+
+ @exception IOException;
+ */
+ public abstract void handshake(Socket sock)
+ throws IOException;
+}
+
Added: branches/7.2.x/src/main/java/org/apache/tomcat/util/net/jsse/JSSEFactory.java
===================================================================
--- branches/7.2.x/src/main/java/org/apache/tomcat/util/net/jsse/JSSEFactory.java
(rev 0)
+++
branches/7.2.x/src/main/java/org/apache/tomcat/util/net/jsse/JSSEFactory.java 2012-09-26
13:54:12 UTC (rev 2087)
@@ -0,0 +1,56 @@
+/*
+ * 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.jsse;
+
+import java.net.Socket;
+
+import javax.net.ssl.SSLSocket;
+
+import org.apache.tomcat.util.net.SSLSupport;
+import org.apache.tomcat.util.net.ServerSocketFactory;
+import javax.net.ssl.SSLSession;
+
+/**
+ * Factory interface to construct components based on the JSSE version
+ * in use.
+ *
+ * @author Bill Barker
+ * @author Filip Hanik
+ */
+
+public class JSSEFactory {
+
+ /**
+ * Returns the ServerSocketFactory to use.
+ */
+ public ServerSocketFactory getSocketFactory() {
+ return new JSSESocketFactory();
+ }
+
+ /**
+ * returns the SSLSupport attached to this socket.
+ */
+ public SSLSupport getSSLSupport(Socket socket) {
+ return new JSSESupport((SSLSocket)socket);
+ }
+
+ public SSLSupport getSSLSupport(SSLSession session) {
+ return new JSSESupport(session);
+ }
+
+};
Added:
branches/7.2.x/src/main/java/org/apache/tomcat/util/net/jsse/JSSEImplementation.java
===================================================================
--- branches/7.2.x/src/main/java/org/apache/tomcat/util/net/jsse/JSSEImplementation.java
(rev 0)
+++
branches/7.2.x/src/main/java/org/apache/tomcat/util/net/jsse/JSSEImplementation.java 2012-09-26
13:54:12 UTC (rev 2087)
@@ -0,0 +1,71 @@
+/*
+ * 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.jsse;
+
+import java.net.Socket;
+
+import org.apache.tomcat.util.net.NioChannel;
+import org.apache.tomcat.util.net.SSLImplementation;
+import org.apache.tomcat.util.net.SSLSupport;
+import org.apache.tomcat.util.net.ServerSocketFactory;
+import javax.net.ssl.SSLSession;
+
+/* JSSEImplementation:
+
+ Concrete implementation class for JSSE
+
+ @author EKR
+*/
+
+public class JSSEImplementation extends SSLImplementation
+{
+ static final String SSLSocketClass = "javax.net.ssl.SSLSocket";
+
+ static org.jboss.logging.Logger logger =
+ org.jboss.logging.Logger.getLogger(JSSEImplementation.class);
+
+ private JSSEFactory factory = null;
+
+ public JSSEImplementation() throws ClassNotFoundException {
+ // Check to see if JSSE is floating around somewhere
+ Class.forName(SSLSocketClass);
+ factory = new JSSEFactory();
+ }
+
+
+ public String getImplementationName(){
+ return "JSSE";
+ }
+
+ public ServerSocketFactory getServerSocketFactory() {
+ ServerSocketFactory ssf = factory.getSocketFactory();
+ return ssf;
+ }
+
+ public SSLSupport getSSLSupport(Socket s) {
+ SSLSupport ssls = factory.getSSLSupport(s);
+ return ssls;
+ }
+
+ public SSLSupport getSSLSupport(SSLSession session) {
+ SSLSupport ssls = factory.getSSLSupport(session);
+ return ssls;
+ }
+
+
+}
Added:
branches/7.2.x/src/main/java/org/apache/tomcat/util/net/jsse/JSSESocketFactory.java
===================================================================
--- branches/7.2.x/src/main/java/org/apache/tomcat/util/net/jsse/JSSESocketFactory.java
(rev 0)
+++
branches/7.2.x/src/main/java/org/apache/tomcat/util/net/jsse/JSSESocketFactory.java 2012-09-26
13:54:12 UTC (rev 2087)
@@ -0,0 +1,805 @@
+/*
+ * 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.jsse;
+
+import static org.jboss.web.CoyoteMessages.MESSAGES;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.InetAddress;
+import java.net.ServerSocket;
+import java.net.Socket;
+import java.net.SocketException;
+import java.security.KeyManagementException;
+import java.security.KeyStore;
+import java.security.NoSuchAlgorithmException;
+import java.security.SecureRandom;
+import java.security.cert.CRL;
+import java.security.cert.CRLException;
+import java.security.cert.CertPathParameters;
+import java.security.cert.CertStore;
+import java.security.cert.CertStoreParameters;
+import java.security.cert.CertificateException;
+import java.security.cert.CertificateFactory;
+import java.security.cert.CollectionCertStoreParameters;
+import java.security.cert.PKIXBuilderParameters;
+import java.security.cert.X509CertSelector;
+import java.util.Collection;
+import java.util.Locale;
+import java.util.Vector;
+
+import javax.net.ssl.CertPathTrustManagerParameters;
+import javax.net.ssl.KeyManager;
+import javax.net.ssl.KeyManagerFactory;
+import javax.net.ssl.ManagerFactoryParameters;
+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;
+import javax.net.ssl.TrustManagerFactory;
+import javax.net.ssl.X509KeyManager;
+
+import org.jboss.web.CoyoteLogger;
+
+/*
+ 1. Make the JSSE's jars available, either as an installed
+ extension (copy them into jre/lib/ext) or by adding
+ them to the Tomcat classpath.
+ 2. keytool -genkey -alias tomcat -keyalg RSA
+ Use "changeit" as password ( this is the default we use )
+ */
+
+/**
+ * SSL server socket factory. It _requires_ a valid RSA key and
+ * JSSE.
+ *
+ * @author Harish Prabandham
+ * @author Costin Manolache
+ * @author Stefan Freyr Stefansson
+ * @author EKR -- renamed to JSSESocketFactory
+ * @author Jan Luehe
+ * @author Bill Barker
+ */
+public class JSSESocketFactory
+ extends org.apache.tomcat.util.net.ServerSocketFactory {
+
+ private static final boolean RFC_5746_SUPPORTED;
+ // defaults
+ static String defaultProtocol = "TLS";
+ static boolean defaultClientAuth = false;
+ static String defaultKeystoreType = "JKS";
+ private static final String defaultKeystoreFile
+ = System.getProperty("user.home") + "/.keystore";
+ private static final String defaultKeyPass = "changeit";
+ private static final int defaultSessionCacheSize = 0;
+ private static final int defaultSessionTimeout = 86400;
+
+ static org.jboss.logging.Logger log =
+ org.jboss.logging.Logger.getLogger(JSSESocketFactory.class);
+
+ static {
+ boolean result = false;
+ SSLContext context;
+ try {
+ context = SSLContext.getInstance("TLS");
+ context.init(null, null, new SecureRandom());
+ SSLServerSocketFactory ssf = context.getServerSocketFactory();
+ String ciphers[] = ssf.getSupportedCipherSuites();
+ for (String cipher : ciphers) {
+ if ("TLS_EMPTY_RENEGOTIATION_INFO_SCSV".equals(cipher)) {
+ result = true;
+ break;
+ }
+ }
+ } catch (NoSuchAlgorithmException e) {
+ // Assume no RFC 5746 support
+ } catch (KeyManagementException e) {
+ // Assume no RFC 5746 support
+ }
+ RFC_5746_SUPPORTED = result;
+ }
+
+ protected boolean initialized;
+ protected String clientAuth = "false";
+ protected SSLServerSocketFactory sslProxy = null;
+ protected String[] enabledCiphers;
+ protected boolean allowUnsafeLegacyRenegotiation = false;
+
+ /**
+ * Flag to state that we require client authentication.
+ */
+ protected boolean requireClientAuth = false;
+
+ /**
+ * Flag to state that we would like client authentication.
+ */
+ protected boolean wantClientAuth = false;
+
+
+ public JSSESocketFactory () {
+ }
+
+ public ServerSocket createSocket (int port)
+ throws IOException
+ {
+ if (!initialized) init();
+ ServerSocket socket = sslProxy.createServerSocket(port);
+ initServerSocket(socket);
+ return socket;
+ }
+
+ public ServerSocket createSocket (int port, int backlog)
+ throws IOException
+ {
+ if (!initialized) init();
+ ServerSocket socket = sslProxy.createServerSocket(port, backlog);
+ initServerSocket(socket);
+ return socket;
+ }
+
+ public ServerSocket createSocket (int port, int backlog,
+ InetAddress ifAddress)
+ throws IOException
+ {
+ if (!initialized) init();
+ ServerSocket socket = sslProxy.createServerSocket(port, backlog,
+ ifAddress);
+ initServerSocket(socket);
+ return socket;
+ }
+
+ public Socket acceptSocket(ServerSocket socket)
+ throws IOException
+ {
+ SSLSocket asock = null;
+ try {
+ asock = (SSLSocket)socket.accept();
+ } catch (SSLException e){
+ throw new SocketException("SSL handshake error" + e.toString());
+ }
+ return asock;
+ }
+
+ public void handshake(Socket sock) throws IOException {
+ // 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");
+
+ if (!allowUnsafeLegacyRenegotiation && !RFC_5746_SUPPORTED) {
+ // Prevent further handshakes by removing all cipher suites
+ ((SSLSocket) sock).setEnabledCipherSuites(new String[0]);
+ }
+ }
+
+ /*
+ * Determines the SSL cipher suites to be enabled.
+ *
+ * @param requestedCiphers Comma-separated list of requested ciphers
+ * @param supportedCiphers Array of supported ciphers
+ *
+ * @return Array of SSL cipher suites to be enabled, or null if none of the
+ * requested ciphers are supported
+ */
+ protected String[] getEnabledCiphers(String requestedCiphers,
+ String[] supportedCiphers) {
+
+ String[] enabledCiphers = null;
+
+ if (requestedCiphers != null) {
+ Vector vec = null;
+ String cipher = requestedCiphers;
+ int index = requestedCiphers.indexOf(',');
+ if (index != -1) {
+ int fromIndex = 0;
+ while (index != -1) {
+ cipher = requestedCiphers.substring(fromIndex, index).trim();
+ if (cipher.length() > 0) {
+ /*
+ * Check to see if the requested cipher is among the
+ * supported ciphers, i.e., may be enabled
+ */
+ for (int i=0; supportedCiphers != null
+ && i<supportedCiphers.length; i++) {
+ if (supportedCiphers[i].equals(cipher)) {
+ if (vec == null) {
+ vec = new Vector();
+ }
+ vec.addElement(cipher);
+ break;
+ }
+ }
+ }
+ fromIndex = index+1;
+ index = requestedCiphers.indexOf(',', fromIndex);
+ } // while
+ cipher = requestedCiphers.substring(fromIndex);
+ }
+
+ if (cipher != null) {
+ cipher = cipher.trim();
+ if (cipher.length() > 0) {
+ /*
+ * Check to see if the requested cipher is among the
+ * supported ciphers, i.e., may be enabled
+ */
+ for (int i=0; supportedCiphers != null
+ && i<supportedCiphers.length; i++) {
+ if (supportedCiphers[i].equals(cipher)) {
+ if (vec == null) {
+ vec = new Vector();
+ }
+ vec.addElement(cipher);
+ break;
+ }
+ }
+ }
+ }
+
+ if (vec != null) {
+ enabledCiphers = new String[vec.size()];
+ vec.copyInto(enabledCiphers);
+ }
+ } else {
+ enabledCiphers = sslProxy.getDefaultCipherSuites();
+ }
+
+ return enabledCiphers;
+ }
+
+ /*
+ * Gets the SSL server's keystore password.
+ */
+ protected String getKeystorePassword() {
+ String keyPass = (String)attributes.get("keypass");
+ if (keyPass == null) {
+ keyPass = defaultKeyPass;
+ }
+ String keystorePass = (String)attributes.get("keystorePass");
+ if (keystorePass == null) {
+ keystorePass = keyPass;
+ }
+ return keystorePass;
+ }
+
+ /*
+ * Gets the SSL server's keystore.
+ */
+ protected KeyStore getKeystore(String type, String provider, String pass)
+ throws IOException {
+
+ String keystoreFile = (String)attributes.get("keystore");
+ if (keystoreFile == null)
+ keystoreFile = defaultKeystoreFile;
+
+ return getStore(type, provider, keystoreFile, pass);
+ }
+
+ /*
+ * Gets the SSL server's truststore.
+ */
+ protected KeyStore getTrustStore(String keystoreType,
+ String keystoreProvider) throws IOException {
+ KeyStore trustStore = null;
+
+ String truststoreFile = (String)attributes.get("truststoreFile");
+ if(truststoreFile == null) {
+ truststoreFile = System.getProperty("javax.net.ssl.trustStore");
+ }
+ if(log.isDebugEnabled()) {
+ log.debug("Truststore = " + truststoreFile);
+ }
+ String truststorePassword = (String)attributes.get("truststorePass");
+ if( truststorePassword == null) {
+ truststorePassword =
System.getProperty("javax.net.ssl.trustStorePassword");
+ }
+ if(log.isDebugEnabled()) {
+ log.debug("TrustPass = " + truststorePassword);
+ }
+ String truststoreType = (String)attributes.get("truststoreType");
+ if( truststoreType == null) {
+ truststoreType =
System.getProperty("javax.net.ssl.trustStoreType");
+ }
+ if(truststoreType == null) {
+ truststoreType = keystoreType;
+ }
+ if(log.isDebugEnabled()) {
+ log.debug("trustType = " + truststoreType);
+ }
+ String truststoreProvider =
+ (String)attributes.get("truststoreProvider");
+ if( truststoreProvider == null) {
+ truststoreProvider =
+ System.getProperty("javax.net.ssl.trustStoreProvider");
+ }
+ if (truststoreProvider == null) {
+ truststoreProvider = keystoreProvider;
+ }
+ if(log.isDebugEnabled()) {
+ log.debug("trustProvider = " + truststoreProvider);
+ }
+
+ if (truststoreFile != null){
+ trustStore = getStore(truststoreType, truststoreProvider,
+ truststoreFile, truststorePassword);
+ }
+
+ return trustStore;
+ }
+
+ /*
+ * Gets the key- or truststore with the specified type, path, and password.
+ */
+ private KeyStore getStore(String type, String provider, String path,
+ String pass) throws IOException {
+
+ KeyStore ks = null;
+ InputStream istream = null;
+ try {
+ if (provider == null) {
+ ks = KeyStore.getInstance(type);
+ } else {
+ ks = KeyStore.getInstance(type, provider);
+ }
+ if(!("PKCS11".equalsIgnoreCase(type) ||
"".equalsIgnoreCase(path))) {
+ File keyStoreFile = new File(path);
+ if (!keyStoreFile.isAbsolute()) {
+ keyStoreFile = new
File(System.getProperty("catalina.base"),
+ path);
+ }
+ istream = new FileInputStream(keyStoreFile);
+ }
+
+ char[] storePass = null;
+ if (pass != null) {
+ storePass = pass.toCharArray();
+ }
+ ks.load(istream, storePass);
+ } catch (FileNotFoundException fnfe) {
+ CoyoteLogger.UTIL_LOGGER.errorLoadingKeystore(type, path,
fnfe.getMessage());
+ throw fnfe;
+ } catch (IOException ioe) {
+ CoyoteLogger.UTIL_LOGGER.errorLoadingKeystoreWithException(type, path,
ioe.getMessage(), ioe);
+ throw ioe;
+ } catch(Exception ex) {
+ CoyoteLogger.UTIL_LOGGER.errorLoadingKeystoreWithException(type, path,
ex.getMessage(), ex);
+ throw new IOException(ex);
+ } finally {
+ if (istream != null) {
+ try {
+ istream.close();
+ } catch (IOException ioe) {
+ // Do nothing
+ }
+ }
+ }
+
+ return ks;
+ }
+
+ /**
+ * Reads the keystore and initializes the SSL socket factory.
+ */
+ void init() throws IOException {
+ try {
+
+ String clientAuthStr = (String) attributes.get("clientauth");
+ if("true".equalsIgnoreCase(clientAuthStr) ||
+ "yes".equalsIgnoreCase(clientAuthStr)) {
+ requireClientAuth = true;
+ } else if("want".equalsIgnoreCase(clientAuthStr)) {
+ wantClientAuth = true;
+ }
+
+ // SSL protocol variant (e.g., TLS, SSL v3, etc.)
+ String protocol = (String) attributes.get("protocol");
+ if (protocol == null) {
+ protocol = defaultProtocol;
+ }
+
+ // Certificate encoding algorithm (e.g., SunX509)
+ String algorithm = (String) attributes.get("algorithm");
+ if (algorithm == null) {
+ algorithm = KeyManagerFactory.getDefaultAlgorithm();;
+ }
+
+ String keystoreType = (String) attributes.get("keystoreType");
+ if (keystoreType == null) {
+ keystoreType = defaultKeystoreType;
+ }
+
+ String keystoreProvider =
+ (String) attributes.get("keystoreProvider");
+
+ String trustAlgorithm =
+ (String)attributes.get("truststoreAlgorithm");
+ if( trustAlgorithm == null ) {
+ trustAlgorithm = TrustManagerFactory.getDefaultAlgorithm();
+ }
+
+ // Create and init SSLContext
+ SSLContext context = (SSLContext) attributes.get("SSLContext");
+ if (context == null) {
+ context = SSLContext.getInstance(protocol);
+ context.init(
+ getKeyManagers(keystoreType, keystoreProvider,
+ algorithm, (String)
attributes.get("keyAlias")),
+ getTrustManagers(keystoreType, keystoreProvider,
+ trustAlgorithm), new SecureRandom());
+ }
+
+ // Configure SSL session cache
+ int sessionCacheSize;
+ if (attributes.get("sessionCacheSize") != null) {
+ sessionCacheSize = Integer.parseInt(
+ (String)attributes.get("sessionCacheSize"));
+ } else {
+ sessionCacheSize = defaultSessionCacheSize;
+ }
+ int sessionCacheTimeout;
+ if (attributes.get("sessionCacheTimeout") != null) {
+ sessionCacheTimeout = Integer.parseInt(
+ (String)attributes.get("sessionCacheTimeout"));
+ } else {
+ sessionCacheTimeout = defaultSessionTimeout;
+ }
+ SSLSessionContext sessionContext =
+ context.getServerSessionContext();
+ if (sessionContext != null) {
+ sessionContext.setSessionCacheSize(sessionCacheSize);
+ sessionContext.setSessionTimeout(sessionCacheTimeout);
+ }
+
+ // create proxy
+ sslProxy = context.getServerSocketFactory();
+
+ // Determine which cipher suites to enable
+ String requestedCiphers = (String)attributes.get("ciphers");
+ enabledCiphers = getEnabledCiphers(requestedCiphers,
+ sslProxy.getSupportedCipherSuites());
+
+ allowUnsafeLegacyRenegotiation =
+
"true".equals(attributes.get("allowUnsafeLegacyRenegotiation"));
+
+ // Check the SSL config is OK
+ checkConfig();
+
+ } catch(Exception e) {
+ if( e instanceof IOException )
+ throw (IOException)e;
+ throw new IOException(e.getMessage());
+ }
+ }
+
+ /**
+ * Gets the initialized key managers.
+ */
+ protected KeyManager[] getKeyManagers(String keystoreType,
+ String keystoreProvider,
+ String algorithm,
+ String keyAlias)
+ throws Exception {
+
+ KeyManager[] kms = null;
+
+ String keystorePass = getKeystorePassword();
+
+ KeyStore ks = getKeystore(keystoreType, keystoreProvider, keystorePass);
+ if (keyAlias != null && !ks.isKeyEntry(keyAlias)) {
+ throw new IOException(MESSAGES.noKeyAlias(keyAlias));
+ }
+
+ KeyManagerFactory kmf = KeyManagerFactory.getInstance(algorithm);
+ kmf.init(ks, keystorePass.toCharArray());
+
+ kms = kmf.getKeyManagers();
+ if (keyAlias != null) {
+ if (JSSESocketFactory.defaultKeystoreType.equals(keystoreType)) {
+ keyAlias = keyAlias.toLowerCase(Locale.ENGLISH);
+ }
+ for(int i=0; i<kms.length; i++) {
+ kms[i] = new JSSEKeyManager((X509KeyManager)kms[i], keyAlias);
+ }
+ }
+
+ return kms;
+ }
+
+ /**
+ * Gets the intialized trust managers.
+ */
+ protected TrustManager[] getTrustManagers(String keystoreType,
+ String keystoreProvider, String algorithm)
+ throws Exception {
+ String crlf = (String) attributes.get("crlFile");
+
+ TrustManager[] tms = null;
+
+ KeyStore trustStore = getTrustStore(keystoreType, keystoreProvider);
+ if (trustStore != null) {
+ if (crlf == null) {
+ TrustManagerFactory tmf = TrustManagerFactory.getInstance(algorithm);
+ tmf.init(trustStore);
+ tms = tmf.getTrustManagers();
+ } else {
+ TrustManagerFactory tmf = TrustManagerFactory.getInstance(algorithm);
+ CertPathParameters params = getParameters(algorithm, crlf, trustStore);
+ ManagerFactoryParameters mfp = new
CertPathTrustManagerParameters(params);
+ tmf.init(mfp);
+ tms = tmf.getTrustManagers();
+ }
+ }
+
+ return tms;
+ }
+
+ /**
+ * Return the initialization parameters for the TrustManager.
+ * Currently, only the default <code>PKIX</code> is supported.
+ *
+ * @param algorithm The algorithm to get parameters for.
+ * @param crlf The path to the CRL file.
+ * @param trustStore The configured TrustStore.
+ * @return The parameters including the CRLs and TrustStore.
+ */
+ protected CertPathParameters getParameters(String algorithm,
+ String crlf,
+ KeyStore trustStore)
+ throws Exception {
+ CertPathParameters params = null;
+ if("PKIX".equalsIgnoreCase(algorithm)) {
+ PKIXBuilderParameters xparams = new PKIXBuilderParameters(trustStore,
+ new
X509CertSelector());
+ Collection crls = getCRLs(crlf);
+ CertStoreParameters csp = new CollectionCertStoreParameters(crls);
+ CertStore store = CertStore.getInstance("Collection", csp);
+ xparams.addCertStore(store);
+ xparams.setRevocationEnabled(true);
+ String trustLength = (String)attributes.get("trustMaxCertLength");
+ if(trustLength != null) {
+ try {
+ xparams.setMaxPathLength(Integer.parseInt(trustLength));
+ } catch(Exception ex) {
+ log.warn("Bad maxCertLength: "+trustLength);
+ }
+ }
+
+ params = xparams;
+ } else {
+ throw new CRLException("CRLs not supported for type: "+algorithm);
+ }
+ return params;
+ }
+
+
+ /**
+ * Load the collection of CRLs.
+ *
+ */
+ protected Collection<? extends CRL> getCRLs(String crlf)
+ throws IOException, CRLException, CertificateException {
+
+ File crlFile = new File(crlf);
+ if( !crlFile.isAbsolute() ) {
+ crlFile = new File(System.getProperty("catalina.base"), crlf);
+ }
+ Collection<? extends CRL> crls = null;
+ InputStream is = null;
+ try {
+ CertificateFactory cf = CertificateFactory.getInstance("X.509");
+ is = new FileInputStream(crlFile);
+ crls = cf.generateCRLs(is);
+ } catch(IOException iex) {
+ throw iex;
+ } catch(CRLException crle) {
+ throw crle;
+ } catch(CertificateException ce) {
+ throw ce;
+ } finally {
+ if(is != null) {
+ try{
+ is.close();
+ } catch(Exception ex) {
+ }
+ }
+ }
+ return crls;
+ }
+
+ /**
+ * Set the SSL protocol variants to be enabled.
+ * @param socket the SSLServerSocket.
+ * @param protocols the protocols to use.
+ */
+ protected void setEnabledProtocols(SSLServerSocket socket, String []protocols){
+ if (protocols != null) {
+ socket.setEnabledProtocols(protocols);
+ }
+ }
+
+ /**
+ * Determines the SSL protocol variants to be enabled.
+ *
+ * @param socket The socket to get supported list from.
+ * @param requestedProtocols Comma-separated list of requested SSL
+ * protocol variants
+ *
+ * @return Array of SSL protocol variants to be enabled, or null if none of
+ * the requested protocol variants are supported
+ */
+ protected String[] getEnabledProtocols(SSLServerSocket socket,
+ String requestedProtocols){
+ String[] supportedProtocols = socket.getSupportedProtocols();
+
+ String[] enabledProtocols = null;
+
+ if (requestedProtocols != null) {
+ Vector vec = null;
+ String protocol = requestedProtocols;
+ int index = requestedProtocols.indexOf(',');
+ if (index != -1) {
+ int fromIndex = 0;
+ while (index != -1) {
+ protocol = requestedProtocols.substring(fromIndex, index).trim();
+ if (protocol.length() > 0) {
+ /*
+ * Check to see if the requested protocol is among the
+ * supported protocols, i.e., may be enabled
+ */
+ for (int i=0; supportedProtocols != null
+ && i<supportedProtocols.length; i++) {
+ if (supportedProtocols[i].equals(protocol)) {
+ if (vec == null) {
+ vec = new Vector();
+ }
+ vec.addElement(protocol);
+ break;
+ }
+ }
+ }
+ fromIndex = index+1;
+ index = requestedProtocols.indexOf(',', fromIndex);
+ } // while
+ protocol = requestedProtocols.substring(fromIndex);
+ }
+
+ if (protocol != null) {
+ protocol = protocol.trim();
+ if (protocol.length() > 0) {
+ /*
+ * Check to see if the requested protocol is among the
+ * supported protocols, i.e., may be enabled
+ */
+ for (int i=0; supportedProtocols != null
+ && i<supportedProtocols.length; i++) {
+ if (supportedProtocols[i].equals(protocol)) {
+ if (vec == null) {
+ vec = new Vector();
+ }
+ vec.addElement(protocol);
+ break;
+ }
+ }
+ }
+ }
+
+ if (vec != null) {
+ enabledProtocols = new String[vec.size()];
+ vec.copyInto(enabledProtocols);
+ }
+ }
+
+ return enabledProtocols;
+ }
+
+ /**
+ * Configure Client authentication for this version of JSSE. The
+ * JSSE included in Java 1.4 supports the 'want' value. Prior
+ * versions of JSSE will treat 'want' as 'false'.
+ * @param socket the SSLServerSocket
+ */
+ protected void configureClientAuth(SSLServerSocket socket){
+ if (wantClientAuth){
+ socket.setWantClientAuth(wantClientAuth);
+ } else {
+ socket.setNeedClientAuth(requireClientAuth);
+ }
+ }
+
+ /**
+ * Configure Client authentication for this version of JSSE. The
+ * JSSE included in Java 1.4 supports the 'want' value. Prior
+ * versions of JSSE will treat 'want' as 'false'.
+ * @param socket the SSLSocket
+ */
+ protected void configureClientAuth(SSLSocket socket){
+ // Per JavaDocs: SSLSockets returned from
+ // SSLServerSocket.accept() inherit this setting.
+ }
+
+ /**
+ * Configures the given SSL server socket with the requested cipher suites,
+ * protocol versions, and need for client authentication
+ */
+ private void initServerSocket(ServerSocket ssocket) {
+
+ SSLServerSocket socket = (SSLServerSocket) ssocket;
+
+ if (enabledCiphers != null) {
+ socket.setEnabledCipherSuites(enabledCiphers);
+ }
+
+ String requestedProtocols = (String) attributes.get("protocols");
+ setEnabledProtocols(socket, getEnabledProtocols(socket,
+ requestedProtocols));
+
+ // we don't know if client auth is needed -
+ // after parsing the request we may re-handshake
+ configureClientAuth(socket);
+ }
+
+ /**
+ * Checks that the certificate is compatible with the enabled cipher suites.
+ * If we don't check now, the JIoEndpoint can enter a nasty logging loop.
+ * See bug 45528.
+ */
+ private void checkConfig() throws IOException {
+ // Create an unbound server socket
+ ServerSocket socket = sslProxy.createServerSocket();
+ initServerSocket(socket);
+
+ try {
+ // Set the timeout to 1ms as all we care about is if it throws an
+ // SSLException on accept.
+ socket.setSoTimeout(1);
+
+ socket.accept();
+ // Will never get here - no client can connect to an unbound port
+ } catch (SSLException ssle) {
+ // SSL configuration is invalid. Possibly cert doesn't match ciphers
+ IOException ioe = new
IOException(MESSAGES.invalidSSLConfiguration(ssle.getMessage()));
+ ioe.initCause(ssle);
+ throw ioe;
+ } catch (Exception e) {
+ /*
+ * Possible ways of getting here
+ * socket.accept() throws a SecurityException
+ * socket.setSoTimeout() throws a SocketException
+ * socket.accept() throws some other exception (after a JDK change)
+ * In these cases the test won't work so carry on - essentially
+ * the behaviour before this patch
+ * socket.accept() throws a SocketTimeoutException
+ * In this case all is well so carry on
+ */
+ } finally {
+ // Should be open here but just in case
+ if (!socket.isClosed()) {
+ socket.close();
+ }
+ }
+
+ }
+
+}
Added: branches/7.2.x/src/main/java/org/apache/tomcat/util/net/jsse/JSSESupport.java
===================================================================
--- branches/7.2.x/src/main/java/org/apache/tomcat/util/net/jsse/JSSESupport.java
(rev 0)
+++
branches/7.2.x/src/main/java/org/apache/tomcat/util/net/jsse/JSSESupport.java 2012-09-26
13:54:12 UTC (rev 2087)
@@ -0,0 +1,246 @@
+/*
+ * 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.jsse;
+
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.SocketException;
+import java.security.cert.Certificate;
+import java.security.cert.CertificateFactory;
+
+import javax.net.ssl.HandshakeCompletedEvent;
+import javax.net.ssl.HandshakeCompletedListener;
+import javax.net.ssl.SSLException;
+import javax.net.ssl.SSLSession;
+import javax.net.ssl.SSLSocket;
+import javax.security.cert.X509Certificate;
+
+import org.apache.tomcat.util.net.Constants;
+import org.apache.tomcat.util.net.SSLSupport;
+
+/** JSSESupport
+
+ Concrete implementation class for JSSE
+ Support classes.
+
+ This will only work with JDK 1.2 and up since it
+ depends on JDK 1.2's certificate support
+
+ @author EKR
+ @author Craig R. McClanahan
+ @author Filip Hanik
+ Parts cribbed from JSSECertCompat
+ Parts cribbed from CertificatesValve
+*/
+
+class JSSESupport implements SSLSupport {
+
+ private static org.jboss.logging.Logger log =
+ org.jboss.logging.Logger.getLogger(JSSESupport.class);
+
+ protected SSLSocket ssl;
+ protected SSLSession session;
+
+ Listener listener = new Listener();
+
+ JSSESupport(SSLSocket sock){
+ ssl=sock;
+ session = sock.getSession();
+ sock.addHandshakeCompletedListener(listener);
+ }
+
+ JSSESupport(SSLSession session) {
+ this.session = session;
+ }
+
+ public String getCipherSuite() throws IOException {
+ // Look up the current SSLSession
+ if (session == null)
+ return null;
+ return session.getCipherSuite();
+ }
+
+ public Object[] getPeerCertificateChain()
+ throws IOException {
+ return getPeerCertificateChain(false);
+ }
+
+ protected java.security.cert.X509Certificate [] getX509Certificates(SSLSession
session)
+ throws IOException {
+ Certificate [] certs=null;
+ try {
+ certs = session.getPeerCertificates();
+ } catch( Throwable t ) {
+ log.debug("Error getting client certs",t);
+ return null;
+ }
+ if( certs==null ) return null;
+
+ java.security.cert.X509Certificate [] x509Certs =
+ new java.security.cert.X509Certificate[certs.length];
+ for(int i=0; i < certs.length; i++) {
+ if (certs[i] instanceof java.security.cert.X509Certificate ) {
+ // always currently true with the JSSE 1.1.x
+ x509Certs[i] = (java.security.cert.X509Certificate) certs[i];
+ } else {
+ try {
+ byte [] buffer = certs[i].getEncoded();
+ CertificateFactory cf =
+ CertificateFactory.getInstance("X.509");
+ ByteArrayInputStream stream =
+ new ByteArrayInputStream(buffer);
+ x509Certs[i] = (java.security.cert.X509Certificate)
cf.generateCertificate(stream);
+ } catch(Exception ex) {
+ log.info("Error translating cert " + certs[i], ex);
+ return null;
+ }
+ }
+ if(log.isTraceEnabled())
+ log.trace("Cert #" + i + " = " + x509Certs[i]);
+ }
+ if(x509Certs.length < 1)
+ return null;
+ return x509Certs;
+ }
+
+ public Object[] getPeerCertificateChain(boolean force)
+ throws IOException {
+ // Look up the current SSLSession
+ if (session == null)
+ return null;
+
+ // Convert JSSE's certificate format to the ones we need
+ X509Certificate [] jsseCerts = null;
+ try {
+ jsseCerts = session.getPeerCertificateChain();
+ } catch(Exception bex) {
+ // ignore.
+ }
+ if (jsseCerts == null)
+ jsseCerts = new X509Certificate[0];
+ if(jsseCerts.length <= 0 && force) {
+ session.invalidate();
+ handShake();
+ session = ssl.getSession();
+ }
+ return getX509Certificates(session);
+ }
+
+ protected void handShake() throws IOException {
+ if( ssl.getWantClientAuth() ) {
+ log.debug("No client cert sent for want");
+ } else {
+ ssl.setNeedClientAuth(true);
+ }
+
+ if (ssl.getEnabledCipherSuites().length == 0) {
+ // Handshake is never going to be successful.
+ // Assume this is because handshakes are disabled
+ log.warn("SSL server initiated renegotiation is disabled, closing
connection");
+ session.invalidate();
+ ssl.close();
+ return;
+ }
+
+ InputStream in = ssl.getInputStream();
+ int oldTimeout = ssl.getSoTimeout();
+ ssl.setSoTimeout(1000);
+ byte[] b = new byte[0];
+ listener.reset();
+ ssl.startHandshake();
+ int maxTries = 60; // 60 * 1000 = example 1 minute time out
+ for (int i = 0; i < maxTries; i++) {
+ if(log.isTraceEnabled())
+ log.trace("Reading for try #" +i);
+ try {
+ int x = in.read(b);
+ } catch(SSLException sslex) {
+ log.info("SSL Error getting client Certs",sslex);
+ throw sslex;
+ } catch (IOException e) {
+ // ignore - presumably the timeout
+ }
+ if (listener.completed) {
+ break;
+ }
+ }
+ ssl.setSoTimeout(oldTimeout);
+ if (listener.completed == false) {
+ throw new SocketException("SSL Cert handshake timeout");
+ }
+
+ }
+
+ /**
+ * Copied from <code>org.apache.catalina.valves.CertificateValve</code>
+ */
+ public Integer getKeySize()
+ throws IOException {
+ // Look up the current SSLSession
+ SSLSupport.CipherData c_aux[]=ciphers;
+ if (session == null)
+ return null;
+ Integer keySize = (Integer) session.getValue(Constants.KEY_SIZE_KEY);
+ if (keySize == null) {
+ int size = 0;
+ String cipherSuite = session.getCipherSuite();
+ for (int i = 0; i < c_aux.length; i++) {
+ if (cipherSuite.indexOf(c_aux[i].phrase) >= 0) {
+ size = c_aux[i].keySize;
+ break;
+ }
+ }
+ keySize = new Integer(size);
+ session.putValue(Constants.KEY_SIZE_KEY, keySize);
+ }
+ return keySize;
+ }
+
+ public String getSessionId()
+ throws IOException {
+ // Look up the current SSLSession
+ if (session == null)
+ return null;
+ // Expose ssl_session (getId)
+ byte [] ssl_session = session.getId();
+ if ( ssl_session == null)
+ return null;
+ StringBuilder buf=new StringBuilder("");
+ for(int x=0; x<ssl_session.length; x++) {
+ String digit=Integer.toHexString((int)ssl_session[x]);
+ if (digit.length()<2) buf.append('0');
+ if (digit.length()>2) digit=digit.substring(digit.length()-2);
+ buf.append(digit);
+ }
+ return buf.toString();
+ }
+
+
+ private static class Listener implements HandshakeCompletedListener {
+ volatile boolean completed = false;
+ public void handshakeCompleted(HandshakeCompletedEvent event) {
+ completed = true;
+ }
+ void reset() {
+ completed = false;
+ }
+ }
+
+}
+
Modified:
branches/7.2.x/src/main/java/org/apache/tomcat/util/net/jsse/NioJSSESocketChannelFactory.java
===================================================================
---
branches/7.2.x/src/main/java/org/apache/tomcat/util/net/jsse/NioJSSESocketChannelFactory.java 2012-09-25
12:49:25 UTC (rev 2086)
+++
branches/7.2.x/src/main/java/org/apache/tomcat/util/net/jsse/NioJSSESocketChannelFactory.java 2012-09-26
13:54:12 UTC (rev 2087)
@@ -112,7 +112,9 @@
break;
}
}
- } catch (NoSuchAlgorithmException | KeyManagementException e) {
+ } catch (NoSuchAlgorithmException e) {
+ // Assume no RFC 5746 support
+ } catch (KeyManagementException e) {
// Assume no RFC 5746 support
}
RFC_5746_SUPPORTED = result;
@@ -362,7 +364,7 @@
for (int i = 0; supportedCiphers != null && i < supportedCiphers.length;
i++) {
if (supportedCiphers[i].equals(cipher)) {
if (vec == null) {
- vec = new Vector<>();
+ vec = new Vector<Object>();
}
vec.addElement(cipher);
break;
@@ -385,7 +387,7 @@
for (int i = 0; supportedCiphers != null && i < supportedCiphers.length;
i++) {
if (supportedCiphers[i].equals(cipher)) {
if (vec == null) {
- vec = new Vector<>();
+ vec = new Vector<Object>();
}
vec.addElement(cipher);
break;
Modified: branches/7.2.x/src/main/java/org/jboss/web/CatalinaLogger.java
===================================================================
--- branches/7.2.x/src/main/java/org/jboss/web/CatalinaLogger.java 2012-09-25 12:49:25 UTC
(rev 2086)
+++ branches/7.2.x/src/main/java/org/jboss/web/CatalinaLogger.java 2012-09-26 13:54:12 UTC
(rev 2087)
@@ -653,4 +653,8 @@
@Message(id = 1140, value = "Failed protocol handler [%s] JMX
registration.")
void failedProtocolJmxRegistration(Object objectName, @Cause Throwable t);
+ @LogMessage(level = INFO)
+ @Message(id = 1141, value = "NIO 2 is not available, the java.io connector will
be used insteaf")
+ void usingJavaIoConnector();
+
}
Modified: branches/7.2.x/webapps/docs/changelog.xml
===================================================================
--- branches/7.2.x/webapps/docs/changelog.xml 2012-09-25 12:49:25 UTC (rev 2086)
+++ branches/7.2.x/webapps/docs/changelog.xml 2012-09-26 13:54:12 UTC (rev 2087)
@@ -16,7 +16,14 @@
<body>
-<section name="JBoss Web 7.7.0.Alpha1 (remm)">
+<section name="JBoss Web 7.2.0.Alpha1 (remm)">
+ <subsection name="General">
+ <changelog>
+ <add>
+ Switch to JBoss Logging for i18n. (remm)
+ </add>
+ </changelog>
+ </subsection>
<subsection name="Catalina">
<changelog>
<fix>
@@ -65,9 +72,6 @@
<add>
NIO 2 HTTP connector. (benothman)
</add>
- <add>
- Drop java.io connectors (AJP and HTTP). AJP now requires native. (remm)
- </add>
</changelog>
</subsection>
<subsection name="Jasper">