com.xx_dev.apn.proxy.ApnProxyUserAgentForwardHandler.java Source code

Java tutorial

Introduction

Here is the source code for com.xx_dev.apn.proxy.ApnProxyUserAgentForwardHandler.java

Source

/*
 * Copyright (c) 2014 The APN-PROXY Project
 *
 * The APN-PROXY Project licenses this file to you under the Apache License,
 * version 2.0 (the "License"); you may not use this file except in compliance
 * with the License. You may obtain a copy of the License at:
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
 * License for the specific language governing permissions and limitations
 * under the License.
 */

package com.xx_dev.apn.proxy;

import com.xx_dev.apn.proxy.ApnProxyRemoteForwardHandler.RemoteChannelInactiveCallback;
import com.xx_dev.apn.proxy.remotechooser.ApnProxyRemote;
import com.xx_dev.apn.proxy.utils.Base64;
import com.xx_dev.apn.proxy.utils.HttpErrorUtil;
import com.xx_dev.apn.proxy.utils.LoggerUtil;
import io.netty.bootstrap.Bootstrap;
import io.netty.buffer.PooledByteBufAllocator;
import io.netty.buffer.Unpooled;
import io.netty.channel.*;
import io.netty.channel.socket.nio.NioSocketChannel;
import io.netty.handler.codec.http.*;
import io.netty.util.ReferenceCountUtil;
import org.apache.commons.lang.StringUtils;
import org.apache.log4j.Logger;

import java.io.UnsupportedEncodingException;
import java.net.InetSocketAddress;
import java.util.*;

/**
 * @author xmx
 * @version $Id: com.xx_dev.apn.proxy.ApnProxyUserAgentForwardHandler 14-1-8 16:13 (xmx) Exp $
 */
public class ApnProxyUserAgentForwardHandler extends ChannelInboundHandlerAdapter
        implements RemoteChannelInactiveCallback {

    private static final Logger logger = Logger.getLogger(ApnProxyUserAgentForwardHandler.class);

    public static final String HANDLER_NAME = "apnproxy.useragent.forward";

    private Map<String, Channel> remoteChannelMap = new HashMap<String, Channel>();

    private List<HttpContent> httpContentBuffer = new ArrayList<HttpContent>();

    @Override
    public void channelRead(final ChannelHandlerContext uaChannelCtx, final Object msg) throws Exception {

        final Channel uaChannel = uaChannelCtx.channel();

        final ApnProxyRemote apnProxyRemote = uaChannel.attr(ApnProxyConnectionAttribute.ATTRIBUTE_KEY).get()
                .getRemote();

        if (msg instanceof HttpRequest) {
            HttpRequest httpRequest = (HttpRequest) msg;

            Channel remoteChannel = remoteChannelMap.get(apnProxyRemote.getRemoteAddr());

            if (remoteChannel != null && remoteChannel.isActive()) {
                LoggerUtil.debug(logger, uaChannel.attr(ApnProxyConnectionAttribute.ATTRIBUTE_KEY),
                        "Use old remote channel");
                HttpRequest request = constructRequestForProxy(httpRequest, apnProxyRemote);
                remoteChannel.writeAndFlush(request);
            } else {
                LoggerUtil.debug(logger, uaChannel.attr(ApnProxyConnectionAttribute.ATTRIBUTE_KEY),
                        "Create new remote channel");

                Bootstrap bootstrap = new Bootstrap();
                bootstrap.group(uaChannel.eventLoop()).channel(NioSocketChannel.class)
                        .option(ChannelOption.CONNECT_TIMEOUT_MILLIS, 10000)
                        .option(ChannelOption.ALLOCATOR, PooledByteBufAllocator.DEFAULT)
                        .option(ChannelOption.AUTO_READ, false)
                        .handler(new ApnProxyRemoteForwardChannelInitializer(uaChannel, this));

                // set local address
                if (StringUtils.isNotBlank(ApnProxyLocalAddressChooser.choose(apnProxyRemote.getRemoteHost()))) {
                    bootstrap.localAddress(new InetSocketAddress(
                            (ApnProxyLocalAddressChooser.choose(apnProxyRemote.getRemoteHost())), 0));
                }

                ChannelFuture remoteConnectFuture = bootstrap.connect(apnProxyRemote.getRemoteHost(),
                        apnProxyRemote.getRemotePort());

                remoteChannel = remoteConnectFuture.channel();
                remoteChannelMap.put(apnProxyRemote.getRemoteAddr(), remoteChannel);

                remoteConnectFuture.addListener(new ChannelFutureListener() {
                    @Override
                    public void operationComplete(ChannelFuture future) throws Exception {
                        if (future.isSuccess()) {
                            future.channel().write(constructRequestForProxy((HttpRequest) msg, apnProxyRemote));

                            for (HttpContent hc : httpContentBuffer) {
                                future.channel().writeAndFlush(hc);

                                if (hc instanceof LastHttpContent) {
                                    future.channel().writeAndFlush(Unpooled.EMPTY_BUFFER)
                                            .addListener(new ChannelFutureListener() {
                                                @Override
                                                public void operationComplete(ChannelFuture future)
                                                        throws Exception {
                                                    if (future.isSuccess()) {
                                                        future.channel().read();
                                                    }

                                                }
                                            });
                                }
                            }
                            httpContentBuffer.clear();
                        } else {
                            LoggerUtil.error(logger, uaChannel.attr(ApnProxyConnectionAttribute.ATTRIBUTE_KEY),
                                    "Remote channel create fail");

                            // send error response
                            String errorMsg = "remote connect to "
                                    + uaChannel.attr(ApnProxyConnectionAttribute.ATTRIBUTE_KEY).get().getRemote()
                                            .getRemoteAddr()
                                    + " fail";
                            HttpMessage errorResponseMsg = HttpErrorUtil
                                    .buildHttpErrorMessage(HttpResponseStatus.INTERNAL_SERVER_ERROR, errorMsg);
                            uaChannel.writeAndFlush(errorResponseMsg);
                            httpContentBuffer.clear();

                            future.channel().close();
                        }
                    }
                });

            }
            ReferenceCountUtil.release(msg);
        } else {
            Channel remoteChannel = remoteChannelMap.get(apnProxyRemote.getRemoteAddr());

            HttpContent hc = ((HttpContent) msg);
            //hc.retain();

            //HttpContent _hc = hc.copy();

            if (remoteChannel != null && remoteChannel.isActive()) {
                remoteChannel.writeAndFlush(hc);

                if (hc instanceof LastHttpContent) {
                    remoteChannel.writeAndFlush(Unpooled.EMPTY_BUFFER).addListener(new ChannelFutureListener() {
                        @Override
                        public void operationComplete(ChannelFuture future) throws Exception {
                            if (future.isSuccess()) {
                                future.channel().read();
                            }

                        }
                    });
                }
            } else {
                httpContentBuffer.add(hc);
            }
        }

    }

    @Override
    public void channelInactive(ChannelHandlerContext uaChannelCtx) throws Exception {
        if (logger.isDebugEnabled()) {
            logger.debug("UA channel: inactive" + uaChannelCtx.attr(ApnProxyConnectionAttribute.ATTRIBUTE_KEY));
        }
        LoggerUtil.debug(logger, uaChannelCtx.attr(ApnProxyConnectionAttribute.ATTRIBUTE_KEY),
                "UA channel inactive");

        for (Map.Entry<String, Channel> entry : remoteChannelMap.entrySet()) {
            final Channel remoteChannel = entry.getValue();
            remoteChannel.writeAndFlush(Unpooled.EMPTY_BUFFER).addListener(new ChannelFutureListener() {
                @Override
                public void operationComplete(ChannelFuture future) throws Exception {
                    remoteChannel.close();
                }
            });
        }
    }

    @Override
    public void exceptionCaught(ChannelHandlerContext uaChannelCtx, Throwable cause) throws Exception {
        logger.error(cause.getMessage() + " " + uaChannelCtx.attr(ApnProxyConnectionAttribute.ATTRIBUTE_KEY),
                cause);
        uaChannelCtx.close();
    }

    @Override
    public void remoteChannelInactive(final Channel uaChannel, String inactiveRemoteAddr) throws Exception {
        LoggerUtil.debug(logger, uaChannel.attr(ApnProxyConnectionAttribute.ATTRIBUTE_KEY),
                "Remote channel inactive, and flush end");

        remoteChannelMap.remove(inactiveRemoteAddr);

        if (uaChannel.isActive()) {
            uaChannel.writeAndFlush(Unpooled.EMPTY_BUFFER);
        }

    }

    private HttpRequest constructRequestForProxy(HttpRequest httpRequest, ApnProxyRemote apnProxyRemote) {

        String uri = httpRequest.getUri();

        if (!apnProxyRemote.isAppleyRemoteRule()) {
            uri = this.getPartialUrl(uri);
        }

        HttpRequest _httpRequest = new DefaultHttpRequest(httpRequest.getProtocolVersion(), httpRequest.getMethod(),
                uri);

        Set<String> headerNames = httpRequest.headers().names();
        for (String headerName : headerNames) {
            if (StringUtils.equalsIgnoreCase(headerName, "Proxy-Connection")) {
                continue;
            }

            if (StringUtils.equalsIgnoreCase(headerName, "Pragma")) {
                continue;
            }

            // if (StringUtils.equalsIgnoreCase(headerName, HttpHeaders.Names.CONNECTION)) {
            // continue;
            // }

            _httpRequest.headers().add(headerName, httpRequest.headers().getAll(headerName));
        }

        _httpRequest.headers().set(HttpHeaders.Names.CONNECTION, HttpHeaders.Values.KEEP_ALIVE);
        // _httpRequest.headers().set(HttpHeaders.Names.ACCEPT_ENCODING, HttpHeaders.Values.IDENTITY);

        if (StringUtils.isNotBlank(apnProxyRemote.getProxyUserName())
                && StringUtils.isNotBlank(apnProxyRemote.getProxyPassword())) {
            String proxyAuthorization = apnProxyRemote.getProxyUserName() + ":" + apnProxyRemote.getProxyPassword();
            try {
                _httpRequest.headers().set("Proxy-Authorization",
                        "Basic " + Base64.encodeBase64String(proxyAuthorization.getBytes("UTF-8")));
            } catch (UnsupportedEncodingException e) {
            }

        }

        return _httpRequest;
    }

    private String getPartialUrl(String fullUrl) {
        if (StringUtils.startsWith(fullUrl, "http")) {
            int idx = StringUtils.indexOf(fullUrl, "/", 7);
            return idx == -1 ? "/" : StringUtils.substring(fullUrl, idx);
        }

        return fullUrl;
    }

}