ozy.client.Client.java Source code

Java tutorial

Introduction

Here is the source code for ozy.client.Client.java

Source

/*
 * Copyright (c) 2016 Catavolt Inc. All rights reserved.
 *
 * This file is part of Ozy.
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Affero General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program 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 Affero General Public License for more details.
 *
 * You should have received a copy of the GNU Affero General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

package ozy.client;

import io.netty.bootstrap.Bootstrap;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.*;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioSocketChannel;
import io.netty.handler.codec.http.*;
import io.netty.handler.ssl.SslContext;
import io.netty.handler.ssl.SslContextBuilder;
import io.netty.handler.ssl.util.InsecureTrustManagerFactory;
import io.netty.util.CharsetUtil;
import ozy.fp.Future;
import ozy.util.ObjectError;
import ozy.fp.Promise;

import java.util.List;
import java.util.Map;

/**
 */
class Client {

    private static final EventLoopGroup _globalEventLoopGroup = new NioEventLoopGroup();

    private final Object LOCK = new Object();

    private boolean active = false;
    private final String _body;
    private final String _host;
    private final HttpMethod _method;
    private final String _path;
    private final Map<String, List<String>> _params;
    private final int _port;
    private final Promise<String> _promise = new Promise<>();
    private final String _scheme;

    //--------------------------------------------------------------------------
    // INNER CLASSES
    //--------------------------------------------------------------------------

    static class ClientHandler extends SimpleChannelInboundHandler<HttpObject> {

        private Promise<String> _promise;
        private StringBuilder _responseBuf = new StringBuilder();

        @Override
        public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
            cause.printStackTrace();
            _promise.failure(new ObjectError("Unhandled exception", new RuntimeException(cause)));
        }

        public void setPromise(Promise<String> promise) {
            _promise = promise;
        }

        @Override
        protected final void channelRead0(ChannelHandlerContext ctx, HttpObject msg) {
            System.out.println("[Client] channel read 0");
            if (msg instanceof HttpContent) {
                HttpContent content = (HttpContent) msg;
                System.out.println("[Client] appending content");
                _responseBuf.append(content.content().toString(CharsetUtil.UTF_8));
                if (content instanceof LastHttpContent) {
                    System.out.println("[Client] last content received");
                    System.out.println("[Client] fulfilling promise");
                    _promise.success(_responseBuf.toString());
                }
            }
        }

    }

    static class ClientInitializer extends ChannelInitializer<SocketChannel> {

        private final SslContext sslCtx;

        public ClientInitializer(SslContext sslCtx) {
            this.sslCtx = sslCtx;
        }

        @Override
        public void initChannel(SocketChannel ch) {

            ChannelPipeline p = ch.pipeline();

            if (sslCtx != null) {
                p.addLast(sslCtx.newHandler(ch.alloc()));
            }

            p.addLast("clientCodec", new HttpClientCodec());
            p.addLast("objectAggregator", new HttpObjectAggregator(1048576));
            p.addLast("clientHandler", new ClientHandler());
        }

    }

    //--------------------------------------------------------------------------
    // CLASS METHODS
    //--------------------------------------------------------------------------

    public static void shutdown() {
        _globalEventLoopGroup.shutdownGracefully();
    }

    //--------------------------------------------------------------------------
    // CONSTRUCTORS
    //--------------------------------------------------------------------------

    public Client(HttpMethod method, String scheme, String host, int port, String path,
            Map<String, List<String>> params, String body) {

        if (method == null) {
            throw new NullPointerException("method");
        }

        if (scheme == null) {
            scheme = "http";
        }

        if (!scheme.equals("http") && !scheme.equals("https")) {
            throw new IllegalArgumentException("Scheme must be http or https: " + scheme);
        }

        if (host == null) {
            host = "127.0.0.1";
        }

        if (port == -1) {
            if (scheme.equalsIgnoreCase("http")) {
                port = 80;
            } else if (scheme.equalsIgnoreCase("https")) {
                port = 443;
            }
        }

        if (path == null || path.trim().equals("")) {
            path = "/";
        }

        if (body == null) {
            body = "";
        }

        _method = method;
        _scheme = scheme;
        _host = host;
        _port = port;
        _path = path;
        _params = params;
        _body = body;
    }

    //--------------------------------------------------------------------------
    // INSTANCE METHODS
    //--------------------------------------------------------------------------

    public final String body() {
        return _body;
    }

    public final String host() {
        return _host;
    }

    public final HttpMethod method() {
        return _method;
    }

    public final String path() {
        return _path;
    }

    public final Map<String, List<String>> params() {
        return _params;
    }

    public final Future<String> perform() throws Exception {
        System.out.println("[Client] performing");

        synchronized (LOCK) {
            if (active) {
                throw new IllegalStateException("Already active");
            }
            active = true;
        }

        try {
            perform(_globalEventLoopGroup);
        } catch (Exception exc) {
            _promise.failure(new ObjectError("Unhandled exception", exc));
        }

        return _promise.future();
    }

    public final int port() {
        return _port;
    }

    public final String scheme() {
        return _scheme;
    }

    // ~~~~~~~~~~~~~~~~~~~~~~~~~ PROTECTED ~~~~~~~~~~~~~~~~~~~~~~~~~ //

    protected void setCookies(HttpHeaders headers) {
        //headers.set(
        //    HttpHeaders.Names.COOKIE,
        //    ClientCookieEncoder.LAX.encode(
        //        new DefaultCookie("my-cookie", "foo"),
        //        new DefaultCookie("another-cookie", "bar")));
    }

    protected void setHeaders(HttpHeaders headers) {
    }

    // ~~~~~~~~~~~~~~~~~~~~~~~~~ PRIVATE ~~~~~~~~~~~~~~~~~~~~~~~~~ //

    private void perform(EventLoopGroup group) throws Exception {
        final boolean ssl = scheme().equalsIgnoreCase("https");
        final SslContext sslCtx;
        if (ssl) {
            sslCtx = SslContextBuilder.forClient().trustManager(InsecureTrustManagerFactory.INSTANCE).build();
        } else {
            sslCtx = null;
        }

        System.out.println("[Client] creating bootstrap");

        Bootstrap b = new Bootstrap();
        b.group(group).channel(NioSocketChannel.class).handler(new ClientInitializer(sslCtx));

        System.out.println("[Client] connecting to host");

        Channel ch = b.connect(host(), port()).sync().channel();

        ClientHandler clientHandler = (ClientHandler) ch.pipeline().get("clientHandler");
        clientHandler.setPromise(_promise);

        ByteBuf requestBytes;
        if (!body().isEmpty()) {
            requestBytes = Unpooled.copiedBuffer(body(), CharsetUtil.UTF_8);
        } else {
            requestBytes = Unpooled.buffer(0);
        }

        String pathWithParams = path();
        if (params() != null) {
            pathWithParams += "?";
            for (Map.Entry<String, List<String>> e : params().entrySet()) {
                for (String v : e.getValue()) {
                    if (!pathWithParams.endsWith("?")) {
                        pathWithParams += "&";
                    }
                    pathWithParams += e.getKey() + "=" + v;
                }
            }
        }

        DefaultFullHttpRequest request = new DefaultFullHttpRequest(HttpVersion.HTTP_1_1, method(), pathWithParams,
                requestBytes);

        HttpHeaders.setHost(request, host());
        HttpHeaders.setKeepAlive(request, false);
        HttpHeaders.setContentLength(request, request.content().readableBytes());

        setCookies(request.headers());

        System.out.println("[Client] writeAndFlush");

        ch.writeAndFlush(request);
    }

}