Client.java [src/cokeyconverter] Revision: default  Date:
/*
 * To change this license header, choose License Headers in Project Properties.
 * To change this template file, choose Tools | Templates
 * and open the template in the editor.
 */
package cokeyconverter;

/*
 * $Id$
 *
 * This file is part of the Cloud Services Integration Platform (CSIP),
 * a Model-as-a-Service framework, API and application suite.
 *
 * 2012-2017, Olaf David and others, OMSLab, Colorado State University.
 *
 * OMSLab licenses this file to you under the MIT license.
 * See the LICENSE file in the project root for more information.
 */

import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.net.HttpURLConnection;
import static java.net.HttpURLConnection.HTTP_OK;
import java.net.URL;
import java.util.Arrays;
import java.util.logging.Level;
import java.util.logging.Logger;
import org.apache.commons.io.FileUtils;
import org.apache.commons.io.IOUtils;
import org.apache.http.HttpEntity;
import org.apache.http.HttpException;
import org.apache.http.HttpHost;
import org.apache.http.HttpRequest;
import org.apache.http.HttpRequestInterceptor;
import org.apache.http.HttpResponse;
import org.apache.http.auth.AuthScope;
import org.apache.http.auth.UsernamePasswordCredentials;
import org.apache.http.client.CredentialsProvider;
import org.apache.http.client.config.AuthSchemes;
import org.apache.http.client.config.CookieSpecs;
import org.apache.http.client.config.RequestConfig;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpDelete;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.client.methods.HttpPut;
import org.apache.http.config.RegistryBuilder;
import org.apache.http.conn.routing.HttpRoute;
import org.apache.http.conn.socket.ConnectionSocketFactory;
import org.apache.http.conn.socket.PlainConnectionSocketFactory;
import org.apache.http.entity.ContentType;
import org.apache.http.entity.StringEntity;
import org.apache.http.entity.mime.MultipartEntityBuilder;
import org.apache.http.entity.mime.content.FileBody;
import org.apache.http.entity.mime.content.StringBody;
import org.apache.http.impl.auth.BasicScheme;
import org.apache.http.impl.client.BasicCredentialsProvider;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClientBuilder;
import org.apache.http.impl.conn.PoolingHttpClientConnectionManager;
import org.apache.http.pool.PoolStats;
import org.apache.http.protocol.BasicHttpContext;
import org.apache.http.protocol.HttpContext;
import org.codehaus.jettison.json.JSONObject;

import org.apache.http.conn.ssl.SSLConnectionSocketFactory;
import org.apache.http.ssl.SSLContexts;

/**
 * HTTP Client for easier CSIP service calling.
 *
 * @author od
 */
public class Client {

    private Logger log;
    private static final PoolingHttpClientConnectionManager cm;
    private CloseableHttpClient httpClient;

    static final int DEF_TIMEOUT = 3600; // 1 h timeout

    private CredentialsProvider creds;
    private BasicHttpContext context;


    static {

        cm = new PoolingHttpClientConnectionManager(
                RegistryBuilder.<ConnectionSocketFactory>create()
                        .register("http", PlainConnectionSocketFactory.getSocketFactory())
                        .register("https", new SSLConnectionSocketFactory(SSLContexts.createSystemDefault()))
                        .build()
        );

        cm.setMaxTotal(250);
        // Increase default max connection per route to 75
        cm.setDefaultMaxPerRoute(100);
        // Increase max connections for localhost:80 to 125
        HttpHost localhost = new HttpHost("locahost", 80);
        cm.setMaxPerRoute(new HttpRoute(localhost), 150);
    }


    /**
     * Create a Client
     */
    public Client() {
        this(DEF_TIMEOUT, Logger.getLogger(Client.class.getName()));
    }


    /**
     * Create a client with a logger.
     * @param log the Logger
     *
     */
    public Client(Logger log) {
        this(DEF_TIMEOUT, log);
    }


    /**
     * Create a client with a custom timeout.
     *
     * @param timeout the timeout in
     */
    public Client(int timeout) {
        this(timeout, Logger.getLogger(Client.class.getName()));
    }


    public Client(int timeout, Logger log) {
        RequestConfig reqConfig = init(timeout, log);
        httpClient = HttpClientBuilder.create()
                .setDefaultRequestConfig(reqConfig)
                .setConnectionManager(cm)
                .build();
    }


    public Client(int timeout, Logger log, String username, String password) {
        RequestConfig reqConfig = init(timeout, log);

        context = new BasicHttpContext();
        BasicScheme basicAuth = new BasicScheme();
        context.setAttribute("preemptive-auth", basicAuth);

        creds = new BasicCredentialsProvider();
        creds.setCredentials(AuthScope.ANY, new UsernamePasswordCredentials(username, password));

        httpClient = HttpClientBuilder.create()
                .setDefaultRequestConfig(reqConfig)
                .setConnectionManager(cm)
                .addInterceptorFirst(new PreemptiveAuthInterceptor())
                .build();
    }


    private RequestConfig init(int timeout, Logger log) {
        this.log = log;
        RequestConfig reqConfig = RequestConfig.custom()
                .setConnectTimeout(timeout * 1000)
                .setConnectionRequestTimeout(timeout * 1000)
                .setSocketTimeout(timeout * 1000) // 10 minute max per model run
                .setCookieSpec(CookieSpecs.BEST_MATCH)
                .setExpectContinueEnabled(true)
                .setStaleConnectionCheckEnabled(true)
                .setTargetPreferredAuthSchemes(Arrays.asList(AuthSchemes.NTLM, AuthSchemes.DIGEST))
                .setProxyPreferredAuthSchemes(Arrays.asList(AuthSchemes.BASIC))
                .build();
        return reqConfig;
    }

    private class PreemptiveAuthInterceptor implements HttpRequestInterceptor {

        @Override
        @SuppressWarnings("deprecation")
        public void process(final HttpRequest request, final HttpContext context) throws HttpException, IOException {
            request.addHeader(BasicScheme.authenticate(creds.getCredentials(AuthScope.ANY), "US-ASCII", false));
        }
    }


    /**
     * Pings a HTTP URL. This effectively sends a HEAD request and returns
     * <code>true</code> if the response code is in the 200-399 range.
     * @param url The HTTP URL to be pinged.
     * @param timeout The timeout in millis for both the connection timeout and
     * the response read timeout. Note that the total timeout is effectively two
     * times the given timeout.
     * @return the time to respond if the given HTTP URL has returned response
     * code 200-399 on a HEAD request within the given timeout, otherwise
     * <code>-1</code>.
     */
    public static long ping(String url, int timeout) {
        url = url.replaceFirst("^https", "http");
        try {
            HttpURLConnection connection = (HttpURLConnection) new URL(url).openConnection();
            connection.setConnectTimeout(timeout);
            connection.setReadTimeout(timeout);
            connection.setRequestMethod("HEAD");
            long start = System.currentTimeMillis();
            int responseCode = connection.getResponseCode();
            long end = System.currentTimeMillis();
            return (200 <= responseCode && responseCode <= 399) ? (end - start) : -1;
        } catch (Exception E) {
            return -1;
        }
    }


    public File doGET(String url, File file) throws Exception {
        if (log != null && log.isLoggable(Level.INFO)) {
            log.info("GET : " + url + " -> " + file.toString());
        }
        HttpGet get = new HttpGet(url);
        try (CloseableHttpResponse resultsResponse = httpClient.execute(get)) {
            InputStream output = resultsResponse.getEntity().getContent();
            FileUtils.copyInputStreamToFile(output, file);
        } finally {
            get.releaseConnection();
        }
        return file;
    }


    public String doGET(String url) throws Exception {
        if (log != null && log.isLoggable(Level.INFO)) {
            log.info("GET : " + url);
        }
        HttpGet get = new HttpGet(url);
        try (CloseableHttpResponse resultsResponse = httpClient.execute(get)) {
            InputStream output = resultsResponse.getEntity().getContent();
            return IOUtils.toString(output);
        } finally {
            get.releaseConnection();
        }
    }


    public int doDelete(String url) throws Exception {
        HttpDelete del = new HttpDelete(url);
        int status = 0;
        try {
            HttpResponse resultsResponse = httpClient.execute(del);
            status = resultsResponse.getStatusLine().getStatusCode();
        } finally {
            del.releaseConnection();
        }
        if (log != null && log.isLoggable(Level.INFO)) {
            log.info("DELETE : " + url + " -> " + status);
        }
        return status;
    }


    /**
     * HTTP Post with the request JSON and files attached.
     *
     * @param uri the endpoint
     * @param req the request JSON
     * @param files the files to attach
     * @return the response JSON
     * @throws Exception if something goes wrong
     */
    public JSONObject doPOST(String uri, JSONObject req, File[] files) throws Exception {
        return doPOST(uri, req, files, null);
    }


    /**
     * HTTP Post with the request JSON.
     *
     * @param uri the endpoint
     * @param req the request JSON
     * @return the response JSON
     * @throws Exception if something goes wrong
     */
    public JSONObject doPOST(String uri, JSONObject req) throws Exception {
        return doPOST(uri, req, new File[]{}, new String[]{});
    }


    public boolean doPUT(String uri, JSONObject req) throws Exception {
        HttpPut put = new HttpPut(uri);
        put.addHeader("Content-Type", "application/json");
        StringEntity entity = new StringEntity(req.toString(), "UTF-8");
        entity.setContentType("application/json");
        if (log != null && log.isLoggable(Level.INFO)) {
            log.info("attaching : " + req.toString());
        }
        put.setEntity(entity);
        if (log != null && log.isLoggable(Level.INFO)) {
            log.info("PUT : " + uri);
        }
        int statusCode;
        try (CloseableHttpResponse response = httpClient.execute(put)) {
            statusCode = response.getStatusLine().getStatusCode();
        } finally {
            put.releaseConnection();
        }
        return statusCode == HTTP_OK;
    }


    public JSONObject doPOST(String uri, JSONObject req, File[] files, String[] names) throws Exception {
        if (log != null && log.isLoggable(Level.INFO)) {
            log.info(" attaching request as 'param':" + req.toString(2));
        }

        MultipartEntityBuilder builder = MultipartEntityBuilder.create();
        builder.addPart("param", new StringBody(req.toString(), ContentType.APPLICATION_JSON));
        for (int i = 0; i < files.length; i++) {
            FileBody b = new FileBody(files[i], ContentType.APPLICATION_OCTET_STREAM, names == null ? files[i].getName() : names[i]);
            builder.addPart("file" + (i + 1), b);
            if (log != null && log.isLoggable(Level.INFO)) {
                log.info(" attached file" + b.getFile().toString() + " as " + b.getFilename());
            }
        }
        HttpEntity reqEntity = builder.build();
        HttpPost post = new HttpPost(uri);
        post.setEntity(reqEntity);

        if (log != null && log.isLoggable(Level.INFO)) {
            log.info("POST: " + uri + " ...");
        }
        String response;
        try (CloseableHttpResponse httpResponse = httpClient.execute(post)) {
            InputStream responseStream = httpResponse.getEntity().getContent();
            response = IOUtils.toString(responseStream);
            if (log != null && log.isLoggable(Level.INFO)) {
                log.info("response: " + response);
                PoolStats ps = cm.getTotalStats();
                log.info("http-client-connections pool [avail=" + ps.getAvailable() + " leased=" + ps.getLeased() + " pending=" + ps.getPending() + "] ");
            }
        } finally {
            post.releaseConnection();
        }
        return new JSONObject(response);
    }


//    static String parseEndpoint(String ep) {
////   [ http://129.82.226.52:8080/csip-gras/m/gras/wgtavgforageprod/1.0   ] , [http://129.82.226.52:8080/csip-gras/m/gras//1.0]"));
//        int s = ep.indexOf('[');
//        int e = ep.indexOf(']', s);
//        if (s == -1 || e == -1) {
//            return null;
//        }
//        String first = ep.substring(s + 1, e);
//        return first.trim();
//    }
    public static void main(String[] args) throws Exception {
//        JSONObject o = new JSONObject("{\"metainfo\":{},\"parameter\":[{\"name\":\"cokey\",\"value\":\"11790680\"}]}");
//        JSONObject r = new Client().doPOST("http://csip.engr.colostate.edu:8092/csip-soils/d/rusle2soilinput/1.0", o);
//        JSONObject r = new Client().doPOST("http://localhost:8080/csip-soils/d/rusle2soilinput/1.0", o);
        JSONObject req = new JSONObject(args[1]);
        JSONObject res = new Client().doPOST(args[0], req);
        System.out.println(res);
    }

}