Package org.voltdb

Source Code of org.voltdb.HTTPClientInterface

/* This file is part of VoltDB.
* Copyright (C) 2008-2014 VoltDB Inc.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program 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 Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with VoltDB.  If not, see <http://www.gnu.org/licenses/>.
*/

package org.voltdb;

import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.atomic.AtomicBoolean;

import javax.servlet.http.HttpServletResponse;

import org.eclipse.jetty.continuation.Continuation;
import org.eclipse.jetty.continuation.ContinuationSupport;
import org.eclipse.jetty.server.Request;
import org.voltcore.logging.VoltLogger;
import org.voltdb.client.AuthenticatedConnectionCache;
import org.voltdb.client.Client;
import org.voltdb.client.ClientResponse;
import org.voltdb.client.ProcedureCallback;
import org.voltcore.logging.Level;
import org.voltcore.utils.EstTime;
import org.voltcore.utils.RateLimitedLogger;
import org.voltdb.utils.Encoder;

public class HTTPClientInterface {

    private static VoltLogger m_log = new VoltLogger("HOST");
    private static final RateLimitedLogger m_rate_limited_log = new RateLimitedLogger(10 * 1000, m_log, Level.WARN);

    AuthenticatedConnectionCache m_connections = null;
    static final int CACHE_TARGET_SIZE = 10;
    private final AtomicBoolean m_shouldUpdateCatalog = new AtomicBoolean(false);

    class JSONProcCallback implements ProcedureCallback {

        final Request m_request;
        final Continuation m_continuation;
        final String m_jsonp;
        final CountDownLatch m_latch = new CountDownLatch(1);

        public JSONProcCallback(Request request, Continuation continuation, String jsonp) {
            assert(request != null);
            assert(continuation != null);

            m_request = request;
            m_continuation = continuation;
            m_jsonp = jsonp;
        }

        @Override
        public void clientCallback(ClientResponse clientResponse) throws Exception {
            ClientResponseImpl rimpl = (ClientResponseImpl) clientResponse;
            String msg = rimpl.toJSONString();

            // handle jsonp pattern
            // http://en.wikipedia.org/wiki/JSON#The_Basic_Idea:_Retrieving_JSON_via_Script_Tags
            if (m_jsonp != null) {
                msg = String.format("%s( %s )", m_jsonp, msg);
            }

            // send the response back through jetty
            HttpServletResponse response = (HttpServletResponse) m_continuation.getServletResponse();
            response.setStatus(HttpServletResponse.SC_OK);
            m_request.setHandled(true);
            response.getWriter().print(msg);
            try{
                m_continuation.complete();
             } catch (IllegalStateException e){
                // Thrown when we shut down the server via the JSON/HTTP (web studio) API
                // Essentially we're closing everything down from underneath the HTTP request.
                 m_log.warn("JSON request completion exception: ", e);
             }
            m_latch.countDown();
        }

        public void waitForResponse() throws InterruptedException {
            m_latch.await();
        }
    }

    public HTTPClientInterface() {
    }

    public void process(Request request, HttpServletResponse response) {
        Client client = null;
        boolean adminMode = false;

        Continuation continuation = ContinuationSupport.getContinuation(request);
        continuation.suspend(response);
        String jsonp = null;
        try {
            // first check for a catalog update and purge the cached connections
            // if one has happened since we were here last
            if (m_shouldUpdateCatalog.compareAndSet(true, false))
            {
                m_connections.closeAll();
                // Just null the old object so we'll create a new one with
                // updated state below
                m_connections = null;
            }

            if (m_connections == null) {
                int port = VoltDB.instance().getConfig().m_port;
                int adminPort = VoltDB.instance().getConfig().m_adminPort;
                String externalInterface = VoltDB.instance().getConfig().m_externalInterface;
                String adminInterface = "localhost";
                String clientInterface = "localhost";
                if (externalInterface != null && !externalInterface.isEmpty()) {
                    clientInterface = externalInterface;
                    adminInterface = externalInterface;
                }
                //If individual override is available use them.
                if (VoltDB.instance().getConfig().m_clientInterface.length() > 0) {
                    clientInterface = VoltDB.instance().getConfig().m_clientInterface;
                }
                if (VoltDB.instance().getConfig().m_adminInterface.length() > 0) {
                    adminInterface = VoltDB.instance().getConfig().m_adminInterface;
                }
                m_connections = new AuthenticatedConnectionCache(10, clientInterface, port, adminInterface, adminPort);
            }

            jsonp = request.getParameter("jsonp");
            if (request.getMethod().equalsIgnoreCase("POST")) {
                int queryParamSize = request.getContentLength();
                if (queryParamSize > 150000) {
                    // We don't want to be building huge strings
                    throw new Exception("Query string too large: " + String.valueOf(request.getContentLength()));
                }
                if (queryParamSize == 0) {
                    throw new Exception("Received POST with no parameters in the body.");
                }
            }

            String username = request.getParameter("User");
            String password = request.getParameter("Password");
            String hashedPassword = request.getParameter("Hashedpassword");
            String procName = request.getParameter("Procedure");
            String params = request.getParameter("Parameters");
            String admin = request.getParameter("admin");

            // check for admin mode
            if (admin != null) {
                if (admin.compareToIgnoreCase("true") == 0) {
                    adminMode = true;
                }
            }

            // null procs are bad news
            if (procName == null) {
                response.setStatus(HttpServletResponse.SC_BAD_REQUEST);
                continuation.complete();
                return;
            }

            // The SHA-1 hash of the password
            byte[] hashedPasswordBytes = null;

            if (password != null) {
                try {
                    // Create a MessageDigest every time because MessageDigest is not thread safe (ENG-5438)
                    MessageDigest md = MessageDigest.getInstance("SHA-1");
                    hashedPasswordBytes = md.digest(password.getBytes("UTF-8"));
                } catch (NoSuchAlgorithmException e) {
                    throw new RuntimeException("JVM doesn't support SHA-1 hashing. Please use a supported JVM", e);
                } catch (UnsupportedEncodingException e) {
                    throw new RuntimeException("JVM doesn't support UTF-8. Please use a supported JVM", e);
                }
            }
            // note that HTTP Var "Hashedpassword" has a higher priority
            // Hashedassword must be a 40-byte hex-encoded SHA-1 hash (20 bytes unencoded)
            if (hashedPassword != null) {
                if (hashedPassword.length() != 40) {
                    throw new Exception("Hashedpassword must be a 40-byte hex-encoded SHA-1 hash (20 bytes unencoded).");
                }
                try {
                    hashedPasswordBytes = Encoder.hexDecode(hashedPassword);
                }
                catch (Exception e) {
                    throw new Exception("Hashedpassword must be a 40-byte hex-encoded SHA-1 hash (20 bytes unencoded).");
                }
            }

            assert((hashedPasswordBytes == null) || (hashedPasswordBytes.length == 20));

            // get a connection to localhost from the pool
            client = m_connections.getClient(username, password, hashedPasswordBytes, adminMode);

            JSONProcCallback cb = new JSONProcCallback(request, continuation, jsonp);
            boolean success;
            if (params != null) {
                ParameterSet paramSet = null;
                try {
                    paramSet = ParameterSet.fromJSONString(params);
                }
                // if decoding params has a fail, then fail
                catch (Exception e) {
                    response.setStatus(HttpServletResponse.SC_BAD_REQUEST);
                    continuation.complete();
                    return;
                }
                // if the paramset has content, but decodes to null, fail
                if (paramSet == null) {
                    response.setStatus(HttpServletResponse.SC_BAD_REQUEST);
                    continuation.complete();
                    return;
                }
                success = client.callProcedure(cb, procName, paramSet.toArray());
            }
            else {
                success = client.callProcedure(cb, procName);
            }
            if (!success) {
                throw new Exception("Server is not accepting work at this time.");
            }
            if (adminMode) {
                cb.waitForResponse();
            }
        }
        catch (java.net.ConnectException c_ex)
        {
            // Clients may attempt to connect to VoltDB before the server
            // is completely initialized (our tests do this, for example).
            // Don't print a stack trace, and return a server unavailable reason.
            ClientResponseImpl rimpl = new ClientResponseImpl(ClientResponse.SERVER_UNAVAILABLE, new VoltTable[0], c_ex.getMessage());
            String msg = rimpl.toJSONString();
            if (jsonp != null) {
                msg = String.format("%s( %s )", jsonp, msg);
            }
            response.setStatus(HttpServletResponse.SC_OK);
            request.setHandled(true);
            try {
                response.getWriter().print(msg);
                continuation.complete();
            } catch (IOException e1) {}
        }
        catch (Exception e) {
            String msg = e.getMessage();
            m_rate_limited_log.log("JSON interface exception: " + msg, EstTime.currentTimeMillis());
            ClientResponseImpl rimpl = new ClientResponseImpl(ClientResponse.UNEXPECTED_FAILURE, new VoltTable[0], msg);
            msg = rimpl.toJSONString();
            if (jsonp != null) {
                msg = String.format("%s( %s )", jsonp, msg);
            }
            response.setStatus(HttpServletResponse.SC_OK);
            request.setHandled(true);
            try {
                response.getWriter().print(msg);
                continuation.complete();
            } catch (IOException e1) {}
        }
        finally {
            if (client != null) {
                assert(m_connections != null);
                // admin connections aren't cached
                if (adminMode) {
                    if (client != null) {
                        try {
                            client.close();
                        } catch (InterruptedException e) {
                            m_log.warn("JSON interface was interrupted while closing an internal admin client connection.");
                        }
                    }
                }
                // other connections are cached
                else {
                    m_connections.releaseClient(client);
                }
            }
        }
    }

    public void notifyOfCatalogUpdate()
    {
        m_shouldUpdateCatalog.set(true);
    }
}
TOP

Related Classes of org.voltdb.HTTPClientInterface

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.