001/* 002 * Licensed to the Apache Software Foundation (ASF) under one 003 * or more contributor license agreements. See the NOTICE file 004 * distributed with this work for additional information 005 * regarding copyright ownership. The ASF licenses this file 006 * to you under the Apache License, Version 2.0 (the 007 * "License"); you may not use this file except in compliance 008 * with the License. You may obtain a copy of the License at 009 * 010 * http://www.apache.org/licenses/LICENSE-2.0 011 * 012 * Unless required by applicable law or agreed to in writing, 013 * software distributed under the License is distributed on an 014 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 015 * KIND, either express or implied. See the License for the 016 * specific language governing permissions and limitations 017 * under the License. 018 * 019 */ 020package org.apache.mina.proxy; 021 022import java.net.InetSocketAddress; 023import java.net.SocketAddress; 024import java.util.concurrent.Executor; 025 026import org.apache.mina.core.buffer.IoBuffer; 027import org.apache.mina.core.file.FileRegion; 028import org.apache.mina.core.filterchain.IoFilter; 029import org.apache.mina.core.future.ConnectFuture; 030import org.apache.mina.core.future.DefaultConnectFuture; 031import org.apache.mina.core.service.AbstractIoConnector; 032import org.apache.mina.core.service.DefaultTransportMetadata; 033import org.apache.mina.core.service.IoHandler; 034import org.apache.mina.core.service.TransportMetadata; 035import org.apache.mina.core.session.IoSession; 036import org.apache.mina.core.session.IoSessionConfig; 037import org.apache.mina.core.session.IoSessionInitializer; 038import org.apache.mina.proxy.filter.ProxyFilter; 039import org.apache.mina.proxy.handlers.socks.SocksProxyRequest; 040import org.apache.mina.proxy.session.ProxyIoSession; 041import org.apache.mina.proxy.session.ProxyIoSessionInitializer; 042import org.apache.mina.transport.socket.DefaultSocketSessionConfig; 043import org.apache.mina.transport.socket.SocketConnector; 044import org.apache.mina.transport.socket.SocketSessionConfig; 045 046/** 047 * ProxyConnector.java - Decorator for {@link SocketConnector} to provide proxy 048 * support, as suggested by MINA list discussions. 049 * <p> 050 * Operates by intercepting connect requests and replacing the endpoint address 051 * with the proxy address, then adding a {@link ProxyFilter} as the first 052 * {@link IoFilter} which performs any necessary handshaking with the proxy 053 * before allowing data to flow normally. During the handshake, any outgoing 054 * write requests are buffered. 055 * 056 * @see <a 057 * href="http://www.nabble.com/Meta-Transport%3A-an-idea-on-implementing-reconnection-and-proxy-td12969001.html">Proxy 058 * reconnection</a> 059 * @see <a href="http://issues.apache.org/jira/browse/DIRMINA-415">Proxy 060 * support</a> 061 * 062 * @author <a href="http://mina.apache.org">Apache MINA Project</a> 063 * @since MINA 2.0.0-M3 064 */ 065public class ProxyConnector extends AbstractIoConnector { 066 private static final TransportMetadata METADATA = new DefaultTransportMetadata("proxy", "proxyconnector", false, 067 true, InetSocketAddress.class, SocketSessionConfig.class, IoBuffer.class, FileRegion.class); 068 069 /** 070 * Wrapped connector to use for outgoing TCP connections. 071 */ 072 private SocketConnector connector = null; 073 074 /** 075 * Proxy filter instance. 076 */ 077 private final ProxyFilter proxyFilter = new ProxyFilter(); 078 079 /** 080 * The {@link ProxyIoSession} in use. 081 */ 082 private ProxyIoSession proxyIoSession; 083 084 /** 085 * This future will notify it's listeners when really connected to the target 086 */ 087 private DefaultConnectFuture future; 088 089 /** 090 * Creates a new proxy connector. 091 */ 092 public ProxyConnector() { 093 super(new DefaultSocketSessionConfig(), null); 094 } 095 096 /** 097 * Creates a new proxy connector. 098 * 099 * @param connector Connector used to establish proxy connections. 100 */ 101 public ProxyConnector(final SocketConnector connector) { 102 this(connector, new DefaultSocketSessionConfig(), null); 103 } 104 105 /** 106 * Creates a new proxy connector. 107 * 108 * @param connector The Connector used to establish proxy connections. 109 * @param config The session confiugarion to use 110 * @param executor The associated executor 111 */ 112 public ProxyConnector(final SocketConnector connector, IoSessionConfig config, Executor executor) { 113 super(config, executor); 114 setConnector(connector); 115 } 116 117 /** 118 * {@inheritDoc} 119 */ 120 public IoSessionConfig getSessionConfig() { 121 return connector.getSessionConfig(); 122 } 123 124 /** 125 * @return the {@link ProxyIoSession} linked with this connector. 126 */ 127 public ProxyIoSession getProxyIoSession() { 128 return proxyIoSession; 129 } 130 131 /** 132 * Sets the proxy session object of this connector. 133 * @param proxyIoSession the configuration of this connector. 134 */ 135 public void setProxyIoSession(ProxyIoSession proxyIoSession) { 136 if (proxyIoSession == null) { 137 throw new IllegalArgumentException("proxySession object cannot be null"); 138 } 139 140 if (proxyIoSession.getProxyAddress() == null) { 141 throw new IllegalArgumentException("proxySession.proxyAddress cannot be null"); 142 } 143 144 proxyIoSession.setConnector(this); 145 setDefaultRemoteAddress(proxyIoSession.getProxyAddress()); 146 this.proxyIoSession = proxyIoSession; 147 } 148 149 /** 150 * Connects to the specified <code>address</code>. If communication starts 151 * successfully, events are fired to the connector's <code>handler</code>. 152 * 153 * @param remoteAddress the remote address to connect to 154 * @param localAddress the local address 155 * @param sessionInitializer the session initializer 156 * @return {@link ConnectFuture} that will tell the result of the connection attempt 157 */ 158 @SuppressWarnings("unchecked") 159 @Override 160 protected ConnectFuture connect0(final SocketAddress remoteAddress, final SocketAddress localAddress, 161 final IoSessionInitializer<? extends ConnectFuture> sessionInitializer) { 162 if (!proxyIoSession.isReconnectionNeeded()) { 163 // First connection 164 IoHandler handler = getHandler(); 165 if (!(handler instanceof AbstractProxyIoHandler)) { 166 throw new IllegalArgumentException("IoHandler must be an instance of AbstractProxyIoHandler"); 167 } 168 169 connector.setHandler(handler); 170 future = new DefaultConnectFuture(); 171 } 172 173 ConnectFuture conFuture = connector.connect(proxyIoSession.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 || proxyIoSession.isReconnectionNeeded()) { 182 return conFuture; 183 } 184 185 return future; 186 } 187 188 /** 189 * Cancels the real connection when reconnection is in use. 190 */ 191 public void cancelConnectFuture() { 192 future.cancel(); 193 } 194 195 /** 196 * Fires the connection event on the real future to notify the client. 197 * 198 * @param session the current session 199 * @return the future holding the connected session 200 */ 201 protected ConnectFuture fireConnected(final IoSession session) { 202 future.setSession(session); 203 return future; 204 } 205 206 /** 207 * @return the {@link SocketConnector} to be used for connections 208 * to the proxy server. 209 */ 210 public final SocketConnector getConnector() { 211 return connector; 212 } 213 214 /** 215 * Sets the {@link SocketConnector} to be used for connections 216 * to the proxy server. 217 * 218 * @param connector the connector to use 219 */ 220 private void setConnector(final SocketConnector connector) { 221 if (connector == null) { 222 throw new IllegalArgumentException("connector cannot be null"); 223 } 224 225 this.connector = connector; 226 String className = ProxyFilter.class.getName(); 227 228 // Removes an old ProxyFilter instance from the chain 229 if (connector.getFilterChain().contains(className)) { 230 connector.getFilterChain().remove(className); 231 } 232 233 // Insert the ProxyFilter as the first filter in the filter chain builder 234 connector.getFilterChain().addFirst(className, proxyFilter); 235 } 236 237 /** 238 * {@inheritDoc} 239 */ 240 @Override 241 protected void dispose0() throws Exception { 242 if (connector != null) { 243 connector.dispose(); 244 } 245 } 246 247 /** 248 * {@inheritDoc} 249 */ 250 public TransportMetadata getTransportMetadata() { 251 return METADATA; 252 } 253}