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.base64.Base64;
24  import io.netty.handler.codec.http.DefaultFullHttpRequest;
25  import io.netty.handler.codec.http.FullHttpRequest;
26  import io.netty.handler.codec.http.HttpClientCodec;
27  import io.netty.handler.codec.http.HttpHeaderNames;
28  import io.netty.handler.codec.http.HttpHeaders;
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.HttpUtil;
33  import io.netty.handler.codec.http.HttpVersion;
34  import io.netty.handler.codec.http.LastHttpContent;
35  import io.netty.util.AsciiString;
36  import io.netty.util.CharsetUtil;
37  
38  import java.net.InetSocketAddress;
39  import java.net.SocketAddress;
40  
41  public final class HttpProxyHandler extends ProxyHandler {
42  
43      private static final String PROTOCOL = "http";
44      private static final String AUTH_BASIC = "basic";
45  
46      private final HttpClientCodec codec = new HttpClientCodec();
47      private final String username;
48      private final String password;
49      private final CharSequence authorization;
50      private final boolean ignoreDefaultPortsInConnectHostHeader;
51      private HttpResponseStatus status;
52      private HttpHeaders headers;
53  
54      public HttpProxyHandler(SocketAddress proxyAddress) {
55          this(proxyAddress, null);
56      }
57  
58      public HttpProxyHandler(SocketAddress proxyAddress, HttpHeaders headers) {
59          this(proxyAddress, headers, false);
60      }
61  
62      public HttpProxyHandler(SocketAddress proxyAddress,
63                              HttpHeaders headers,
64                              boolean ignoreDefaultPortsInConnectHostHeader) {
65          super(proxyAddress);
66          username = null;
67          password = null;
68          authorization = null;
69          this.headers = headers;
70          this.ignoreDefaultPortsInConnectHostHeader = ignoreDefaultPortsInConnectHostHeader;
71      }
72  
73      public HttpProxyHandler(SocketAddress proxyAddress, String username, String password) {
74          this(proxyAddress, username, password, null);
75      }
76  
77      public HttpProxyHandler(SocketAddress proxyAddress, String username, String password,
78                              HttpHeaders headers) {
79          this(proxyAddress, username, password, headers, false);
80      }
81  
82      public HttpProxyHandler(SocketAddress proxyAddress,
83                              String username,
84                              String password,
85                              HttpHeaders headers,
86                              boolean ignoreDefaultPortsInConnectHostHeader) {
87          super(proxyAddress);
88          if (username == null) {
89              throw new NullPointerException("username");
90          }
91          if (password == null) {
92              throw new NullPointerException("password");
93          }
94          this.username = username;
95          this.password = password;
96  
97          ByteBuf authz = Unpooled.copiedBuffer(username + ':' + password, CharsetUtil.UTF_8);
98          ByteBuf authzBase64 = Base64.encode(authz, false);
99  
100         authorization = new AsciiString("Basic " + authzBase64.toString(CharsetUtil.US_ASCII));
101 
102         authz.release();
103         authzBase64.release();
104 
105         this.headers = headers;
106         this.ignoreDefaultPortsInConnectHostHeader = ignoreDefaultPortsInConnectHostHeader;
107     }
108 
109     @Override
110     public String protocol() {
111         return PROTOCOL;
112     }
113 
114     @Override
115     public String authScheme() {
116         return authorization != null? AUTH_BASIC : AUTH_NONE;
117     }
118 
119     public String username() {
120         return username;
121     }
122 
123     public String password() {
124         return password;
125     }
126 
127     @Override
128     protected void addCodec(ChannelHandlerContext ctx) throws Exception {
129         ChannelPipeline p = ctx.pipeline();
130         String name = ctx.name();
131         p.addBefore(name, null, codec);
132     }
133 
134     @Override
135     protected void removeEncoder(ChannelHandlerContext ctx) throws Exception {
136         codec.removeOutboundHandler();
137     }
138 
139     @Override
140     protected void removeDecoder(ChannelHandlerContext ctx) throws Exception {
141         codec.removeInboundHandler();
142     }
143 
144     @Override
145     protected Object newInitialMessage(ChannelHandlerContext ctx) throws Exception {
146         InetSocketAddress raddr = destinationAddress();
147 
148         String hostString = HttpUtil.formatHostnameForHttp(raddr);
149         int port = raddr.getPort();
150         String url = hostString + ":" + port;
151         String hostHeader = (ignoreDefaultPortsInConnectHostHeader && (port == 80 || port == 443)) ?
152                 hostString :
153                 url;
154 
155         FullHttpRequest req = new DefaultFullHttpRequest(
156                 HttpVersion.HTTP_1_1, HttpMethod.CONNECT,
157                 url,
158                 Unpooled.EMPTY_BUFFER, false);
159 
160         req.headers().set(HttpHeaderNames.HOST, hostHeader);
161 
162         if (authorization != null) {
163             req.headers().set(HttpHeaderNames.PROXY_AUTHORIZATION, authorization);
164         }
165 
166         if (headers != null) {
167             req.headers().add(headers);
168         }
169 
170         return req;
171     }
172 
173     @Override
174     protected boolean handleResponse(ChannelHandlerContext ctx, Object response) throws Exception {
175         if (response instanceof HttpResponse) {
176             if (status != null) {
177                 throw new ProxyConnectException(exceptionMessage("too many responses"));
178             }
179             status = ((HttpResponse) response).status();
180         }
181 
182         boolean finished = response instanceof LastHttpContent;
183         if (finished) {
184             if (status == null) {
185                 throw new ProxyConnectException(exceptionMessage("missing response"));
186             }
187             if (status.code() != 200) {
188                 throw new ProxyConnectException(exceptionMessage("status: " + status));
189             }
190         }
191 
192         return finished;
193     }
194 }