org.waarp.gateway.kernel.rest.client.HttpRestClientHelper.java Source code

Java tutorial

Introduction

Here is the source code for org.waarp.gateway.kernel.rest.client.HttpRestClientHelper.java

Source

/**
   This file is part of Waarp Project.
    
   Copyright 2009, Frederic Bregier, and individual contributors by the @author
   tags. See the COPYRIGHT.txt in the distribution for a full listing of
   individual contributors.
    
   All Waarp Project is free software: you can redistribute it and/or 
   modify it under the terms of the GNU General Public License as published 
   by the Free Software Foundation, either version 3 of the License, or
   (at your option) any later version.
    
   Waarp is distributed in the hope that it will be useful,
   but WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   GNU General Public License for more details.
    
   You should have received a copy of the GNU General Public License
   along with Waarp .  If not, see <http://www.gnu.org/licenses/>.
 */
package org.waarp.gateway.kernel.rest.client;

import java.io.File;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.Map;
import java.util.Map.Entry;

import io.netty.bootstrap.Bootstrap;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.Channel;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.handler.codec.http.DefaultFullHttpRequest;
import io.netty.handler.codec.http.DefaultHttpRequest;
import io.netty.handler.codec.http.FullHttpRequest;
import io.netty.handler.codec.http.HttpHeaderNames;
import io.netty.handler.codec.http.HttpHeaderValues;
import io.netty.handler.codec.http.HttpHeaders;
import io.netty.handler.codec.http.HttpMethod;
import io.netty.handler.codec.http.HttpRequest;
import io.netty.handler.codec.http.HttpVersion;
import io.netty.handler.codec.http.QueryStringEncoder;

import org.joda.time.DateTime;
import org.waarp.common.crypto.HmacSha256;
import org.waarp.common.crypto.ssl.WaarpSslUtility;
import org.waarp.common.exception.CryptoException;
import org.waarp.common.logging.WaarpLogger;
import org.waarp.common.logging.WaarpLoggerFactory;
import org.waarp.common.logging.WaarpSlf4JLoggerFactory;
import org.waarp.common.utility.WaarpNettyUtil;
import org.waarp.common.utility.WaarpStringUtils;
import org.waarp.common.utility.WaarpThreadFactory;
import org.waarp.gateway.kernel.exception.HttpInvalidAuthenticationException;
import org.waarp.gateway.kernel.rest.RestArgument;

/**
 * Http Rest Client helper
 * 
 * @author "Frederic Bregier"
 *
 */
public class HttpRestClientHelper {
    private static WaarpLogger logger = null;

    /**
     * ExecutorService Worker Boss
     */
    private final EventLoopGroup workerGroup;

    private final Bootstrap bootstrap;

    private final HttpHeaders headers;

    private String baseUri = "/";

    /**
     * @param baseUri
     *            base of all URI, in general simply "/" (default if null)
     * @param nbclient
     *            max number of client connected at once
     * @param timeout
     *            timeout used in connection
     * @param Initializer
     *            the associated client pipeline factory
     */
    public HttpRestClientHelper(String baseUri, int nbclient, long timeout,
            ChannelInitializer<SocketChannel> Initializer) {
        if (logger == null) {
            logger = WaarpLoggerFactory.getLogger(HttpRestClientHelper.class);
        }
        if (baseUri != null) {
            this.baseUri = baseUri;
        }
        // Configure the client.
        bootstrap = new Bootstrap();
        workerGroup = new NioEventLoopGroup(nbclient, new WaarpThreadFactory("Rest_" + baseUri + "_"));
        WaarpNettyUtil.setBootstrap(bootstrap, workerGroup, 30000);
        // Configure the pipeline factory.
        bootstrap.handler(Initializer);

        // will ignore real request
        HttpRequest request = new DefaultHttpRequest(HttpVersion.HTTP_1_1, HttpMethod.GET, baseUri);
        headers = request.headers();
        headers.set(HttpHeaderNames.ACCEPT_ENCODING, HttpHeaderValues.GZIP + "," + HttpHeaderValues.DEFLATE);
        headers.set(HttpHeaderNames.ACCEPT_CHARSET, "utf-8;q=0.7,*;q=0.7");
        headers.set(HttpHeaderNames.ACCEPT_LANGUAGE, "fr,en");
        headers.set(HttpHeaderNames.USER_AGENT, "Netty Simple Http Rest Client side");
        headers.set(HttpHeaderNames.ACCEPT,
                "text/html,text/plain,application/xhtml+xml,application/xml,application/json;q=0.9,*/*;q=0.8");
        // connection will not close but needed
        /*request.headers().set(HttpHeaders.Names.CONNECTION,
        HttpHeaders.Values.KEEP_ALIVE);*/
        // request.setHeader("Connection","keep-alive");
        // request.setHeader("Keep-Alive","300");
    }

    /**
     * Create one new connection to the remote host using port
     * 
     * @param host
     * @param port
     * @return the channel if connected or Null if not
     */
    public Channel getChannel(String host, int port) {
        // Start the connection attempt.
        ChannelFuture future = bootstrap.connect(new InetSocketAddress(host, port));
        // Wait until the connection attempt succeeds or fails.
        Channel channel = WaarpSslUtility.waitforChannelReady(future);
        if (channel != null) {
            RestFuture futureChannel = new RestFuture(true);
            channel.attr(HttpRestClientSimpleResponseHandler.RESTARGUMENT).set(futureChannel);
        }
        return channel;
    }

    /**
     * Send an HTTP query using the channel for target, using signature
     * 
     * @param hmacSha256
     *            SHA-256 key to create the signature
     * @param channel
     *            target of the query
     * @param method
     *            HttpMethod to use
     * @param host
     *            target of the query (shall be the same as for the channel)
     * @param addedUri
     *            additional uri, added to baseUri (shall include also extra arguments) (might be null)
     * @param user
     *            user to use in authenticated Rest procedure (might be null)
     * @param pwd
     *            password to use in authenticated Rest procedure (might be null)
     * @param uriArgs
     *            arguments for Uri if any (might be null)
     * @param json
     *            json to send as body in the request (might be null); Useful in PUT, POST but should not in GET, DELETE, OPTIONS
     * @return the RestFuture associated with this request
     */
    public RestFuture sendQuery(HmacSha256 hmacSha256, Channel channel, HttpMethod method, String host,
            String addedUri, String user, String pwd, Map<String, String> uriArgs, String json) {
        // Prepare the HTTP request.
        logger.debug("Prepare request: " + method + ":" + addedUri + ":" + json);
        RestFuture future = channel.attr(HttpRestClientSimpleResponseHandler.RESTARGUMENT).get();
        QueryStringEncoder encoder = null;
        if (addedUri != null) {
            encoder = new QueryStringEncoder(baseUri + addedUri);
        } else {
            encoder = new QueryStringEncoder(baseUri);
        }
        // add Form attribute
        if (uriArgs != null) {
            for (Entry<String, String> elt : uriArgs.entrySet()) {
                encoder.addParam(elt.getKey(), elt.getValue());
            }
        }
        String[] result = null;
        try {
            result = RestArgument.getBaseAuthent(hmacSha256, encoder, user, pwd);
            logger.debug("Authent encoded");
        } catch (HttpInvalidAuthenticationException e) {
            logger.error(e.getMessage(), e);
            future.setFailure(e);
            return future;
        }
        URI uri;
        try {
            uri = encoder.toUri();
        } catch (URISyntaxException e) {
            logger.error(e.getMessage());
            future.setFailure(e);
            return future;
        }
        logger.debug("Uri ready: " + uri.toASCIIString());

        FullHttpRequest request;
        if (json != null) {
            logger.debug("Add body");
            ByteBuf buffer = Unpooled.wrappedBuffer(json.getBytes(WaarpStringUtils.UTF8));
            request = new DefaultFullHttpRequest(HttpVersion.HTTP_1_1, method, uri.toASCIIString(), buffer);
            request.headers().set(HttpHeaderNames.CONTENT_LENGTH, buffer.readableBytes());
        } else {
            request = new DefaultFullHttpRequest(HttpVersion.HTTP_1_1, method, uri.toASCIIString());
        }
        // it is legal to add directly header or cookie into the request until finalize
        request.headers().add(this.headers);
        request.headers().set(HttpHeaderNames.HOST, host);
        if (user != null) {
            request.headers().set((CharSequence) RestArgument.REST_ROOT_FIELD.ARG_X_AUTH_USER.field, user);
        }
        request.headers().set((CharSequence) RestArgument.REST_ROOT_FIELD.ARG_X_AUTH_TIMESTAMP.field, result[0]);
        request.headers().set((CharSequence) RestArgument.REST_ROOT_FIELD.ARG_X_AUTH_KEY.field, result[1]);
        // send request
        logger.debug("Send request");
        channel.writeAndFlush(request);
        logger.debug("Request sent");
        return future;
    }

    /**
     * Send an HTTP query using the channel for target, but without any Signature
     * 
     * @param channel
     *            target of the query
     * @param method
     *            HttpMethod to use
     * @param host
     *            target of the query (shall be the same as for the channel)
     * @param addedUri
     *            additional uri, added to baseUri (shall include also extra arguments) (might be null)
     * @param user
     *            user to use in authenticated Rest procedure (might be null)
     * @param uriArgs
     *            arguments for Uri if any (might be null)
     * @param json
     *            json to send as body in the request (might be null); Useful in PUT, POST but should not in GET, DELETE, OPTIONS
     * @return the RestFuture associated with this request
     */
    public RestFuture sendQuery(Channel channel, HttpMethod method, String host, String addedUri, String user,
            Map<String, String> uriArgs, String json) {
        // Prepare the HTTP request.
        logger.debug("Prepare request: " + method + ":" + addedUri + ":" + json);
        RestFuture future = channel.attr(HttpRestClientSimpleResponseHandler.RESTARGUMENT).get();
        QueryStringEncoder encoder = null;
        if (addedUri != null) {
            encoder = new QueryStringEncoder(baseUri + addedUri);
        } else {
            encoder = new QueryStringEncoder(baseUri);
        }
        // add Form attribute
        if (uriArgs != null) {
            for (Entry<String, String> elt : uriArgs.entrySet()) {
                encoder.addParam(elt.getKey(), elt.getValue());
            }
        }
        URI uri;
        try {
            uri = encoder.toUri();
        } catch (URISyntaxException e) {
            logger.error(e.getMessage());
            future.setFailure(e);
            return future;
        }
        logger.debug("Uri ready: " + uri.toASCIIString());

        FullHttpRequest request;
        if (json != null) {
            logger.debug("Add body");
            ByteBuf buffer = Unpooled.wrappedBuffer(json.getBytes(WaarpStringUtils.UTF8));
            request = new DefaultFullHttpRequest(HttpVersion.HTTP_1_1, method, uri.toASCIIString(), buffer);
            request.headers().set(HttpHeaderNames.CONTENT_LENGTH, buffer.readableBytes());
        } else {
            request = new DefaultFullHttpRequest(HttpVersion.HTTP_1_1, method, uri.toASCIIString());
        }

        // it is legal to add directly header or cookie into the request until finalize
        request.headers().add(this.headers);
        request.headers().set(HttpHeaderNames.HOST, host);
        if (user != null) {
            request.headers().set((CharSequence) RestArgument.REST_ROOT_FIELD.ARG_X_AUTH_USER.field, user);
        }
        request.headers().set((CharSequence) RestArgument.REST_ROOT_FIELD.ARG_X_AUTH_TIMESTAMP.field,
                new DateTime().toString());
        // send request
        logger.debug("Send request");
        channel.writeAndFlush(request);
        logger.debug("Request sent");
        return future;
    }

    /**
     * Finalize the HttpRestClientHelper
     */
    public void closeAll() {
        bootstrap.group().shutdownGracefully();
    }

    /**
     * 
     * @param args
     *            as uri (http://host:port/uri method user pwd sign=path|nosign [json])
     */
    public static void main(String[] args) {
        WaarpLoggerFactory.setDefaultFactory(new WaarpSlf4JLoggerFactory(null));
        final WaarpLogger logger = WaarpLoggerFactory.getLogger(HttpRestClientHelper.class);
        if (args.length < 5) {
            logger.error("Need more arguments: http://host:port/uri method user pwd sign=path|nosign [json]");
            return;
        }
        String uri = args[0];
        String meth = args[1];
        String user = args[2];
        String pwd = args[3];
        boolean sign = args[4].toLowerCase().contains("sign=");
        HmacSha256 hmacSha256 = null;
        if (sign) {
            String file = args[4].replace("sign=", "");
            hmacSha256 = new HmacSha256();
            try {
                hmacSha256.setSecretKey(new File(file));
            } catch (CryptoException e) {
                logger.error("Need more arguments: http://host:port/uri method user pwd sign=path|nosign [json]");
                return;
            } catch (IOException e) {
                logger.error("Need more arguments: http://host:port/uri method user pwd sign=path|nosign [json]");
                return;
            }
        }
        String json = null;
        if (args.length > 5) {
            json = args[5].replace("'", "\"");
        }
        HttpMethod method = HttpMethod.valueOf(meth);
        int port = -1;
        String host = null;
        String path = null;
        try {
            URI realUri = new URI(uri);
            port = realUri.getPort();
            host = realUri.getHost();
            path = realUri.getPath();
        } catch (URISyntaxException e) {
            logger.error("Error", e);
            return;
        }
        HttpRestClientHelper client = new HttpRestClientHelper(path, 1, 30000,
                new HttpRestClientSimpleInitializer());
        Channel channel = client.getChannel(host, port);
        if (channel == null) {
            client.closeAll();
            logger.error("Cannot connect to " + host + " on port " + port);
            return;
        }
        RestFuture future = null;
        if (sign) {
            future = client.sendQuery(hmacSha256, channel, method, host, null, user, pwd, null, json);
        } else {
            future = client.sendQuery(channel, method, host, null, user, null, json);
        }
        try {
            future.await();
        } catch (InterruptedException e) {
            client.closeAll();
            logger.error("Interruption", e);
            return;
        }
        WaarpSslUtility.closingSslChannel(channel);
        if (future.isSuccess()) {
            logger.warn(future.getRestArgument().prettyPrint());
        } else {
            RestArgument ra = future.getRestArgument();
            if (ra != null) {
                logger.error(ra.prettyPrint());
            } else {
                logger.error("Query in error", future.getCause());
            }
        }
        client.closeAll();
    }
}