/*
* 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.http;
import com.sun.enterprise.web.connector.grizzly.SelectorThread;
import com.sun.enterprise.web.connector.grizzly.ssl.SSLOutputWriter;
import com.sun.grizzly.ssl.SSLOutputBuffer;
import com.sun.enterprise.web.portunif.*;
import com.sun.enterprise.web.portunif.util.ProtocolInfo;
import com.sun.grizzly.http.SocketChannelOutputBuffer;
import com.sun.grizzly.tcp.Request;
import com.sun.grizzly.tcp.Response;
import com.sun.grizzly.util.http.MimeHeaders;
import org.jvnet.glassfish.comms.clb.proxy.HttpProxy;
import org.jvnet.glassfish.comms.clb.proxy.ProxyRequestHandler;
import org.jvnet.glassfish.comms.clb.proxy.api.Endpoint;
import org.jvnet.glassfish.comms.clb.proxy.config.ProxyConfig;
import org.jvnet.glassfish.comms.clb.proxy.config.LoadBalancerProxyConstants;
import org.jvnet.glassfish.comms.clb.proxy.http.util.ObjectManager;
import org.jvnet.glassfish.comms.clb.proxy.portunif.ClbProxyProtocolInfo;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.channels.SelectionKey;
import java.util.Enumeration;
import java.util.logging.Level;
import java.util.logging.Logger;
/**
* Redirect the request to the proper protocol, which can be http or https.
*
* @author
*/
public class LoadBalancerProxyHandler implements ProtocolHandler {
/**
* The protocols supported by this handler.
*/
protected String[] protocols = {"lb/http", "lb/https"};
private HttpProxy proxy;
private Logger _logger = null;
private ObjectManager objManager;
private static ByteBuffer sslErrorBuffer = ByteBuffer.allocate(
LoadBalancerProxyConstants.SSL_HEADER_SIZE);
public LoadBalancerProxyHandler() {
_logger = ProxyConfig.getInstance().getLogger();
proxy = HttpProxy.getInstance();
objManager = ObjectManager.getInstance();
}
/**
* Redirect the request to the protocol defined in the
* <code>protocolInfo</code>. Protocols supported are http and https.
*
* @param protocolInfo The protocol that needs to be redirected.
*/
public void handle(ProtocolInfo protocolInfo) throws IOException {
try {
_handle(protocolInfo);
} catch (Throwable t) {
_logger.log(Level.SEVERE,"Handler excepion ", t);
throw new IOException(t.getMessage());
}
}
private void _handle(ProtocolInfo protocolInfo) throws IOException {
if (_logger.isLoggable(Level.FINEST)) {
_logger.log(Level.FINEST, "clb.proxy.http.protocol_handler_invoked", protocolInfo.key);
}
Object ptask = ((ClbProxyProtocolInfo) protocolInfo).object;
ProxyRequestHandler task = null;
Endpoint endpoint = null;
Request request = null;
Response response = null;
proxy.setSelectorThread(((ClbProxyProtocolInfo) protocolInfo).selectorThread);
if (ptask != null) {
task = (ProxyRequestHandler) ptask;
response = task.getResponse();
request = task.getRequest();
if (response.getStatus() != 200) {
if (response.getStatus() == -1) {
if (_logger.isLoggable(Level.FINEST)){
_logger.log(Level.FINEST, "http-maximum-overload");
}
doOverloadAction(response);
/* maximum overload condition
* sleeping keeps the thread occupied,
* we close the connection and release the task later
*/
} else {
_logger.log(Level.SEVERE,
"clb.proxy.http.handler_error_response");
response.setHeader(LoadBalancerProxyConstants.HTTP_CONNECTION_HEADER,
LoadBalancerProxyConstants.HTTP_CONNECTION_CLOSE_VALUE);
if (task.isSecure()) {
sendSecureResponse(request, response);
} else {
sendResponse(request, response);
}
if (_logger.isLoggable(Level.FINE)) {
_logger.log(Level.FINE,
"clb.proxy.http.handler_sent_error_response");
}
}
task.recycle();
objManager.offerTask(task, protocolInfo.isSecure);
if (_logger.isLoggable(Level.FINEST)) {
_logger.log(Level.FINEST, "clb.proxy.http.handler_released_resources");
}
protocolInfo.keepAlive = false;
return;
}
endpoint = task.getEndpoint();
if (_logger.isLoggable(Level.FINE)) {
_logger.log(Level.FINE,
"clb.proxy.http.handler.endpoint", endpoint);
}
} else {
/*
* Means its not a new request but there is more data to
* read from an older socket
*/
if (_logger.isLoggable(Level.FINEST)) {
_logger.log(Level.FINEST, "clb.proxy.http.handler_more_data");
}
}
/** Invoke the proxy API
*/
/**
* We have
* the buffer where the headers are read completely
* the key
* the remote address is available through a ThreadLocal
* object from the HA LB. This invocation is just like whats done
* in WSTCPProtocolHandler
*/
((ClbProxyProtocolInfo) protocolInfo).cacheHandler = proxy.doProxyHttp(task,
protocolInfo.byteBuffer, protocolInfo.key);
if (_logger.isLoggable(Level.FINE)) {
_logger.log(Level.FINE,
"clb.proxy.http.handler_doproxy_return",
((ClbProxyProtocolInfo) protocolInfo).cacheHandler);
}
/* If we return false here the grizzly 1.0 controller will cancel key and close
* the channel. Its a weird scenario because we will have to wait until a
* response is received from the backend, whateven async processing we
* do with the backend is of little use becase this thread cannot return.
* If ret is true it means that the current requets has been read completely and
* any more bytes on the chanenl is a new request which has to go thro
* the finder
*
*/
if (task == null) {
task = proxy.getConnectionManager().getServerEndpoint(protocolInfo.key);
}
if ((task != null) && (task.getError())) {
// need to see how we should handle keepAlive here
// always keep alive for now, because we will cancel key through the
// call back
protocolInfo.keepAlive = false;
send503Response(task.getRequest(), task.getResponse());
} else {
protocolInfo.keepAlive = true;
}
if (!((ClbProxyProtocolInfo) protocolInfo).cacheHandler) {
protocolInfo.mappedProtocols.remove(protocolInfo.key);
if (task != null) {
HttpProxy.getInstance().getConnectionManager().
removeClientEndpoint(task.getSelectionKey());
task.recycle();
objManager.offerTask(task, protocolInfo.isSecure);
} else {
_logger.log(Level.SEVERE, "clb.proxy.http.handler_release_fail");
}
((ClbProxyProtocolInfo) protocolInfo).parkRequest = protocolInfo.keepAlive;
} else {
((ClbProxyProtocolInfo) protocolInfo).parkRequest = false;
}
if (_logger.isLoggable(Level.FINE)) {
_logger.log(Level.FINE,
"clb.proxy.http.handler_keep_alive" +
protocolInfo.keepAlive);
}
}
//=========================================================
/**
* Returns an array of supported protocols.
* @return an array of supported protocols.
*/
public String[] getProtocols() {
return protocols;
}
/**
* Invoked when the SelectorThread is about to expire a SelectionKey.
* @return true if the SelectorThread should expire the SelectionKey, false
* if not.
*/
public boolean expireKey(SelectionKey key) {
_logger.log(Level.SEVERE, "clb.proxy.http.handler_expire_key", key);
proxy.expireKey(key);
return true;
}
protected void send503Response(Request request, Response response) {
SocketChannelOutputBuffer outputBuffer = (SocketChannelOutputBuffer) response.getOutputBuffer();
response.setStatus(503);
response.setMessage(
LoadBalancerProxyConstants.SERVICE_UNAVAILABLE);
MimeHeaders headers = response.getMimeHeaders();
headers.setValue(LoadBalancerProxyConstants.HTTP_CONNECTION_HEADER).
setString(LoadBalancerProxyConstants.HTTP_CONNECTION_CLOSE_VALUE);
headers.setValue(LoadBalancerProxyConstants.SERVER_HEADER)
.setString(SelectorThread.SERVER_NAME);
headers.setValue(LoadBalancerProxyConstants.CONTENT_LENGTH_HEADER)
.setInt(
LoadBalancerProxyConstants.SERVICE_UNAVAILABLE_LENGTH);
headers.setValue(LoadBalancerProxyConstants.CONTENT_TYPE_HEADER)
.setString("text/html");
outputBuffer.sendStatus();
int size = headers.size();
for (int i = 0; i < size; i++) {
outputBuffer.sendHeader(headers.getName(i), headers.getValue(i));
}
outputBuffer.endHeaders();
try {
/**Browsers are not happy with the status alone, need to send
* some html message so that it can be displayed to user.
* Improvement could be to read the error message from a configured
* error file. TODO see how we can combine this method &
* sendresponse method.
*/
outputBuffer.getOutputStream().write(
LoadBalancerProxyConstants.SERVICE_UNAVAILABLE_BYTES);
outputBuffer.endRequest();
outputBuffer.flush();
outputBuffer.commit();
} catch (IOException ex) {
_logger.log(Level.SEVERE, "clb.proxy.http.handler_error_response");
}
}
protected void sendResponse(Request request, Response response) {
SocketChannelOutputBuffer outputBuffer = (SocketChannelOutputBuffer) response.getOutputBuffer();
MimeHeaders headers = response.getMimeHeaders();
headers.setValue(LoadBalancerProxyConstants.HTTP_CONNECTION_HEADER).
setString(LoadBalancerProxyConstants.HTTP_CONNECTION_CLOSE_VALUE);
if (SelectorThread.SERVER_NAME != null &&
!"".equals(SelectorThread.SERVER_NAME)) {
headers.setValue(LoadBalancerProxyConstants.SERVER_HEADER)
.setString(SelectorThread.SERVER_NAME);
}
// Build the response header
outputBuffer.sendStatus();
int size = headers.size();
for (int i = 0; i < size; i++) {
outputBuffer.sendHeader(headers.getName(i), headers.getValue(i));
}
outputBuffer.endHeaders();
try {
outputBuffer.endRequest();
outputBuffer.flush();
outputBuffer.commit();
} catch (IOException ex) {
_logger.log(Level.SEVERE, "Exception in send response ", ex);
}
}
protected void sendSecureResponse(Request request, Response response) {
/**
* If we use the SSLOutputBuffer in 1.6 grizzly, which is what we have
* to use because only that allows us to use the tcp.Response interface,
* we would have a problem when we flush the response. This is because
* the 1.6 SSLOutputWriter tries to cast the workerthread to a 1.6 object
* which will fail because we are still executing in a 1.0 worker.
* So, as of now the best way to send a response is from here.
*/
SSLOutputBuffer outputBuffer = (SSLOutputBuffer) response.getOutputBuffer();
sslErrorBuffer.clear();
MimeHeaders headers = response.getMimeHeaders();
Enumeration names = headers.names();
write(LoadBalancerProxyConstants.HTTP_11, sslErrorBuffer);
write(response.getStatus() + " ", sslErrorBuffer);
write(response.getMessage(), sslErrorBuffer);
write(LoadBalancerProxyConstants.CRLF, sslErrorBuffer);
while (names.hasMoreElements()) {
String headername = (String) names.nextElement();
String headervalue = headers.getHeader(headername);
write(headername + ": " + headervalue, sslErrorBuffer);
write(LoadBalancerProxyConstants.CRLF, sslErrorBuffer);
}
write(LoadBalancerProxyConstants.CRLF, sslErrorBuffer);
sslErrorBuffer.flip();
try {
SSLOutputWriter.flushChannel(outputBuffer.getChannel(),
sslErrorBuffer.slice());
} catch (IOException ex) {
_logger.log(Level.SEVERE, "clb.proxy.handler_ssl_error_failed", ex);
}
}
/**
* 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, ByteBuffer buffer) {
if (s == null) {
return;
}
byte[] b = s.getBytes();
buffer.put(b);
}
private static final String dumpBuffer(ByteBuffer byteBuffer) {
ByteBuffer dd = byteBuffer.duplicate();
dd.flip();
int length = dd.limit();
byte[] dump = new byte[length];
dd.get(dump, 0, length);
return (new String(dump));
}
@SuppressWarnings("empty-statement")
private void doOverloadAction(Response response) {
// sleep so that cpu cycles are not consumed
int waittime = -1;
try {
waittime = Integer.parseInt(response.getMessage());
} catch (Exception t){
;
}
try {
// take from configuration once its available
if (waittime >= 0){
Thread.sleep(waittime*1000);
}
} catch (Exception se){
;
}
}
}