/* * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF licenses this file * to you under the Apache License, Version 2.0 (the * "License"); you may not use this file except in compliance * with the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package net.jini.jeri.http; import com.sun.jini.jeri.internal.http.ConnectionTimer; import com.sun.jini.jeri.internal.http.HttpClientConnection; import com.sun.jini.jeri.internal.http.HttpClientManager; import com.sun.jini.jeri.internal.http.HttpClientSocketFactory; import com.sun.jini.jeri.internal.http.HttpSettings; import com.sun.jini.jeri.internal.runtime.Util; import com.sun.jini.logging.Levels; import com.sun.jini.logging.LogUtil; import java.io.IOException; import java.io.InputStream; import java.io.InvalidObjectException; import java.io.ObjectInputStream; import java.io.OutputStream; import java.io.Serializable; import java.lang.ref.Reference; import java.lang.ref.SoftReference; import java.net.InetAddress; import java.net.InetSocketAddress; import java.net.Socket; import java.net.SocketAddress; import java.net.SocketException; import java.net.SocketTimeoutException; import java.net.UnknownHostException; import java.security.AccessController; import java.security.PrivilegedAction; import java.security.PrivilegedActionException; import java.security.PrivilegedExceptionAction; import java.util.Collection; import java.util.HashSet; import java.util.Iterator; import java.util.Map; import java.util.NoSuchElementException; import java.util.Set; import java.util.WeakHashMap; import java.util.logging.Level; import java.util.logging.Logger; import javax.net.SocketFactory; import net.jini.core.constraint.InvocationConstraints; import net.jini.jeri.Endpoint; import net.jini.jeri.OutboundRequest; import net.jini.jeri.OutboundRequestIterator; import net.jini.security.proxytrust.TrustEquivalence; /** * An implementation of the {@link Endpoint} abstraction that uses HTTP * messages sent over TCP sockets (instances of {@link Socket}) for the * underlying communication mechanism. * * <p><code>HttpEndpoint</code> instances contain a host name and a * TCP port number, as well as an optional {@link SocketFactory} for * customizing the type of <code>Socket</code> to use. The host name * and port number are used as the remote address to connect to when * making socket connections. Note that constructing an * <code>HttpEndpoint</code> with a <code>SocketFactory</code> * instance that produces SSL sockets does not result in an endpoint * that is fully HTTPS capable. * * <p><code>HttpEndpoint</code> instances map outgoing requests to HTTP * request/response messages; when possible, underlying TCP connections are * reused for multiple non-overlapping outgoing requests. Outbound request * data is sent as the <code>entity-body</code> of an HTTP POST request; * inbound response data is received as the <code>entity-body</code> of the * corresponding HTTP response message. For information on HTTP, refer to <a * href="http://www.ietf.org/rfc/rfc2616.txt">RFC 2616</a>. * * <p><code>HttpEndpoint</code> can be configured via system properties to send * HTTP messages through an intermediary HTTP proxy server. It also supports * basic and digest HTTP authentication, specified in <a * href="http://www.ietf.org/rfc/rfc2617.txt">RFC 2617</a>. The mechanisms * involved in configuring each of these features are the same as those used by * {@link java.net.HttpURLConnection}; for details, see the * {@link net.jini.jeri.http} package documentation. * * <p>A <code>SocketFactory</code> used with an * <code>HttpEndpoint</code> should be serializable and must implement * {@link Object#equals Object.equals} to obey the guidelines that are * specified for <code>equals</code> methods of {@link Endpoint} * instances. * * @author Sun Microsystems, Inc. * @see HttpServerEndpoint * @since 2.0 **/ public final class HttpEndpoint implements Endpoint, TrustEquivalence, Serializable { private static final long serialVersionUID = -7094180943307123931L; /** set of canonical instances */ private static final Map internTable = new WeakHashMap(); /** HTTP client manager */ private static final HttpClientManager clientManager; /** idle connection timer */ private static final ConnectionTimer connTimer; static { HttpSettings hs = getHttpSettings(); clientManager = new HttpClientManager(hs.getResponseAckTimeout()); connTimer = new ConnectionTimer(hs.getConnectionTimeout()); } /** client transport logger */ private static final Logger logger = Logger.getLogger("net.jini.jeri.http.client"); /** * The host that this <code>HttpEndpoint</code> connects to. * * @serial **/ private final String host; /** * The TCP port that this <code>HttpEndpoint</code> connects to. * * @serial **/ private final int port; /** * The socket factory that this <code>HttpEndpoint</code> uses to * create {@link Socket} objects. * * @serial **/ private final SocketFactory sf; /** idle connection cache */ private transient Set connections; /** current proxy host, or empty string if not proxied */ private transient String proxyHost; /** current proxy port, or -1 if not proxied */ private transient int proxyPort; /** true if using persistent connections */ private transient boolean persist; /** Time at which the server endpoint was last pinged. */ private transient long timeLastVerified; /** * Returns an <code>HttpEndpoint</code> instance for the given host name * and TCP port number. Note that if HTTP proxying is in effect, then an * explicit host name or IP address (i.e., not "localhost") must be * provided, or else the returned <code>HttpEndpoint</code> will be unable * to properly send requests through the proxy. * * <p>The {@link SocketFactory} contained in the returned * <code>HttpEndpoint</code> will be <code>null</code>, indicating * that this endpoint will create {@link Socket} objects directly. * * @param host the host for the endpoint to connect to * * @param port the TCP port on the given host for the endpoint to * connect to * * @return an <code>HttpEndpoint</code> instance * * @throws IllegalArgumentException if the port number is out of * the range <code>1</code> to <code>65535</code> (inclusive) * * @throws NullPointerException if <code>host</code> is * <code>null</code> **/ public static HttpEndpoint getInstance(String host, int port) { return intern(new HttpEndpoint(host, port, null)); } /** * Returns an <code>HttpEndpoint</code> instance for the given * host name and TCP port number that contains the given {@link * SocketFactory}. Note that if HTTP proxying is in effect, then * an explicit host name or IP address (i.e., not "localhost") * must be provided, or else the returned * <code>HttpEndpoint</code> will be unable to properly send * requests through the proxy. * * <p>If the socket factory argument is <code>null</code>, then * this endpoint will create {@link Socket} objects directly. * * @param host the host for the endpoint to connect to * * @param port the TCP port on the given host for the endpoint to * connect to * * @param sf the <code>SocketFactory</code> to use for this * <code>HttpEndpoint</code>, or <code>null</code> * * @return an <code>HttpEndpoint</code> instance * * @throws IllegalArgumentException if the port number is out of * the range <code>1</code> to <code>65535</code> (inclusive) * * @throws NullPointerException if <code>host</code> is * <code>null</code> **/ public static HttpEndpoint getInstance(String host, int port, SocketFactory sf) { return intern(new HttpEndpoint(host, port, sf)); } /** * Returns canonical instance equivalent to given instance. **/ private static HttpEndpoint intern(HttpEndpoint endpoint) { synchronized (internTable) { Reference ref = (SoftReference) internTable.get(endpoint); if (ref != null) { HttpEndpoint canonical = (HttpEndpoint) ref.get(); if (canonical != null) { return canonical; } } endpoint.init(); internTable.put(endpoint, new SoftReference(endpoint)); return endpoint; } } /** * Constructs a new (not fully initialized) instance. **/ private HttpEndpoint(String host, int port, SocketFactory sf) { if (host == null) { throw new NullPointerException(); } if (port < 1 || port > 0xFFFF) { throw new IllegalArgumentException( "port number out of range: " + port); } this.host = host; this.port = port; this.sf = sf; } /* * [This is not a doc comment to prevent its appearance in * HttpEndpoint's serialized form specification.] * * Resolves deserialized instance to equivalent canonical instance. */ private Object readResolve() { return intern(this); } /** * Initializes new instance obtained either from private constructor or * deserialization. **/ private void init() { connections = new HashSet(5); proxyHost = ""; proxyPort = -1; } /** * Returns the host that this <code>HttpEndpoint</code> connects to. * * @return the host that this endpoint connects to **/ public String getHost() { return host; } /** * Returns the TCP port that this <code>HttpEndpoint</code> connects to. * * @return the TCP port that this endpoint connects to **/ public int getPort() { return port; } /** * Returns the {@link SocketFactory} that this endpoint uses to * create {@link Socket} objects. * * @return the socket factory that this endpoint uses to create * sockets, or <code>null</code> if this endpoint creates sockets * directly **/ public SocketFactory getSocketFactory() { return sf; } /** * {@inheritDoc} * * <p>The returned <code>OutboundRequestIterator</code>'s {@link * OutboundRequestIterator#next next} method behaves as follows: * * <blockquote> * * Initiates an attempt to communicate the request to this remote * endpoint. * * <p>When the implementation of this method needs to create a new * <code>Socket</code>, it will do so by invoking one of the * <code>createSocket</code> methods on the * <code>SocketFactory</code> of this <code>HttpEndpoint</code> * (which produced this iterator) if non-<code>null</code>, or it * will create a <code>Socket</code> directly otherwise. * * <p>When the implementation needs to connect a * <code>Socket</code>, if the host name to connect to (if an HTTP * proxy is to be used for the communication, the proxy's host * name; otherwise, this <code>HttpEndpoint</code>'s host name) * resolves to multiple addresses (according to {@link * InetAddress#getAllByName InetAddress.getAllByName}), it * attempts to connect to the first resolved address; if that * attempt fails with an <code>IOException</code> or (as is * possible in the case that an HTTP proxy is not to be used) a * <code>SecurityException</code>, it then attempts to connect to * the next address; and this iteration continues as long as there * is another resolved address and the attempt to connect to the * previous address fails with an <code>IOException</code> or a * <code>SecurityException</code>. If the host name resolves to * just one address, the implementation makes one attempt to * connect to that address. If the host name does not resolve to * any addresses (<code>InetAddress.getAllByName</code> would * throw an <code>UnknownHostException</code>), the implementation * still makes an attempt to connect the <code>Socket</code> to * that host name, which could result in an * <code>UnknownHostException</code>. If the final connection * attempt fails with an <code>IOException</code> or a * <code>SecurityException</code>, then if any connection attempt * failed with an <code>IOException</code>, this method throws an * <code>IOException</code>, and otherwise (if all connection * attempts failed with a <code>SecurityException</code>), this * method throws a <code>SecurityException</code>. * * <p>If there is a security manager and an HTTP proxy is to be * used for the communication, the security manager's {@link * SecurityManager#checkConnect(String,int) checkConnect} method * is invoked with this <code>HttpEndpoint</code>'s host and port; * if this results in a <code>SecurityException</code>, this * method throws that exception. * * <p>If there is a security manager and an HTTP proxy is not to * be used for the communication: * * <ul> * * <li>If a new connection is to be created, the security * manager's {@link SecurityManager#checkConnect(String,int) * checkConnect} method is invoked with this * <code>HttpEndpoint</code>'s host and <code>-1</code> for the * port; if this results in a <code>SecurityException</code>, this * method throws that exception. <code>checkConnect</code> is * also invoked for each connection attempt, with the remote IP * address (or the host name, if it could not be resolved) and * port to connect to; this could result in a * <code>SecurityException</code> for that attempt. (Note that * the implementation may carry out these security checks * indirectly, such as through invocations of * <code>InetAddress.getAllByName</code> or <code>Socket</code>'s * constructors or <code>connect</code> method.) * * <li><p>In order to reuse an existing connection for the * communication, the current security context must have all of * the permissions that would be necessary if the connection were * being created. Specifically, it must be possible to invoke * <code>checkConnect</code> in the current security context with * this <code>HttpEndpoint</code>'s host and <code>-1</code> for * the port without resulting in a <code>SecurityException</code>, * and it also must be possible to invoke * <code>checkConnect</code> with the remote IP address and port * of the <code>Socket</code> without resulting in a * <code>SecurityException</code> (if the remote socket address is * unresolved, its host name is used instead). If no existing * connection satisfies these requirements, then this method must * behave as if there are no existing connections. * * </ul> * * <p>Throws {@link NoSuchElementException} if this iterator does * not support making another attempt to communicate the request * (that is, if <code>hasNext</code> would return * <code>false</code>). * * <p>Throws {@link IOException} if an I/O exception occurs while * performing this operation, such as if a connection attempt * timed out or was refused. * * <p>Throws {@link SecurityException} if there is a security * manager and an invocation of its <code>checkConnect</code> * method fails. * * </blockquote> * * @throws NullPointerException {@inheritDoc} **/ public OutboundRequestIterator newRequest(final InvocationConstraints constraints) { if (constraints == null) { throw new NullPointerException(); } return new OutboundRequestIterator() { private boolean nextCalled = false; private OutboundRequest currentRequest; public OutboundRequest next() throws IOException { if (!hasNext()) { throw new NoSuchElementException(); } nextCalled = true; currentRequest = nextRequest(constraints); return currentRequest; } public boolean hasNext() { // NYI: determine if HTTP failure suggests retry return !nextCalled; } }; } private OutboundRequest nextRequest(InvocationConstraints constraints) throws IOException { final Constraints.Distilled distilled = Constraints.distill(constraints, false); final OutboundRequest request = nextRequest(distilled); // must wrap to provide getUnfulfilledConstraints implementation return new OutboundRequest() { public void populateContext(Collection context) { request.populateContext(context); } public InvocationConstraints getUnfulfilledConstraints() { return distilled.getUnfulfilledConstraints(); } public OutputStream getRequestOutputStream() { return request.getRequestOutputStream(); } public InputStream getResponseInputStream() { return request.getResponseInputStream(); } public boolean getDeliveryStatus() { return request.getDeliveryStatus(); } public void abort() { request.abort(); } }; } /** * Describes an action to be performed on a Connection. */ private interface ConnectionAction { Object run(Connection conn) throws IOException; } /** * Find an existing connection and perform the specified action on * the connection. If there is no suitable existing connection, then * create a connection meeting the specified constraints and perform * the specified action on the connection. */ private Object connectionAction(final Constraints.Distilled distilled, final String phost, final int pport, final boolean ppersist, ConnectionAction action) throws IOException { if (logger.isLoggable(Level.FINEST)) { logger.log(Level.FINEST, "about to perform nextRequest on {0}", this ); } boolean usingProxy = (phost.length() != 0); Connection conn; synchronized (connections) { if (!(proxyHost.equals(phost) && proxyPort == pport && persist == ppersist)) { proxyHost = phost; proxyPort = pport; persist = ppersist; shedConnections(); } boolean checkedResolvePermission = false; for (Iterator i = connections.iterator(); i.hasNext();) { conn = (Connection) i.next(); if (!usingProxy) { if (!checkedResolvePermission) { try { checkResolvePermission(); } catch (SecurityException e) { if (logger.isLoggable(Levels.FAILED)) { LogUtil.logThrow(logger, Levels.FAILED, HttpEndpoint.class, "nextRequest", "exception resolving host {0}", new Object[] { host }, e); } throw e; } checkedResolvePermission = true; } try { conn.checkConnectPermission(); } catch (SecurityException e) { if (logger.isLoggable(Levels.HANDLED)) { LogUtil.logThrow(logger, Levels.HANDLED, HttpEndpoint.class, "nextRequest", "access to reuse connection {0} denied", new Object[] { conn.getSocket() }, e); } continue; } } i.remove(); if (connTimer.cancelTimeout(conn)) { try { Object obj = action.run(conn); if (logger.isLoggable(Level.FINE)) { logger.log(Level.FINE, "nextRequest on existing connection {0}", conn.getSocket()); } return obj; } catch (IOException ex) { if (logger.isLoggable(Levels.HANDLED)) { LogUtil.logThrow(logger, Levels.HANDLED, HttpEndpoint.class, "nextRequest", "nextRequest on existing " + "connection {0} throws", new Object[] { this }, ex); } } } conn.shutdown(true); } } try { if (!usingProxy) { conn = new Connection(host, port, distilled); } else { conn = (Connection) AccessController.doPrivileged( new PrivilegedExceptionAction() { public Object run() throws IOException { return new Connection(host, port, phost, pport, ppersist, distilled); } }); } } catch (PrivilegedActionException e) { throw (IOException) e.getCause(); } try { Object obj = action.run(conn); if (logger.isLoggable(Level.FINEST)) { logger.log(Level.FINEST, "nextRequest on new connection {0}", this); } return obj; } catch (IOException ex) { if (logger.isLoggable(Levels.FAILED)) { LogUtil.logThrow(logger, Levels.FAILED, HttpEndpoint.class, "nextRequest", "nextRequest on new " + "connection {0} throws IOException", new Object[] { this }, ex); } throw ex; } } private OutboundRequest nextRequest(final Constraints.Distilled distilled) throws IOException { HttpSettings settings = getHttpSettings(); String phost = settings.getProxyHost(host); boolean usingProxy = (phost.length() != 0); int pport; boolean ppersist; // If looking for a connection through an HTTP proxy, and if the // pingProxyConnectionTimeout has passed, then ping the server // endpoint to verify that the endpoint is alive and reachable. // This reduces the likelihood that a server endpoint having // terminated or having become unreachable won't be noticed // until after some or all of the data destined for the server // has already been sent to the proxy. if (!usingProxy) { pport = -1; ppersist = true; } else { pport = settings.getProxyPort(); ppersist = !settings.getDisableProxyPersistentConnections(); SecurityManager sm = System.getSecurityManager(); if (sm != null) { sm.checkConnect(host, port); } long now = System.currentTimeMillis(); if (settings.getPingProxyConnections() && now - timeLastVerified > settings.getPingProxyConnectionTimeout()) { Object obj = connectionAction(distilled, phost, pport, ppersist, new ConnectionAction() { public Object run(Connection conn) throws IOException { return Boolean.valueOf(conn.ping()); } }); if (!((Boolean) obj).booleanValue()) { throw new IOException("HTTP ping via proxy failed."); } timeLastVerified = System.currentTimeMillis(); } } // Find a suitable existing connection or create a new // connection, and set up a request on that connection. Object obj = connectionAction(distilled, phost, pport, ppersist, new ConnectionAction() { public Object run(Connection conn) throws IOException { return conn.newRequest(); } }); return (OutboundRequest) obj; } /** * Closes all idle connections cached by this HTTP endpoint. **/ private void shedConnections() { synchronized (connections) { Object[] conns = connections.toArray(); connections.clear(); for (int i = 0; i < conns.length; i++) { ((Connection) conns[i]).shutdown(true); } } } private void checkResolvePermission() { SecurityManager sm = System.getSecurityManager(); if (sm != null) { sm.checkConnect(host, -1); } } /** * Returns the hash code value for this <code>HttpEndpoint</code>. * * @return the hash code value for this <code>HttpEndpoint</code> **/ public int hashCode() { return host.hashCode() ^ port ^ (sf != null ? sf.hashCode() : 0); } /** * Compares the specified object with this * <code>HttpEndpoint</code> for equality. * * <p>This method returns <code>true</code> if and only if * * <ul> * * <li>the specified object is also an <code>HttpEndpoint</code>, * * <li>the host and port in the specified object are equal to the * host and port in this object, and * * <li>either this object and the specified object both have no * <code>SocketFactory</code> or the <code>SocketFactory</code> in * the specified object has the same class and is equal to the one * in this object. * * </ul> * * @param obj the object to compare with * * @return <code>true</code> if <code>obj</code> is equivalent to * this object; <code>false</code> otherwise **/ public boolean equals(Object obj) { if (obj == this) { return true; } else if (!(obj instanceof HttpEndpoint)) { return false; } HttpEndpoint other = (HttpEndpoint) obj; return host.equals(other.host) && port == other.port && Util.sameClassAndEquals(sf, other.sf); } /** * Returns <code>true</code> if the specified object (which is not * yet known to be trusted) is equivalent in trust, content, and * function to this known trusted object, and <code>false</code> * otherwise. * * <p>This method returns <code>true</code> if and only if * * <ul> * * <li>the specified object is also an <code>HttpEndpoint</code>, * * <li>the host and port in the specified object are equal to the * host and port in this object, and * * <li>either this object and the specified object both have no * <code>SocketFactory</code> or the <code>SocketFactory</code> in * the specified object has the same class and is equal to the one * in this object. * * </ul> **/ public boolean checkTrustEquivalence(Object obj) { if (obj == this) { return true; } else if (!(obj instanceof HttpEndpoint)) { return false; } HttpEndpoint other = (HttpEndpoint) obj; return host.equals(other.host) && port == other.port && Util.sameClassAndEquals(sf, other.sf); } /** * Returns a string representation of this * <code>HttpEndpoint</code>. * * @return a string representation of this * <code>HttpEndpoint</code> **/ public String toString() { return "HttpEndpoint[" + host + ":" + port + (sf != null ? "," + sf : "") + "]"; } /** * @throws InvalidObjectException if the host name is * <code>null</code> or if the port number is out of the range * <code>1</code> to <code>65535</code> (inclusive) **/ private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException { in.defaultReadObject(); if (host == null) { throw new InvalidObjectException("null host"); } if (port < 1 || port > 0xFFFF) { throw new InvalidObjectException( "port number out of range: " + port); } } /** * Returns current HTTP system property settings. **/ static HttpSettings getHttpSettings() { return (HttpSettings) AccessController.doPrivileged( new PrivilegedAction() { public Object run() { return HttpSettings.getHttpSettings(false); } }); } /** * SocketFactory -> HttpClientSocketFactory adapter. **/ private static final class SocketFactoryAdapter implements HttpClientSocketFactory { private final SocketFactory sf; private final Constraints.Distilled distilled; SocketFactoryAdapter(SocketFactory sf, Constraints.Distilled distilled) { this.sf = sf; this.distilled = distilled; } public Socket createSocket(String host, int port) throws IOException { Socket socket = connectToHost(host, port, distilled); if (logger.isLoggable(Level.FINE)) { logger.log(Level.FINE, "connected socket {0}", socket); } setSocketOptions(socket); return socket; } /** * Returns a socket connected to the specified host and port, * according to the specified constraints. If the host name * resolves to multiple addresses, attempts to connect to each * of them in order until one succeeds. **/ private Socket connectToHost(String host, int port, Constraints.Distilled distilled) throws IOException { InetAddress[] addresses; try { addresses = InetAddress.getAllByName(host); } catch (UnknownHostException uhe) { try { /* * Creating the InetSocketAddress attempts to * resolve the host again; in J2SE 5.0, there is a * factory method for creating an unresolved * InetSocketAddress directly. */ return connectToSocketAddress( new InetSocketAddress(host, port), distilled); } catch (IOException e) { if (logger.isLoggable(Levels.FAILED)) { LogUtil.logThrow(logger, Levels.FAILED, SocketFactoryAdapter.class, "connectToHost", "exception connecting to unresolved host {0}", new Object[] { host + ":" + port }, e); } throw e; } catch (SecurityException e) { if (logger.isLoggable(Levels.FAILED)) { LogUtil.logThrow(logger, Levels.FAILED, SocketFactoryAdapter.class, "connectToHost", "exception connecting to unresolved host {0}", new Object[] { host + ":" + port }, e); } throw e; } } catch (SecurityException e) { if (logger.isLoggable(Levels.FAILED)) { LogUtil.logThrow(logger, Levels.FAILED, SocketFactoryAdapter.class, "connectToHost", "exception resolving host {0}", new Object[] { host }, e); } throw e; } IOException lastIOException = null; SecurityException lastSecurityException = null; for (int i = 0; i < addresses.length; i++) { SocketAddress socketAddress = new InetSocketAddress(addresses[i], port); try { return connectToSocketAddress(socketAddress, distilled); } catch (IOException e) { if (logger.isLoggable(Levels.HANDLED)) { LogUtil.logThrow(logger, Levels.HANDLED, SocketFactoryAdapter.class, "connectToHost", "exception connecting to {0}", new Object[] { socketAddress }, e); } lastIOException = e; if (e instanceof SocketTimeoutException) { break; } } catch (SecurityException e) { if (logger.isLoggable(Levels.HANDLED)) { LogUtil.logThrow(logger, Levels.HANDLED, SocketFactoryAdapter.class, "connectToHost", "exception connecting to {0}", new Object[] { socketAddress }, e); } lastSecurityException = e; } } if (lastIOException != null) { if (logger.isLoggable(Levels.FAILED)) { LogUtil.logThrow(logger, Levels.FAILED, SocketFactoryAdapter.class, "connectToHost", "exception connecting to {0}", new Object[] { host + ":" + port }, lastIOException); } throw lastIOException; } assert lastSecurityException != null; if (logger.isLoggable(Levels.FAILED)) { LogUtil.logThrow(logger, Levels.FAILED, SocketFactoryAdapter.class, "connectToHost", "exception connecting to {0}", new Object[] { host + ":" + port }, lastSecurityException); } throw lastSecurityException; } /** * Returns a socket connected to the specified address, with a * timeout governed by the specified constraints. **/ private Socket connectToSocketAddress(SocketAddress socketAddress, Constraints.Distilled distilled) throws IOException { int timeout; if (distilled.hasConnectDeadline()) { long now = System.currentTimeMillis(); long deadline = distilled.getConnectDeadline(); if (deadline <= now) { throw new SocketTimeoutException( "deadline past before connect attempt"); } assert now > 0; // if not, we could overflow long delta = deadline - now; // assume that no socket connect will last over 24 days timeout = (delta > Integer.MAX_VALUE ? 0 : (int) delta); } else { timeout = 0; } Socket socket = newSocket(); boolean ok = false; try { socket.connect(socketAddress, timeout); ok = true; return socket; } finally { if (!ok) { try { socket.close(); } catch (IOException e) { } } } } /** * Returns a new unconnnected socket, using this endpoint's * socket factory if non-null. **/ private Socket newSocket() throws IOException { Socket socket; if (sf != null) { socket = sf.createSocket(); } else { socket = new Socket(); } if (logger.isLoggable(Level.FINE)) { logger.log(Level.FINE, (sf == null ? "created socket {0}" : "created socket {0} using factory {1}"), new Object[] { socket, sf }); } return socket; } public Socket createTunnelSocket(Socket s) throws IOException { // proxy tunneling never used throw new UnsupportedOperationException(); } } /** * HTTP connection for sending requests. **/ private final class Connection extends HttpClientConnection { private final String proxyHost; private final int proxyPort; /** * Creates a direct connection to given host/port. **/ Connection(String host, int port, Constraints.Distilled distilled) throws IOException { super(host, port, new SocketFactoryAdapter(sf, distilled), clientManager); proxyHost = ""; proxyPort = -1; } /** * Creates a proxied connection to given host/port. **/ Connection(String host, int port, String proxyHost, int proxyPort, boolean persist, Constraints.Distilled distilled) throws IOException { super(host, port, proxyHost, proxyPort, false, persist, new SocketFactoryAdapter(sf, distilled), clientManager); this.proxyHost = proxyHost; this.proxyPort = proxyPort; } /** * Adds connection to idle connection cache, schedules timeout. **/ protected void idle() { synchronized (connections) { if (proxyHost.equals(HttpEndpoint.this.proxyHost) && proxyPort == HttpEndpoint.this.proxyPort && persist == HttpEndpoint.this.persist) { connections.add(this); connTimer.scheduleTimeout(this, false); } else { super.shutdown(true); } } } /** * Attempts to close connection. **/ public boolean shutdown(boolean force) { Socket sock = getSocket(); boolean socketClosed; synchronized (connections) { socketClosed = super.shutdown(force); if (socketClosed) { connections.remove(this); connTimer.cancelTimeout(this); } } if (socketClosed) { if (logger.isLoggable(Level.FINE)) { logger.log(Level.FINE, "closed socket {0}", sock); } return true; } return false; } void checkConnectPermission() { SecurityManager sm = System.getSecurityManager(); if (sm != null) { Socket socket = getSocket(); InetSocketAddress address = (InetSocketAddress) socket.getRemoteSocketAddress(); if (address.isUnresolved()) { sm.checkConnect(address.getHostName(), socket.getPort()); } else { sm.checkConnect(address.getAddress().getHostAddress(), socket.getPort()); } } } } /** * Attempts to set desired socket options for a connected socket * (TCP_NODELAY and SO_KEEPALIVE); ignores SocketException. **/ private static void setSocketOptions(Socket socket) { try { socket.setTcpNoDelay(true); } catch (SocketException e) { if (logger.isLoggable(Levels.HANDLED)) { LogUtil.logThrow(logger, Levels.HANDLED, HttpEndpoint.class, "setSocketOptions", "exception setting TCP_NODELAY on socket {0}", new Object[] { socket }, e); } } try { socket.setKeepAlive(true); } catch (SocketException e) { if (logger.isLoggable(Levels.HANDLED)) { LogUtil.logThrow(logger, Levels.HANDLED, HttpEndpoint.class, "setSocketOptions", "exception setting SO_KEEPALIVE on socket {0}", new Object[] { socket }, e); } } } }