首页 > 代码库 > Java Networking and Proxies(译文)

Java Networking and Proxies(译文)



JavaNetworking and Proxies

比较早的文章,正好在研究java proxy的用法,就翻译了一下

原文地址:

http://docs.oracle.com/javase/6/docs/technotes/guides/net/proxies.html

 

  1. 概述

    在如今的网络环境下,尤其是合作项目,项目开发者不得不频繁的处理代理问题。

    有时候项目使用系统默认参数即可,但有些情况下,希望能够详密掌控代理的使用情况。

    很多项目会提供用户GUI来自己设定代理参数。

    无论如何,一个开发平台,比如java,应该提供灵活强大的机制来处理代理问题。Javase5.0处理了这类问题

     

  2. 系统特性

    JavaSe1.4中,只能通过systemproperties去设置代理服务器。复杂一点的情况下,这些参数的名字已经被改来改去,面目全非。

    使用systemproperties有个很大的限制。就是一旦代理设定了某个协议,那么所有的链接都将遵循这个协议。非黑即白,无法自由设置。这是机械化的做法,一点都不人性化。

    有两个方法设置systemproperties

  1. 借助虚拟机使用命令行

  2. 使用System.setProperty(String,String)方法

所有的代理被定义为一个主机名+一个端口号。

 

2.1 HTTP

有三个properties可以指定HTTP协议的proxy

http.proxyHost

http.proxyPort:默认值80

http.nonProxyHosts:一些将不会用代理服务器链接的地址,用“|”分割

我们来看一些例子:

例一:

$ java-Dhttp.proxyHost=webcache.example.com GetURL

所有http链接都将通过“webcache.example.com”这个代理服务器的80端口

 

例二:

$ java -Dhttp.proxyHost=webcache.example.com-Dhttp.proxyPort=8080

-Dhttp.nonProxyHosts=”localhost|host.example.com” GetURL

   在第二个例子中,代理服务器还是“webcache.example.com”,但是端口变成了8080,并且当链接localhost或者是host.mydonain.com时将不会使用代理服务器。

 

   之前提到的,这些设定会影响所有的HTTP链接。

   当然,我们可以使用代码让这个设定变得轻巧,动态:

   //Set the http proxy to webcache.example.com:8080

 

System.setProperty("http.proxyHost","webcache.example.com");

System.setProperty("http.proxyPort","8080");

 

// Next connection will be through proxy.

URL url = new URL("http://java.example.org/");

InputStream in = url.openStream();

 

// Now, let‘s ‘unset‘ the proxy.

System.setProperty("http.proxyHost", null);

 

// From now on http connections will be done directly.

   即便是有些繁琐,代码工作正常,但是在多线程的应用中会变得有些微妙。记住,systemproperties是虚拟机的参数,所以所有的线程都会被影响到。

 

   2.2 HTTPS

   htttps.proxyHost

   https.proxyPort:默认443

 

   2.3 FTP

  ftp.proxHost

ftp.proxyPort:默认80

ftp.nonProxyHosts

 

2.4 SOCKS

SOCKS协议,在RFC1928中定义,提供一个框架给C/S应用,用于TCP/UDP层(传输层)安全的穿过防火墙。这个比高层协议比如HTTP或者FTP(应用层)更通用

socksProxyHost

socksProxyPort:默认1080

如果同时设定了SOCKSHTTP代理怎么办?

原则是优先设定更高层协议,比如HTTP或者FTP将会有更高的优先权。

例子:同时设定HTTPSOCKS代理:

$ java -Dhttp.proxyHost=webcache.example.com -Dhttp.proxyPort=8080
-DsocksProxyHost=socks.example.com GetURL

HTTPURL-HTTPProxy

FTP-SOCKSProxy

3)   Proxy

system properties很有用,但是不灵活。非黑即白的方式对于开发者来说是一个非常苛刻的限制。所以一个灵活的API将满足这个问题。

 

这个API就是Proxy类,定义了代理的3种类型:

  1. DIRECT:不使用代理

  2. HTTP:使用HTTP协议的代理

  3. SOCKS:使用SOCKS v4或者v5协议的代理

    所以,建立一个HTTP代理将用以下方法:

SocketAddress addr = new
InetSocketAddress("webcache.example.com", 8080);
Proxy proxy = new Proxy(Proxy.Type.HTTP, addr);

记住!这个Proxy实例只是代表着一个代理的定义。

怎么使用呢?

URL url = new URL("http://java.example.org/");
URConnection conn = url.openConnection(proxy);

同样的机制可以指定一个特殊的链接不使用代理,用这种方式不用定义Proxy的实例。

URL url2 = new URL("http://infos.example.com/");

URLConnection conn2 =url2.openConnection(Proxy.NO_PROXY);

一样的,可以用SOCKS代理进行连接,方式也一样

SocketAddress addr = new InetSocketAddress("socks.example.com", 1080);
Proxy proxy = new Proxy(Proxy.Type.SOCKS, addr);
URL url = new URL("ftp://ftp.gnu.org/README");
URLConnection conn = url.openConnection(proxy);

 

使用Proxy建立TCP链接:

SocketAddress addr = new InetSocketAddress("socks.example.com", 1080);
Proxy proxy = new Proxy(Proxy.Type.SOCKS, addr);
Socket socket = new Socket(proxy);
InetSocketAddress dest = new InetSocketAddress("server.example.org", 1234);

socket.connect(dest);

同样可以有另外一种方式让TCP链接使用代理:

Socket socket = new Socket(Proxy.NO_PROXY);
socket.connect(new InetAddress("localhost", 1234));

注意:参数只能是SOCKS或者DIRECT

 

4)   ProxySelector

       使用JAVA SE5.0,开发者可以很灵活的使用proxies

       但是仍然有需求能够动态决定使用哪个代理,比如要做代理服务器的负载均衡,或者要指定一些代理服务器,用目前的api则会显得相当笨重。

       ProxySelector闪亮登场。

       一言概之,ProxySelector就是告诉程序去用哪个代理。如果不这样做,请看下面的问题:

URL url = newURL("http://java.example.org/index.html");

URLConnection conn = url.openConnection();

InputStream in = conn.getInputStream();

 

走到这里,HTTP协议handler被激活,并且要问proxySelector,类似如下谈话:

Handler:嘿,老弟,我要连接java.example.org,要通过代理吗?

PS:你要用什么协议?

HandlerHTTP

PS:默认端口?

Handler:我瞅瞅。。。嗯,就是默认端口

PS:明白,你用webcache.example.com8080作为代理去连

Handler:多谢。数秒后。。。用不了啊,还有别的选择吗

PS:用webcache.example.com28080试试?

Handler:可以工作了,3q

从上面的对话我们可以明白ProxySelector是驱动型。如果你要的不在默认选项里,你可以自己定义替代品。

我们看下ProxySelector是如何定义的:

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

ProxySelector是一个抽象类:2个抽象方法setget本身,2个抽象方法让协议handlers可以决定使用哪个代理或者哪个代理使用不了。

如果要提供自己重写的ProxySelector,你只需要继承这个类,提供一个接口给这两个抽象方法,然后调用ProxySelector.setDefault()把你创建的实例传进去。

在讨论如何写ProxySelector之前,我们看下默认的情况。

J2SE5.0提供了一个默认的实现类可以向后兼容。这个默认的ProxySelector将会检测systemproperties,并决定使用哪个代理。

注意,在window系统和Gnome2.x平台上,可以告诉默认的ProxySelector使用systemproxysettings。如果system property java.net.useSystemProxies设定为true(默认是false),则默认的ProxySelector将会使用这些设定。

下面我们测试下如何写,并且安装一个新的ProxySelector

一般我们会为这些协议提供不止一个代理地址,如果一个失败了,我们可以试另一个,有些失败的代理地址,我们可以从代理列表中移除掉。

我们只要写一个ProxySelector的子类,并且实现select()和connectFailed()方法。

Select()方法在准备链接目标地址前被protocolhandlers调用。返回值是一组Proxylist

URL url = new URL("http://java.example.org/index.html");

InputStream in = url.openStream();

List<Proxy> l = ProxySelector.getDefault().select(newURI("http://java.example.org/"));

在我们实现中,我们需要做的就是判断这个协议是否是http或者https,如果是的话将会返回一个proxylist,否则的话(SocksFTP)将会返回默认的。

public class MyProxySelector extends ProxySelector {

       // Keep a reference on theprevious default

   ProxySelector defsel = null;

       

       /*

        * Inner class representinga Proxy and a few extra data

        */

       class InnerProxy {

       Proxy proxy;

               SocketAddress addr;

               // How many timesdid we fail to reach this proxy?

               int failedCount = 0;

               

               InnerProxy(InetSocketAddress a) {

                       addr = a;

                       proxy = newProxy(Proxy.Type.HTTP, a);

               }

               

               SocketAddressaddress() {

                       return addr;

               }

               

               Proxy toProxy() {

                       returnproxy;

               }

               

               int failed() {

                       return++failedCount;

               }

       }

       

       /*

        * A list of proxies,indexed by their address.

        */

       HashMap<SocketAddress,InnerProxy> proxies = new HashMap<SocketAddress, InnerProxy>();

 

       MyProxySelector(ProxySelectordef) {

         // Save the previousdefault

         defsel = def;

         

         // Populate the HashMap(List of proxies)

         InnerProxy i = newInnerProxy(new InetSocketAddress("webcache1.example.com", 8080));

         proxies.put(i.address(),i);

         i = new InnerProxy(newInetSocketAddress("webcache2.example.com", 8080));

         proxies.put(i.address(),i);

         i = new InnerProxy(newInetSocketAddress("webcache3.example.com", 8080));

         proxies.put(i.address(),i);

         }

         

         /*

          * This is the method thatthe handlers will call.

          * Returns a List ofproxy.

          */

         publicjava.util.List<Proxy> select(URI uri) {

               // Let‘s stick tothe specs.

               if (uri == null) {

                       throw newIllegalArgumentException("URI can‘t be null.");

               }

               

               /*

                * If it‘s a http(or https) URL, then we use our own

                * list.

                */

               String protocol =uri.getScheme();

               if("http".equalsIgnoreCase(protocol) ||

                       "https".equalsIgnoreCase(protocol)) {

                       ArrayList<Proxy>l = new ArrayList<Proxy>();

                       for(InnerProxy p : proxies.values()) {

                         l.add(p.toProxy());

                       }

                       return l;

               }

               

               /*

                * Not HTTP or HTTPS(could be SOCKS or FTP)

                * defer to thedefault selector.

                */

               if (defsel != null){

                       returndefsel.select(uri);

               } else {

                       ArrayList<Proxy> l = new ArrayList<Proxy>();

                       l.add(Proxy.NO_PROXY);

                       return l;

               }

       }

       

       /*

        * Method called by thehandlers when it failed to connect

        * to one of the proxiesreturned by select().

        */

       public voidconnectFailed(URI uri, SocketAddress sa, IOException ioe) {

               // Let‘s stick tothe specs again.

               if (uri == null ||sa == null || ioe == null) {

                       throw newIllegalArgumentException("Arguments can‘t be null.");

               }

               

               /*

                * Let‘s lookup forthe proxy

                */

               InnerProxy p = proxies.get(sa);

                       if (p !=null) {

                               /*

                                *It‘s one of ours, if it failed more than 3 times

                                *let‘s remove it from the list.

                                */

                               if(p.failed() >= 3)

                                       proxies.remove(sa);

                       } else {

                               /*

                                *Not one of ours, let‘s delegate to the default.

                                */

                               if(defsel != null)

                                 defsel.connectFailed(uri, sa, ioe);

                       }

    }

}

 

Java Networking and Proxies(译文)