URLConnection是一个抽象类,表示指向URL指定资源的活动连接。与URL类相比,它对服务器的交互提供了更多的控制。URLConnection类是Java协议处理器(protocol handler)机制的一部分,它们将处理协议的细节与处理特定数据类型分开,提供相应的用户接口,并完成完整web浏览器所完成的其他操作。
1、打开URLConnection
直接使用URLConnection类的程序遵循以下基本步骤:
- 构造一个URL对象。
- 调用这个URL对象的openConnection()获取一个对应该URL的URLConnection对象。
- 配置这个URLConnection。
- 读取首部字段。
- 获得输入流并读取数据。
- 获得输出流并写入数据。
- 关闭连接。
第一次构造URLConnection时,它是未连接的,也就是说本地和远程主机无法发送和接收数据。connect()方法在本地和远程主机之间建立一个连接(一般使用TCP socket,但也可能通过其他机制来建立)。不过对于getInputStream()、getContent()等方法,如果连接尚未打开,它们就会调用connect(),因此你很少需要直接调用connect()。
2、读取服务器的数据
下面是使用URLConnection对象从一个URL获取数据所需的最起码的步骤:
- 构造一个URL对象。
- 调用这个URL对象的openConnection()获取一个对应该URL的URLConnection对象。
- 调用这个URLConnection的getInputStream()方法
- 使用通常的流API读取输入流。
URL和URLConnection这两个类最大的不同在于:URLConnection提供了对HTTP首部的访问,可以配置发送给服务器的请求参数,除了读取服务器数据外还可以向服务器写入数据。
import java.io.BufferedInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLConnection;
public class BinarySaver {
public static void saveBinaryFile(URL url) throws IOException {
URLConnection urlConnection = url.openConnection();
String contentType = urlConnection.getContentType();
int contentLength = urlConnection.getContentLength();
if (contentType.startsWith("text/") || contentLength == -1) {
throw new IOException("这不是二进制文件");
}
try (InputStream raw = urlConnection.getInputStream()) {
InputStream in = new BufferedInputStream(raw);
byte[] data = new byte[contentLength];
int offset = 0;
while (offset < contentLength) {
int bytesRead = in.read(data, offset, data.length - offset);
if (bytesRead == -1) {
break;
}
offset += bytesRead;
}
if (offset != contentLength) {
throw new IOException("期望" + contentLength + "字节,实际" + offset + "字节");
}
String fileName = url.getFile();
fileName = fileName.substring(fileName.lastIndexOf("/") + 1);
try (FileOutputStream fOut = new FileOutputStream(fileName)) {
fOut.write(data);
fOut.flush();
}
}
}
public static void main(String[] args) {
try {
URL url = new URL("http://money.innovatelife.net/download?type=money-phone.apk");
saveBinaryFile(url);
} catch (MalformedURLException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
}
getContentType()返回响应主体的MIME内容类型,getContentLength()告诉你内容有多少字节,getContentEncoding()返回内容的编码,getDate()返回文档何时发送等等。
3、缓存
Web浏览器多年来一直在缓存页面和图片,如果一个logo在网站的每一个页面上重复出现,浏览器一般只会从远程服务器上加载一次,将它保存在缓存中,每次需要的时候会从缓存重新加载。一些HTTP首部(包括Expires和Cache-Control)可以控制缓存。
- Expires首部(主要针对HTTP 1.0)指示可以缓存这个资源表示,直到指定的时候为止。
- Cache-Control首部(HTTP 1.1)提供了细粒度的缓存策略(如果跟Expires都出现会覆盖Expires):
- max-age=[seconds]:从现在直到缓存项过期之前的秒数。
- s-maxage=[seconds]:从现在起,直到缓存项在共享缓存中过期之前的秒数,私有缓存可以将缓存项保存更长时间。
- public:可以缓存一个经过认证的响应。否则已认证的响应不能缓存。
- private:仅单个用户缓存可以保存响应,而共享缓存不应保存。
- no-cache:这个策略的作用与名字不太一致,缓存项仍然可以缓存,不过客户端在每次访问时要用一个Etag或Last-modified首部重新验证响应的状态。
- no-store:不管怎样都不缓存。
- Last-modified首部指示资源最后一次修改的日期,客户端可以使用一个HEAD请求来检查这个日期,只有当本地缓存的副本早于Last-modified日期时,它才会真正执行GET来获取资源。
- Etag首部(HTTP 1.1)是资源改变时这个资源的唯一标识符。
Java的Web缓存:要安装URL类使用的系统级缓存,需要有ResponseCache、CacheRequest、CacheResponse这些的具体子类。
4、配置连接
URLConnection类有7个保护实例字段,定义了客户端如何向服务器做出请求。例如,如果doOutPut为true,那么除了通过这个URLConnection读取数据外,还可以将数据写入到服务器。如果useCaches为false,连接会绕过所有本地缓存,重新从服务器下载文件。只能在URLConnection连接之前修改这些字段,对于设置字段的方法,如果调用这些方法时连接已经打开,大多数方法会抛出一个IllegalSateException。字段如下:
- url:指定了这个URLConnection连接的URL。
- doInput:URLConnection可以用来读取服务器为true,否则为false。
- doOutput:URLConnection可以用于写入为true,否则为false。
- allowUserInteraction:是否允许用户交互。
- useCaches:是否可以使用缓存。
- ifModifiedSince:判断服务器上的文档是否有变化。
- connected:连接打开为true,关闭为false。
setConnectTimeout()/getConnectTimeout()控制socket等待建立连接的时间,setReadTimeout()/getReadTimeout()控制输入流等待数据到达的时间。
5、配置客户端请求HTTP首部
Web服务器可以根据首部信息向不同的客户端提供不同的页面,获取和设置cookie,通过口令认证用户等。每个URLConnection会在首部默认设置一些不同的名-值对,打开连接前,可以使用setRequestProperty()方法为HTTP首部增加首部字段。如:
urlConnection.setRequestProperty("Token", "token123456");
6、向服务器写入数据
有时你需要向URLConnection写入数据,例如使用POST向Web服务器提交表单,或者使用PUT上传文件。getOutputStream()方法返回一个OutputStream,可以用来写入数据传送给服务器。
try {
URL url = new URL("http://money.innovatelife.net/cost/insertCost");
//打开连接,准备POST
URLConnection urlConnection = url.openConnection();
urlConnection.setDoOutput(true);//设置允许输出,请求方法由GET变为POST
OutputStream outputStream=urlConnection.getOutputStream();
OutputStream buffered=new BufferedOutputStream(outputStream);
OutputStreamWriter out=new OutputStreamWriter(buffered,"UTF-8");
out.write("moneyOut=100&remark=吃饭");
out.flush();
out.close();
try(InputStream in=urlConnection.getInputStream()){
Reader reader=new InputStreamReader(in);
int c;
while((c=reader.read())!=-1){
System.out.print((char)c);
}
System.out.println();
} catch (Exception e) {
e.printStackTrace();
}
} catch (IOException e) {
e.printStackTrace();
}
7、URLConnection的安全考虑
建立网络连接、读/写文件等存在一些常见的安全限制,URLConnection对象会受到这些安全限制的约束。在尝试连接一个URL前,你可能想知道是否允许连接,为此URLConnection类包含了一个getPermission()方法,指出连接这个URL所需的权限。如果不需要任务权限,它会返回null。
8、猜测MIME媒体类型
我们不仅要处理在MIME出现之前已有的老协议,如FTO,而且很多要使用MIME的HTTTP服务器根本不提供MIME首部,或者会提供不正确的首部,URLConnection类提供了两个静态方法,可以帮助程序确定某些数据的MIME类型。guessContentTypeFromName()尝试根据对象URL的文件扩展名部分猜测对象的内容类型,guessContentTypeFromStream()尝试查看流中前几字节来猜测内容类型。
9、HttpURLConnection
java.net.HttpURLConnection类是URLConnection的抽象子类,它提供了另外一些方法,在处理hhtp URL时尤其有帮助。具体地,它包含的方法可以获得和设置请求方法、确定是否重定向、获得响应码和消息,以及确定是否使用了代理服务器。具体的API不再细述。