/** * Helios, OpenSource Monitoring * Brought to you by the Helios Development Group * * Copyright 2007, Helios Development Group and individual contributors * as indicated by the @author tags. See the copyright.txt file in the * distribution for a full listing of individual contributors. * * This is free software; you can redistribute it and/or modify it * under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation; either version 2.1 of * the License, or (at your option) any later version. * * This software 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this software; if not, write to the Free * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA * 02110-1301 USA, or see the FSF site: http://www.fsf.org. * */ package org.helios.apmrouter.server.unification.pipeline.http.proxy; import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicLong; import org.apache.log4j.Logger; import org.jboss.netty.channel.Channel; import org.jboss.netty.channel.ChannelEvent; import org.jboss.netty.channel.ChannelFuture; import org.jboss.netty.channel.ChannelFutureListener; import org.jboss.netty.channel.ChannelHandlerContext; import org.jboss.netty.channel.ChannelLocal; import org.jboss.netty.channel.ChannelStateEvent; import org.jboss.netty.channel.ChannelUpstreamHandler; import org.jboss.netty.channel.Channels; import org.jboss.netty.channel.DownstreamMessageEvent; import org.jboss.netty.channel.MessageEvent; import org.jboss.netty.handler.codec.http.HttpHeaders; import org.jboss.netty.handler.codec.http.HttpRequest; import org.jboss.netty.handler.codec.http.HttpResponse; import org.jboss.netty.handler.codec.http.HttpResponseStatus; /** * <p>Title: ProxyResponseHandler</p> * <p>Description: A channel handler that waits for an {@link HttpResponse} to come down the pipe from a proxied server and sends it to the original requesting client.</p> * <p>Company: Helios Development Group LLC</p> * @author Whitehead (nwhitehead AT heliosdev DOT org) * <p><code>org.helios.apmrouter.server.unification.pipeline.http.proxy.ProxyResponseHandler</code></p> */ public class ProxyResponseHandler implements ChannelUpstreamHandler { /** Instance logger */ protected final Logger log = Logger.getLogger(getClass()); /** The host to proxy for */ protected final String targetHost; /** The port to proxy for */ protected final int targetPort; /** The remote key */ protected final String remoteKey; /** The port as a string for parsing a 302 */ protected final String portKey; /** The port key length */ protected final int portKeyLength; /** A channel local for holding the original http request in case we need to handle a 302 */ public static final ChannelLocal<HttpRequest> httpRequestChannelLocal = new ChannelLocal<HttpRequest>(true); /** A channel local for holding the original channel handler context */ public static final ChannelLocal<ChannelHandlerContext> ctxChannelLocal = new ChannelLocal<ChannelHandlerContext>(true); /** A counter to track the number of in-flight requests */ protected final AtomicInteger inFlightRequests; /** A counter for outgoing responses */ protected final AtomicLong outgoingResponses; /** Traffic lock */ protected final Object trafficLock; /** * Creates a new ProxyResponseHandler * @param targetHost The target host * @param targetPort The target port * @param inFlightRequests Counter to decrement on task completion * @param outgoingResponses Cummulative counter for outgoing responses * @param trafficLock Synchronizer for channel state setting */ public ProxyResponseHandler(String targetHost, int targetPort, AtomicInteger inFlightRequests, AtomicLong outgoingResponses, Object trafficLock) { this.targetHost = targetHost; this.targetPort = targetPort; this.inFlightRequests = inFlightRequests; this.outgoingResponses = outgoingResponses; remoteKey = targetHost + ":" + targetPort; this.trafficLock = trafficLock; portKey = ":" + targetPort; portKeyLength = portKey.length(); } public void channelInterestChanged(ChannelHandlerContext ctx, ChannelStateEvent cse) { synchronized(trafficLock) { if(cse.getChannel().isWritable()) { log.info("PROXY CHANNEL OK !!"); } } } /** * {@inheritDoc} * @see org.jboss.netty.channel.ChannelUpstreamHandler#handleUpstream(org.jboss.netty.channel.ChannelHandlerContext, org.jboss.netty.channel.ChannelEvent) */ @Override public void handleUpstream(final ChannelHandlerContext proxyCtx, final ChannelEvent proxyChannelEvent) throws Exception { final Channel proxyChannel = proxyCtx.getChannel(); proxyCtx.setAttachment(null); if(proxyChannelEvent instanceof MessageEvent) { final HttpRequest newRequest = httpRequestChannelLocal.get(proxyChannel); final ChannelHandlerContext originalCtx = ctxChannelLocal.get(proxyChannel); final Channel originalChannel = originalCtx.getChannel(); httpRequestChannelLocal.remove(proxyChannel); ctxChannelLocal.remove(proxyChannel); MessageEvent me = (MessageEvent)proxyChannelEvent; Object message = me.getMessage(); if(message instanceof HttpResponse) { // if(log.isDebugEnabled()) log.debug("Received response from remote [" + message + "]"); final HttpResponse resp = (HttpResponse)message; log.info("Received response from remote [" + resp.getHeader("Content-Type") + "]:" + HttpHeaders.getContentLength(resp, 0) + " [" + resp.getStatus() + "]"); if(resp.getStatus().equals(HttpResponseStatus.FOUND)) { String reUri = resp.getHeader("Location"); reUri = reUri.substring(reUri.indexOf(portKey)+portKeyLength); newRequest.setUri(reUri); proxyChannel.write(newRequest).addListener(new ChannelFutureListener() { @Override public void operationComplete(ChannelFuture future) throws Exception { if(!future.isSuccess()) {//inFlightRequests.decrementAndGet(); log.error("ProxyChannel Write Failed", future.getCause()); } } }); httpRequestChannelLocal.remove(originalChannel); } else { // Send the response back to the caller ChannelFuture cf = Channels.future(originalChannel); originalCtx.sendDownstream(new DownstreamMessageEvent(originalChannel, cf, resp, originalChannel.getRemoteAddress())); cf.addListener(new ChannelFutureListener() { @Override public void operationComplete(ChannelFuture f) throws Exception { inFlightRequests.decrementAndGet(); if(f.isSuccess()) { if(f.isSuccess()) { outgoingResponses.incrementAndGet(); log.info("Completed response write back to caller [" + resp.getHeader("Content-Type") + "]:" + HttpHeaders.getContentLength(resp, 0) + " [" + resp.getStatus() + "]"); } if(log.isDebugEnabled()) log.debug("Completed response write back to caller"); } else { log.error("Failed to write response back to caller", f.getCause()); f.getCause().printStackTrace(System.err); } } }); } } } else { proxyCtx.sendUpstream(proxyChannelEvent); } } }