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

net.lightbody.bmp.proxy.http.SimulatedSocketFactory Maven / Gradle / Ivy

package net.lightbody.bmp.proxy.http;

import org.apache.http.HttpHost;
import org.apache.http.conn.ConnectTimeoutException;
import org.apache.http.conn.HttpInetSocketAddress;
import org.apache.http.conn.scheme.HostNameResolver;
import org.apache.http.conn.socket.ConnectionSocketFactory;
import org.apache.http.protocol.HttpContext;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.net.InetSocketAddress;
import java.net.Socket;
import java.net.SocketAddress;
import java.net.SocketTimeoutException;
import java.util.Date;

public class SimulatedSocketFactory implements ConnectionSocketFactory {
    protected static final Logger logger = LoggerFactory.getLogger(SimulatedSocketFactory.class);
    /**
     * Prevent unnecessary class inspection at runtime.
     */
    private static Method getHostMethod;

    static {
        try {
            getHostMethod = InetSocketAddress.class.getDeclaredMethod("getHostString", new Class[]{});
            if (!Modifier.isPublic(getHostMethod.getModifiers())) {
                getHostMethod = null;
            }
        } catch (Exception e) {
            // ok to ignore, try the fall back
        }

        if (getHostMethod == null) {
            try {
                getHostMethod = InetSocketAddress.class.getDeclaredMethod("getHostName", new Class[]{});
                logger.warn("Using InetSocketAddress.getHostName() rather than InetSocketAddress.getHostString(). Consider upgrading to Java 7 for faster performance!");
            } catch (NoSuchMethodException e) {
                String msg = "Something is wrong inside SimulatedSocketFactory and I don't know why!";
                logger.error(msg, e);
                throw new RuntimeException(msg, e);
            }
        }

        getHostMethod.setAccessible(true);
    }

    private HostNameResolver hostNameResolver;
    private int requestTimeout;

    public SimulatedSocketFactory(HostNameResolver hostNameResolver, int requestTimeout) {
        super();
        assert hostNameResolver != null;
        this.hostNameResolver = hostNameResolver;
        this.requestTimeout = requestTimeout;
    }

    private void configureSocket(Socket socket) {
        // Configure the socket to be Load Test Friendly!
        // If we don't set these, we can easily use up too many sockets, even when we're cleaning/closing the sockets
        // responsibly. The reason is that they will stick around in TIME_WAIT for some time (ie: 1-4 minutes) and once
        // they get to 64K (on Linux) or 16K (on Mac) we can't make any more requests. While those limits can be raised
        // with a configuration setting in the OS, we really don't need to change things globally. We just need to make
        // sure that when we close a socket it gets ditched right away and doesn't stick around in TIME_WAIT.
        //
        // This problem is most easily noticable/problematic for load tests that use a single transaction to issue
        // one HTTP request and then end the transaction, thereby shutting down the HTTP socket. This can easily create
        // 64K+ sockets in TIME_WAIT state, preventing any other requests from going out and producing a false-negative
        // "connection refused" error message.
        //
        // For further reading, check out HttpClient's FAQ on this subject:
        // http://wiki.apache.org/HttpComponents/FrequentlyAskedConnectionManagementQuestions
        try {
            socket.setReuseAddress(true);
            socket.setSoLinger(true, 0);
        } catch (Exception e) {
            //this is fine not to do anything here
        }
    }

    /**
     * A minor optimization to prevent possible host resolution when inspecting a InetSocketAddress for a hostname....
     *
     * @param remoteAddress
     * @return
     * @throws IOException
     */
    private String resolveHostName(InetSocketAddress remoteAddress) {
        String hostString = null;
        try {
            hostString = (String) getHostMethod.invoke(remoteAddress, new Object[]{});
        } catch (InvocationTargetException | IllegalAccessException e) {
            throw new RuntimeException("Expecting InetSocketAddress to have a package scoped \"getHostString\" method which returns a String and takes no input");
        }
        return hostString;
    }

    @Override
    public Socket createSocket(HttpContext context) throws IOException {
        //Ignoring httpParams
        //apparently it's only useful to pass through a SOCKS server
        //see: http://svn.apache.org/repos/asf/httpcomponents/httpclient/trunk/httpclient/src/examples/org/apache/http/examples/client/ClientExecuteSOCKS.java

        //creating an anonymous class deriving from socket
        Socket newSocket = new Socket() {
            @Override
            public void connect(SocketAddress endpoint) throws IOException {
                super.connect(endpoint);
            }

            @Override
            public void connect(SocketAddress endpoint, int timeout) throws IOException {
                super.connect(endpoint, timeout);
            }

            @Override
            public InputStream getInputStream() throws IOException {
                return super.getInputStream();
            }

            @Override
            public OutputStream getOutputStream() throws IOException {
                return super.getOutputStream();
            }
        };
        configureSocket(newSocket);
        return newSocket;
    }

    @Override
    public Socket connectSocket(int connectTimeout, Socket sock, HttpHost host, InetSocketAddress remoteAddress, InetSocketAddress localAddress, HttpContext context) throws IOException {
        if (remoteAddress == null) {
            throw new IllegalArgumentException("Target host may not be null.");
        }

        if (context == null) {
            throw new IllegalArgumentException("Context may not be null.");
        }

        if (sock == null) {
            sock = createSocket(null);
        }

        if ((localAddress != null)) {
            sock.bind(localAddress);
        }

        String hostName;
        if (remoteAddress instanceof HttpInetSocketAddress) {
            hostName = ((HttpInetSocketAddress) remoteAddress).getHttpHost().getHostName();
        } else {
            hostName = resolveHostName(remoteAddress);
        }

        InetSocketAddress remoteAddr = remoteAddress;
        if (this.hostNameResolver != null) {
            remoteAddr = new InetSocketAddress(this.hostNameResolver.resolve(hostName), remoteAddress.getPort());
        }

        try {
            sock.connect(remoteAddr, connectTimeout);
        } catch (SocketTimeoutException ex) {
            throw new ConnectTimeoutException("Connect to " + remoteAddress + " timed out");
        }

        return sock;
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy