divconq.api.internal.UploadPostHandler.java Source code

Java tutorial

Introduction

Here is the source code for divconq.api.internal.UploadPostHandler.java

Source

/* ************************************************************************
#
#  DivConq
#
#  http://divconq.com/
#
#  Copyright:
#    Copyright 2014 eTimeline, LLC. All rights reserved.
#
#  License:
#    See the license.txt file in the project's top-level directory for details.
#
#  Authors:
#    * Andy White
#
************************************************************************ */
package divconq.api.internal;

import java.io.IOException;
import java.nio.channels.ReadableByteChannel;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.atomic.AtomicReference;

import divconq.api.HyperSession;
import divconq.hub.Hub;
import divconq.lang.op.OperationCallback;
import divconq.lang.op.OperationResult;
import io.netty.bootstrap.Bootstrap;
import io.netty.channel.Channel;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelOption;
import io.netty.channel.ChannelPipeline;
import io.netty.channel.SimpleChannelInboundHandler;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioSocketChannel;
import io.netty.handler.codec.http.ClientCookieEncoder;
import io.netty.handler.codec.http.Cookie;
import io.netty.handler.codec.http.DefaultHttpRequest;
import io.netty.handler.codec.http.HttpClientCodec;
import io.netty.handler.codec.http.HttpHeaders;
import io.netty.handler.codec.http.HttpMethod;
import io.netty.handler.codec.http.HttpObject;
import io.netty.handler.codec.http.HttpRequest;
import io.netty.handler.codec.http.HttpVersion;
import io.netty.handler.codec.http.HttpHeaders.Names;
import io.netty.handler.codec.http.HttpHeaders.Values;
import io.netty.handler.codec.http.multipart.DefaultHttpDataFactory;
import io.netty.handler.codec.http.multipart.HttpDataFactory;
import io.netty.handler.codec.http.multipart.HttpPostRequestEncoder;
import io.netty.handler.codec.http.multipart.HttpPostRequestEncoder.ErrorDataEncoderException;
import divconq.net.ssl.SslHandler;
import io.netty.handler.stream.ChunkedWriteHandler;
import io.netty.util.concurrent.Future;

public class UploadPostHandler extends SimpleChannelInboundHandler<HttpObject> {
    protected Channel dest = null;
    protected ReadableByteChannel src = null;
    protected Map<String, Cookie> cookies = new HashMap<>();
    protected OperationCallback callback = null;

    public Channel allocateChannel(final HyperSession parent, OperationResult or) {
        final AtomicReference<Future<Channel>> sslready = new AtomicReference<>();

        Bootstrap b = new Bootstrap();

        b.group(Hub.instance.getEventLoopGroup()).channel(NioSocketChannel.class)
                .option(ChannelOption.ALLOCATOR, Hub.instance.getBufferAllocator())
                .handler(new ChannelInitializer<SocketChannel>() {
                    @Override
                    public void initChannel(SocketChannel ch) throws Exception {
                        ChannelPipeline pipeline = ch.pipeline();

                        if (parent.getInfo().isSecurel()) {
                            SslHandler sh = new SslHandler(parent.getSsl().getClientEngine());
                            sslready.set(sh.handshakeFuture());
                            pipeline.addLast("ssl", sh);
                        }

                        pipeline.addLast("codec", new HttpClientCodec());

                        // Remove the following line if you don't want automatic content decompression.
                        //pipeline.addLast("inflater", new HttpContentDecompressor());

                        // to be used since huge file transfer
                        pipeline.addLast("chunkedWriter", new ChunkedWriteHandler());

                        // so we can get the upload response (200 or not)
                        pipeline.addLast("handler", UploadPostHandler.this);
                    }
                });

        System.out.println("Web Data Client connecting");

        try {
            // must wait here to make sure we don't release connectLock too soon
            // we want chanel init (above) to complete before we try connect again
            ChannelFuture f = b.connect(parent.getInfo().getAddress()).sync();

            if (!f.isSuccess()) {
                or.error(1, "Web Client unable to successfully connect: " + f.cause());
                System.out.println("Web Client unable to successfully connect: " + f.cause());
            }

            // it has appeared that sometimes we "overshoot" the ssl handshake in code - to prevent
            // that lets wait for the handshake to be done for sure
            if (sslready.get() != null) {
                Future<Channel> sf = sslready.get().sync();

                if (!sf.isSuccess()) {
                    or.error(1, "Web Client unable to securely connect: " + sf.cause());
                    System.out.println("Web Client unable to securely connect: " + sf.cause());
                }
            }

            return f.channel();
        } catch (InterruptedException x) {
            or.error(1, "Web Client interrupted while connecting: " + x);
            System.out.println("Web Client interrupted while connecting: " + x);
        } catch (Exception x) {
            or.error(1, "Web Client unable to connect: " + x);
            System.out.println("Web Client unable to connect: " + x);
        }

        return null;
    }

    public void start(final HyperSession parent, ReadableByteChannel src, String chanid,
            Map<String, Cookie> cookies, long size, final OperationCallback callback) {
        this.src = src;
        this.cookies = cookies;
        this.callback = callback;

        this.dest = this.allocateChannel(parent, callback);

        if (this.callback.hasErrors()) {
            callback.complete();
            return;
        }

        // send a request to get things going      

        HttpDataFactory factory = new DefaultHttpDataFactory(false); // no disk 

        HttpRequest req = new DefaultHttpRequest(HttpVersion.HTTP_1_1, HttpMethod.POST,
                "/upload/" + chanid + "/Final");

        req.headers().set(Names.HOST, parent.getInfo().getHost());
        req.headers().set(Names.USER_AGENT, "DivConq HyperAPI Client 1.0");
        req.headers().set(Names.CONNECTION, HttpHeaders.Values.CLOSE);
        req.headers().set(Names.COOKIE, ClientCookieEncoder.encode(this.cookies.values()));
        req.headers().set(Names.TRANSFER_ENCODING, Values.CHUNKED);

        HttpPostRequestEncoder bodyRequestEncoder = null;

        try {
            bodyRequestEncoder = new HttpPostRequestEncoder(factory, req, true); // true => multipart

            bodyRequestEncoder.addBodyHttpData(new UploadStream(src, "file", "fname", "application/octet-stream",
                    "binary", null, size, callback));

            req = bodyRequestEncoder.finalizeRequest();
        } catch (ErrorDataEncoderException x) {
            callback.error(1, "Problem with send encoder: " + x);
            callback.complete();
            return;
        }

        // send request headers
        this.dest.write(req);

        try {
            this.dest.writeAndFlush(bodyRequestEncoder).sync();

            // wait for a response - then close, see messageReceived
        } catch (InterruptedException x) {
            callback.error(1, "Unable to write to socket: " + x);
            callback.complete();
        }
    }

    public void finish() {
        //System.out.println("client finished with content");

        try {
            this.src.close();
        } catch (IOException x) {
        }

        this.closeDest();
        this.callback.complete();
    }

    @Override
    public void channelRead0(ChannelHandlerContext ctx, HttpObject msg) throws Exception {
        //System.out.println("upload client got object: " + msg.getClass().getName());

        this.finish();
    }

    @Override
    public void channelInactive(ChannelHandlerContext ctx) throws Exception {
        // TODO logging
        System.out.println("Web Data Client disconnected!");
    }

    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
        // TODO logging
        cause.printStackTrace();
        ctx.close();
    }

    public void closeDest() {
        try {
            if (this.dest != null)
                this.dest.close().await(2000);
        } catch (InterruptedException x) {
            // ignore 
        }
    }
}