上一章讲了如何通过主机名和IP地址确定主机在Internet的地址,这一章进一步学习如何确定资源的地址。URL可以唯一地标识一个资源在Internet上的位置,URL是最常见的URI(Uniform Resource Identifier),URI可以由资源的网络位置来标识资源,也可以由资源的名字、编号或其他特性来标识。
1、URI
URI是标识一个资源的字符串,它所标识的资源可能是服务器上的一个文件,也可能是一个邮件地址 、图书或人名。URI的语法由一个模式和一个模式特定部分组成,即模式:模式特定部分,特定部分的月份取决于所用的模式。模式包括:
- data,链接中直接包含的Base64编码数据,参见RFC 2397
- file,本地磁盘上的文件
- ftp,FTP服务器
- http,使用超文本传输协议的国际互联网服务器
- mailto,电子邮件地址
- magnet,可以通过对等网络(如BitTorrent)下载的资源
- telnet,与基于Telnet的服务的连接
- urn,统一资源名(Uniform Resource Name,URN)
此外,Java还大量使用了一些非标准的定制模式,如rmi、jar、jndi和doc,来实现不同用途。
URI中的模式特定部分并没有特定的语法,但很多都采用一种层次结构形式,如://auth/path?query,URI 的模式为http,授权机构为money.innovatelife.net,路径为/cost/list,查询为startDate=2017-03-01。而URI urn:isbn:15851810245模式为urn,但模式特定部分没有采用层次结构的//auth/path?query形式。URI还可以提供可选的用户名和端口,使授权机构更为特定,如
URL是一个URI,除了标识一个资源,还会为资源提供一个特定的网络位置,客户端可以用它来获取这个资源的一个表示。通用的URI可以告诉你一个资源是什么,但是无法告诉你它在哪里,以及如何得到这个资源。
2、URL类
java.net.URL类是对统一资源定位符(如 或 )的抽象,它扩展了java.lang.Object,是一个final类,不能对其派生子类。它不依赖于继承来配置不同类型URL的实例,而使用了策略(startegy)设计模式。协议处理器就是策略,URL类构成上下文,通过它来选择不同的策略。
与InetAddress对象不同,你可以构造java.net.URL的实例。例如从字符串构造URL:
try { URL url=new URL("https://www.innovatelife.net"); } catch (MalformedURLException e) { e.printStackTrace(); }
构造相对URL:
try { URL url1=new URL("https://www.innovatelife.net/html/help.jsp"); URL url2=new URL(url1,"account.jsp"); } catch (MalformedURLException e) { e.printStackTrace(); }
除了构造函数,Java类库中的其他一些方法也返回URL对象。java.io.File类有一个toURL()方法,它返回与指定文件匹配的file URL。类加载器ClassLoader也能返回一个URL,可以加载资源(类、图片和音频文件)。
仅仅有URL并不太让人兴奋,大家关心的是URL所指向的文档中包含的数据,URL类有几个方法可以从URL获取数据。最基本也是最常用的是openStrem(),它会返回一个InputStream,可以从这个流中读取数据,如果需要更多地控制下载过程,应当调用openConnection(),这会提供一个可以配置的URLConnection,再由它得到一个InputStream,我们在第7章讨论这个方法。最后可以用getContent()向URL请求其内容,这会提供一个更完整的对象,如String或Image,同样它也会给出一个InputStream。
URL由5部分组成:模式(协议)、授权机构、路径、片段标识符(段或ref)、查询字符串。如 :8080。9个公共方法提供了URL这些部分的只读访问:getFile()、getHost()、getPort()、getProtocol()、getRef()、getQuery()、getPath()、getUserInfo()和getAuthority()。
3、URI类
URI是对URL的抽象,实际使用的URI大多是URL,但大多数规范和标准(如XML)都是用URL定义的。java.net.URI和java.net.URL类的区别表现在3个重要的方面:
- URI类完全有关于资源的标识和URI的解析,它没有提供方法来获取URI所标识资源的表示。
- 相比URL类,URI类与相关的规范更一致。
- URI对象可以表示相对URI,URL类在存储URI之前会将其绝对化。
正常情况下,假如你想下载一个URL的内容,应当使用URL类,如果想使用URL来完成标识而不是获取(例如表示一个XML命名空间),就应当使用URI类。二者都需要时,可以通过toURL()方法将URI转换为URL,还可以使用toURI()方法将URL转换为URI。
4、x-www-form-urlencoded
URL中使用的字符必须来自ASCII的一个固定的子集,包括:
- 大写字母A-Z。
- 小写字母a-z。
- 数字0-9。
- 标点符号字符-_.!~*'(,)。
字符:/&?;$+=%也可以使用,但只用于特定的用途。如果这些字符出现在路径或查询字符串中,它们以及所有其他字符都应当编码。Java提供了URLEncoder和URLDecoder类,可以对这种格式的字符串编解码。
import java.io.UnsupportedEncodingException;import java.net.URLEncoder;public class QueryString { private StringBuilder query=new StringBuilder(); public QueryString() { } public synchronized void add(String name,String value){ query.append("&"); encode(name, value); } private synchronized void encode(String name,String value){ try { query.append(URLEncoder.encode(name, "UTF-8")); query.append("="); query.append(URLEncoder.encode(value, "UTF-8")); } catch (UnsupportedEncodingException e) { e.printStackTrace(); } } public synchronized String getQuery(){ return query.toString(); } @Override public String toString() { return getQuery(); }}
使用这个类,可以对URL参数编码:
QueryString queryString = new QueryString(); queryString.add("startTime", "2017-04-24 00:00:00"); queryString.add("endTime", "2017-04-25 00:00:00"); queryString.add("cost_type", "daily"); String url = "http://money.innovatelife.net/cost/list?" + queryString; System.out.println(url); //http://money.innovatelife.net/cost/list?&startTime=2017-04-24+00%3A00%3A00&endTime=2017-04-25+00%3A00%3A00&cost_type=daily
对应的URLDecoder类有一个静态方法decode(),它会对用x-www-form-urlencoded格式编码的字符串进行解码。也就是说,将所有加号转换为空格,所有百分号转义字符转换为对应的字符。
5、代理
许多系统通过代理服务器(proxy server)访问Web,有时还回访问Internet的其他非HTTP部分。代理服务器接收到本地客户端到远程服务器的请求,代理服务器向远程服务器发出请求,再将结果转发回本地客户端。这样做1、处于安全原因,如防止远程主机了解关于本地网络配置的秘密细节,2、限制可以浏览的网站,3、处于性能的考虑,缓存文档。基于URL类的Java程序可以使用大多数常见的代理服务器和协议,你要选择URL类,而不是在原始socket上处理你自己的HTTP或其他客户端。