/* * 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.remotechooser.ApnProxyRemote; import com.xx_dev.apn.proxy.utils.Base64; 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.CharsetUtil; 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.Set; /** * @author xmx * @version $Id: com.xx_dev.apn.proxy.ApnProxyUserAgentTunnelHandler 14-1-8 16:13 (xmx) Exp $ */ public class ApnProxyUserAgentTunnelHandler extends ChannelInboundHandlerAdapter { private static final Logger logger = Logger.getLogger(ApnProxyUserAgentTunnelHandler.class); public static final String HANDLER_NAME = "apnproxy.useragent.tunnel"; @Override public void channelRead(final ChannelHandlerContext uaChannelCtx, Object msg) throws Exception { if (msg instanceof HttpRequest) { final HttpRequest httpRequest = (HttpRequest) msg; //Channel uaChannel = uaChannelCtx.channel(); // connect remote Bootstrap bootstrap = new Bootstrap(); bootstrap.group(uaChannelCtx.channel().eventLoop()).channel(NioSocketChannel.class) .option(ChannelOption.CONNECT_TIMEOUT_MILLIS, 10000) .option(ChannelOption.ALLOCATOR, PooledByteBufAllocator.DEFAULT) .option(ChannelOption.AUTO_READ, false) .handler(new ApnProxyTunnelChannelInitializer(uaChannelCtx.channel())); final ApnProxyRemote apnProxyRemote = uaChannelCtx.channel() .attr(ApnProxyConnectionAttribute.ATTRIBUTE_KEY).get().getRemote(); // set local address if (StringUtils.isNotBlank(ApnProxyLocalAddressChooser.choose(apnProxyRemote .getRemoteHost()))) { bootstrap.localAddress(new InetSocketAddress((ApnProxyLocalAddressChooser .choose(apnProxyRemote.getRemoteHost())), 0)); } bootstrap.connect(apnProxyRemote.getRemoteHost(), apnProxyRemote.getRemotePort()) .addListener(new ChannelFutureListener() { @Override public void operationComplete(final ChannelFuture future1) throws Exception { if (future1.isSuccess()) { if (apnProxyRemote.isAppleyRemoteRule()) { uaChannelCtx.pipeline().remove("codec"); uaChannelCtx.pipeline().remove(ApnProxyPreHandler.HANDLER_NAME); uaChannelCtx.pipeline().remove(ApnProxyUserAgentTunnelHandler.HANDLER_NAME); // add relay handler uaChannelCtx.pipeline().addLast( new ApnProxyRelayHandler("UA --> Remote", future1.channel())); future1.channel().writeAndFlush( Unpooled.copiedBuffer( constructConnectRequestForProxy(httpRequest, apnProxyRemote), CharsetUtil.UTF_8)) .addListener(new ChannelFutureListener() { @Override public void operationComplete(ChannelFuture future2) throws Exception { if (!future2.channel().config() .getOption(ChannelOption.AUTO_READ)) { future2.channel().read(); } } }); } else { HttpResponse proxyConnectSuccessResponse = new DefaultFullHttpResponse( HttpVersion.HTTP_1_1, new HttpResponseStatus(200, "Connection established")); uaChannelCtx.writeAndFlush(proxyConnectSuccessResponse).addListener( new ChannelFutureListener() { @Override public void operationComplete(ChannelFuture future2) throws Exception { // remove handlers uaChannelCtx.pipeline().remove("codec"); uaChannelCtx.pipeline().remove(ApnProxyPreHandler.HANDLER_NAME); uaChannelCtx.pipeline().remove( ApnProxyUserAgentTunnelHandler.HANDLER_NAME); // add relay handler uaChannelCtx.pipeline().addLast( new ApnProxyRelayHandler("UA --> " + apnProxyRemote .getRemoteAddr(), future1 .channel())); } }); } } else { if (uaChannelCtx.channel().isActive()) { uaChannelCtx.channel().writeAndFlush(Unpooled.EMPTY_BUFFER) .addListener(ChannelFutureListener.CLOSE); } } } }); } ReferenceCountUtil.release(msg); } private String constructConnectRequestForProxy(HttpRequest httpRequest, ApnProxyRemote apnProxyRemote) { String CRLF = "\r\n"; String url = httpRequest.getUri(); StringBuilder sb = new StringBuilder(); sb.append(httpRequest.getMethod().name()).append(" ").append(url).append(" ") .append(httpRequest.getProtocolVersion().text()).append(CRLF); Set<String> headerNames = httpRequest.headers().names(); for (String headerName : headerNames) { if (StringUtils.equalsIgnoreCase(headerName, "Proxy-Connection")) { continue; } if (StringUtils.equalsIgnoreCase(headerName, HttpHeaders.Names.CONNECTION)) { continue; } for (String headerValue : httpRequest.headers().getAll(headerName)) { sb.append(headerName).append(": ").append(headerValue).append(CRLF); } } if (StringUtils.isNotBlank(apnProxyRemote.getProxyUserName()) && StringUtils.isNotBlank(apnProxyRemote.getProxyPassword())) { String proxyAuthorization = apnProxyRemote.getProxyUserName() + ":" + apnProxyRemote.getProxyPassword(); try { sb.append( "Proxy-Authorization: Basic " + Base64.encodeBase64String(proxyAuthorization.getBytes("UTF-8"))) .append(CRLF); } catch (UnsupportedEncodingException e) { } } sb.append(CRLF); return sb.toString(); } @Override public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception { LoggerUtil.error(logger, cause.getMessage(), cause, ctx.attr(ApnProxyConnectionAttribute.ATTRIBUTE_KEY)); ctx.close(); } }