Java ProxySelector

Java Networking and Proxies

As you can see, with J2SE 5.0, the developer gains quite a bit of control and flexibility when it comes to proxies. Still, there are situations where one would like to decide which proxy to use dynamically, for instance to do some load balancing between proxies, or depending on the destination, in which case the API described so far would be quite cumbersome. That’s where the ProxySelector comes into play.

In a nutshell the ProxySelector is a piece of code that will tell the protocol handlers which proxy to use, if any, for any given URL. For example, consider the following code:

URL url = new URL(“http://java.sun.com/index.html");
URLConnection conn = url.openConnection();
InputStream in = conn.getInputStream();

At that point the HTTP protocol handler is invoked and it will query the proxySelector. The dialog might go something like that:

Handler: Hey dude, I’m trying to reach java.sun.com, should I use a proxy?
ProxySelector: Which protocol do you intend to use?
Handler: http, of course!
ProxySelector: On the default port?
Handler: Let me check…. Yes, default port.
ProxySelector: I see. Then you shall use webcache.mydomain.com on port 8080 as a proxy.
Handler: Thanks. Dude, webcache.mydomain.com:8080 doesn’t seem to be responding! Any other option?
ProxySelector: Dang! OK, try webcache2.mydomain.com, on port 8080 as well.
Handler: Sure. Seems to be working. Thanks.
ProxySelector: No sweat. Bye.

Of course I’m embellishing a bit, but you get the idea.

The best thing about the ProxySelector is that it is plugable! Which means that if you have needs that are not covered by the default one, you can write a replacement for it and plug it in!

So what is a ProxySelector? Let’s take a look at the class definition:

public abstract class ProxySelector {
public static ProxySelector getDefault();
public static void setDefault(ProxySelector ps);
public abstract List select(URI uri);
public abstract void connectFailed(URI uri,
SocketAddress sa, IOException ioe);
}

As we can see, ProxySelector is an abstract class with 2 static methods to set, or get, the default implementation, and 2 instance methods that will be used by the protocol handlers to determine which proxy to use or to notify that a proxy seems to be unreachable. If you want to provide your own ProxySelector, all you have to do is extend this class, provide an implementation for these 2 instance methods then call ProxySelector.setDefault() passing an instance of your new class as an argument. At this point the protocol handlers, like http or ftp, will query the new ProxySelector when trying to decide what proxy to use.

Before we see in details how to write such a ProxySelector, let’s talk about the default one. J2SE 5.0 provides a default implementation which enforces backward compatibility. In other terms, the default ProxySelector will check the system properties described earlier to determine which proxy to use. However, there is a new, optional feature: On recent Windows systems and on Gnome 2.x platforms it is possible to tell the default ProxySelector to use the system proxy settings (both recent versions of Windows and Gnome 2.x let you set proxies globally through their user interface). If the system property java.net.useSystemProxies is set to true (by default it is set to false for compatibility sake), then the default ProxySelector will try to use these settings. You can set that system property on the command line, or you can edit the JRE installation file lib/net.properties, that way you have to change it only once on a given system.

Now let’s examine how to write, and install, a new ProxySelector.

Here is what we want to achieve: We’re pretty happy with the default ProxySelector behavior, except when it comes to http and https. On our network we have more than one possible proxy for these protocols and we we’d like our application to try them in sequence (i.e.: if the 1st one doesn’t respond, then try the second one and so on). Even more, if one of them fails too many time, we’ll remove it from the list in order to optimize things a bit.

All we need to do is subclass java.net.ProxySelector and provide implementations for both the select() and connectFailed() methods.

The select() method is called by the protocol handlers before trying to connect to a destination. The argument passed is a URI describing the resource (protocol, host and port number). The method will then return a List of Proxies. For instance the following code:

URL url = new URL(“http://java.sun.com/index.html");
InputStream in = url.openStream();

will trigger the following pseudo-call in the protocol handler:

List l = ProxySelector.getDefault().select(new URI(“http://java.sun.com/"));

In our implementation, all we’ll have to do is check that the protocol from the URI is indeed http (or https), in which case we will return the list of proxies, otherwise we just delegate to the default one. To do that, we’ll need, in the constructor, to store a reference to the old default, because ours will become the default.

So it is starting to look like this:

public class MyProxySelector extends ProxySelector {
ProxySelector defsel = null;
MyProxySelector(ProxySelector def) {
defsel = def;
}

public java.util.List select(URI uri) {
if (uri == null) {
throw new IllegalArgumentException(“URI can’t be null.");
}
String protocol = uri.getScheme();
if (“http".equalsIgnoreCase(protocol) ||
“https".equalsIgnoreCase(protocol)) {
ArrayList l = new ArrayList();
// Populate the ArrayList with proxies
return l;
}
if (defsel != null) {
return defsel.select(uri);
} else {
ArrayList l = new ArrayList();
l.add(Proxy.NO_PROXY);
return l;
}
}
}

First note the constructor that keeps a reference to the old default selector. Second, notice the check for illegal argument in the select() method in order to respect the specifications. Finally, notice how the code defers to the old default, if there was one, when necessary. Of course, in this example, I didn’t detail how to populate the ArrayList, as it not of particular interest, but the complete code is available in the appendix if you’re curious.

As it is, the class is incomplete since we didn’t provide an implementation for the connectFailed() method. That’s our very next step.

The connectFailed() method is called by the protocol handler whenever it failed to connect to one of the proxies returned by the select() method. 3 arguments are passed: the URI the handler was trying to reach, which should be the one used when select() was called, the SocketAddress of the proxy that the handler was trying to contact and the IOException that was thrown when trying to connect to the proxy. With that information, we’ll just do the following: If the proxy is in our list, and it failed 3 times or more, we’ll just remove it from our list, making sure it won’t be used again in the future. So the code is now:

public void connectFailed(URI uri, SocketAddress sa, IOException ioe) {
if (uri == null || sa == null || ioe == null) {
throw new IllegalArgumentException(“Arguments can’t be null.");
}
InnerProxy p = proxies.get(sa);
if (p != null) {
if (p.failed() >= 3)
proxies.remove(sa);
} else {
if (defsel != null)
defsel.connectFailed(uri, sa, ioe);
}
}

Pretty straightforward isn’t it. Again we have to check the validity of the arguments (specifications again). The only thing we do take into account here is the SocketAddress, if it’s one of the proxies in our list, then we do deal with it, otherwise we defer, again, to the default selector.

Now that our implementation is, mostly, complete, all we have to do in the application is to register it and we’re done:

public static void main(String[] args) {
MyProxySelector ps = new MyProxySelector(ProxySelector.getDefault());
ProxySelector.setDefault(ps);
// rest of the application
}

發表迴響

你的電子郵件位址並不會被公開。 必要欄位標記為 *

What is 15 + 4 ?
Please leave these two fields as-is:
IMPORTANT! To be able to proceed, you need to solve the following simple math (so we know that you are a human) :-)