View Javadoc
1   /*
2    * Copyright 2014 The Netty Project
3    *
4    * The Netty Project licenses this file to you under the Apache License,
5    * version 2.0 (the "License"); you may not use this file except in compliance
6    * with the License. You may obtain a copy of the License at:
7    *
8    *   http://www.apache.org/licenses/LICENSE-2.0
9    *
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
12   * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
13   * License for the specific language governing permissions and limitations
14   * under the License.
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.ChannelPipeline;
23  import io.netty.handler.codec.AsciiString;
24  import io.netty.handler.codec.base64.Base64;
25  import io.netty.handler.codec.http.DefaultFullHttpRequest;
26  import io.netty.handler.codec.http.FullHttpRequest;
27  import io.netty.handler.codec.http.HttpClientCodec;
28  import io.netty.handler.codec.http.HttpHeaderNames;
29  import io.netty.handler.codec.http.HttpMethod;
30  import io.netty.handler.codec.http.HttpResponse;
31  import io.netty.handler.codec.http.HttpResponseStatus;
32  import io.netty.handler.codec.http.HttpVersion;
33  import io.netty.handler.codec.http.LastHttpContent;
34  import io.netty.util.CharsetUtil;
35  
36  import java.net.InetSocketAddress;
37  import java.net.SocketAddress;
38  
39  public final class HttpProxyHandler extends ProxyHandler {
40  
41      private static final String PROTOCOL = "http";
42      private static final String AUTH_BASIC = "basic";
43  
44      private final HttpClientCodec codec = new HttpClientCodec();
45      private final String username;
46      private final String password;
47      private final CharSequence authorization;
48      private HttpResponseStatus status;
49  
50      public HttpProxyHandler(SocketAddress proxyAddress) {
51          super(proxyAddress);
52          username = null;
53          password = null;
54          authorization = null;
55      }
56  
57      public HttpProxyHandler(SocketAddress proxyAddress, String username, String password) {
58          super(proxyAddress);
59          if (username == null) {
60              throw new NullPointerException("username");
61          }
62          if (password == null) {
63              throw new NullPointerException("password");
64          }
65          this.username = username;
66          this.password = password;
67  
68          ByteBuf authz = Unpooled.copiedBuffer(username + ':' + password, CharsetUtil.UTF_8);
69          ByteBuf authzBase64 = Base64.encode(authz, false);
70  
71          authorization = new AsciiString("Basic " + authzBase64.toString(CharsetUtil.US_ASCII));
72  
73          authz.release();
74          authzBase64.release();
75      }
76  
77      @Override
78      public String protocol() {
79          return PROTOCOL;
80      }
81  
82      @Override
83      public String authScheme() {
84          return authorization != null? AUTH_BASIC : AUTH_NONE;
85      }
86  
87      public String username() {
88          return username;
89      }
90  
91      public String password() {
92          return password;
93      }
94  
95      @Override
96      protected void addCodec(ChannelHandlerContext ctx) throws Exception {
97          ChannelPipeline p = ctx.pipeline();
98          String name = ctx.name();
99          p.addBefore(name, null, codec);
100     }
101 
102     @Override
103     protected void removeEncoder(ChannelHandlerContext ctx) throws Exception {
104         ctx.pipeline().remove(codec.encoder());
105     }
106 
107     @Override
108     protected void removeDecoder(ChannelHandlerContext ctx) throws Exception {
109         ctx.pipeline().remove(codec.decoder());
110     }
111 
112     @Override
113     protected Object newInitialMessage(ChannelHandlerContext ctx) throws Exception {
114         InetSocketAddress raddr = destinationAddress();
115         String rhost;
116         if (raddr.isUnresolved()) {
117             rhost = raddr.getHostString();
118         } else {
119             rhost = raddr.getAddress().getHostAddress();
120         }
121 
122         FullHttpRequest req = new DefaultFullHttpRequest(
123                 HttpVersion.HTTP_1_0, HttpMethod.CONNECT,
124                 rhost + ':' + raddr.getPort(),
125                 Unpooled.EMPTY_BUFFER, false);
126 
127         SocketAddress proxyAddress = proxyAddress();
128         if (proxyAddress instanceof InetSocketAddress) {
129             InetSocketAddress hostAddr = (InetSocketAddress) proxyAddress;
130             req.headers().set(HttpHeaderNames.HOST, hostAddr.getHostString() + ':' + hostAddr.getPort());
131         }
132 
133         if (authorization != null) {
134             req.headers().set(HttpHeaderNames.PROXY_AUTHORIZATION, authorization);
135         }
136 
137         return req;
138     }
139 
140     @Override
141     protected boolean handleResponse(ChannelHandlerContext ctx, Object response) throws Exception {
142         if (response instanceof HttpResponse) {
143             if (status != null) {
144                 throw new ProxyConnectException(exceptionMessage("too many responses"));
145             }
146             status = ((HttpResponse) response).status();
147         }
148 
149         boolean finished = response instanceof LastHttpContent;
150         if (finished) {
151             if (status == null) {
152                 throw new ProxyConnectException(exceptionMessage("missing response"));
153             }
154             if (status.code() != 200) {
155                 throw new ProxyConnectException(exceptionMessage("status: " + status));
156             }
157         }
158 
159         return finished;
160     }
161 }