netty5/handler-proxy/src/main/java/io/netty/handler/proxy/HttpProxyHandler.java
Spencer Fang ec490b2a88 HttpProxyHandler: allow setting headers
Motivation:

In some environments, the HTTP CONNECT handshake requires special headers to work.

Modification:

Update HttpProxyHandler to accept a HttpHeaders argument.

Result:

The header is passed along in the HTTP CONNECT request, and the proxy request can be successfully completed.
2017-07-06 20:02:06 +02:00

171 lines
5.4 KiB
Java

/*
* 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:
*
* 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 io.netty.handler.proxy;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelPipeline;
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.HttpMethod;
import io.netty.handler.codec.http.HttpResponse;
import io.netty.handler.codec.http.HttpResponseStatus;
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.NetUtil;
import java.net.InetSocketAddress;
import java.net.SocketAddress;
public final class HttpProxyHandler extends ProxyHandler {
private static final String PROTOCOL = "http";
private static final String AUTH_BASIC = "basic";
private final HttpClientCodec codec = new HttpClientCodec();
private final String username;
private final String password;
private final CharSequence authorization;
private HttpResponseStatus status;
private HttpHeaders headers;
public HttpProxyHandler(SocketAddress proxyAddress) {
this(proxyAddress, null);
}
public HttpProxyHandler(SocketAddress proxyAddress, HttpHeaders headers) {
super(proxyAddress);
username = null;
password = null;
authorization = null;
this.headers = headers;
}
public HttpProxyHandler(SocketAddress proxyAddress, String username, String password) {
this(proxyAddress, username, password, null);
}
public HttpProxyHandler(SocketAddress proxyAddress, String username, String password,
HttpHeaders headers) {
super(proxyAddress);
if (username == null) {
throw new NullPointerException("username");
}
if (password == null) {
throw new NullPointerException("password");
}
this.username = username;
this.password = password;
ByteBuf authz = Unpooled.copiedBuffer(username + ':' + password, CharsetUtil.UTF_8);
ByteBuf authzBase64 = Base64.encode(authz, false);
authorization = new AsciiString("Basic " + authzBase64.toString(CharsetUtil.US_ASCII));
authz.release();
authzBase64.release();
this.headers = headers;
}
@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, codec);
}
@Override
protected void removeEncoder(ChannelHandlerContext ctx) throws Exception {
codec.removeOutboundHandler();
}
@Override
protected void removeDecoder(ChannelHandlerContext ctx) throws Exception {
codec.removeInboundHandler();
}
@Override
protected Object newInitialMessage(ChannelHandlerContext ctx) throws Exception {
InetSocketAddress raddr = destinationAddress();
final String host = NetUtil.toSocketAddressString(raddr);
FullHttpRequest req = new DefaultFullHttpRequest(
HttpVersion.HTTP_1_1, HttpMethod.CONNECT,
host,
Unpooled.EMPTY_BUFFER, false);
req.headers().set(HttpHeaderNames.HOST, host);
if (authorization != null) {
req.headers().set(HttpHeaderNames.PROXY_AUTHORIZATION, authorization);
}
if (headers != null) {
req.headers().add(headers);
}
return req;
}
@Override
protected boolean handleResponse(ChannelHandlerContext ctx, Object response) throws Exception {
if (response instanceof HttpResponse) {
if (status != null) {
throw new ProxyConnectException(exceptionMessage("too many responses"));
}
status = ((HttpResponse) response).status();
}
boolean finished = response instanceof LastHttpContent;
if (finished) {
if (status == null) {
throw new ProxyConnectException(exceptionMessage("missing response"));
}
if (status.code() != 200) {
throw new ProxyConnectException(exceptionMessage("status: " + status));
}
}
return finished;
}
}