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 HttpResponseStatus status;
74 private HttpHeaders inboundHeaders;
75
76 public HttpProxyHandler(SocketAddress proxyAddress) {
77 this(proxyAddress, null);
78 }
79
80 public HttpProxyHandler(SocketAddress proxyAddress, HttpHeaders headers) {
81 this(proxyAddress, headers, false);
82 }
83
84 public HttpProxyHandler(SocketAddress proxyAddress,
85 HttpHeaders headers,
86 boolean ignoreDefaultPortsInConnectHostHeader) {
87 super(proxyAddress);
88 username = null;
89 password = null;
90 authorization = null;
91 this.outboundHeaders = headers;
92 this.ignoreDefaultPortsInConnectHostHeader = ignoreDefaultPortsInConnectHostHeader;
93 }
94
95 public HttpProxyHandler(SocketAddress proxyAddress, String username, String password) {
96 this(proxyAddress, username, password, null);
97 }
98
99 public HttpProxyHandler(SocketAddress proxyAddress, String username, String password,
100 HttpHeaders headers) {
101 this(proxyAddress, username, password, headers, false);
102 }
103
104 public HttpProxyHandler(SocketAddress proxyAddress,
105 String username,
106 String password,
107 HttpHeaders headers,
108 boolean ignoreDefaultPortsInConnectHostHeader) {
109 super(proxyAddress);
110 this.username = ObjectUtil.checkNotNull(username, "username");
111 this.password = ObjectUtil.checkNotNull(password, "password");
112
113 ByteBuf authz = Unpooled.copiedBuffer(username + ':' + password, CharsetUtil.UTF_8);
114 ByteBuf authzBase64;
115 try {
116 authzBase64 = Base64.encode(authz, false);
117 } finally {
118 authz.release();
119 }
120 try {
121 authorization = new AsciiString("Basic " + authzBase64.toString(CharsetUtil.US_ASCII));
122 } finally {
123 authzBase64.release();
124 }
125
126 this.outboundHeaders = headers;
127 this.ignoreDefaultPortsInConnectHostHeader = ignoreDefaultPortsInConnectHostHeader;
128 }
129
130 @Override
131 public String protocol() {
132 return PROTOCOL;
133 }
134
135 @Override
136 public String authScheme() {
137 return authorization != null? AUTH_BASIC : AUTH_NONE;
138 }
139
140 public String username() {
141 return username;
142 }
143
144 public String password() {
145 return password;
146 }
147
148 @Override
149 protected void addCodec(ChannelHandlerContext ctx) throws Exception {
150 ChannelPipeline p = ctx.pipeline();
151 String name = ctx.name();
152 p.addBefore(name, null, codecWrapper);
153 }
154
155 @Override
156 protected void removeEncoder(ChannelHandlerContext ctx) throws Exception {
157 codecWrapper.codec.removeOutboundHandler();
158 }
159
160 @Override
161 protected void removeDecoder(ChannelHandlerContext ctx) throws Exception {
162 codecWrapper.codec.removeInboundHandler();
163 }
164
165 @Override
166 protected Object newInitialMessage(ChannelHandlerContext ctx) throws Exception {
167 InetSocketAddress raddr = destinationAddress();
168
169 String hostString = HttpUtil.formatHostnameForHttp(raddr);
170 int port = raddr.getPort();
171 String url = hostString + ":" + port;
172 String hostHeader = (ignoreDefaultPortsInConnectHostHeader && (port == 80 || port == 443)) ?
173 hostString :
174 url;
175
176 HttpHeadersFactory headersFactory = DefaultHttpHeadersFactory.headersFactory().withValidation(false);
177 FullHttpRequest req = new DefaultFullHttpRequest(
178 HttpVersion.HTTP_1_1, HttpMethod.CONNECT,
179 url,
180 Unpooled.EMPTY_BUFFER, headersFactory, headersFactory);
181
182 req.headers().set(HttpHeaderNames.HOST, hostHeader);
183
184 if (authorization != null) {
185 req.headers().set(HttpHeaderNames.PROXY_AUTHORIZATION, authorization);
186 }
187
188 if (outboundHeaders != null) {
189 req.headers().add(outboundHeaders);
190 }
191
192 return req;
193 }
194
195 @Override
196 protected boolean handleResponse(ChannelHandlerContext ctx, Object response) throws Exception {
197 if (response instanceof HttpResponse) {
198 if (status != null) {
199 throw new HttpProxyConnectException(exceptionMessage("too many responses"), null);
200 }
201 HttpResponse res = (HttpResponse) response;
202 status = res.status();
203 inboundHeaders = res.headers();
204 }
205
206 boolean finished = response instanceof LastHttpContent;
207 if (finished) {
208 if (status == null) {
209 throw new HttpProxyConnectException(exceptionMessage("missing response"), inboundHeaders);
210 }
211 if (status.code() != 200) {
212 throw new HttpProxyConnectException(exceptionMessage("status: " + status), inboundHeaders);
213 }
214 }
215
216 return finished;
217 }
218
219
220
221
222 public static final class HttpProxyConnectException extends ProxyConnectException {
223 private static final long serialVersionUID = -8824334609292146066L;
224
225 private final HttpHeaders headers;
226
227
228
229
230
231 public HttpProxyConnectException(String message, HttpHeaders headers) {
232 super(message);
233 this.headers = headers;
234 }
235
236
237
238
239 public HttpHeaders headers() {
240 return headers;
241 }
242 }
243
244 private static final class HttpClientCodecWrapper implements ChannelInboundHandler, ChannelOutboundHandler {
245 final HttpClientCodec codec = new HttpClientCodec();
246
247 @Override
248 public void handlerAdded(ChannelHandlerContext ctx) throws Exception {
249 codec.handlerAdded(ctx);
250 }
251
252 @Override
253 public void handlerRemoved(ChannelHandlerContext ctx) throws Exception {
254 codec.handlerRemoved(ctx);
255 }
256
257 @Override
258 public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
259 codec.exceptionCaught(ctx, cause);
260 }
261
262 @Override
263 public void channelRegistered(ChannelHandlerContext ctx) throws Exception {
264 codec.channelRegistered(ctx);
265 }
266
267 @Override
268 public void channelUnregistered(ChannelHandlerContext ctx) throws Exception {
269 codec.channelUnregistered(ctx);
270 }
271
272 @Override
273 public void channelActive(ChannelHandlerContext ctx) throws Exception {
274 codec.channelActive(ctx);
275 }
276
277 @Override
278 public void channelInactive(ChannelHandlerContext ctx) throws Exception {
279 codec.channelInactive(ctx);
280 }
281
282 @Override
283 public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
284 codec.channelRead(ctx, msg);
285 }
286
287 @Override
288 public void channelReadComplete(ChannelHandlerContext ctx) throws Exception {
289 codec.channelReadComplete(ctx);
290 }
291
292 @Override
293 public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception {
294 codec.userEventTriggered(ctx, evt);
295 }
296
297 @Override
298 public void channelWritabilityChanged(ChannelHandlerContext ctx) throws Exception {
299 codec.channelWritabilityChanged(ctx);
300 }
301
302 @Override
303 public void bind(ChannelHandlerContext ctx, SocketAddress localAddress,
304 ChannelPromise promise) throws Exception {
305 codec.bind(ctx, localAddress, promise);
306 }
307
308 @Override
309 public void connect(ChannelHandlerContext ctx, SocketAddress remoteAddress, SocketAddress localAddress,
310 ChannelPromise promise) throws Exception {
311 codec.connect(ctx, remoteAddress, localAddress, promise);
312 }
313
314 @Override
315 public void disconnect(ChannelHandlerContext ctx, ChannelPromise promise) throws Exception {
316 codec.disconnect(ctx, promise);
317 }
318
319 @Override
320 public void close(ChannelHandlerContext ctx, ChannelPromise promise) throws Exception {
321 codec.close(ctx, promise);
322 }
323
324 @Override
325 public void deregister(ChannelHandlerContext ctx, ChannelPromise promise) throws Exception {
326 codec.deregister(ctx, promise);
327 }
328
329 @Override
330 public void read(ChannelHandlerContext ctx) throws Exception {
331 codec.read(ctx);
332 }
333
334 @Override
335 public void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise) throws Exception {
336 codec.write(ctx, msg, promise);
337 }
338
339 @Override
340 public void flush(ChannelHandlerContext ctx) throws Exception {
341 codec.flush(ctx);
342 }
343 }
344 }