JavaNetworking and Proxies
比较早的文章,正好在研究java proxy的用法,就翻译了一下
原文地址:
http://docs.oracle.com/javase/6/docs/technotes/guides/net/proxies.html
- 概述
在如今的网络环境下,尤其是合作项目,项目开发者不得不频繁的处理代理问题。
有时候项目使用系统默认参数即可,但有些情况下,希望能够详密掌控代理的使用情况。
很多项目会提供用户GUI来自己设定代理参数。
无论如何,一个开发平台,比如java,应该提供灵活强大的机制来处理代理问题。Javase5.0处理了这类问题
- 系统特性
在JavaSe1.4中,只能通过systemproperties去设置代理服务器。复杂一点的情况下,这些参数的名字已经被改来改去,面目全非。
使用systemproperties有个很大的限制。就是一旦代理设定了某个协议,那么所有的链接都将遵循这个协议。非黑即白,无法自由设置。这是机械化的做法,一点都不人性化。
有两个方法设置systemproperties
- 借助虚拟机使用命令行
使用
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
如果同时设定了SOCKS和HTTP代理怎么办?
原则是优先设定更高层协议,比如HTTP或者FTP将会有更高的优先权。
例子:同时设定HTTP和SOCKS代理:
$ 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种类型:
- DIRECT:不使用代理
- HTTP:使用HTTP协议的代理
- 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:你要用什么协议?
Handler:HTTP啦
PS:默认端口?
Handler:我瞅瞅。。。嗯,就是默认端口
PS:明白,你用webcache.example.com:8080作为代理去连
Handler:多谢。数秒后。。。用不了啊,还有别的选择吗
PS:用webcache.example.com2:8080试试?
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个抽象方法set和get本身,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调用。返回值是一组Proxy的list。
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,如果是的话将会返回一个proxy的list,否则的话(Socks,FTP)将会返回默认的。
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);
}
}
}