1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17 package io.netty.handler.proxy;
18
19 import io.netty.buffer.ByteBuf;
20 import io.netty.buffer.Unpooled;
21 import io.netty.channel.ChannelHandlerContext;
22 import io.netty.channel.ChannelInboundHandler;
23 import io.netty.channel.ChannelOutboundHandler;
24 import io.netty.channel.ChannelPipeline;
25 import io.netty.channel.ChannelPromise;
26 import io.netty.handler.codec.base64.Base64;
27 import io.netty.handler.codec.http.DefaultFullHttpRequest;
28 import io.netty.handler.codec.http.FullHttpRequest;
29 import io.netty.handler.codec.http.HttpClientCodec;
30 import io.netty.handler.codec.http.HttpHeaderNames;
31 import io.netty.handler.codec.http.HttpHeaders;
32 import io.netty.handler.codec.http.DefaultHttpHeadersFactory;
33 import io.netty.handler.codec.http.HttpHeadersFactory;
34 import io.netty.handler.codec.http.HttpMethod;
35 import io.netty.handler.codec.http.HttpResponse;
36 import io.netty.handler.codec.http.HttpResponseStatus;
37 import io.netty.handler.codec.http.HttpUtil;
38 import io.netty.handler.codec.http.HttpVersion;
39 import io.netty.handler.codec.http.LastHttpContent;
40 import io.netty.util.AsciiString;
41 import io.netty.util.CharsetUtil;
42 import io.netty.util.internal.ObjectUtil;
43
44 import java.net.InetSocketAddress;
45 import java.net.SocketAddress;
46
47
48
49
50
51
52
53
54
55
56 public final class HttpProxyHandler extends ProxyHandler {
57
58 private static final String PROTOCOL = "http";
59 private static final String AUTH_BASIC = "basic";
60
61
62
63
64
65
66
67 private final HttpClientCodecWrapper codecWrapper = new HttpClientCodecWrapper();
68 private final String username;
69 private final String password;
70 private final CharSequence authorization;
71 private final HttpHeaders outboundHeaders;
72 private final boolean ignoreDefaultPortsInConnectHostHeader;
73 private final boolean validateInitialHeaders;
74 private HttpResponseStatus status;
75 private HttpHeaders inboundHeaders;
76
77 public HttpProxyHandler(SocketAddress proxyAddress) {
78 this(proxyAddress, null);
79 }
80
81 public HttpProxyHandler(SocketAddress proxyAddress, HttpHeaders headers) {
82 this(proxyAddress, headers, false);
83 }
84
85 public HttpProxyHandler(SocketAddress proxyAddress,
86 HttpHeaders headers,
87 boolean ignoreDefaultPortsInConnectHostHeader) {
88 this(proxyAddress, headers, ignoreDefaultPortsInConnectHostHeader, true);
89 }
90
91 public HttpProxyHandler(SocketAddress proxyAddress,
92 HttpHeaders headers,
93 boolean ignoreDefaultPortsInConnectHostHeader,
94 boolean validateInitialHeaders) {
95 super(proxyAddress);
96 username = null;
97 password = null;
98 authorization = null;
99 this.outboundHeaders = headers;
100 this.ignoreDefaultPortsInConnectHostHeader = ignoreDefaultPortsInConnectHostHeader;
101 this.validateInitialHeaders = validateInitialHeaders;
102 }
103
104 public HttpProxyHandler(SocketAddress proxyAddress, String username, String password) {
105 this(proxyAddress, username, password, null);
106 }
107
108 public HttpProxyHandler(SocketAddress proxyAddress, String username, String password,
109 HttpHeaders headers) {
110 this(proxyAddress, username, password, headers, false, true);
111 }
112
113 public HttpProxyHandler(SocketAddress proxyAddress,
114 String username,
115 String password,
116 HttpHeaders headers,
117 boolean ignoreDefaultPortsInConnectHostHeader) {
118 this(proxyAddress, username, password, headers, ignoreDefaultPortsInConnectHostHeader, true);
119 }
120
121 public HttpProxyHandler(SocketAddress proxyAddress,
122 String username,
123 String password,
124 HttpHeaders headers,
125 boolean ignoreDefaultPortsInConnectHostHeader,
126 boolean validateInitialHeaders) {
127 super(proxyAddress);
128 this.username = ObjectUtil.checkNotNull(username, "username");
129 this.password = ObjectUtil.checkNotNull(password, "password");
130
131 ByteBuf authz = Unpooled.copiedBuffer(username + ':' + password, CharsetUtil.UTF_8);
132 ByteBuf authzBase64;
133 try {
134 authzBase64 = Base64.encode(authz, false);
135 } finally {
136 authz.release();
137 }
138 try {
139 authorization = new AsciiString("Basic " + authzBase64.toString(CharsetUtil.US_ASCII));
140 } finally {
141 authzBase64.release();
142 }
143
144 this.outboundHeaders = headers;
145 this.ignoreDefaultPortsInConnectHostHeader = ignoreDefaultPortsInConnectHostHeader;
146 this.validateInitialHeaders = validateInitialHeaders;
147 }
148
149 @Override
150 public String protocol() {
151 return PROTOCOL;
152 }
153
154 @Override
155 public String authScheme() {
156 return authorization != null? AUTH_BASIC : AUTH_NONE;
157 }
158
159 public String username() {
160 return username;
161 }
162
163 public String password() {
164 return password;
165 }
166
167 @Override
168 protected void addCodec(ChannelHandlerContext ctx) throws Exception {
169 ChannelPipeline p = ctx.pipeline();
170 String name = ctx.name();
171 p.addBefore(name, null, codecWrapper);
172 }
173
174 @Override
175 protected void removeEncoder(ChannelHandlerContext ctx) throws Exception {
176 codecWrapper.codec.removeOutboundHandler();
177 }
178
179 @Override
180 protected void removeDecoder(ChannelHandlerContext ctx) throws Exception {
181 codecWrapper.codec.removeInboundHandler();
182 }
183
184 @Override
185 protected Object newInitialMessage(ChannelHandlerContext ctx) throws Exception {
186 InetSocketAddress raddr = destinationAddress();
187
188 String hostString = HttpUtil.formatHostnameForHttp(raddr);
189 int port = raddr.getPort();
190 String url = hostString + ":" + port;
191 String hostHeader = (ignoreDefaultPortsInConnectHostHeader && (port == 80 || port == 443)) ?
192 hostString :
193 url;
194
195 HttpHeadersFactory headersFactory = DefaultHttpHeadersFactory.headersFactory()
196 .withValidation(validateInitialHeaders);
197 FullHttpRequest req = new DefaultFullHttpRequest(
198 HttpVersion.HTTP_1_1, HttpMethod.CONNECT,
199 url,
200 Unpooled.EMPTY_BUFFER, headersFactory, headersFactory);
201
202 req.headers().set(HttpHeaderNames.HOST, hostHeader);
203
204 if (authorization != null) {
205 req.headers().set(HttpHeaderNames.PROXY_AUTHORIZATION, authorization);
206 }
207
208 if (outboundHeaders != null) {
209 req.headers().add(outboundHeaders);
210 }
211
212 return req;
213 }
214
215 @Override
216 protected boolean handleResponse(ChannelHandlerContext ctx, Object response) throws Exception {
217 if (response instanceof HttpResponse) {
218 if (status != null) {
219 throw new HttpProxyConnectException(exceptionMessage("too many responses"), null);
220 }
221 HttpResponse res = (HttpResponse) response;
222 status = res.status();
223 inboundHeaders = res.headers();
224 }
225
226 boolean finished = response instanceof LastHttpContent;
227 if (finished) {
228 if (status == null) {
229 throw new HttpProxyConnectException(exceptionMessage("missing response"), inboundHeaders);
230 }
231 if (status.code() != 200) {
232 throw new HttpProxyConnectException(exceptionMessage("status: " + status), inboundHeaders);
233 }
234 }
235
236 return finished;
237 }
238
239
240
241
242 public static final class HttpProxyConnectException extends ProxyConnectException {
243 private static final long serialVersionUID = -8824334609292146066L;
244
245 private final HttpHeaders headers;
246
247
248
249
250
251 public HttpProxyConnectException(String message, HttpHeaders headers) {
252 super(message);
253 this.headers = headers;
254 }
255
256
257
258
259 public HttpHeaders headers() {
260 return headers;
261 }
262 }
263
264 private static final class HttpClientCodecWrapper implements ChannelInboundHandler, ChannelOutboundHandler {
265 final HttpClientCodec codec = new HttpClientCodec();
266
267 @Override
268 public void handlerAdded(ChannelHandlerContext ctx) throws Exception {
269 codec.handlerAdded(ctx);
270 }
271
272 @Override
273 public void handlerRemoved(ChannelHandlerContext ctx) throws Exception {
274 codec.handlerRemoved(ctx);
275 }
276
277 @Override
278 public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
279 codec.exceptionCaught(ctx, cause);
280 }
281
282 @Override
283 public void channelRegistered(ChannelHandlerContext ctx) throws Exception {
284 codec.channelRegistered(ctx);
285 }
286
287 @Override
288 public void channelUnregistered(ChannelHandlerContext ctx) throws Exception {
289 codec.channelUnregistered(ctx);
290 }
291
292 @Override
293 public void channelActive(ChannelHandlerContext ctx) throws Exception {
294 codec.channelActive(ctx);
295 }
296
297 @Override
298 public void channelInactive(ChannelHandlerContext ctx) throws Exception {
299 codec.channelInactive(ctx);
300 }
301
302 @Override
303 public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
304 codec.channelRead(ctx, msg);
305 }
306
307 @Override
308 public void channelReadComplete(ChannelHandlerContext ctx) throws Exception {
309 codec.channelReadComplete(ctx);
310 }
311
312 @Override
313 public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception {
314 codec.userEventTriggered(ctx, evt);
315 }
316
317 @Override
318 public void channelWritabilityChanged(ChannelHandlerContext ctx) throws Exception {
319 codec.channelWritabilityChanged(ctx);
320 }
321
322 @Override
323 public void bind(ChannelHandlerContext ctx, SocketAddress localAddress,
324 ChannelPromise promise) throws Exception {
325 codec.bind(ctx, localAddress, promise);
326 }
327
328 @Override
329 public void connect(ChannelHandlerContext ctx, SocketAddress remoteAddress, SocketAddress localAddress,
330 ChannelPromise promise) throws Exception {
331 codec.connect(ctx, remoteAddress, localAddress, promise);
332 }
333
334 @Override
335 public void disconnect(ChannelHandlerContext ctx, ChannelPromise promise) throws Exception {
336 codec.disconnect(ctx, promise);
337 }
338
339 @Override
340 public void close(ChannelHandlerContext ctx, ChannelPromise promise) throws Exception {
341 codec.close(ctx, promise);
342 }
343
344 @Override
345 public void deregister(ChannelHandlerContext ctx, ChannelPromise promise) throws Exception {
346 codec.deregister(ctx, promise);
347 }
348
349 @Override
350 public void read(ChannelHandlerContext ctx) throws Exception {
351 codec.read(ctx);
352 }
353
354 @Override
355 public void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise) throws Exception {
356 codec.write(ctx, msg, promise);
357 }
358
359 @Override
360 public void flush(ChannelHandlerContext ctx) throws Exception {
361 codec.flush(ctx);
362 }
363 }
364 }