All Downloads are FREE. Search and download functionalities are using the official Maven repository.

io.netty.handler.proxy.HttpProxyHandler Maven / Gradle / Ivy

There is a newer version: 5.0.0.Alpha2
Show newest version
/*
 * Copyright 2014 The Netty Project
 *
 * The Netty 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:
 *
 *   https://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 io.netty.handler.proxy;

import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandler;
import io.netty.channel.ChannelOutboundHandler;
import io.netty.channel.ChannelPipeline;
import io.netty.channel.ChannelPromise;
import io.netty.handler.codec.base64.Base64;
import io.netty.handler.codec.http.DefaultFullHttpRequest;
import io.netty.handler.codec.http.FullHttpRequest;
import io.netty.handler.codec.http.HttpClientCodec;
import io.netty.handler.codec.http.HttpHeaderNames;
import io.netty.handler.codec.http.HttpHeaders;
import io.netty.handler.codec.http.DefaultHttpHeadersFactory;
import io.netty.handler.codec.http.HttpHeadersFactory;
import io.netty.handler.codec.http.HttpMethod;
import io.netty.handler.codec.http.HttpResponse;
import io.netty.handler.codec.http.HttpResponseStatus;
import io.netty.handler.codec.http.HttpUtil;
import io.netty.handler.codec.http.HttpVersion;
import io.netty.handler.codec.http.LastHttpContent;
import io.netty.util.AsciiString;
import io.netty.util.CharsetUtil;
import io.netty.util.internal.ObjectUtil;

import java.net.InetSocketAddress;
import java.net.SocketAddress;

/**
 * Handler that establishes a blind forwarding proxy tunnel using
 * HTTP/1.1 CONNECT request. It can be used to
 * establish plaintext or secure tunnels.
 * 

* HTTP users who need to connect to a * message-forwarding HTTP proxy agent instead of a * tunneling proxy should not use this handler. */ public final class HttpProxyHandler extends ProxyHandler { private static final String PROTOCOL = "http"; private static final String AUTH_BASIC = "basic"; // Wrapper for the HttpClientCodec to prevent it to be removed by other handlers by mistake (for example the // WebSocket*Handshaker. // // See: // - https://github.com/netty/netty/issues/5201 // - https://github.com/netty/netty/issues/5070 private final HttpClientCodecWrapper codecWrapper = new HttpClientCodecWrapper(); private final String username; private final String password; private final CharSequence authorization; private final HttpHeaders outboundHeaders; private final boolean ignoreDefaultPortsInConnectHostHeader; private HttpResponseStatus status; private HttpHeaders inboundHeaders; public HttpProxyHandler(SocketAddress proxyAddress) { this(proxyAddress, null); } public HttpProxyHandler(SocketAddress proxyAddress, HttpHeaders headers) { this(proxyAddress, headers, false); } public HttpProxyHandler(SocketAddress proxyAddress, HttpHeaders headers, boolean ignoreDefaultPortsInConnectHostHeader) { super(proxyAddress); username = null; password = null; authorization = null; this.outboundHeaders = headers; this.ignoreDefaultPortsInConnectHostHeader = ignoreDefaultPortsInConnectHostHeader; } public HttpProxyHandler(SocketAddress proxyAddress, String username, String password) { this(proxyAddress, username, password, null); } public HttpProxyHandler(SocketAddress proxyAddress, String username, String password, HttpHeaders headers) { this(proxyAddress, username, password, headers, false); } public HttpProxyHandler(SocketAddress proxyAddress, String username, String password, HttpHeaders headers, boolean ignoreDefaultPortsInConnectHostHeader) { super(proxyAddress); this.username = ObjectUtil.checkNotNull(username, "username"); this.password = ObjectUtil.checkNotNull(password, "password"); ByteBuf authz = Unpooled.copiedBuffer(username + ':' + password, CharsetUtil.UTF_8); ByteBuf authzBase64; try { authzBase64 = Base64.encode(authz, false); } finally { authz.release(); } try { authorization = new AsciiString("Basic " + authzBase64.toString(CharsetUtil.US_ASCII)); } finally { authzBase64.release(); } this.outboundHeaders = headers; this.ignoreDefaultPortsInConnectHostHeader = ignoreDefaultPortsInConnectHostHeader; } @Override public String protocol() { return PROTOCOL; } @Override public String authScheme() { return authorization != null? AUTH_BASIC : AUTH_NONE; } public String username() { return username; } public String password() { return password; } @Override protected void addCodec(ChannelHandlerContext ctx) throws Exception { ChannelPipeline p = ctx.pipeline(); String name = ctx.name(); p.addBefore(name, null, codecWrapper); } @Override protected void removeEncoder(ChannelHandlerContext ctx) throws Exception { codecWrapper.codec.removeOutboundHandler(); } @Override protected void removeDecoder(ChannelHandlerContext ctx) throws Exception { codecWrapper.codec.removeInboundHandler(); } @Override protected Object newInitialMessage(ChannelHandlerContext ctx) throws Exception { InetSocketAddress raddr = destinationAddress(); String hostString = HttpUtil.formatHostnameForHttp(raddr); int port = raddr.getPort(); String url = hostString + ":" + port; String hostHeader = (ignoreDefaultPortsInConnectHostHeader && (port == 80 || port == 443)) ? hostString : url; HttpHeadersFactory headersFactory = DefaultHttpHeadersFactory.headersFactory().withValidation(false); FullHttpRequest req = new DefaultFullHttpRequest( HttpVersion.HTTP_1_1, HttpMethod.CONNECT, url, Unpooled.EMPTY_BUFFER, headersFactory, headersFactory); req.headers().set(HttpHeaderNames.HOST, hostHeader); if (authorization != null) { req.headers().set(HttpHeaderNames.PROXY_AUTHORIZATION, authorization); } if (outboundHeaders != null) { req.headers().add(outboundHeaders); } return req; } @Override protected boolean handleResponse(ChannelHandlerContext ctx, Object response) throws Exception { if (response instanceof HttpResponse) { if (status != null) { throw new HttpProxyConnectException(exceptionMessage("too many responses"), /*headers=*/ null); } HttpResponse res = (HttpResponse) response; status = res.status(); inboundHeaders = res.headers(); } boolean finished = response instanceof LastHttpContent; if (finished) { if (status == null) { throw new HttpProxyConnectException(exceptionMessage("missing response"), inboundHeaders); } if (status.code() != 200) { throw new HttpProxyConnectException(exceptionMessage("status: " + status), inboundHeaders); } } return finished; } /** * Specific case of a connection failure, which may include headers from the proxy. */ public static final class HttpProxyConnectException extends ProxyConnectException { private static final long serialVersionUID = -8824334609292146066L; private final HttpHeaders headers; /** * @param message The failure message. * @param headers Header associated with the connection failure. May be {@code null}. */ public HttpProxyConnectException(String message, HttpHeaders headers) { super(message); this.headers = headers; } /** * Returns headers, if any. May be {@code null}. */ public HttpHeaders headers() { return headers; } } private static final class HttpClientCodecWrapper implements ChannelInboundHandler, ChannelOutboundHandler { final HttpClientCodec codec = new HttpClientCodec(); @Override public void handlerAdded(ChannelHandlerContext ctx) throws Exception { codec.handlerAdded(ctx); } @Override public void handlerRemoved(ChannelHandlerContext ctx) throws Exception { codec.handlerRemoved(ctx); } @Override public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception { codec.exceptionCaught(ctx, cause); } @Override public void channelRegistered(ChannelHandlerContext ctx) throws Exception { codec.channelRegistered(ctx); } @Override public void channelUnregistered(ChannelHandlerContext ctx) throws Exception { codec.channelUnregistered(ctx); } @Override public void channelActive(ChannelHandlerContext ctx) throws Exception { codec.channelActive(ctx); } @Override public void channelInactive(ChannelHandlerContext ctx) throws Exception { codec.channelInactive(ctx); } @Override public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception { codec.channelRead(ctx, msg); } @Override public void channelReadComplete(ChannelHandlerContext ctx) throws Exception { codec.channelReadComplete(ctx); } @Override public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception { codec.userEventTriggered(ctx, evt); } @Override public void channelWritabilityChanged(ChannelHandlerContext ctx) throws Exception { codec.channelWritabilityChanged(ctx); } @Override public void bind(ChannelHandlerContext ctx, SocketAddress localAddress, ChannelPromise promise) throws Exception { codec.bind(ctx, localAddress, promise); } @Override public void connect(ChannelHandlerContext ctx, SocketAddress remoteAddress, SocketAddress localAddress, ChannelPromise promise) throws Exception { codec.connect(ctx, remoteAddress, localAddress, promise); } @Override public void disconnect(ChannelHandlerContext ctx, ChannelPromise promise) throws Exception { codec.disconnect(ctx, promise); } @Override public void close(ChannelHandlerContext ctx, ChannelPromise promise) throws Exception { codec.close(ctx, promise); } @Override public void deregister(ChannelHandlerContext ctx, ChannelPromise promise) throws Exception { codec.deregister(ctx, promise); } @Override public void read(ChannelHandlerContext ctx) throws Exception { codec.read(ctx); } @Override public void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise) throws Exception { codec.write(ctx, msg, promise); } @Override public void flush(ChannelHandlerContext ctx) throws Exception { codec.flush(ctx); } } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy