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.HttpVersion;
33  import io.netty.handler.codec.http.LastHttpContent;
34  import io.netty.util.AsciiString;
35  import io.netty.util.CharsetUtil;
36  import io.netty.util.NetUtil;
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 HttpResponseStatus status;
51      private HttpHeaders headers;
52  
53      public HttpProxyHandler(SocketAddress proxyAddress) {
54          this(proxyAddress, null);
55      }
56  
57      public HttpProxyHandler(SocketAddress proxyAddress, HttpHeaders headers) {
58          super(proxyAddress);
59          username = null;
60          password = null;
61          authorization = null;
62          this.headers = headers;
63      }
64  
65      public HttpProxyHandler(SocketAddress proxyAddress, String username, String password) {
66          this(proxyAddress, username, password, null);
67      }
68  
69      public HttpProxyHandler(SocketAddress proxyAddress, String username, String password,
70                              HttpHeaders headers) {
71          super(proxyAddress);
72          if (username == null) {
73              throw new NullPointerException("username");
74          }
75          if (password == null) {
76              throw new NullPointerException("password");
77          }
78          this.username = username;
79          this.password = password;
80  
81          ByteBuf authz = Unpooled.copiedBuffer(username + ':' + password, CharsetUtil.UTF_8);
82          ByteBuf authzBase64 = Base64.encode(authz, false);
83  
84          authorization = new AsciiString("Basic " + authzBase64.toString(CharsetUtil.US_ASCII));
85  
86          authz.release();
87          authzBase64.release();
88  
89          this.headers = headers;
90      }
91  
92      @Override
93      public String protocol() {
94          return PROTOCOL;
95      }
96  
97      @Override
98      public String authScheme() {
99          return authorization != null? AUTH_BASIC : AUTH_NONE;
100     }
101 
102     public String username() {
103         return username;
104     }
105 
106     public String password() {
107         return password;
108     }
109 
110     @Override
111     protected void addCodec(ChannelHandlerContext ctx) throws Exception {
112         ChannelPipeline p = ctx.pipeline();
113         String name = ctx.name();
114         p.addBefore(name, null, codec);
115     }
116 
117     @Override
118     protected void removeEncoder(ChannelHandlerContext ctx) throws Exception {
119         codec.removeOutboundHandler();
120     }
121 
122     @Override
123     protected void removeDecoder(ChannelHandlerContext ctx) throws Exception {
124         codec.removeInboundHandler();
125     }
126 
127     @Override
128     protected Object newInitialMessage(ChannelHandlerContext ctx) throws Exception {
129         InetSocketAddress raddr = destinationAddress();
130         final String host = NetUtil.toSocketAddressString(raddr);
131         FullHttpRequest req = new DefaultFullHttpRequest(
132                 HttpVersion.HTTP_1_1, HttpMethod.CONNECT,
133                 host,
134                 Unpooled.EMPTY_BUFFER, false);
135 
136         req.headers().set(HttpHeaderNames.HOST, host);
137 
138         if (authorization != null) {
139             req.headers().set(HttpHeaderNames.PROXY_AUTHORIZATION, authorization);
140         }
141 
142         if (headers != null) {
143             req.headers().add(headers);
144         }
145 
146         return req;
147     }
148 
149     @Override
150     protected boolean handleResponse(ChannelHandlerContext ctx, Object response) throws Exception {
151         if (response instanceof HttpResponse) {
152             if (status != null) {
153                 throw new ProxyConnectException(exceptionMessage("too many responses"));
154             }
155             status = ((HttpResponse) response).status();
156         }
157 
158         boolean finished = response instanceof LastHttpContent;
159         if (finished) {
160             if (status == null) {
161                 throw new ProxyConnectException(exceptionMessage("missing response"));
162             }
163             if (status.code() != 200) {
164                 throw new ProxyConnectException(exceptionMessage("status: " + status));
165             }
166         }
167 
168         return finished;
169     }
170 }