com.twitter.finagle.httpproxy.HttpConnectHandler.scala Maven / Gradle / Ivy
The newest version!
package com.twitter.finagle.httpproxy
import java.net.{InetSocketAddress, SocketAddress}
import java.util.concurrent.atomic.AtomicReference
import org.jboss.netty.buffer.{ChannelBuffer, ChannelBuffers}
import org.jboss.netty.channel._
import org.jboss.netty.handler.codec.http._
import com.twitter.finagle.{ChannelClosedException, ConnectionFailedException, InconsistentStateException}
/**
* Handle SSL connections through a proxy that accepts HTTP CONNECT.
*
* See http://www.w3.org/Protocols/rfc2616/rfc2616-sec9.html#9.9
*
*/
object HttpConnectHandler {
def addHandler(proxyAddr: SocketAddress, addr: InetSocketAddress, pipeline: ChannelPipeline): HttpConnectHandler = {
val clientCodec = new HttpClientCodec()
val handler = new HttpConnectHandler(proxyAddr, addr, clientCodec)
pipeline.addFirst("httpProxyCodec", handler)
pipeline.addFirst("clientCodec", clientCodec)
handler
}
}
class HttpConnectHandler(proxyAddr: SocketAddress, addr: InetSocketAddress, clientCodec: HttpClientCodec)
extends SimpleChannelHandler
{
import HttpConnectHandler._
private[this] val connectFuture = new AtomicReference[ChannelFuture](null)
private[this] def fail(c: Channel, t: Throwable) {
Option(connectFuture.get) foreach { _.setFailure(t) }
Channels.close(c)
}
private[this] def writeRequest(ctx: ChannelHandlerContext, e: ChannelStateEvent) {
val hostNameWithPort = addr.getAddress.getHostName + ":" + addr.getPort
val req = new DefaultHttpRequest(HttpVersion.HTTP_1_1, HttpMethod.CONNECT, hostNameWithPort)
req.headers().set("Host", hostNameWithPort)
Channels.write(ctx, Channels.future(ctx.getChannel), req, null)
}
override def connectRequested(ctx: ChannelHandlerContext, e: ChannelStateEvent) {
e match {
case de: DownstreamChannelStateEvent =>
if (!connectFuture.compareAndSet(null, e.getFuture)) {
fail(ctx.getChannel, new InconsistentStateException(addr))
return
}
// proxy cancellation
val wrappedConnectFuture = Channels.future(de.getChannel, true)
de.getFuture.addListener(new ChannelFutureListener {
def operationComplete(f: ChannelFuture) {
if (f.isCancelled)
wrappedConnectFuture.cancel()
}
})
// Proxy failures here so that if the connect fails, it is
// propagated to the listener, not just on the channel.
wrappedConnectFuture.addListener(new ChannelFutureListener {
def operationComplete(f: ChannelFuture) {
if (f.isSuccess || f.isCancelled)
return
fail(f.getChannel, f.getCause)
}
})
val wrappedEvent = new DownstreamChannelStateEvent(
de.getChannel, wrappedConnectFuture,
de.getState, proxyAddr)
super.connectRequested(ctx, wrappedEvent)
case _ =>
fail(ctx.getChannel, new InconsistentStateException(addr))
}
}
// we delay propagating connection upstream until we've completed the proxy connection.
override def channelConnected(ctx: ChannelHandlerContext, e: ChannelStateEvent) {
if (connectFuture.get eq null) {
fail(ctx.getChannel, new InconsistentStateException(addr))
return
}
// proxy cancellations again.
connectFuture.get.addListener(new ChannelFutureListener {
def operationComplete(f: ChannelFuture) {
if (f.isSuccess)
HttpConnectHandler.super.channelConnected(ctx, e)
else if (f.isCancelled)
fail(ctx.getChannel, new ChannelClosedException(addr))
}
})
writeRequest(ctx, e)
}
override def messageReceived(ctx: ChannelHandlerContext, e: MessageEvent) {
if (connectFuture.get eq null) {
fail(ctx.getChannel, new InconsistentStateException(addr))
return
}
val resp = e.getMessage.asInstanceOf[DefaultHttpResponse]
if (resp.getStatus == HttpResponseStatus.OK) {
ctx.getPipeline.remove(clientCodec)
ctx.getPipeline.remove(this)
connectFuture.get.setSuccess()
} else {
val cause = new Throwable("unexpected response status received by HttpConnectHandler:"
+ resp.getStatus)
fail(e.getChannel, new ConnectionFailedException(cause, addr))
}
}
}
© 2015 - 2024 Weber Informatics LLC | Privacy Policy