All Downloads are FREE. Search and download functionalities are using the official Maven repository.

io.undertow.server.handlers.proxy.mod_cluster.MCMPHandler Maven / Gradle / Ivy

Go to download

This artifact provides a single jar that contains all classes required to use remote EJB and JMS, including all dependencies. It is intended for use by those not using maven, maven users should just import the EJB and JMS BOM's instead (shaded JAR's cause lots of problems with maven, as it is very easy to inadvertently end up with different versions on classes on the class path).

There is a newer version: 34.0.0.Final
Show newest version
/*
 * JBoss, Home of Professional Open Source.
 * Copyright 2014 Red Hat, Inc., and individual contributors
 * as indicated by the @author tags.
 *
 * 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.
 */

package io.undertow.server.handlers.proxy.mod_cluster;

import io.undertow.UndertowLogger;
import io.undertow.UndertowMessages;
import io.undertow.Version;
import io.undertow.io.Sender;
import io.undertow.server.HttpHandler;
import io.undertow.server.HttpServerExchange;
import io.undertow.server.handlers.form.FormData;
import io.undertow.server.handlers.form.FormDataParser;
import io.undertow.server.handlers.form.FormEncodedDataDefinition;
import io.undertow.server.handlers.form.FormParserFactory;
import io.undertow.util.Headers;
import io.undertow.util.HttpString;
import io.undertow.util.StatusCodes;
import org.xnio.OptionMap;
import org.xnio.Options;
import org.xnio.ssl.XnioSsl;

import java.io.IOException;
import java.net.InetSocketAddress;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.Deque;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.TimeUnit;

import static io.undertow.server.handlers.proxy.mod_cluster.MCMPConstants.ALIAS;
import static io.undertow.server.handlers.proxy.mod_cluster.MCMPConstants.BALANCER;
import static io.undertow.server.handlers.proxy.mod_cluster.MCMPConstants.CONTEXT;
import static io.undertow.server.handlers.proxy.mod_cluster.MCMPConstants.DOMAIN;
import static io.undertow.server.handlers.proxy.mod_cluster.MCMPConstants.FLUSH_PACKET;
import static io.undertow.server.handlers.proxy.mod_cluster.MCMPConstants.FLUSH_WAIT;
import static io.undertow.server.handlers.proxy.mod_cluster.MCMPConstants.HOST;
import static io.undertow.server.handlers.proxy.mod_cluster.MCMPConstants.JVMROUTE;
import static io.undertow.server.handlers.proxy.mod_cluster.MCMPConstants.LOAD;
import static io.undertow.server.handlers.proxy.mod_cluster.MCMPConstants.MAXATTEMPTS;
import static io.undertow.server.handlers.proxy.mod_cluster.MCMPConstants.PORT;
import static io.undertow.server.handlers.proxy.mod_cluster.MCMPConstants.REVERSED;
import static io.undertow.server.handlers.proxy.mod_cluster.MCMPConstants.SCHEME;
import static io.undertow.server.handlers.proxy.mod_cluster.MCMPConstants.SMAX;
import static io.undertow.server.handlers.proxy.mod_cluster.MCMPConstants.STICKYSESSION;
import static io.undertow.server.handlers.proxy.mod_cluster.MCMPConstants.STICKYSESSIONCOOKIE;
import static io.undertow.server.handlers.proxy.mod_cluster.MCMPConstants.STICKYSESSIONFORCE;
import static io.undertow.server.handlers.proxy.mod_cluster.MCMPConstants.STICKYSESSIONPATH;
import static io.undertow.server.handlers.proxy.mod_cluster.MCMPConstants.STICKYSESSIONREMOVE;
import static io.undertow.server.handlers.proxy.mod_cluster.MCMPConstants.TIMEOUT;
import static io.undertow.server.handlers.proxy.mod_cluster.MCMPConstants.TTL;
import static io.undertow.server.handlers.proxy.mod_cluster.MCMPConstants.TYPE;
import static io.undertow.server.handlers.proxy.mod_cluster.MCMPConstants.WAITWORKER;

/**
 * The mod cluster management protocol http handler.
 *
 * @author Emanuel Muckenhuber
 */
class MCMPHandler implements HttpHandler {

    enum MCMPAction {

        ENABLE,
        DISABLE,
        STOP,
        REMOVE,
        ;

    }

    public static final HttpString CONFIG = new HttpString("CONFIG");
    public static final HttpString ENABLE_APP = new HttpString("ENABLE-APP");
    public static final HttpString DISABLE_APP = new HttpString("DISABLE-APP");
    public static final HttpString STOP_APP = new HttpString("STOP-APP");
    public static final HttpString REMOVE_APP = new HttpString("REMOVE-APP");
    public static final HttpString STATUS = new HttpString("STATUS");
    public static final HttpString DUMP = new HttpString("DUMP");
    public static final HttpString INFO = new HttpString("INFO");
    public static final HttpString PING = new HttpString("PING");

    private static final Set HANDLED_METHODS = Collections.unmodifiableSet(new HashSet<>(Arrays.asList(CONFIG, ENABLE_APP, DISABLE_APP, STOP_APP, REMOVE_APP, STATUS, INFO, DUMP, PING)));

    protected static final String VERSION_PROTOCOL = "0.2.1";
    protected static final String MOD_CLUSTER_EXPOSED_VERSION = "mod_cluster_undertow/" + Version.getVersionString();

    private static final String CONTENT_TYPE = "text/plain; charset=ISO-8859-1";

    /* the syntax error messages */
    private static final String TYPESYNTAX = MCMPConstants.TYPESYNTAX;
    private static final String SCONBAD = "SYNTAX: Context without Alias";
    private static final String SBADFLD = "SYNTAX: Invalid field ";
    private static final String SBADFLD1 = " in message";
    private static final String SMISFLD = "SYNTAX: Mandatory field(s) missing in message";

    private final FormParserFactory parserFactory;
    private final MCMPConfig config;
    private final HttpHandler next;
    private final long creationTime = System.currentTimeMillis(); // This should change with each restart
    private final ModCluster modCluster;
    protected final ModClusterContainer container;

    MCMPHandler(MCMPConfig config, ModCluster modCluster, HttpHandler next) {
        this.config = config;
        this.next = next;
        this.modCluster = modCluster;
        this.container = modCluster.getContainer();
        this.parserFactory = FormParserFactory.builder(false).addParser(new FormEncodedDataDefinition().setForceCreation(true)).build();
        UndertowLogger.ROOT_LOGGER.mcmpHandlerCreated();
    }

    @Override
    public void handleRequest(HttpServerExchange exchange) throws Exception {
        final HttpString method = exchange.getRequestMethod();
        if(!handlesMethod(method)) {
            next.handleRequest(exchange);
            return;
        }
        /*
         * Proxy the request that needs to be proxied and process others
         */
        // TODO maybe this should be handled outside here?
        final InetSocketAddress addr = exchange.getConnection().getLocalAddress(InetSocketAddress.class);
        if (!addr.isUnresolved() && addr.getPort() != config.getManagementSocketAddress().getPort() || !Arrays.equals(addr.getAddress().getAddress(), config.getManagementSocketAddress().getAddress().getAddress())) {
            next.handleRequest(exchange);
            return;
        }

        if(exchange.isInIoThread()) {
            //for now just do all the management stuff in a worker, as it uses blocking IO
            exchange.dispatch(this);
            return;
        }

        try {
            handleRequest(method, exchange);
        } catch (Exception e) {
            UndertowLogger.ROOT_LOGGER.failedToProcessManagementReq(e);
            exchange.setStatusCode(StatusCodes.INTERNAL_SERVER_ERROR);
            exchange.getResponseHeaders().add(Headers.CONTENT_TYPE, CONTENT_TYPE);
            final Sender sender = exchange.getResponseSender();
            sender.send("failed to process management request");
        }
    }

    protected boolean handlesMethod(HttpString method) {
        return HANDLED_METHODS.contains(method);
    }

    /**
     * Handle a management+ request.
     *
     * @param method   the http method
     * @param exchange the http server exchange
     * @throws Exception
     */
    protected void handleRequest(final HttpString method, HttpServerExchange exchange) throws Exception {
        final RequestData requestData = parseFormData(exchange);
        boolean persistent = exchange.isPersistent();
        exchange.setPersistent(false); //UNDERTOW-947 MCMP should not use persistent connections
        if (CONFIG.equals(method)) {
            processConfig(exchange, requestData);
        } else if (ENABLE_APP.equals(method)) {
            processCommand(exchange, requestData, MCMPAction.ENABLE);
        } else if (DISABLE_APP.equals(method)) {
            processCommand(exchange, requestData, MCMPAction.DISABLE);
        } else if (STOP_APP.equals(method)) {
            processCommand(exchange, requestData, MCMPAction.STOP);
        } else if (REMOVE_APP.equals(method)) {
            processCommand(exchange, requestData, MCMPAction.REMOVE);
        } else if (STATUS.equals(method)) {
            processStatus(exchange, requestData);
        } else if (INFO.equals(method)) {
            processInfo(exchange);
        } else if (DUMP.equals(method)) {
            processDump(exchange);
        } else if (PING.equals(method)) {
            processPing(exchange, requestData);
        } else {
            exchange.setPersistent(persistent);
            next.handleRequest(exchange);
        }
    }

    /**
     * Process the node config.
     *
     * @param exchange the http server exchange
     * @param requestData the request data
     */
    private void processConfig(final HttpServerExchange exchange, final RequestData requestData) {

        // Get the node builder
        List hosts = null;
        List contexts = null;
        final Balancer.BalancerBuilder balancer = Balancer.builder();
        final NodeConfig.NodeBuilder node = NodeConfig.builder(modCluster);
        final Iterator i = requestData.iterator();
        while (i.hasNext()) {
            final HttpString name = i.next();
            final String value = requestData.getFirst(name);

            UndertowLogger.ROOT_LOGGER.mcmpKeyValue(name, value);
            if (!checkString(value)) {
                processError(TYPESYNTAX, SBADFLD + name + SBADFLD1, exchange);
                return;
            }

            if (BALANCER.equals(name)) {
                node.setBalancer(value);
                balancer.setName(value);
            } else if (MAXATTEMPTS.equals(name)) {
                balancer.setMaxRetries(Integer.parseInt(value));
            } else if (STICKYSESSION.equals(name)) {
                if ("No".equalsIgnoreCase(value)) {
                    balancer.setStickySession(false);
                }
            } else if (STICKYSESSIONCOOKIE.equals(name)) {
                balancer.setStickySessionCookie(value);
            } else if (STICKYSESSIONPATH.equals(name)) {
                balancer.setStickySessionPath(value);
            } else if (STICKYSESSIONREMOVE.equals(name)) {
                if ("Yes".equalsIgnoreCase(value)) {
                    balancer.setStickySessionRemove(true);
                }
            } else if (STICKYSESSIONFORCE.equals(name)) {
                if ("no".equalsIgnoreCase(value)) {
                    balancer.setStickySessionForce(false);
                }
            } else if (JVMROUTE.equals(name)) {
                node.setJvmRoute(value);
            } else if (DOMAIN.equals(name)) {
                node.setDomain(value);
            } else if (HOST.equals(name)) {
                node.setHostname(value);
            } else if (PORT.equals(name)) {
                node.setPort(Integer.parseInt(value));
            } else if (TYPE.equals(name)) {
                node.setType(value);
            } else if (REVERSED.equals(name)) {
                continue; // ignore
            } else if (FLUSH_PACKET.equals(name)) {
                if ("on".equalsIgnoreCase(value)) {
                    node.setFlushPackets(true);
                } else if ("auto".equalsIgnoreCase(value)) {
                    node.setFlushPackets(true);
                }
            } else if (FLUSH_WAIT.equals(name)) {
                node.setFlushwait(Integer.parseInt(value));
            } else if (MCMPConstants.PING.equals(name)) {
                node.setPing(Integer.parseInt(value));
            } else if (SMAX.equals(name)) {
                node.setSmax(Integer.parseInt(value));
            } else if (TTL.equals(name)) {
                node.setTtl(TimeUnit.SECONDS.toMillis(Long.parseLong(value)));
            } else if (TIMEOUT.equals(name)) {
                node.setTimeout(Integer.parseInt(value));
            } else if (CONTEXT.equals(name)) {
                final String[] context = value.split(",");
                contexts = Arrays.asList(context);
            } else if (ALIAS.equals(name)) {
                final String[] alias = value.split(",");
                hosts = Arrays.asList(alias);
            } else if(WAITWORKER.equals(name)) {
                node.setWaitWorker(Integer.parseInt(value));
            } else {
                processError(TYPESYNTAX, SBADFLD + name + SBADFLD1, exchange);
                return;
            }
        }

        final NodeConfig config;
        try {
            // Build the config
            config = node.build();
            if (container.addNode(config, balancer, exchange.getIoThread(), exchange.getConnection().getByteBufferPool())) {
                // Apparently this is hard to do in the C part, so maybe we should just remove this
                if (contexts != null && hosts != null) {
                    for (final String context : contexts) {
                        container.enableContext(context, config.getJvmRoute(), hosts);
                    }
                }
                processOK(exchange);
            } else {
                processError(MCMPErrorCode.NODE_STILL_EXISTS, exchange);
            }
        } catch (Exception e) {
            processError(MCMPErrorCode.CANT_UPDATE_NODE, exchange);
        }
    }

    /**
     * Process a mod_cluster mgmt command.
     *
     * @param exchange the http server exchange
     * @param requestData the request data
     * @param action   the mgmt action
     * @throws IOException
     */
    void processCommand(final HttpServerExchange exchange, final RequestData requestData, final MCMPAction action) throws IOException {
        if (exchange.getRequestPath().equals("*") || exchange.getRequestPath().endsWith("/*")) {
            processNodeCommand(exchange, requestData, action);
        } else {
            processAppCommand(exchange, requestData, action);
        }
    }

    /**
     * Process a mgmt command targeting a node.
     *
     * @param exchange the http server exchange
     * @param requestData the request data
     * @param action   the mgmt action
     * @throws IOException
     */
    void processNodeCommand(final HttpServerExchange exchange, final RequestData requestData, final MCMPAction action) throws IOException {
        final String jvmRoute = requestData.getFirst(JVMROUTE);
        if (jvmRoute == null) {
            processError(TYPESYNTAX, SMISFLD, exchange);
            return;
        }
        if (processNodeCommand(jvmRoute, action)) {
            processOK(exchange);
        } else {
            processError(MCMPErrorCode.CANT_UPDATE_NODE, exchange);
        }
    }

    boolean processNodeCommand(final String jvmRoute, final MCMPAction action) throws IOException {
        switch (action) {
            case ENABLE:
                return container.enableNode(jvmRoute);
            case DISABLE:
                return container.disableNode(jvmRoute);
            case STOP:
                return container.stopNode(jvmRoute);
            case REMOVE:
                return container.removeNode(jvmRoute) != null;
        }
        return false;
    }

    /**
     * Process a command targeting an application.
     *
     * @param exchange the http server exchange
     * @param requestData the request data
     * @param action   the mgmt action
     * @return
     * @throws IOException
     */
    void processAppCommand(final HttpServerExchange exchange, final RequestData requestData, final MCMPAction action) throws IOException {

        final String contextPath = requestData.getFirst(CONTEXT);
        final String jvmRoute = requestData.getFirst(JVMROUTE);
        final String aliases = requestData.getFirst(ALIAS);

        if (contextPath == null || jvmRoute == null || aliases == null) {
            processError(TYPESYNTAX, SMISFLD, exchange);
            return;
        }
        final List virtualHosts = Arrays.asList(aliases.split(","));
        if (virtualHosts == null || virtualHosts.isEmpty()) {
            processError(TYPESYNTAX, SCONBAD, exchange);
            return;
        }

        String response = null;
        switch (action) {
            case ENABLE:
                if (!container.enableContext(contextPath, jvmRoute, virtualHosts)) {
                    processError(MCMPErrorCode.CANT_UPDATE_CONTEXT, exchange);
                    return;
                }
                break;
            case DISABLE:
                if (!container.disableContext(contextPath, jvmRoute, virtualHosts)) {
                    processError(MCMPErrorCode.CANT_UPDATE_CONTEXT, exchange);
                    return;
                }
                break;
            case STOP:
                int i = container.stopContext(contextPath, jvmRoute, virtualHosts);
                final StringBuilder builder = new StringBuilder();
                builder.append("Type=STOP-APP-RSP,JvmRoute=").append(jvmRoute);
                builder.append("Alias=").append(aliases);
                builder.append("Context=").append(contextPath);
                builder.append("Requests=").append(i);
                response = builder.toString();
                break;
            case REMOVE:
                if (!container.removeContext(contextPath, jvmRoute, virtualHosts)) {
                    processError(MCMPErrorCode.CANT_UPDATE_CONTEXT, exchange);
                    return;
                }
                break;
            default: {
                processError(TYPESYNTAX, SMISFLD, exchange);
                return;
            }
        }
        if (response != null) {
            sendResponse(exchange, response);
        } else {
            processOK(exchange);
        }
    }

    /**
     * Process the status request.
     *
     * @param exchange the http server exchange
     * @param requestData the request data
     * @throws IOException
     */
    void processStatus(final HttpServerExchange exchange, final RequestData requestData) throws IOException {

        final String jvmRoute = requestData.getFirst(JVMROUTE);
        final String loadValue = requestData.getFirst(LOAD);

        if (loadValue == null || jvmRoute == null) {
            processError(TYPESYNTAX, SMISFLD, exchange);
            return;
        }

        UndertowLogger.ROOT_LOGGER.receivedNodeLoad(jvmRoute, loadValue);
        final int load = Integer.parseInt(loadValue);
        if (load > 0 || load == -2) {

            final Node node = container.getNode(jvmRoute);
            if (node == null) {
                processError(MCMPErrorCode.CANT_READ_NODE, exchange);
                return;
            }

            final NodePingUtil.PingCallback callback = new NodePingUtil.PingCallback() {
                @Override
                public void completed() {
                    final String response = "Type=STATUS-RSP&State=OK&JVMRoute=" + jvmRoute + "&id=" + creationTime;
                    try {
                        if (load > 0) {
                            node.updateLoad(load);
                        }
                        sendResponse(exchange, response);
                    } catch (Exception e) {
                        UndertowLogger.ROOT_LOGGER.failedToSendPingResponse(e);
                    }
                }

                @Override
                public void failed() {
                    final String response = "Type=STATUS-RSP&State=NOTOK&JVMRoute=" + jvmRoute + "&id=" + creationTime;
                    try {
                        node.markInError();
                        sendResponse(exchange, response);
                    } catch (Exception e) {
                        UndertowLogger.ROOT_LOGGER.failedToSendPingResponseDBG(e, node.getJvmRoute(), jvmRoute);
                    }
                }
            };

            // Ping the node
            node.ping(exchange, callback);

        } else if (load == 0) {
            final Node node = container.getNode(jvmRoute);
            if (node != null) {
                node.hotStandby();
                sendResponse(exchange, "Type=STATUS-RSP&State=OK&JVMRoute=" + jvmRoute + "&id=" + creationTime);
            } else {
                processError(MCMPErrorCode.CANT_READ_NODE, exchange);
            }
        } else if (load == -1) {
            // Error, disable node
            final Node node = container.getNode(jvmRoute);
            if (node != null) {
                node.markInError();
                sendResponse(exchange, "Type=STATUS-RSP&State=NOTOK&JVMRoute=" + jvmRoute + "&id=" + creationTime);
            } else {
                processError(MCMPErrorCode.CANT_READ_NODE, exchange);
            }
        } else {
            processError(TYPESYNTAX, SMISFLD, exchange);
        }
    }

    /**
     * Process the ping request.
     *
     * @param exchange the http server exchange
     * @param requestData the request data
     * @throws IOException
     */
    void processPing(final HttpServerExchange exchange, final RequestData requestData) throws IOException {

        final String jvmRoute = requestData.getFirst(JVMROUTE);
        final String scheme = requestData.getFirst(SCHEME);
        final String host = requestData.getFirst(HOST);
        final String port = requestData.getFirst(PORT);

        final String OK = "Type=PING-RSP&State=OK&id=" + creationTime;
        final String NOTOK = "Type=PING-RSP&State=NOTOK&id=" + creationTime;

        if (jvmRoute != null) {
            // ping the corresponding node.
            final Node nodeConfig = container.getNode(jvmRoute);
            if (nodeConfig == null) {
                sendResponse(exchange, NOTOK);
                return;
            }
            final NodePingUtil.PingCallback callback = new NodePingUtil.PingCallback() {
                @Override
                public void completed() {
                    try {
                        sendResponse(exchange, OK);
                    } catch (Exception e) {
                        e.printStackTrace();
                    }
                }

                @Override
                public void failed() {
                    try {
                        nodeConfig.markInError();
                        sendResponse(exchange, NOTOK);
                    } catch (Exception e) {
                        e.printStackTrace();
                    }
                }
            };
            nodeConfig.ping(exchange, callback);
        } else {
            if (scheme == null && host == null && port == null) {
                sendResponse(exchange, OK);
                return;
            } else {
                if (host == null || port == null) {
                    processError(TYPESYNTAX, SMISFLD, exchange);
                    return;
                }
                // Check whether we can reach the host
                checkHostUp(scheme, host, Integer.parseInt(port), exchange, new NodePingUtil.PingCallback() {
                    @Override
                    public void completed() {
                        sendResponse(exchange, OK);
                    }

                    @Override
                    public void failed() {
                        sendResponse(exchange, NOTOK);
                    }
                });
                return;
            }
        }
    }

    /*
     * Something like:
     *
     * Node: [1],Name: 368e2e5c-d3f7-3812-9fc6-f96d124dcf79,Balancer:
     * cluster-prod-01,LBGroup: ,Host: 127.0.0.1,Port: 8443,Type:
     * https,Flushpackets: Off,Flushwait: 10,Ping: 10,Smax: 21,Ttl: 60,Elected:
     * 0,Read: 0,Transfered: 0,Connected: 0,Load: 1 Vhost: [1:1:1], Alias:
     * default-host Vhost: [1:1:2], Alias: localhost Vhost: [1:1:3], Alias:
     * example.com Context: [1:1:1], Context: /myapp, Status: ENABLED
     */

    /**
     * Process INFO request
     *
     * @throws IOException
     */
    protected void processInfo(HttpServerExchange exchange) throws IOException {
        final String data = processInfoString();
        exchange.getResponseHeaders().add(Headers.SERVER, MOD_CLUSTER_EXPOSED_VERSION);
        sendResponse(exchange, data);
    }

    protected String processInfoString() {
        final StringBuilder builder = new StringBuilder();
        final List vHosts = new ArrayList<>();
        final List contexts = new ArrayList<>();
        final Collection nodes = container.getNodes();
        for (final Node node : nodes) {
            MCMPInfoUtil.printInfo(node, builder);
            vHosts.addAll(node.getVHosts());
            contexts.addAll(node.getContexts());
        }
        for (final Node.VHostMapping vHost : vHosts) {
            MCMPInfoUtil.printInfo(vHost, builder);
        }
        for (final Context context : contexts) {
            MCMPInfoUtil.printInfo(context, builder);
        }
        return builder.toString();
    }

    /*
     * something like:
     *
     * balancer: [1] Name: cluster-prod-01 Sticky: 1 [JSESSIONID]/[jsessionid]
     * remove: 0 force: 0 Timeout: 0 maxAttempts: 1 node: [1:1],Balancer:
     * cluster-prod-01,JVMRoute: 368e2e5c-d3f7-3812-9fc6-f96d124dcf79,LBGroup:
     * [],Host: 127.0.0.1,Port: 8443,Type: https,flushpackets: 0,flushwait:
     * 10,ping: 10,smax: 21,ttl: 60,timeout: 0 host: 1 [default-host] vhost: 1
     * node: 1 host: 2 [localhost] vhost: 1 node: 1 host: 3 [example.com] vhost:
     * 1 node: 1 context: 1 [/myapp] vhost: 1 node: 1 status: 1
     */

    /**
     * Process DUMP request
     *
     * @param exchange
     * @throws IOException
     */
    protected void processDump(HttpServerExchange exchange) throws IOException {
        final String data = processDumpString();
        exchange.getResponseHeaders().add(Headers.SERVER, MOD_CLUSTER_EXPOSED_VERSION);
        sendResponse(exchange, data);
    }

    protected String processDumpString() {

        final StringBuilder builder = new StringBuilder();
        final Collection balancers = container.getBalancers();
        for (final Balancer balancer : balancers) {
            MCMPInfoUtil.printDump(balancer, builder);
        }

        final List vHosts = new ArrayList<>();
        final List contexts = new ArrayList<>();
        final Collection nodes = container.getNodes();
        for (final Node node : nodes) {
            MCMPInfoUtil.printDump(node, builder);
            vHosts.addAll(node.getVHosts());
            contexts.addAll(node.getContexts());
        }
        for (final Node.VHostMapping vHost : vHosts) {
            MCMPInfoUtil.printDump(vHost, builder);
        }
        for (final Context context : contexts) {
            MCMPInfoUtil.printDump(context, builder);
        }
        return builder.toString();
    }

    /**
     * Check whether a host is up.
     *
     * @param scheme      the scheme
     * @param host        the host
     * @param port        the port
     * @param exchange    the http server exchange
     * @param callback    the ping callback
     */
    protected void checkHostUp(final String scheme, final String host, final int port, final HttpServerExchange exchange, final NodePingUtil.PingCallback callback) {

        final XnioSsl xnioSsl = null; // TODO
        final OptionMap options = OptionMap.builder()
                .set(Options.TCP_NODELAY, true)
                .getMap();

        try {
            // http, ajp and maybe more in future
            if ("ajp".equalsIgnoreCase(scheme) || "http".equalsIgnoreCase(scheme)) {
                final URI uri = new URI(scheme, null, host, port, "/", null, null);
                NodePingUtil.pingHttpClient(uri, callback, exchange, container.getClient(), xnioSsl, options);
            } else {
                final InetSocketAddress address = new InetSocketAddress(host, port);
                NodePingUtil.pingHost(address, exchange, callback, options);
            }
        } catch (URISyntaxException e) {
            callback.failed();
        }
    }

    /**
     * Send a simple response string.
     *
     * @param exchange    the http server exchange
     * @param response    the response string
     */
    static void sendResponse(final HttpServerExchange exchange, final String response) {
        exchange.setStatusCode(StatusCodes.OK);
        exchange.getResponseHeaders().add(Headers.CONTENT_TYPE, CONTENT_TYPE);
        final Sender sender = exchange.getResponseSender();
        UndertowLogger.ROOT_LOGGER.mcmpSendingResponse(exchange.getSourceAddress(), exchange.getStatusCode(), exchange.getResponseHeaders(), response);
        sender.send(response);
    }

    /**
     * If the process is OK, then add 200 HTTP status and its "OK" phrase
     *
     * @throws IOException
     */
    static void processOK(HttpServerExchange exchange) throws IOException {
        exchange.setStatusCode(StatusCodes.OK);
        exchange.getResponseHeaders().add(Headers.CONTENT_TYPE, CONTENT_TYPE);
        exchange.endExchange();
    }

    static void processError(MCMPErrorCode errorCode, HttpServerExchange exchange) {
        processError(errorCode.getType(), errorCode.getMessage(), exchange);
    }

    /**
     * Send an error message.
     *
     * @param type         the error type
     * @param errString    the error string
     * @param exchange     the http server exchange
     */
    static void processError(String type, String errString, HttpServerExchange exchange) {
        exchange.setStatusCode(StatusCodes.INTERNAL_SERVER_ERROR);
        exchange.getResponseHeaders().add(Headers.CONTENT_TYPE, CONTENT_TYPE);
        exchange.getResponseHeaders().add(new HttpString("Version"), VERSION_PROTOCOL);
        exchange.getResponseHeaders().add(new HttpString("Type"), type);
        exchange.getResponseHeaders().add(new HttpString("Mess"), errString);
        exchange.endExchange();
        UndertowLogger.ROOT_LOGGER.mcmpProcessingError(type, errString);
    }

    /**
     * Transform the form data into an intermediate request data which can me used
     * by the web manager
     *
     * @param exchange    the http server exchange
     * @return
     * @throws IOException
     */
    RequestData parseFormData(final HttpServerExchange exchange) throws IOException {
        // Read post parameters
        final FormDataParser parser = parserFactory.createParser(exchange);
        final FormData formData = parser.parseBlocking();
        final RequestData data = new RequestData();
        for (String name : formData) {
            final HttpString key = new HttpString(name);
            data.add(key, formData.get(name));
        }
        return data;
    }

    private static void checkStringForSuspiciousCharacters(String data) {
        for(int i = 0; i < data.length(); ++i) {
            char c = data.charAt(i);
            if(c == '>' || c == '<' || c == '\\' || c == '\"' || c == '\n' || c == '\r') {
                throw UndertowMessages.MESSAGES.mcmpMessageRejectedDueToSuspiciousCharacters(data);
            }
        }
    }

    static class RequestData {

        private final Map> values = new LinkedHashMap<>();

        Iterator iterator() {
            return values.keySet().iterator();
        }

        void add(final HttpString name, Deque values) {
            checkStringForSuspiciousCharacters(name.toString());
            for (final FormData.FormValue value : values) {
                add(name, value);
            }
        }



        void addValues(final HttpString name, Deque value) {
            Deque values = this.values.get(name);
            for(String i : value) {
                checkStringForSuspiciousCharacters(i);
            }
            if (values == null) {
                this.values.put(name, value);
            } else {
                values.addAll(value);
            }
        }

        void add(final HttpString name, final FormData.FormValue value) {
            Deque values = this.values.get(name);
            if (values == null) {
                this.values.put(name, values = new ArrayDeque<>(1));
            }
            String stringVal = value.getValue();
            checkStringForSuspiciousCharacters(stringVal);
            values.add(stringVal);
        }

        String getFirst(HttpString name) {
            final Deque deque = values.get(name);
            return deque == null ? null : deque.peekFirst();
        }

    }

    static boolean checkString(final String value) {
        return value != null && value.length() > 0;
    }

}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy