/*
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
*
* Copyright 1997-2009 Sun Microsystems, Inc. All rights reserved.
* Copyright (c) Ericsson AB, 2004-2008. All rights reserved.
*
* The contents of this file are subject to the terms of either the GNU
* General Public License Version 2 only ("GPL") or the Common Development
* and Distribution License("CDDL") (collectively, the "License"). You
* may not use this file except in compliance with the License. You can obtain
* a copy of the License at https://glassfish.dev.java.net/public/CDDL+GPL.html
* or glassfish/bootstrap/legal/LICENSE.txt. See the License for the specific
* language governing permissions and limitations under the License.
*
* When distributing the software, include this License Header Notice in each
* file and include the License file at glassfish/bootstrap/legal/LICENSE.txt.
* Sun designates this particular file as subject to the "Classpath" exception
* as provided by Sun in the GPL Version 2 section of the License file that
* accompanied this code. If applicable, add the following below the License
* Header, with the fields enclosed by brackets [] replaced by your own
* identifying information: "Portions Copyrighted [year]
* [name of copyright owner]"
*
* Contributor(s):
*
* If you wish your version of this file to be governed by only the CDDL or
* only the GPL Version 2, indicate your decision by adding "[Contributor]
* elects to include this software in this distribution under the [CDDL or GPL
* Version 2] license." If you don't indicate a single choice of license, a
* recipient has the option to distribute your version of this file under
* either the CDDL, the GPL Version 2 or to extend the choice of license to
* its licensees as provided above. However, if you add GPL Version 2 code
* and therefore, elected the GPL Version 2 license, then the option applies
* only if the new code is made subject to such option by the copyright
* holder.
*/
package com.ericsson.ssa.container;
import com.ericsson.ssa.utils.ChunkedInputStream;
import com.ericsson.ssa.utils.ContentLengthInputStream;
import org.apache.catalina.Request;
import org.apache.catalina.Response;
import org.apache.coyote.tomcat5.CoyoteRequest;
import org.apache.coyote.tomcat5.CoyoteResponse;
import org.apache.tomcat.util.buf.MessageBytes;
import org.apache.tomcat.util.http.MimeHeaders;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.StringTokenizer;
import java.util.logging.Level;
// inserted by hockey (automatic)
import java.util.logging.Logger;
import org.jvnet.glassfish.comms.util.LogUtil;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import javax.servlet.ServletRequest;
/**
* This class implements proxy functionality for proxying Tomcat HTTP requests
* to back-end servers. The proxy uses connection pooling of connections to the
* back-end servers for better performance.
*
* @author epiesan
* @since Apr 16, 2006
* @reviewed ejoelbi 2006-oct-23
* @reviewed qmaghes 2007-mars-14
*
*/
public class HttpProxy {
private static final Logger log = LogUtil.SIP_LOGGER.getLogger();
private final static String PROXY_REQUEST_BODY_NOTE = "ProxyRequestBody";
private final static String EAS_HEADER_FRONTEND_IS_SECURE = "eas_schema";
private final static String EAS_HEADER_FRONTEND_LOCAL_ADDRESS = "eas_localaddress";
private final static String EAS_HEADER_FRONTEND_LOCAL_PORT = "eas_localport";
private static HashMap<HttpHost, HttpConnectionPool> pools = new HashMap<HttpHost, HttpConnectionPool>();
private static HttpProxySettings settings;
public static final ArrayList<String> droppedRequestHeaders = new ArrayList<String>();
public static final ArrayList<String> droppedResponseHeaders = new ArrayList<String>();
/**
* The parser regex pattern to split status line into parts.
*/
public static final Pattern statusLinePattern;
static {
// Initializing dropped request headers
droppedRequestHeaders.add("connection");
droppedRequestHeaders.add("content-length");
// Initializing dropped response headers
droppedResponseHeaders.add("connection");
droppedResponseHeaders.add("transfer-encoding");
droppedResponseHeaders.add("content-length");
droppedResponseHeaders.add("content-type");
droppedResponseHeaders.add("server");
droppedResponseHeaders.add("date");
// Initializing parser patterns.
statusLinePattern = Pattern.compile("HTTP/\\d+\\.\\d+ (\\d{3}) (.*)");
}
private HttpProxy() {
// Prevent instantiation.
}
public static void setProxySettings(HttpProxySettings settings) {
HttpProxy.settings = settings;
}
/**
* Proxy the request to a host using the default number of retries.
*
* @param request
* The request.
* @param response
* The response.
* @param host
* The host to proxy the request to.
* @throws IOException
* Thrown if the response could not be sent.
*/
public static void proxy(Request request, Response response, HttpHost host)
throws IOException {
proxy(request, response, host, settings.getRetries());
}
/**
* Proxy the request to a host using the specified number of retries.
*
* @param request
* The request.
* @param response
* The response.
* @param host
* The host to proxy the request to.
* @param retries
* The number of proxy attempts.
* @throws IOException
* Thrown if the response could not be sent.
*/
public static void proxy(Request request, Response response, HttpHost host,
int retries) throws IOException {
// Obtain a connection from the connection pool for this host.
HttpConnection connection = getConnection(host);
// GLASSFISH Request/Response does not extend HttpServletRequest/Respose
// Therefore we need to cast to CoyoteRequest/Response
CoyoteRequest cRequest = (CoyoteRequest) request;
if (connection == null) {
// No connection could be obtained, so respond with 503 Service
// Unavailable
if (log.isLoggable(Level.FINE)) {
log.log(Level.FINE,
"No connection could be obtained within the " +
"configured time limit when proxying the request '" +
cRequest.getRequestURL() + "' to '" + host + "'.");
}
((CoyoteResponse) response).sendError(CoyoteResponse.SC_SERVICE_UNAVAILABLE);
return;
}
if (log.isLoggable(Level.FINER)) {
log.log(Level.FINER, "The proxy obtained a connection: " +
connection);
}
try {
// Proxy the request and retry if necessary.
if (log.isLoggable(Level.FINE)) {
log.log(Level.FINE,
"Proxying the request '" + cRequest.getMethod() + " " +
cRequest.getRequestURI() + "' to '" + host +
"' over the connection '" + connection + "'.");
}
do {
try {
proxyInternal(connection, request, response, host);
if (log.isLoggable(Level.FINE)) {
log.log(Level.FINE,
"Successfully proxied the request '" +
cRequest.getMethod() + " " +
cRequest.getRequestURI() + "' to '" + host +
"' over the connection '" + connection + "'.");
}
return;
} catch (IOException e) {
// Try again
if (log.isLoggable(Level.FINE)) {
log.log(Level.FINE,
"Failed to proxy the request'" +
cRequest.getRequestURI() + "' to '" + host +
"' over the connection '" + connection +
"'. Retries left: " + retries, e);
}
// Make sure this connection is closed!
connection.close();
retries--;
} catch (Exception e) {
// Make sure this connection is closed!
connection.close();
log.log(Level.SEVERE,
"Failed to proxy the request'" +
cRequest.getRequestURI() + "' to '" + host +
"' over the connection '" + connection +
"'. Will not retry!", e);
// Do not retry!
break;
}
} while (retries >= 0);
// Something went seriously wrong!
sendInternalError(request, response, host);
// Make sure this connection is closed!
connection.close();
} finally {
// Always release the connection when done.
if (connection != null) {
connection.release();
}
}
}
/**
* Update the request object if the request has been proxied.<br>
* The following methods on the <code>ServletRequest</code> are updated
* with the values recived at the frontend.<br>
*
* <ul>
* <li> {@link ServletRequest#isSecure()}</li>
* <li> {@link ServletRequest#getLocalAddr()}</li>
* <li> {@link ServletRequest#getLocalPort()}</li>
* <li> {@link ServletRequest#getScheme()}</li>
* </ul>
*
* For the servlet that recives the request it should <strong>not</strong>
* be seen that the request has been proxied internally in EAS. <br>
* If the request has not been proxied this method will do nothing.
*
* @param request
* The request.
*/
public static void updateRequest(Request request) {
// GLASSFISH Request/Response does not extend HttpRequest/Response so
// we need to cast to Coyote Request/Response
CoyoteRequest cRequest = (CoyoteRequest) request;
String exists = cRequest.getHeader(EAS_HEADER_FRONTEND_IS_SECURE);
if (exists == null) {
return;
}
org.apache.coyote.Request coyoteRequest = cRequest.getCoyoteRequest();
MimeHeaders mimeHeaders = coyoteRequest.getMimeHeaders();
String isSecure = cRequest.getHeader(EAS_HEADER_FRONTEND_IS_SECURE);
String localAddress = cRequest.getHeader(EAS_HEADER_FRONTEND_LOCAL_ADDRESS);
String localPort = cRequest.getHeader(EAS_HEADER_FRONTEND_LOCAL_PORT);
mimeHeaders.removeHeader(EAS_HEADER_FRONTEND_IS_SECURE);
mimeHeaders.removeHeader(EAS_HEADER_FRONTEND_LOCAL_ADDRESS);
mimeHeaders.removeHeader(EAS_HEADER_FRONTEND_LOCAL_PORT);
if ("true".equals(isSecure)) {
request.setSecure(true);
coyoteRequest.scheme()
.setBytes("https".getBytes(), 0, "https".length());
}
setFieldValue(request, Integer.parseInt(localPort), "localPort");
setFieldValue(request, localAddress, "localAddr");
}
/**
* Set a field's value
*
* @param object
* The filed's object
* @param value
* The new value
* @param fieldName
* The name of the field.
*/
private static void setFieldValue(Object object, Object value,
String fieldName) {
try {
Field f = getPrivateField(object, fieldName);
f.set(object, value);
} catch (IllegalArgumentException e) {
throw new RuntimeException(e);
} catch (IllegalAccessException e) {
throw new RuntimeException(e);
} catch (SecurityException e) {
throw new RuntimeException(e);
}
}
/**
* Gets a field from an object.
*
* @param o
* The object that has the field.
* @param fieldName
* The name of the field.
* @return An accessible field
*/
private static Field getPrivateField(Object o, String fieldName) {
final Field[] fields = o.getClass().getDeclaredFields();
for (int i = 0; i < fields.length; ++i) {
if (fieldName.equals(fields[i].getName())) {
fields[i].setAccessible(true);
return fields[i];
}
}
throw new RuntimeException("Unable to lookup field=" + fieldName +
" in object=" + o);
}
/**
* Shuts down the proxy and all its connection pools. This method should be
* called to clean up all connection pools since these are static resources.
*/
public static void shutdown() {
synchronized (pools) {
for (HttpConnectionPool pool : pools.values()) {
try {
pool.shutdown();
} catch (IllegalStateException e) {
log.log(Level.WARNING,
"Problem when shutting down proxy " +
"connection pool: " + e.getMessage());
}
}
}
}
/**
* Performs the actual proxying operation.
*
* @param connection
* The connection to proxy the request over.
* @param request
* The request.
* @param response
* Te response.
* @param host
* The host to proxy the request to.
* @throws IOException
* Thrown if any IO exception ocurrs.
* @throws HttpProxyRequestException
* Thrown if the proxy can't proxy this request due to an
* erroneous request.
*/
private static void proxyInternal(HttpConnection connection,
Request request, Response response, HttpHost host)
throws IOException {
// Make sure the connection is open.
openConnection(connection);
// Proxy the request to the back-end server.
proxyRequest(connection, request);
// Proxy the response back to the client.
int maxLoop = 3;
do {
proxyResponse(connection, response);
maxLoop--;
} while ((maxLoop > 0) &&
(((CoyoteResponse) response).getStatus() == CoyoteResponse.SC_CONTINUE));
}
/**
* Obtains a connection from the connection pool for this host. This method
* will also create a new pool for each new host that it encounters. This
* method is blocking if no connection is available in the connection pool.
*
* @param host
* The host we want a connection for.
* @return A connection to te specified host.
*/
private static HttpConnection getConnection(HttpHost host) {
// Obtain a reference to the connection pool.
HttpConnectionPool pool = getConnectionPool(host);
// Obtain a connection from the pool.
return pool.getConnection(settings.getConnectionTimeout());
}
/**
* Return a reference to the connection pool for this specific host. If no
* connection pool has been created for this host yet, this method will also
* create it and store it for later retrieval.
*
* @param host
* The host we want a connection pool reference for.
* @return The connection pool for this specific host.
*/
private static HttpConnectionPool getConnectionPool(HttpHost host) {
HttpConnectionPool pool;
synchronized (pools) {
pool = pools.get(host);
// Create a new pool if necessary.
if (pool == null) {
if (log.isLoggable(Level.FINE)) {
log.log(Level.FINE,
"Creating a new connection pool with " +
settings.getPoolSize() + " connections to host '" +
host + "'.");
}
// Create the new connection pool.
pool = new HttpConnectionPool(host, settings.getPoolSize(),
settings.getSocketTimeout());
// Store the connection pool in the pool of pools.
pools.put(host, pool);
}
}
return pool;
}
/**
* Make sure that the passed connection is opened. If the connection has gone
* stale, we reopen it.
*
* @param connection
* The connection we want opened.
* @throws IOException
* If an IO exception occurred wen opening the connection.
*/
private static void openConnection(HttpConnection connection)
throws IOException {
if (!connection.isOpen()) {
connection.open();
if (log.isLoggable(Level.FINE)) {
log.log(Level.FINE, "The connection was opened: " + connection);
}
} else {
if (connection.isStale()) {
if (log.isLoggable(Level.FINE)) {
log.log(Level.FINE,
"The connection to '" + connection.getHost() +
"' appears to be stale. Reconnecting!");
}
connection.close();
connection.open();
if (log.isLoggable(Level.FINE)) {
log.log(Level.FINE,
"The connection was reopened: " + connection);
}
}
}
}
/**
* Proxy the request to the back-end server.
*
* @param connection
* The xonnection to the back-end server.
* @param request
* The request to proxy.
* @throws IOException
* Thrown if any IO problem occurs.
*/
private static void proxyRequest(HttpConnection connection, Request request)
throws IOException {
// GLASSFISH Request/Response does not extend HttpRequest/Response so
// we need to cast to Coyote Request/Response
CoyoteRequest cRequest = (CoyoteRequest) request;
// CoyoteResponse cResponse = (CoyoteResponse) response;
String contentType = null;
int contentLength = 0;
// Emit the request line
connection.printLine(createRequestLine(request));
ArrayList<String> connectionToken = checkConnectionsToken(cRequest.getHeader(
"Connection"));
// Emit some of the headers from the original request.
Enumeration hdrNames = cRequest.getHeaderNames();
while (hdrNames.hasMoreElements()) {
String headerName = (String) hdrNames.nextElement();
String headerValue = cRequest.getHeader(headerName);
// Check for message content
if ("Content-Type".equalsIgnoreCase(headerName)) {
contentType = headerValue;
}
// Check for content length
if ("Content-Length".equalsIgnoreCase(headerName)) {
contentLength = Integer.parseInt(headerValue);
}
// If header is in the not-to-copy list, the header is
// not copied to the proxied request.
if (droppedRequestHeaders.contains(headerName.toLowerCase())) {
continue;
}
// If the header is a connection token "extended not-to-copy" list.
if ((connectionToken != null) &&
connectionToken.contains(headerName.toLowerCase())) {
continue;
}
// Emit this header.
connection.printLine(headerName + ": " + headerValue);
}
// Add EAS specific headers.
addEasHeaders(connection, request);
// If this is a retry, get the message body from the request note.
MessageContent mc = (MessageContent) request.getNote(PROXY_REQUEST_BODY_NOTE);
if (mc == null) {
// Read the entire request body.
mc = new MessageContent(getRequestInputStream(request, contentType,
contentLength));
// This is the first try, so store the message body as a request note.
// We need to store the request body somewhere for the retry mechanism
// to
// work since we will already have consumed the input during the first
// try.
request.setNote(PROXY_REQUEST_BODY_NOTE, mc);
}
// Only emit content length header if there is any content.
if (mc.getContentLength() > 0) {
connection.printLine("Content-Length: " + mc.getContentLength());
// Complete headers with a CRLF.
connection.writeLine();
// Write the request body, if any, to the remote host.
if (mc.getContentLength() > 0) {
connection.write(mc.getContentAsBytes(), 0,
mc.getContentLength());
}
} else {
// Complete headers with a CRLF.
connection.writeLine();
}
// Make sure the request is sent on the socket.
connection.flushOutputStream();
}
/**
* Proxies the response back to the client.
*
* @param connection
* The connection to read the response from.
* @param response
* The client response.
* @throws IOException
* Thrown if any IO problem occurs.
*/
private static void proxyResponse(HttpConnection connection,
Response response) throws IOException {
// GLASSFISH Request/Response does not extend HttpRequest/Response so
// we need to cast to Coyote Request/Response
CoyoteResponse cResponse = (CoyoteResponse) response;
boolean chunked = false;
boolean forcedClose = false;
int contentLength = -1;
try {
// Transfer the status code and message to the response.
int code = setStatusCode(connection, response);
if (code == CoyoteResponse.SC_CONTINUE) {
cResponse.sendAcknowledgement();
return;
}
// Parse headers
MimeHeaders headers = cResponse.getCoyoteResponse().getMimeHeaders();
while (true) {
// Read a header line.
String header = connection.readLine();
if ((header == null) || (header.trim().length() < 1)) {
break;
}
// Parse Headers
int colon = header.indexOf(":");
if (colon < 0) {
continue;
}
String hdr = header.substring(0, colon).trim();
String val = new StringBuffer(header.substring(colon + 1).trim()).toString();
// "Content-Length" and "Content-Type" should not be copied with
// setValue
// Those needs to be added by their own API's
if ("Content-Length".equalsIgnoreCase(hdr)) {
contentLength = Integer.parseInt(val);
response.getResponse().setContentLength(contentLength);
}
if ("Content-Type".equalsIgnoreCase(hdr)) {
response.getResponse().setContentType(val);
}
if ("Transfer-Encoding".equalsIgnoreCase(hdr)) {
if ("chunked".equalsIgnoreCase(val)) {
chunked = true;
}
}
if ("Connection".equalsIgnoreCase(hdr)) {
if ("close".equalsIgnoreCase(val)) {
if (log.isLoggable(Level.FINE)) {
log.log(Level.FINE,
"Back-end wants to close this connection");
}
// This connection will not be reused after this response.
forcedClose = true;
}
}
// If header is in the not-to-copy list, the header is
// not copied back to the original response
if (droppedResponseHeaders.contains(hdr.toLowerCase())) {
continue;
}
if ((hdr != null) && (val != null)) {
MessageBytes mb = headers.addValue(hdr);
mb.setString(val);
}
}
if (isResponseWithBody(code)) {
OutputStream os = response.getResponse().getOutputStream();
InputStream is = connection.getInputStream();
if (chunked) {
is = new ChunkedInputStream(is);
} else {
is = new ContentLengthInputStream(is, contentLength);
}
byte[] buffer = new byte[8192];
int length;
while ((length = is.read(buffer)) > 0) {
os.write(buffer, 0, length);
}
os.flush();
}
} finally {
if (forcedClose) {
connection.close();
}
}
}
/**
* Retrieve an InputStream to read the request body from.
*
* @param request
* The request.
* @param contentType
* The content type if any is specified.
* @param contentLength
* The content length (if any) specified in the request.
* @return An InputStream for the request body.
* @throws IOException
* If any IO problem ocurred.
*/
private static InputStream getRequestInputStream(Request request,
String contentType, int contentLength) throws IOException {
// GLASSFISH Request/Response does not extend HttpRequest/Response so
// we need to cast to Coyote Request/Response
CoyoteRequest cRequest = (CoyoteRequest) request;
InputStream in;
// If this is a POST request with posted parameters we
// read the message body directly from the request since
// this data may have already been consumed by Tomcat.
if ((contentType != null) &&
contentType.toLowerCase()
.startsWith("application/x-www-form-urlencoded") &&
cRequest.getMethod().equalsIgnoreCase("POST")) {
// Wrapping the already read POST data in a bounded InputStream.
HttpFormData data = new HttpFormData(request);
in = new ContentLengthInputStream(new ByteArrayInputStream(
data.getData()), data.getLength());
} else {
// Using the standard InputStream of the request.
in = new ContentLengthInputStream(cRequest.getInputStream(),
contentLength);
}
return in;
}
/**
* Read the status code from the status line of the response.
*
* @param connection
* The connection the response will be read from.
* @param response
* The response to copy the information to.
* @return The status code read from the status line.
* @throws IOException
* If no response code could be read.
*/
private static int setStatusCode(HttpConnection connection,
Response response) throws IOException {
// GLASSFISH Request/Response does not extend HttpRequest/Response so
// we need to cast to Coyote Request/Response
CoyoteResponse cResponse = (CoyoteResponse) response;
String statusLine = readStatusLine(connection);
try {
Matcher matcher = statusLinePattern.matcher(statusLine);
if (matcher.matches()) {
int code = Integer.parseInt(matcher.group(1));
String message = matcher.group(2);
// Set the response code in the response to return.
cResponse.getResponse().setStatus(code);
cResponse.getCoyoteResponse().setMessage(message);
return code;
}
} catch (NumberFormatException e) {
// Fall through.
}
throw new IOException("Malformed response");
}
/**
* Reads the status line from the response (the first line of the response).
*
* @param connection
* The connection to read the response from.
* @return The status line.
* @throws IOException
* If an IO exception occurs when reading the response from the
* connection.
*/
private static String readStatusLine(HttpConnection connection)
throws IOException {
String statusLine;
do {
statusLine = connection.readLine();
if (statusLine == null) {
throw new IOException("No response could be read.");
}
} while (statusLine.trim().length() == 0);
return statusLine;
}
/**
* Determines if this response may have a body or not.
*
* @param statusCode
* The status code.
* @return True if this response type may have a body.
*/
private static boolean isResponseWithBody(int statusCode) {
if (((statusCode >= 100) && (statusCode <= 199)) ||
(statusCode == 204) || (statusCode == 304)) {
return false;
}
return true;
}
/**
* Create the request line out of the original request sent to the proxy.
*
* @param request
* The original request.
* @return The request line.
*/
private static String createRequestLine(Request request) {
// GLASSFISH Request/Response does not extend HttpRequest/Response so
// we need to cast to Coyote Request/Response
CoyoteRequest cRequest = (CoyoteRequest) request;
StringBuilder requestLine = new StringBuilder(cRequest.getMethod());
requestLine.append(" ").append(cRequest.getRequestURI());
if (cRequest.getQueryString() != null) {
requestLine.append("?");
requestLine.append(cRequest.getQueryString());
}
requestLine.append(" HTTP/1.1");
return requestLine.toString();
}
/**
* Add EAS specific headers.
*
* @param connection
* The connection we want the headers written to.
* @param request
* The original request.
* @throws IOException
* Thrown if the added headers could not be written to the
* connection.
*/
private static void addEasHeaders(HttpConnection connection, Request request)
throws IOException {
// GLASSFISH Request/Response does not extend HttpRequest/Response so
// we need to cast to Coyote Request/Response
CoyoteRequest cRequest = (CoyoteRequest) request;
// The front-end will add a HTTP header with information required for
// single signon
// e.g. x-eas-usercentric-ipcontext:
// clientip=1.2.3.4,clientport=5000,serverip=5.6.7.8,serverport=9080
String contextValue = "clientip=" + cRequest.getRemoteAddr() +
",clientport=" + cRequest.getRemotePort() + ",serverip=" +
cRequest.getLocalAddr() + ",serverport=" + cRequest.getLocalPort();
connection.printLine("x-eas-usercentric-ipcontext: " + contextValue);
// Send information to backend TrafficProcessor.
connection.printLine(EAS_HEADER_FRONTEND_IS_SECURE + ": " +
cRequest.isSecure());
connection.printLine(EAS_HEADER_FRONTEND_LOCAL_ADDRESS + ": " +
cRequest.getLocalAddr());
connection.printLine(EAS_HEADER_FRONTEND_LOCAL_PORT + ": " +
cRequest.getLocalPort());
}
/**
* Sends a 500 Server Internal Error to the client.
*
* @param request
* The request.
* @param response
* The response.
* @param host
* The host the request was proxied to.
* @throws IOException
* Thrown if the error response could not be sent to the client.
*/
private static void sendInternalError(Request request, Response response,
HttpHost host) throws IOException {
// GLASSFISH Request/Response does not extend HttpRequest/Response so
// we need to cast to Coyote Request/Response
CoyoteRequest cRequest = (CoyoteRequest) request;
CoyoteResponse cResponse = (CoyoteResponse) response;
// If the reponse is still not committed...
if (!cResponse.isCommitted()) {
// Failed to proxy this request, inform client.
if (log.isLoggable(Level.FINE)) {
log.log(Level.FINE,
"The attempt to proxy '" + cRequest.getRequestURL() +
"' to '" + host + "' failed. " +
"Responding with 500 Internal Server Error.");
}
cResponse.sendError(CoyoteResponse.SC_INTERNAL_SERVER_ERROR);
}
}
/**
* The method will parse the Connection header value and return an ArrayList
* contains all the token in the header.
*
* @param connectionValue
* A string of the Connection header value(s).
* @return An ArrayList with the all Connection tokens.
*/
protected static ArrayList<String> checkConnectionsToken(
String connectionValue) {
if (connectionValue == null) { // if the connectionValue does not contains anything.
return null;
}
if (!connectionValue.contains(",")) { // It will contain one token.
ArrayList<String> al = new ArrayList<String>();
al.add(connectionValue);
return al;
}
ArrayList<String> connectionsTokens = new ArrayList<String>();
if (connectionValue.contains(":") &&
connectionValue.toLowerCase().startsWith("connection:")) {
int colon = connectionValue.indexOf(":");
connectionValue = connectionValue.substring(colon + 1,
connectionValue.length());
}
if (connectionValue.endsWith(";")) {
connectionValue = connectionValue.substring(0,
connectionValue.length() - 1);
}
// Parse the connection tokens
StringTokenizer stringTokenizer = new StringTokenizer(connectionValue,
",");
while (stringTokenizer.hasMoreTokens()) {
String token = stringTokenizer.nextToken();
connectionsTokens.add(token.trim().toLowerCase());
}
return connectionsTokens;
}
}