14.7 使用代理服务器
代理服务器简介
从Java 5开始在java.net包下提供了Proxy和ProxySelector两个类,其中Proxy代表一个代理服务器,可以在打开URLConnection连接时指定Proxy,创建Socket连接时也可以指定Proxy;而ProxySelector就代表一个代理选择器,它提供了对代理服务器更加灵活的控制,它可以对HTTP、HTTPS、FTP、Socket等进行分别设置,而且还可以设置不需要通过代理服务器的主机和地址。通过使用ProxySelector,可以实现向Internet Explore、Firefox等软件中设置服务器类似的效果。
使用代理服务器的优点:
1、突破自身IP限制,对外隐藏自身IP地址。包括访问国外受限站点,国内特定单位、团体的内部资源。
2、提高访问速度,代理服务器具有缓冲功能避免每个用户直接访问远程主机。
一、直接使用Proxy创建连接
Proxy有一个构造器:
Proxy(Proxy.Type type,SocketAddress sa):创建代理服务器的Proxy对象。其中sa参数指定代理服务器的地址,type表示该代理服务器的类型,该服务器的类型有三种:
(1)Proxy.Type.DIRECT:表示直接连接,不使用代理。
(2)Proxy.Type.HTTP:表示支持高级协议代理,如HTTP或FTP。
(3)Proxy.Type.SOCKET:表示SOCKS(V4或V5)代理。
一旦创建了Proxy对象后,程序就可以使用URLConnection打开连接时,或者创建Socket连接时传入一个Proxy对象,作为本次连接使用的代理服务器。其中URL包含一个URLConnection openConnection(Proxy proxy)方法,该方法使用指定的代理服务器来打开连接;而Socket择提高一个Socket(Proxy proxy)构造器,该构造器使用指定的代理服务器创建一个没有连接的Socket对象。
下面以URLConnection为例来介绍如何在URLConnection中使用代理服务器:
package Proxy;
import java.io.*;
import java.net.*;
import java.util.*;
public class ProxyTest
{
// 下面是代理服务器的地址和端口,
// 换成实际有效的代理服务器的地址和端口
final String PROXY_ADDR = "129.82.12.188";
final int PROXY_PORT = 3124;
// 定义需要访问的网站地址
String urlStr = "http://www.crazyit.org";
public void init() throws IOException, MalformedURLException
{
var url = new URL(urlStr);
// 创建一个代理服务器对象
Proxy proxy = new Proxy(Proxy.Type.HTTP,
new InetSocketAddress(PROXY_ADDR, PROXY_PORT));//1
// 使用指定的代理服务器打开连接
URLConnection conn = url.openConnection(proxy);//2
// 设置超时时长。
conn.setConnectTimeout(5000);
try (
// 通过代理服务器读取数据的Scanner
var scan = new Scanner(conn.getInputStream(), "utf-8");
var ps = new PrintStream("index.htm"))
{
while (scan.hasNextLine())
{
String line = scan.nextLine();
// 在控制台输出网页资源内容
System.out.println(line);
// 将网页资源内容输出到指定输出流
ps.println(line);
}
}
}
public static void main(String[] args)
throws IOException, MalformedURLException
{
new ProxyTest().init();
}
}
上面程序代码1创建了一个Proxy对象,代码2处就是应用Proxy对象打开URLConnection连接。接下来程序使用URLConnection读取一份网络资源,此时的URLConnection并不是直接连接到www.crazyit.org,而是通过代理服务器去访问该网站。
二、使用ProxySelector自动选择代理服务器
前面使用Proxy对象可以在打开URLConnection或Socket时指定代理服务器,但使用这种方式每次打开连接时都需要显示地设置代理服务器,比较麻烦。如果每次打开连接时总是具有默认的代理服务器,择可以借用ProxySelector来实现。
ProxySelector代表一个代理选择器,它本身是一个抽象类,程序无法创建它的实例,开发者可以考虑继承ProxySelector来实现自己的代理选择器实现ProxySelector的步骤非常简单,程序只需要定义一个继承ProxySelector的类,并让该类实现两个抽象方法:
(1)List
(2)connectFailed(URL url,SocketAddress sa,IOException ioe):连接代理服务器失败时回调该方法。
提示:系统默认的代理服务器选择器也重写了connectFailed方法,它重写该方法的处理策略是:当系统设置的代理服务器失败时,默认的代理服务器会采用智联的方式连接远程资源,所以运行上面的程序等待足够长的时间时,程序依然可以打印处该远程资源的所有内容。
实现自己的ProxySelector类之后,调用ProxySelector的setDefault(ProxySelector ps)静态方法来注册该代理选择器即可。
下面程序示范了如何让自定义的connectFailed来自动选择代理选择器。
package Proxy;
import java.io.*;
import java.net.*;
import java.util.*;
public class ProxySelectorTest
{
// 下面是代理服务器的地址和端口,
// 随便一个代理服务器的地址和端口
final String PROXY_ADDR = "139.82.12.188";
final int PROXY_PORT = 3124;
// 定义需要访问的网站地址
String urlStr = "http://www.crazyit.org";
public void init() throws IOException, MalformedURLException
{
// 注册默认的代理选择器
ProxySelector.setDefault(new ProxySelector()
{
@Override
public void connectFailed(URI uri,
SocketAddress sa, IOException ioe)
{
System.out.println("无法连接到指定代理服务器!");
}
// 根据"业务需要"返回特定的对应的代理服务器
@Override
public List<Proxy> select(URI uri)
{
// 本程序总是返回某个固定的代理服务器。
List<Proxy> result = new ArrayList<>();
result.add(new Proxy(Proxy.Type.HTTP,
new InetSocketAddress(PROXY_ADDR, PROXY_PORT)));
return result;
}
});
var url = new URL(urlStr);
// 没有指定代理服务器、直接打开连接
URLConnection conn = url.openConnection(); // ①
// 设置超时时长。
conn.setConnectTimeout(3000);
try (
// 通过代理服务器读取数据的Scanner
var scan = new Scanner(conn.getInputStream());
var ps = new PrintStream("index.htm"))
{
while (scan.hasNextLine())
{
String line = scan.nextLine();
// 在控制台输出网页资源内容
System.out.println(line);
// 将网页资源内容输出到指定输出流
ps.println(line);
}
}
}
public static void main(String[] args)
throws IOException, MalformedURLException
{
new ProxySelectorTest().init();
}
}
上面程序采用匿名内部类实现了一个ProxySelector,该ProxySelector的select()方法总是返回一个固定的代理服务器,也就是说,程序总是默认使用该代理服务器。因此程序在①好代码处打开连接时虽然没有指定代理服务器,但实际上程序还是会使用代理服务器——如果用户设置了一个无效的代理服务器,系统将会在连接失败时回调ProxySelector的connectFailed()方法,这可以说明代理服务器起作用了。
其实Java也提供了一个默认实现的代理选择器,即sun.net.spi.DefaultProxySelectore,自己未设置之前使用的就是这个Sun公司提供的默认代理选择器,因此程序可以调用ProxySelector.getDefault()方法来获取DefaultProxySelector实例。它的实现策略是:
(1)connectFailed():如果连接失败,DefaultProxySelector将会尝试不使用代理服务器,直接连接远程资源;
(2)select():select方法的实现策略就是就是根据给定的URI返回系统属性表中指定的代理,只不过初始时属性表中的代理都是为空,因此默认采取的是直连而已;
sun.net.spi.DefaultProxySelector虽然存在并且运行着,但不过该API没有公开,目的就是防止用户显示使用它造成不安全因素,因此如果你想改变默认选择器对代理的选择只能通过修改系统属性表的方式得到想要的代理;
系统属性表中的代理属性:
i. 代理的属性名格式为“协议名.XXX”,协议支持http、https、ftp、socks等;
ii. XXX是代理的信息,主要有3种:
a. proxyHost:代理的IP地址
b. proxyPort:代理服务器的端口
c. nonProxyHosts:不需要使用代理就可以访问的网址(主机),可以指定多个,多个网址之间用|隔开,允许使用通配符*表示
下面程序示范了通过该表系统属性来改变默认的代理服务器。
package Proxy;
import java.io.*;
import java.net.*;
import java.util.*;
public class DefaultProxySelectorTest
{
// 定义需要访问的网站地址
static String urlStr = "http://www.crazyit.org";
public static void main(String[] args) throws Exception
{
// 获取系统的默认属性
Properties props = System.getProperties();
// 通过系统属性设置HTTP访问所用的代理服务器的主机地址、端口
props.setProperty("http.proxyHost", "192.168.10.96");
props.setProperty("http.proxyPort", "8080");
// 通过系统属性设置HTTP访问无需使用代理服务器的主机
// 可以使用*通配符,多个地址用|分隔
props.setProperty("http.nonProxyHosts", "localhost|192.168.10.*");
// 通过系统属性设置HTTPS访问所用的代理服务器的主机地址、端口
props.setProperty("https.proxyHost", "192.168.10.96");
props.setProperty("https.proxyPort", "443");
/* DefaultProxySelector不支持https.nonProxyHosts属性,
DefaultProxySelector直接按http.nonProxyHosts的设置规则处理 */
// 通过系统属性设置FTP访问所用的代理服务器的主机地址、端口
props.setProperty("ftp.proxyHost", "192.168.10.96");
props.setProperty("ftp.proxyPort", "2121");
// 通过系统属性设置FTP访问无需使用代理服务器的主机
props.setProperty("ftp.nonProxyHosts", "localhost|192.168.10.*");
// 通过系统属性设置设置SOCKS代理服务器的主机地址、端口
props.setProperty("socks.ProxyHost", "192.168.10.96");
props.setProperty("socks.ProxyPort", "1080");
// 获取系统默认的代理选择器
ProxySelector selector = ProxySelector.getDefault(); // ①
System.out.println("系统默认的代理选择器:" + selector);
// 根据URI动态决定所使用的代理服务器
System.out.println("系统为ftp://www.crazyit.org选择的代理服务器为:"
+ ProxySelector.getDefault().select(new URI("ftp://www.crazyit.org"))); // ②
var url = new URL(urlStr);
// 直接打开连接,默认的代理选择器会使用http.proxyHost、
// http.proxyPort系统属性设置的代理服务器,
// 如果无法连接代理服务器,默认的代理选择器会尝试直接连接
URLConnection conn = url.openConnection(); // ③
// 设置超时时长。
conn.setConnectTimeout(3000);
try (
var scan = new Scanner(conn.getInputStream(), "utf-8"))
{
// 读取远程主机的内容
while (scan.hasNextLine())
{
System.out.println(scan.nextLine());
}
}
}
}
程序代码①处返回了系统默认注册的ProxySelector,并返回了DefaulProxySelector实例。程序中代码1、2、3设置了HTTP访问的代理服务器属性,其中前两行设置了代理服务器的地址和端口,第三行设置了HTTP访问那些主机时不需要使用代理服务器。程序中代码③处直接代理一个URLConnection,系统会在打开该URLConnection时使用代理服务器。程序在代码②处让默认的ProxySelector为ftp://www.crazyit.org选择代理服务器,他将使用ftp.ProxyHost属性设置的代理服务器。