Package org.jvnet.glassfish.comms.clb.proxy.http

Source Code of org.jvnet.glassfish.comms.clb.proxy.http.LoadBalancerProxyHandler

/*
* 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){
            ;
        }
    }
}
TOP

Related Classes of org.jvnet.glassfish.comms.clb.proxy.http.LoadBalancerProxyHandler

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.