Apache HTTP Client 4.3.4 Example with Timeouts, Client Cert and Trusted Cert from KeyStores

If you have ever used or tried to use the Apache HTTP Client library, you’ve probably discovered that is is very useful and powerful, but they change the library so much each version that it is hard to find proper examples of how to do complex things like adding connection timeouts, basic auth, or adding a client cert, or trusting a self-signed cert. Here’s a code snippet of getting a client cert and a trusted server cert loaded with timeouts in version 4.3.4 of the library.

    <dependency>
	<groupId>org.apache.httpcomponents</groupId>
	<artifactId>httpclient</artifactId>
	<version>4.3.4</version>
    </dependency>

    public static HttpClient getSSLClient(String serverKeystoreFile, String serverKeystorePassword, String serverKeystoreType,
                                         String clientCertFile, String clientCertPassword, String clientKeystoreType) throws Exception {

        // Server cert trust stuff
        KeyStore trustStore = KeyStore.getInstance(serverKeystoreType);
        trustStore.load(new FileInputStream(new File(serverKeystoreFile)), serverKeystorePassword.toCharArray());
        TrustManagerFactory trustFactory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
        trustFactory.init(trustStore);
        TrustManager[] trustManagers = trustFactory.getTrustManagers();

        // Client cert stuff
        KeyStore clientCert = KeyStore.getInstance(clientKeystoreType);
        clientCert.load(new FileInputStream(new File(clientCertFile)), clientCertPassword.toCharArray());
        KeyManagerFactory keyFactory = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
        keyFactory.init(clientCert, clientCertPassword.toCharArray());
        KeyManager[] keyManagers = keyFactory.getKeyManagers();

        SSLContext sslcontext = SSLContext.getInstance("TLS");
        sslcontext.init(keyManagers, trustManagers, new SecureRandom());

        SSLConnectionSocketFactory sslsf = new SSLConnectionSocketFactory(sslcontext);

        HttpClient httpClient = HttpClients.custom()
                .setSSLSocketFactory(sslsf)
                .build();

        return httpClient;
    }

    public static String executePost() throws Exception {

	 URI uri = new URIBuilder("http://my.superawesome.fakeurlstuffandstuff.com/path1/path2/file.php")).build();
	 String serverKeystore = Properties.get("TRUSTED_CERT_KEYSTORE_FILE_PATH"); // JKS FILE
	 String serverPass = Properties.get("TRUSTED_CERT_KEYSTORE_PASSWORD");
	 String clientKeystore = Properties.get("CLIENT_KEYSTORE"); // PFX FILE
	 String clientPass = Properties.get("CLIENT_KEYSTORE_PASSWORD");

	 HttpClient client = HttpJsonUtil.getClient(serverKeystore, serverPass, "JKS", clientKeystore, clientPass, "PKCS12");

	 RequestConfig.Builder requestConfigBuilder = RequestConfig.custom();
	 requestConfigBuilder.setMaxRedirects(2);
	 requestConfigBuilder.setConnectionRequestTimeout(Properties.getInt("SERVER_REQUEST_TIMEOUT_MILLIS", 30_000));
	 requestConfigBuilder.setConnectTimeout(Properties.getInt("SERVER_CONNECT_TIMEOUT_MILLIS", 10_000));

	 httpPost = new HttpPost(uri);
	 httpPost.setConfig(requestConfigBuilder.build());
	 httpPost.setEntity(new StringEntity("REQUEST BODY"));
	 httpPost.setHeader("Content-Type", "application/json");
	 httpPost.setHeader("Accept", "application/json");
	 HttpResponse response = client.execute(httpPost);
	 return EntityUtils.toString(response.getEntity());       
    }

Java – IntelliJ – Maven – Remote deploy/debug standalone apps

This method uses a Maven plugin called Wagon to upload and execute/debug a jar on another machine.  IntelliJ supports executing any kind of scripts or programs during a remote deploy beyond Maven commands, so you may find those easier to create and use. 

Install an SSH server on your remote box.

Windows Servers:

Linux:

  • sudo apt-get install openssh-server
  • sudo vim /etc/ssh/sshd_config
  • sudo /etc/init.d/ssh restart

Add a <server> block to settings.xml in .m2 folder on the box with the code:

<server>

    <id>remote-box</id>

    <username>sshuser</username>

    <password>mysshpassword</password>

</server>

 

Your project’s pom.xml additions:

Replace [ip.address] with your remote box’s ip.  Replace the <fromDir> with your project’s target directory.  Replace the <includes> with the jar or file patterns that you wish to deploy.

<build>

        <extensions>

            <extension>

                <groupId>org.apache.maven.wagon</groupId>

                <artifactId>wagon-ssh</artifactId>

                <version>1.0</version>

            </extension>

        </extensions>

        <plugins>

            <plugin>

                <groupId>org.codehaus.mojo</groupId>

                <artifactId>wagon-maven-plugin</artifactId>

                <version>1.0-beta-5</version>

                <configuration>

                    <url>scp://[ip.address]</url>

                    <serverId>dev-box</serverId>

                    <fromDir>C:/myproject/target/</fromDir>

                    <includes>testcron-1.0-SNAPSHOT.jar</includes>

                    <commands>

                        <command>./start.sh</command>

                    </commands>

                </configuration>

                <executions>

                    <execution>

                        <id>upload-jar</id>

                        <phase>deploy</phase>

                        <goals>

                            <goal>upload</goal>

                        </goals>

                    </execution>

                    <execution>

                        <id>execute-test-commands</id>

                        <phase>deploy</phase>

                        <goals>

                            <goal>sshexec</goal>

                        </goals>

                        <configuration>

                            <serverId>remote-box</serverId>

                        </configuration>

                    </execution>

                </executions>

            </plugin>

        </plugins>

    </build>

 

Place a startup script ‘start.sh’ in the root SSH directory of your remote box:

Replace the classpath with the root scp folder and your project’s jar artifact.  Replace the TestCron class with your main class.  Replace 4001 with your desired debugging port (open this port in your remote box firewall).  Use suspend=n if you don’t want to wait for the debugger.  Replace log.txt with what you want to catch stdin and stderror.

java -Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=4001 -cp “C:\Program Files (x86)\ICW\testcron-1.0-SNAPSHOT.jar” com.test.TestCron > log.txt 2>&1 &

Alternative:

You can also put the start.sh command(s) in the pom.xml file in the <commands> section.  I had issues with the ampersands in xml, so I created a script to execute instead.

Create a new Run/Debug Configuration in IntelliJ on your code box with the following params and Maven goals:

Host: 192.168.1.10 (your remote box’s ip)

Port: 4001 (The debug port from the start.sh script)

Maven goals:

  • jar:jar
  • wagon:upload
  • wagon:sshexec

 

Click ‘Debug’ and your jar will be built, scp’d to your remote box, executed with an ssh exec of your start.sh script, and the IntellIJ debugger will connect.  Debug as normal and tail log.txt for output, or whatever file log4j is set to.  Package any property files into the jar or place them in the root scp folder for the jar to use.

  

References:

java.lang.NoSuchMethodError

Maven can be nice, but sometimes you can end up with one jar loading before another and that causes unpredictable results at run-time.

An example exception:

  
Exception in thread "Thread-24" java.lang.NoSuchMethodError: 
 com.sun.mail.util.SocketFetcher.getSocket(Ljava/lang/String;ILjava/util/Properties;Ljava/lang/String;Z)Ljava/net/Socket;
	at  com.sun.mail.smtp.SMTPTransport.openServer(SMTPTransport.java:1359)
	at com.sun.mail.smtp.SMTPTransport.protocolConnect(SMTPTransport.java:412)
	at javax.mail.Service.connect(Service.java:288)
	at javax.mail.Service.connect(Service.java:169)

How to find the offending jar file:

     
     Class cls = SocketFetcher.class;
     ProtectionDomain pDomain = cls.getProtectionDomain();
     CodeSource cSource = pDomain.getCodeSource();
     URL loc = cSource.getLocation();
     System.out.println(loc.toString());

How to fix it:

   <dependency>
      ...
      <exclusions>
        <exclusionv
          <groupId>com.google</groupId>
          <artifactId>googleapi-nomail</artifactId>
        </exclusion>
     </exclusions>
   </dependency>