View Javadoc

1   /*
2    *  Licensed to the Apache Software Foundation (ASF) under one
3    *  or more contributor license agreements.  See the NOTICE file
4    *  distributed with this work for additional information
5    *  regarding copyright ownership.  The ASF licenses this file
6    *  to you under the Apache License, Version 2.0 (the
7    *  "License"); you may not use this file except in compliance
8    *  with the License.  You may obtain a copy of the License at
9    *
10   *    http://www.apache.org/licenses/LICENSE-2.0
11   *
12   *  Unless required by applicable law or agreed to in writing,
13   *  software distributed under the License is distributed on an
14   *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15   *  KIND, either express or implied.  See the License for the
16   *  specific language governing permissions and limitations
17   *  under the License.
18   *
19   */
20  package org.apache.mina.proxy;
21  
22  import java.net.InetSocketAddress;
23  import java.net.SocketAddress;
24  import java.util.concurrent.Executor;
25  
26  import org.apache.mina.core.buffer.IoBuffer;
27  import org.apache.mina.core.file.FileRegion;
28  import org.apache.mina.core.filterchain.IoFilter;
29  import org.apache.mina.core.future.ConnectFuture;
30  import org.apache.mina.core.future.DefaultConnectFuture;
31  import org.apache.mina.core.service.AbstractIoConnector;
32  import org.apache.mina.core.service.DefaultTransportMetadata;
33  import org.apache.mina.core.service.IoHandler;
34  import org.apache.mina.core.service.TransportMetadata;
35  import org.apache.mina.core.session.IoSession;
36  import org.apache.mina.core.session.IoSessionConfig;
37  import org.apache.mina.core.session.IoSessionInitializer;
38  import org.apache.mina.proxy.filter.ProxyFilter;
39  import org.apache.mina.proxy.handlers.socks.SocksProxyRequest;
40  import org.apache.mina.proxy.session.ProxyIoSession;
41  import org.apache.mina.proxy.session.ProxyIoSessionInitializer;
42  import org.apache.mina.transport.socket.DefaultSocketSessionConfig;
43  import org.apache.mina.transport.socket.SocketConnector;
44  import org.apache.mina.transport.socket.SocketSessionConfig;
45  
46  /**
47   * ProxyConnector.java - Decorator for {@link SocketConnector} to provide proxy support, 
48   * as suggested by MINA list discussions.
49   * <p>
50   * Operates by intercepting connect requests and replacing the endpoint address with the 
51   * proxy address, then adding a {@link ProxyFilter} as the first {@link IoFilter} which 
52   * performs any necessary handshaking with the proxy before allowing data to flow 
53   * normally. During the handshake, any outgoing write requests are buffered.
54   * 
55   * @see        http://www.nabble.com/Meta-Transport%3A-an-idea-on-implementing-reconnection-and-proxy-td12969001.html
56   * @see        http://issues.apache.org/jira/browse/DIRMINA-415
57   * 
58   * @author <a href="http://mina.apache.org">Apache MINA Project</a>
59   * @since MINA 2.0.0-M3
60   */
61  public class ProxyConnector extends AbstractIoConnector {
62      private static final TransportMetadata METADATA = new DefaultTransportMetadata(
63              "proxy", "proxyconnector", false, true, InetSocketAddress.class,
64              SocketSessionConfig.class, IoBuffer.class, FileRegion.class);
65  
66      /**
67       * Wrapped connector to use for outgoing TCP connections.
68       */
69      private SocketConnector connector = null;
70  
71      /**
72       * Proxy filter instance.
73       */
74      private final ProxyFilter proxyFilter = new ProxyFilter();
75  
76      /**
77       * The {@link ProxyIoSession} in use.
78       */
79      private ProxyIoSession proxyIoSession;
80  
81      /**
82       * This future will notify it's listeners when really connected to the target
83       */
84      private DefaultConnectFuture future;
85  
86      /**
87       * Creates a new proxy connector.
88       */
89      public ProxyConnector() {
90          super(new DefaultSocketSessionConfig(), null);
91      }
92  
93      /**
94       * Creates a new proxy connector.
95       * 
96       * @param connector Connector used to establish proxy connections.
97       */
98      public ProxyConnector(final SocketConnector connector) {        
99          this(connector, new DefaultSocketSessionConfig(), null);
100     }
101 
102     /**
103      * Creates a new proxy connector. 
104      * @see AbstractIoConnector(IoSessionConfig, Executor).
105      */
106     public ProxyConnector(final SocketConnector connector, IoSessionConfig config, Executor executor) {
107         super(config, executor);
108         setConnector(connector);
109     }    
110     
111     /**
112      * {@inheritDoc}
113      */
114     @Override
115     public IoSessionConfig getSessionConfig() {
116         return connector.getSessionConfig();
117     }
118 
119     /**
120      * Returns the {@link ProxyIoSession} linked with this connector.
121      */
122     public ProxyIoSession getProxyIoSession() {
123         return proxyIoSession;
124     }
125 
126     /**
127      * Sets the proxy session object of this connector.
128      * @param proxyIoSession the configuration of this connector.
129      */
130     public void setProxyIoSession(ProxyIoSession proxyIoSession) {
131         if (proxyIoSession == null) {
132             throw new IllegalArgumentException("proxySession object cannot be null");
133         }
134 
135         if (proxyIoSession.getProxyAddress() == null) {
136             throw new IllegalArgumentException(
137                     "proxySession.proxyAddress cannot be null");
138         }
139 
140         proxyIoSession.setConnector(this);
141         setDefaultRemoteAddress(proxyIoSession.getProxyAddress());
142         this.proxyIoSession = proxyIoSession;
143     }
144 
145     /**
146      * Connects to the specified <code>address</code>.  If communication starts
147      * successfully, events are fired to the connector's <code>handler</code>.
148      * 
149      * @param remoteAddress the remote address to connect to
150      * @param localAddress the local address
151      * @param sessionInitializer the session initializer
152      * @return {@link ConnectFuture} that will tell the result of the connection attempt
153      */
154     @SuppressWarnings("unchecked")
155     @Override
156     protected ConnectFuture connect0(
157             final SocketAddress remoteAddress,
158             final SocketAddress localAddress,
159             final IoSessionInitializer<? extends ConnectFuture> sessionInitializer) {
160         if (!proxyIoSession.isReconnectionNeeded()) {
161             // First connection
162             IoHandler handler = getHandler();
163             if (!(handler instanceof AbstractProxyIoHandler)) {
164                 throw new IllegalArgumentException(
165                         "IoHandler must be an instance of AbstractProxyIoHandler");
166             }
167 
168             connector.setHandler(handler);
169             future = new DefaultConnectFuture();
170         }
171 
172         ConnectFuture conFuture = connector.connect(proxyIoSession
173                 .getProxyAddress(), new ProxyIoSessionInitializer(
174                 sessionInitializer, proxyIoSession));
175 
176         // If proxy does not use reconnection like socks the connector's 
177         // future is returned. If we're in the middle of a reconnection
178         // then we send back the connector's future which is only used
179         // internally while <code>future</code> will be used to notify
180         // the user of the connection state.
181         if (proxyIoSession.getRequest() instanceof SocksProxyRequest
182                 || proxyIoSession.isReconnectionNeeded()) {
183             return conFuture;
184         }
185 
186         return future;
187     }
188 
189     /**
190      * Cancels the real connection when reconnection is in use.
191      */
192     public void cancelConnectFuture() {
193         future.cancel();
194     }
195 
196     /**
197      * Fires the connection event on the real future to notify the client.
198      * 
199      * @param session the current session
200      * @return the future holding the connected session
201      */
202     protected ConnectFuture fireConnected(final IoSession session) {
203         future.setSession(session);
204         return future;
205     }
206 
207     /**
208      * Get the {@link SocketConnector} to be used for connections
209      * to the proxy server.
210      */
211     public final SocketConnector getConnector() {
212         return connector;
213     }
214 
215     /**
216      * Sets the {@link SocketConnector} to be used for connections
217      * to the proxy server.
218      * 
219      * @param connector the connector to use
220      */
221     private final void setConnector(final SocketConnector connector) {
222         if (connector == null) {
223             throw new IllegalArgumentException("connector cannot be null");
224         }
225 
226         this.connector = connector;
227         String className = ProxyFilter.class.getName();
228 
229         // Removes an old ProxyFilter instance from the chain
230         if (connector.getFilterChain().contains(className)) {
231             connector.getFilterChain().remove(className);
232         }
233 
234         // Insert the ProxyFilter as the first filter in the filter chain builder        
235         connector.getFilterChain().addFirst(className, proxyFilter);
236     }
237 
238     /**
239      * {@inheritDoc}
240      */
241     @Override
242     protected void dispose0() throws Exception {
243         if (connector != null) {
244             connector.dispose();
245         }
246     }
247 
248     /**
249      * {@inheritDoc}
250      */
251     public TransportMetadata getTransportMetadata() {
252         return METADATA;
253     }
254 }