/*
* Copyright 1999-2004 The Apache Software Foundation
*
* Licensed 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.
*/
/*
* 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 org.jvnet.glassfish.comms.clb.proxy;
import com.sun.grizzly.ConnectorHandler;
import com.sun.grizzly.ssl.SSLOutputBuffer;
import com.sun.enterprise.web.connector.grizzly.ssl.SSLByteBufferInputStream;
import com.sun.grizzly.http.Constants;
import com.sun.grizzly.http.SocketChannelOutputBuffer;
import com.sun.grizzly.tcp.Request;
import com.sun.grizzly.tcp.Response;
import com.sun.grizzly.util.ByteBufferInputStream;
import com.sun.grizzly.util.buf.Ascii;
import com.sun.grizzly.util.buf.ByteChunk;
import com.sun.grizzly.util.buf.MessageBytes;
import com.sun.grizzly.util.http.MimeHeaders;
import com.sun.grizzly.util.net.SSLSupport;
import java.io.InputStream;
import java.nio.ByteBuffer;
import java.nio.channels.SelectionKey;
import java.nio.channels.SocketChannel;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.net.ssl.SSLEngine;
import org.jvnet.glassfish.comms.clb.proxy.api.Endpoint;
import org.jvnet.glassfish.comms.clb.proxy.config.LoadBalancerProxyConstants;
import org.jvnet.glassfish.comms.clb.proxy.config.ProxyConfig;
import org.jvnet.glassfish.comms.clb.proxy.http.util.HttpInputBuffer;
import org.jvnet.glassfish.comms.clb.proxy.http.util.HttpRequest;
import java.security.cert.Certificate;
import javax.net.ssl.SSLSession;
import org.apache.catalina.util.Base64;
/**
* This class encapsulates the pluggable framework invocation.
* Implementation has some code that has been used from Apache.
* The inputs to the task are the channel and the byte buffer that has
* been read. The task invokes all the HttpLayers that have been configured
* in the dispatcher.xml, any functionality that has to be plugged in
* has to be done through dispatcher.xml
*
* @author
*/
public class ProxyRequestHandler {
/**
* Byte buffer that has been read from the client channel
*/
protected ByteBuffer buffer;
/**
* Client socket channel
*/
protected SocketChannel clientChannel;
/**
* Http request interface, extended from grizzly tcp request interface.
*/
protected HttpRequest request;
/**
* Http response interface, this is a grizzly tcp response interface.
*/
protected Response response;
/**
* Extended from the grizzly inter
*/
protected HttpInputBuffer inputBuffer;
protected SocketChannelOutputBuffer outputBuffer;
protected SelectionKey clientKey;
protected SelectionKey serverKey;
protected Logger _logger;
protected InputStream inputStream;
/**
* Holds value of property bytesWritten.
*/
protected long bytesWritten =0;
protected long payloadlength = 0;
protected boolean recycle = true;
protected boolean initialized = false;
protected boolean keepAlive = false;
protected SSLEngine sslEngine;
protected ByteBuffer sslInputBB;
protected ByteBuffer sslOutputBB;
protected ConnectorHandler connectorHandler;
protected boolean transferencoding = false;
protected boolean hasRemaining = false;
protected boolean error = false;
/**
* Creates a new instance of ProxyRequestHandler
*/
public ProxyRequestHandler(){
this(false);
}
public ProxyRequestHandler(boolean secure) {
_logger = ProxyConfig.getInstance().getLogger();
initialize(secure);
}
public void initialize(boolean secure){
request = new HttpRequest();
inputBuffer = new HttpInputBuffer(request);
inputStream = secure ? new SSLByteBufferInputStream()
: new ByteBufferInputStream();
inputBuffer.setInputStream(inputStream);
request.setInputBuffer(inputBuffer);
response = new Response();
outputBuffer = secure ? new SSLOutputBuffer(response,
LoadBalancerProxyConstants.DEFAULT_HTTP_HEADER_BUFFER_SIZE,
false):
new SocketChannelOutputBuffer
(response,
LoadBalancerProxyConstants.DEFAULT_HTTP_HEADER_BUFFER_SIZE,
false);
response.setOutputBuffer(outputBuffer);
request.setResponse(response);
initialized = true;
}
public void setByteBuffer(ByteBuffer buffer){
this.buffer = buffer;
}
public void setChannel(SocketChannel channel){
clientChannel = channel;
}
public SelectionKey getSelectionKey(){
return clientKey;
}
public void setSelectionKey(SelectionKey key){
clientKey = key;
if (key != null){
clientChannel = (SocketChannel)key.channel();
request.setSelectionKey(key);
outputBuffer.setChannel(clientChannel);
}
}
public SelectionKey getServerSelectionKey(){
return serverKey;
}
public void setServerSelectionKey(SelectionKey key){
serverKey = key;
}
public void setConnectorHandler(ConnectorHandler handler){
connectorHandler = handler;
}
public ConnectorHandler getConnectorHandler(){
return connectorHandler;
}
public void recycle() {
inputBuffer.nextRequest();
outputBuffer.nextRequest();
request.recycle();
response.recycle();
payloadlength = 0;
bytesWritten = 0;
clientChannel = null;
buffer = null;
clientKey = null;
serverKey = null;
transferencoding = false;
connectorHandler = null;
error = false;
}
public void doTask(){
if ((buffer == null) || (clientChannel == null)){
_logger.log(Level.SEVERE,"clb.proxy.task_null_buffer_channel");
return;
}
if (_logger.isLoggable(Level.FINEST)){
_logger.log(Level.FINEST,"clb.proxy.requesthandler.start");
}
process();
if (_logger.isLoggable(Level.FINEST)){
_logger.log(Level.FINEST,"clb.proxy.requesthandler.finished_process");
}
Endpoint endpoint = getEndpoint();
if( endpoint != null && !endpoint.isLocal())
handleRemoteTermination();
postProcess();
if (_logger.isLoggable(Level.FINEST)){
_logger.log(Level.FINEST,"clb.proxy.requesthandler.finished_postprocess");
}
}
public void process(){
buffer.flip();
if (isSecure()) {
((SSLByteBufferInputStream)inputStream).setByteBuffer(buffer);
((SSLByteBufferInputStream)inputStream).setSelectionKey(clientKey);
((SSLByteBufferInputStream)inputStream).setReadTimeout(
ProxyConfig.getInstance().getReadTimeOutInt());
if (_logger.isLoggable(Level.FINEST)){
_logger.log(Level.FINEST,"clb.proxy.requesthandler.ssl_stream");
}
} else {
((ByteBufferInputStream)inputStream).setByteBuffer(buffer);
((ByteBufferInputStream)inputStream).setSelectionKey(clientKey);
((ByteBufferInputStream)inputStream).setReadTimeout(
ProxyConfig.getInstance().getReadTimeOutInt());
}
/** Try catch exception and check for failures
*/
HttpProxy.getInstance().getLayerHandler().doInvoke(request, response);
/* The above API can return under 2 conditions.
* 1. With status 200, which means all layers went through
* and we have to query the Endpoint to remote this request
* 2. With status NON 200, means that this is an error response
* and needs to be returned to the HTTP client.
*/
}
public boolean getKeepAlive(){
return keepAlive;
}
public void setKeepAlive(boolean keepalive){
keepAlive = keepalive;
}
public Endpoint getEndpoint(){
return request.getConvergedLoadBalancerEndpoint();
}
private void reallocateBuffer() {
buffer = ByteBuffer.allocate(inputBuffer.lastValidPos());
}
//Method to add required proxy headers when request is being
//proxied to remote instance
private void addProxyHeaders() {
/**
* Do we need a re-handshake here ?
* TODO !
*/
if (sslEngine != null) {
SSLSession sslSession = sslEngine.getSession();
//Adding cipher key size as proxy header
String keySize = getKeySize(sslSession);
if(keySize != null){
request.addHeader(LoadBalancerProxyConstants.
PROXY_KEYSIZE, keySize);
}
//Adding client certificate chain as proxy headers
Certificate[] certs = null;
try {
certs = sslSession.getPeerCertificates();
if (certs != null){
for (Certificate cert: certs){
String clientCert =
new String(Base64.encode(cert.getEncoded()));
request.addHeader(LoadBalancerProxyConstants.CERT_HEADER,
clientCert);
}
}
} catch( Throwable t ) {
if ( _logger.isLoggable(Level.FINEST))
_logger.log(Level.FINEST,"Error getting client certs");
}
}
//Adding proxy header for client ip address
request.addHeader(LoadBalancerProxyConstants.PROXY_CLIENT_IP,
"" + request.remoteAddr().toString());
//Adding proxy header for client port
request.addHeader(LoadBalancerProxyConstants.PROXY_CLIENT_PORT,
"" + request.getRemotePort());
//Adding proxy header for proxy local ip address
request.addHeader(LoadBalancerProxyConstants.PROXY_LOCAL_IP,
request.localAddr().toString());
//Adding proxy header for proxy local port
request.addHeader(LoadBalancerProxyConstants.PROXY_LOCAL_PORT,
"" + request.getLocalPort());
}
//Method to get key size from cipher suite
private String getKeySize(SSLSession sslSession) {
//Code to get key size picked from
//glassfish/appserv-webtier/src/java/org/apache/tomcat/util/net/jsse/JSSESupport.java
Integer keySize = (Integer) sslSession
.getValue(SSLSupport.KEY_SIZE_KEY);
if(keySize != null)
return keySize.toString();
SSLSupport.CipherData c_aux[] = SSLSupport.ciphers;
String cipherSuite = sslSession.getCipherSuite();
for (int i = 0; i < c_aux.length; i++) {
if (cipherSuite.indexOf(c_aux[i].phrase) > -1) {
return "" + c_aux[i].keySize;
}
}
return null;
}
public void postProcess(){
buffer.clear();
if (inputBuffer.lastValidPos() > buffer.capacity()){
reallocateBuffer();
}
buffer.put(inputBuffer.getBytes(), buffer.position(),
inputBuffer.lastValidPos());
}
private void handleRemoteTermination(){
addProxyHeaders();
String transferencodingname =
request.getHeader(Constants.TRANSFERENCODING);
if ((transferencodingname != null) &&
(transferencodingname.trim().equals(Constants.CHUNKED))) {
transferencoding = true;
int ret = findBytes(inputBuffer.getBytes(),
LoadBalancerProxyConstants.END_BYTES, 0, inputBuffer.getBytes().length);
hasRemaining = (ret < 0);
}
long contentlength = (request.getContentLengthLong() < 0) ? 0 :
request.getContentLengthLong();
payloadlength = contentlength +
inputBuffer.getHeaderLength();
if (_logger.isLoggable(Level.FINE)){
_logger.log(Level.FINE, "clb.proxy.requesthandler.content_length",
contentlength);
_logger.log(Level.FINE, "clb.proxy.requesthandler.payload_length",
payloadlength);
}
MessageBytes protocolMB = request.protocol();
if (protocolMB.equals(Constants.HTTP_11)) {
keepAlive = true;
} else if (protocolMB.equals(Constants.HTTP_10)) {
keepAlive = false;
} else if (protocolMB.equals("")) {
// HTTP/0.9
keepAlive = false;
} else {
if (_logger.isLoggable(Level.FINE)){
_logger.log(Level.FINE, "clb.proxy.requesthandler.set_error_response");
}
response.setStatus(505);
}
MimeHeaders headers = request.getMimeHeaders();
// Check connection header
MessageBytes connectionValueMB = headers.getValue(
LoadBalancerProxyConstants.HTTP_CONNECTION_HEADER);
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;
}
}
}
public void setSecure(boolean secure){
request.setSecure(secure);
}
public boolean isSecure(){
return request.isSecure();
}
/**
* Specialized utility method: find a sequence of lower case bytes inside
* a ByteChunk.
*/
protected int findBytes(ByteChunk bc, byte[] b) {
byte[] buff = bc.getBuffer();
int start = bc.getStart();
int end = bc.getEnd();
return findBytes(buff, b, start, end);
}
/**
* This method has been taken from the grizzly DefaultProcessorTask.
* @param buff
* @param b
* @param start
* @param end
* @return
*/
public static int findBytes(byte [] buff, byte [] b, int start, int end){
int srcEnd = b.length;
byte first = b[0];
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;
}
public ByteBuffer getBuffer() {
return buffer;
}
public long getPayloadLength(){
return payloadlength;
}
public boolean isTransferEncoding(){
return transferencoding;
}
public void updateEnd(ByteBuffer buff, boolean moredata){
int pos = moredata ? buff.position() : request.getHeaderLength();
int ret = findBytes(buff.array(),
LoadBalancerProxyConstants.END_BYTES, pos, buff.limit());
hasRemaining = (ret < 0);
}
public Request getRequest(){
return request;
}
public Response getResponse(){
return response;
}
/**
* Getter for property bytesWritten.
* @return Value of property bytesWritten.
*/
public long getBytesWritten() {
return bytesWritten;
}
/**
* Setter for property bytesWritten.
* @param bytesWritten New value of property bytesWritten.
*/
public void setBytesWritten(long bytesWritten) {
this.bytesWritten = bytesWritten;
}
/**
* Util method to check if there are more
* bytes in this request
*/
public boolean hasRemaining() {
return (transferencoding ? hasRemaining : (bytesWritten < payloadlength));
}
public void setSSLEngine(SSLEngine engine){
sslEngine = engine;
}
public SSLEngine getSSLEngine(){
return sslEngine;
}
public void setInputBB(ByteBuffer bb){
sslInputBB = bb;
}
public ByteBuffer getInputBB() {
return sslInputBB;
}
public void setOutputBB(ByteBuffer bb){
sslOutputBB = bb;
}
public ByteBuffer getOutputBB() {
return sslOutputBB;
}
public void setError(boolean er){
error = er;
}
public boolean getError(){
return error;
}
}