Package org.restlet.engine.connector

Source Code of org.restlet.engine.connector.ClientConnectionHelper

/**
* Copyright 2005-2011 Noelios Technologies.
*
* The contents of this file are subject to the terms of one of the following
* open source licenses: LGPL 3.0 or LGPL 2.1 or CDDL 1.0 or EPL 1.0 (the
* "Licenses"). You can select the license that you prefer but you may not use
* this file except in compliance with one of these Licenses.
*
* You can obtain a copy of the LGPL 3.0 license at
* http://www.opensource.org/licenses/lgpl-3.0.html
*
* You can obtain a copy of the LGPL 2.1 license at
* http://www.opensource.org/licenses/lgpl-2.1.php
*
* You can obtain a copy of the CDDL 1.0 license at
* http://www.opensource.org/licenses/cddl1.php
*
* You can obtain a copy of the EPL 1.0 license at
* http://www.opensource.org/licenses/eclipse-1.0.php
*
* See the Licenses for the specific language governing permissions and
* limitations under the Licenses.
*
* Alternatively, you can obtain a royalty free commercial license with less
* limitations, transferable or non-transferable, directly at
* http://www.noelios.com/products/restlet-engine
*
* Restlet is a registered trademark of Noelios Technologies.
*/

package org.restlet.engine.connector;

import java.io.IOException;
import java.net.InetSocketAddress;
import java.net.Socket;
import java.net.UnknownHostException;
import java.nio.channels.SocketChannel;
import java.util.Iterator;
import java.util.concurrent.CountDownLatch;
import java.util.logging.Level;

import org.restlet.Client;
import org.restlet.Request;
import org.restlet.Response;
import org.restlet.data.Reference;
import org.restlet.data.Status;

/**
* Base client helper based on NIO non blocking sockets. Here is the list of
* parameters that are supported. They should be set in the Client's context
* before it is started:
* <table>
* <tr>
* <th>Parameter name</th>
* <th>Value type</th>
* <th>Default value</th>
* <th>Description</th>
* </tr>
* <tr>
* <td>proxyHost</td>
* <td>String</td>
* <td>System property "http.proxyHost"</td>
* <td>The host name of the HTTP proxy.</td>
* </tr>
* <tr>
* <td>proxyPort</td>
* <td>int</td>
* <td>System property "http.proxyPort"</td>
* <td>The port of the HTTP proxy.</td>
* </tr>
* <tr>
* <td>socketConnectTimeoutMs</td>
* <td>int</td>
* <td>0</td>
* <td>The socket connection timeout or 0 for unlimited wait.</td>
* </tr>
* <tr>
* <td>socketKeepAlive</td>
* <td>boolean</td>
* <td>true</td>
* <td>Indicates if a TCP connection should be automatically kept alive after 2
* hours of inactivity.</td>
* </tr>
* <tr>
* <td>socketOobInline</td>
* <td>boolean</td>
* <td>false</td>
* <td>Indicates if urgent TCP data received on the socket will be received
* through the socket input stream.</td>
* </tr>
* <tr>
* <td>socketLingerTimeMs</td>
* <td>int</td>
* <td>-1</td>
* <td>Time to block when a socket close is requested or -1 to not block at all.
* </td>
* </tr>
* <tr>
* <td>socketNoDelay</td>
* <td>boolean</td>
* <td>false</td>
* <td>Enables Nagle's algorithm if set to false, preventing sending of small
* TCP packets.</td>
* </tr>
* <tr>
* <td>socketSendBufferSize</td>
* <td>int</td>
* <td>8192</td>
* <td>The hinted size of the underlying TCP buffers used by the platform for
* outbound network I/O.</td>
* </tr>
* <tr>
* <td>socketTrafficClass</td>
* <td>int</td>
* <td>0</td>
* <td>Type of service to set in IP packets.</td>
* </tr>
* </table>
*
* @author Jerome Louvel
*/
public abstract class ClientConnectionHelper extends ConnectionHelper<Client> {

    protected static final String CONNECTOR_LATCH = "org.restlet.engine.connector.latch";

    /**
     * Constructor.
     *
     * @param connector
     *            The helped client connector.
     */
    public ClientConnectionHelper(Client connector) {
        super(connector, true);
    }

    @Override
    protected Connection<Client> createConnection(SocketChannel socketChannel,
            ConnectionController controller, InetSocketAddress socketAddress)
            throws IOException {
        return new Connection<Client>(this, socketChannel, controller,
                socketAddress, getInboundBufferSize(), getOutboundBufferSize());
    }

    @Override
    protected ConnectionController createController() {
        return new ConnectionController(this);
    }

    /**
     * Creates the socket that will be used to send the request and get the
     * response. This method is called by {@link #getBestConnection(Request)}
     * when a new connection is to be created. By default, calls the
     * {@link #createSocketChannel(boolean, String, int)} method.
     *
     * @param secure
     *            Indicates if messages will be exchanged confidentially, for
     *            example via a SSL-secured connection.
     * @param socketAddress
     *            The holder of a host/port pair.
     * @return The created socket.
     * @throws UnknownHostException
     * @throws IOException
     */
    protected SocketChannel createSocketChannel(boolean secure,
            InetSocketAddress socketAddress) throws UnknownHostException,
            IOException {
        return createSocketChannel(secure, socketAddress.getHostName(),
                socketAddress.getPort());
    }

    /**
     * Creates the socket channel that will be used to send the request and get
     * the response.
     *
     * @param secure
     *            Indicates if messages will be exchanged confidentially, for
     *            example via a SSL-secured connection.
     * @param hostDomain
     *            The target host domain name.
     * @param hostPort
     *            The target host port.
     * @return The created socket channel.
     * @throws UnknownHostException
     * @throws IOException
     */
    protected SocketChannel createSocketChannel(boolean secure,
            String hostDomain, int hostPort) throws UnknownHostException,
            IOException {
        SocketChannel result = SocketChannel.open();
        result.configureBlocking(false);

        // Configure socket
        Socket socket = result.socket();
        socket.setKeepAlive(isSocketKeepAlive());
        socket.setOOBInline(isSocketOobInline());
        socket.setReceiveBufferSize(getSocketReceiveBufferSize());
        socket.setReuseAddress(isSocketReuseAddress());
        socket.setSoLinger(getSocketLingerTimeMs() > 0, getSocketLingerTimeMs());
        socket.setSendBufferSize(getSocketSendBufferSize());
        socket.setSoTimeout(getMaxIoIdleTimeMs());
        socket.setTcpNoDelay(isSocketNoDelay());
        socket.setTrafficClass(getSocketTrafficClass());

        InetSocketAddress address = new InetSocketAddress(hostDomain, hostPort);
        result.connect(address);
        return result;
    }

    @Override
    public void doHandleInbound(Response response) {
        if (response != null) {
            getLogger().finer("Handling response...");
            boolean handled = false;

            if ((response.getRequest() != null)
                    && (response.getRequest().getOnResponse() != null)) {
                response.getRequest().getOnResponse()
                        .handle(response.getRequest(), response);
                handled = true;
            }

            if (!response.getStatus().isInformational()) {
                // Informational response shouldn't unblock a synchronous
                // call waiting for a final response.
                unblock(response);
            } else if (!handled) {
                getLogger().info("Provisional response ignored: " + response);
            }
        }
    }

    @Override
    public void doHandleOutbound(Response response) {
        try {
            if ((response != null) && (response.getRequest() != null)) {
                if (getLogger().isLoggable(Level.FINE)) {
                    getLogger().log(
                            Level.FINE,
                            "Client request to be sent: "
                                    + response.getRequest());
                }

                Connection<Client> bestConn = getBestConnection(response
                        .getRequest());

                if (bestConn != null) {
                    bestConn.getOutboundWay().handle(response);
                    getConnections().add(bestConn);
                } else {
                    getLogger().log(Level.WARNING,
                            "Unable to find a connection to send the request");
                    response.setStatus(Status.CONNECTOR_ERROR_COMMUNICATION,
                            "Unable to find a connection to send the request");
                    unblock(response);
                }
            }
        } catch (Throwable t) {
            getLogger()
                    .log(Level.FINE,
                            "An error occured during the communication with the remote server.",
                            t);
            response.setStatus(Status.CONNECTOR_ERROR_COMMUNICATION, t);
            unblock(response);
        }
    }

    /**
     * Tries to reuse an existing connection for the given request, or creates a
     * new one. It may return null if the maximum number of connections per host
     * or in general is reached.
     *
     * @param request
     *            The request to handle.
     * @return An existing connection able to handle the request or new one.
     * @throws UnknownHostException
     * @throws IOException
     */
    protected Connection<Client> getBestConnection(Request request)
            throws UnknownHostException, IOException {
        Connection<Client> result = null;

        // Try to reuse an existing connection for the same host and
        // port
        int hostConnectionCount = 0;
        int bestScore = 0;
        boolean foundConn = false;

        // Determine the target host domain and port of the request.
        InetSocketAddress socketAddress = getSocketAddress(request);

        if (socketAddress == null) {
            getLogger()
                    .log(Level.WARNING,
                            "Unable to create a socket address related to the request.");
        } else {
            // Associate the given request to the first available connection
            // opened on the same host domain and port.
            for (Iterator<Connection<Client>> iterator = getConnections()
                    .iterator(); !foundConn && iterator.hasNext();) {
                Connection<Client> currConn = iterator.next();

                if (socketAddress.equals(currConn.getSocketAddress())) {
                    if (currConn.isAvailable()) {
                        result = currConn;
                        foundConn = true;
                    } else {
                        // Assign the request to the busy connection that
                        // handles the less number of messages. This is useful
                        // in case the maximum number of connections has been
                        // reached. As a drawback, the message will only be
                        // handled as soon as possible.
                        int currScore = currConn.getLoadScore();

                        if (bestScore > currScore) {
                            bestScore = currScore;
                            result = currConn;
                        }

                        hostConnectionCount++;
                    }
                }
            }

            // No connection has been found, try to create a new one that will
            // handle the message soon.
            if (foundConn) {
                getLogger().log(
                        Level.FINE,
                        "Reusing an existing client connection to: "
                                + socketAddress);
            } else if ((getMaxTotalConnections() != -1)
                    && (getConnections().size() >= getMaxTotalConnections())) {
                getLogger()
                        .log(Level.WARNING,
                                "Unable to create a new connection. Maximum total number of connections reached!");
            } else if ((getMaxConnectionsPerHost() != -1)
                    && (hostConnectionCount >= getMaxConnectionsPerHost())) {
                getLogger()
                        .log(Level.WARNING,
                                "Unable to create a new connection. Maximum number of connections reached for host: "
                                        + socketAddress);
            } else {
                // Create a new connection
                if (getLogger().isLoggable(Level.FINE)) {
                    getLogger().log(
                            Level.FINE,
                            "Creating a new client connection to: "
                                    + socketAddress);
                }

                result = checkout(
                        createSocketChannel(request.isConfidential(),
                                socketAddress), getController(), socketAddress);
                getConnections().add(result);
            }
        }

        return result;
    }

    /**
     * Returns the host name of the HTTP proxy, if specified.
     *
     * @return the host name of the HTTP proxy, if specified.
     */
    public String getProxyHost() {
        return getHelpedParameters().getFirstValue("proxyHost",
                System.getProperty("http.proxyHost"));
    }

    /**
     * Returns the port of the HTTP proxy, if specified, 3128 otherwise.
     *
     * @return the port of the HTTP proxy.
     */
    public int getProxyPort() {
        String proxyPort = getHelpedParameters().getFirstValue("proxyPort",
                System.getProperty("http.proxyPort"));

        if (proxyPort == null) {
            proxyPort = "3128";
        }

        return Integer.parseInt(proxyPort);
    }

    /**
     * Returns an IP socket address representing the target host domain and port
     * for a given request. If the helper relies on a proxy, the socket
     * represents the domain and port of the proxy host. Used by the
     * {@link #getBestConnection(Request)} method.
     *
     * @param request
     *            The given request
     * @return The IP socket address representing the target host domain and
     *         port for a given request.
     * @throws UnknownHostException
     *             If the proxy port is invalid or the host unresolved.
     */
    protected InetSocketAddress getSocketAddress(Request request)
            throws UnknownHostException {
        InetSocketAddress result = null;
        String hostDomain = null;
        int hostPort = 0;

        // Does this helper relies on a proxy?
        String proxyDomain = getProxyHost();

        if (proxyDomain != null && !"".equals(proxyDomain)) {
            hostDomain = proxyDomain;
            try {
                hostPort = getProxyPort();
            } catch (NumberFormatException nfe) {
                getLogger().log(Level.WARNING,
                        "The proxy port must be a valid numeric value.", nfe);
                throw new UnknownHostException();
            }
        } else {
            // Resolve relative references
            Reference resourceRef = request.getResourceRef().isRelative() ? request
                    .getResourceRef().getTargetRef() : request.getResourceRef();

            // Extract the host info
            hostDomain = resourceRef.getHostDomain();
            hostPort = resourceRef.getHostPort();
            if (hostPort == -1) {
                if (resourceRef.getSchemeProtocol() != null) {
                    hostPort = resourceRef.getSchemeProtocol().getDefaultPort();
                } else {
                    hostPort = getProtocols().get(0).getDefaultPort();
                }
            }
        }

        if (hostDomain != null) {
            result = new InetSocketAddress(hostDomain, hostPort);
            if (result != null && result.getAddress() == null) {
                throw new UnknownHostException(hostDomain);
            }
        }

        return result;
    }

    /**
     * Returns the socket connection timeout.
     *
     * @return The socket connection timeout.
     */
    @SuppressWarnings("deprecation")
    public int getSocketConnectTimeoutMs() {
        int result = getHelped().getConnectTimeout();

        if (getHelpedParameters().getNames().contains("socketConnectTimeoutMs")) {
            result = Integer.parseInt(getHelpedParameters().getFirstValue(
                    "socketConnectTimeoutMs", "0"));
        }

        return result;
    }

    /**
     * Returns the time to block when a socket close is requested or -1 to not
     * block at all.
     *
     * @return The time to block when a socket close is requested or -1 to not
     *         block at all.
     */
    public int getSocketLingerTimeMs() {
        return Integer.parseInt(getHelpedParameters().getFirstValue(
                "socketLingerTimeMs", "-1"));

    }

    /**
     * Returns the hinted size of the underlying TCP buffers used by the
     * platform for outbound network I/O.
     *
     * @return The hinted size of the underlying TCP buffers used by the
     *         platform for outbound network I/O.
     */
    public int getSocketSendBufferSize() {
        return Integer.parseInt(getHelpedParameters().getFirstValue(
                "socketSendBufferSize", "8192"));

    }

    /**
     * Returns the type of service to set in IP packets.
     *
     * @return The type of service to set in IP packets.
     */
    public int getSocketTrafficClass() {
        return Integer.parseInt(getHelpedParameters().getFirstValue(
                "socketTrafficClass", "0"));

    }

    @Override
    public void handle(Request request, Response response) {
        try {
            if (getLogger().isLoggable(Level.FINE)) {
                getLogger().log(Level.FINE,
                        "Handling client request: " + request);
            }

            if (isSynchronous(request) && request.isExpectingResponse()) {
                // Prepare the latch to block the caller thread
                CountDownLatch latch = new CountDownLatch(1);
                request.getAttributes().put(CONNECTOR_LATCH, latch);

                // Add the message to the outbound queue for processing
                getOutboundMessages().add(response);

                // Await on the latch
                latch.await();
            } else {
                // Add the message to the outbound queue for processing
                getOutboundMessages().add(response);
            }
        } catch (Exception e) {
            getLogger().log(
                    Level.INFO,
                    "Error while handling a " + request.getProtocol().getName()
                            + " client request", e);
            response.setStatus(Status.CONNECTOR_ERROR_INTERNAL, e);
        }
    }

    @Override
    protected void handleInbound(Response response) {
        handleInbound(response, isSynchronous(response.getRequest()));
    }

    @Override
    protected void handleOutbound(Response response) {
        handleOutbound(response, true);
    }

    @Override
    public boolean isControllerDaemon() {
        return Boolean.parseBoolean(getHelpedParameters().getFirstValue(
                "controllerDaemon", "true"));
    }

    @Override
    public boolean isProxying() {
        return getProxyHost() != null;
    }

    /**
     * Indicates if a TCP connection should be automatically kept alive after 2
     * hours of inactivity.
     *
     * @return True if a TCP connection should be automatically kept alive after
     *         2 hours of inactivity.
     */
    public boolean isSocketKeepAlive() {
        return Boolean.parseBoolean(getHelpedParameters().getFirstValue(
                "socketKeepAlive", "true"));
    }

    /**
     * Enables Nagle's algorithm if set to false, preventing sending of small
     * TCP packets.
     *
     * @return True if Nagle's algorithm should be disabled.
     */
    public boolean isSocketNoDelay() {
        return Boolean.parseBoolean(getHelpedParameters().getFirstValue(
                "socketNoDelay", "false"));
    }

    /**
     * Indicates if urgent TCP data received on the socket will be received
     * through the socket input stream.
     *
     * @return True if urgent TCP data received on the socket will be received
     *         through the socket input stream.
     */
    public boolean isSocketOobInline() {
        return Boolean.parseBoolean(getHelpedParameters().getFirstValue(
                "socketOobInline", "false"));
    }

    /**
     * Indicates if the given request is handled in a synchronous way, blocking
     * the calling thread.
     *
     * @param request
     *            The request to test.
     * @return True if the given request is handled in a synchronous way.
     */
    public boolean isSynchronous(Request request) {
        return (request == null) || (request.getOnResponse() == null);
    }

    @Override
    public void start() throws Exception {
        getLogger().info("Starting the internal " + getProtocols() + " client");
        super.start();
    }

    @Override
    public void stop() throws Exception {
        getLogger().info("Stopping the internal" + getProtocols() + " client");
        super.stop();
    }

    /**
     * Unblocks the thread that handles the given request/response pair.
     *
     * @param response
     *            The response.
     */
    protected void unblock(Response response) {
        if (response.getRequest() != null) {
            CountDownLatch latch = (CountDownLatch) response.getRequest()
                    .getAttributes().get(CONNECTOR_LATCH);

            if (latch != null) {
                latch.countDown();
            }
        } else {
            getLogger().warning(
                    "The client of the following response couldn't be unblocked: "
                            + response);
        }
    }

}
TOP

Related Classes of org.restlet.engine.connector.ClientConnectionHelper

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.