URL与URI
java中对资源的抽象可以简单的概括为URI类与URL类
而在概念上URI是URL的超集,URL通常是代表网络资源,与上一节提到的InetAddress关系密切
接下来,我们详细地看看这两个东西是怎么回事
URI
URI能标识资源,而不包含在哪里;于此相对的URL既能标识还能给出位置。
URI的写法为
模式:模式特定部分
这里的模式可以有
data 链接中包含Base64编码
file 本地磁盘上的文件
ftp FTP服务器
http 使用HTTP的服务器
mailto 电子邮件地址
magnet 可以通过对等网络比如BitTorrent下载的资源
telnet 与基于Telnet的服务链接
urn 统一资源名
而模式特定部分并没有特别的形式,不过都采用一种层次结构形式,注意的是下面的每个部分里如果有特殊字符则需要进行编码
//authrity/path?query
URL与URL类
URL中的网络位置通常包括用来访问服务器的协议(例如FTP,HTTP),服务器的主机名或者ip,或者文件在服务器上的路径。
url的语法为:
protocol://userInfo@host:port/path?query#fragment
协议:// 用户信息 @ 主机 :端口 / 路径 ? 查询 #片段
useInfo@host:port即URI中权威机构
URL类的使用
(1) URL实例创建
创建实例的构造方法有很多,如果url的语法不对或者为不支持的协议创建实例则会爆出异常
public URL(String protocol, String host, int port, String file)
public URL(String protocol, String host, String file)
public URL(String protocol, String host, int port, String file,URLStreamHandler handler)
public URL(String spec)
public URL(URL context, String spec) //相对url构造
public URL(URL context, String spec, URLStreamHandler handler)//这个URLStreamHandler能不用就不用,带来的麻烦会更多
以下代码可以尝试解析某种特定的协议
private static void testProtocol(String url) {
try {
URL u = new URL(url);
System.out.println(u.getProtocol() + " is supported");
} catch (MalformedURLException ex) {
String protocol = url.substring(0, url.indexOf(':'));
System.out.println(protocol + " is not supported");
}
}
@Test
public void t(){
// hypertext transfer protocol
testProtocol("http://www.adc.org");
// secure http
testProtocol("https://www.amazon.com/exec/obidos/order2/");
// file transfer protocol
testProtocol("ftp://ibiblio.org/pub/languages/java/javafaq/");
// Simple Mail Transfer Protocol
testProtocol("mailto:elharo@ibiblio.org");
// telnet
testProtocol("telnet://dibner.poly.edu/");
// local file access
testProtocol("file:///etc/passwd");
// gopher
testProtocol("gopher://gopher.anc.org.za/");
// Lightweight Directory Access Protocol
testProtocol("ldap://ldap.itd.umich.edu/o=University%20of%20Michigan,c=US?postalAddress");
// JAR
testProtocol(
"jar:http://cafeaulait.org/books/javaio/ioexamples/javaio.jar!" + "/com/macfaq/io/StreamCopier.class");
// NFS, Network File System
testProtocol("nfs://utopia.poly.edu/usr/tmp/");
// a custom protocol for JDBC
testProtocol("jdbc:mysql://luna.ibiblio.org:3306/NEWS");
// rmi, a custom protocol for remote method invocation
testProtocol("rmi://ibiblio.org/RenderEngine");
// custom protocols for HotJava
testProtocol("doc:/UsersGuide/release.html");
testProtocol("netdoc:/UsersGuide/release.html");
testProtocol("systemresource://www.adc.org/+/index.html");
testProtocol("verbatim:http://www.adc.org/");
}
(2) 从url得到数据
//1
InputStream in = url.openStrem(); //1
//2 你可以得到更多的信息,可以喝服务器直接对话,对连接进行写入操作
URLConnection uc = url.openConnection();
InputStream in == uc.getInputStream();
//3 url.getContent()
(3) url内含信息获取
通过url的实例得到其内部的信息,如以下的代码
public class URLSplitter {
public static void main(String args[]) {
try {
URL u = new URL("http://a@www.baidu.com:8000/aa/aa?c=s");
System.out.println("The URL is " + u);
System.out.println("The scheme is " + u.getProtocol());
System.out.println("The user info is " + u.getUserInfo());
String host = u.getHost();
if (host != null) {
int atSign = host.indexOf('@');
if (atSign != -1)
host = host.substring(atSign + 1);
System.out.println("The host is " + host);
} else {
System.out.println("The host is null.");
}
System.out.println("The port is " + u.getPort());
System.out.println("The path is " + u.getPath());
System.out.println("The ref is " + u.getRef());
System.out.println("The query string is " + u.getQuery());
} catch (MalformedURLException ex) {
ex.printStackTrace();
//System.err.println(args[i] + " is not a URL I understand.");
}
System.out.println();
}
}
(4) 比较方法
equals与hashCode就如预想的一样使用即可
equal()方法会尝试用DNS解析来判断两个主机是否相同
URI类使用
URL对象是对应网络获取的应用层协议的一个表示。
URI纯用于解析和处理字符串,URI类没有网络获取功能。
(1) 构造
public URI(String str) throws URISyntaxException
public URI(String scheme,String userInfo, String host, int port,
String path, String query, String fragment) throws URISyntaxException
public URI(String scheme, String authority,
String path, String query, String fragment)throws URISyntaxException
public URI(String scheme, String host, String path, String fragment) throws URISyntaxException
public URI(String scheme, String ssp, String fragment) throws URISyntaxException
//静态工厂方法,最终调用的是URI(String str)
public static URI create(String str)
之所以没有getRawScheme()方法,是因为URI规范中强制规定,所有的模式名称必须由URI规范中合法的ASCII字符组成,也就是模式名称中不允许存在百分号转义。
上面的getRawSchemeSpecificPart()是返回原始的模式特定部分,getSchemeSpecificPart()返回了经过解码(decode)的模式特定部分。
同理,getRawFragment()返回原始的片段标识符,而getFragment()返回经过解码(decode)的片段标识符
(2) 获取uri各部分
public String getScheme()
public String getRawSchemeSpecificPart()
public String getSchemeSpecificPart()
public String getFragment()
public String getRawFragment()
(3) uri的基本属性
//是否绝对URI
public boolean isAbsolute()
//是否不透明的URI,如果isOpaque()返回true,URI是不透明的
//只能获取到模式、模式特定部分和片段标识符,获取不了host、port等
public boolean isOpaque()
public String getAuthority()
public String getRawAuthority()
public String getRawUserInfo()
public String getUserInfo()
public String getHost()
public int getPort()
public String getRawPath()
public String getPath()
public String getRawQuery()
public String getQuery()
isOpaque()方法为true的时候,说明URI是不透明的,不透明的URI无法获取授权机构、路径、端口和查询参数等。
另外,上面的获取属性的方法有些方法存在getRawFoo()的对应方法,这些getRawFoo()方法就是获取原始属性的值,如果没有Raw关键字,则返回解码后的字符串值
(4) uri 与 url的转化
URI中提供了三个方法用于绝对URI和相对URI之间的转换:
//resolve方法是基于绝对URI和相对URI把相对URI补全为绝对URI,例如:
public URI resolve(URI uri)
public URI resolve(String str)
//relativize方法是基于绝对URI和相对URI反解出绝对URI中的相对URI部分,例如:
public URI relativize(URI uri)
public static void main(String[] args) throws Exception{
URI absolute = URI.create("http://localhost:8080/index.html");
URI relative = URI.create("/hello.html");
URI resolve = absolute.resolve(relative);
//将会打印http://localhost:8080/hello.html
System.out.println(resolve);
}
public static void main(String[] args) throws Exception{
URI absolute = URI.create("http://localhost:8080/index.html");
URI relative = URI.create("http://localhost:8080/");
URI resolve = relative.relativize(absolute);
//将会打印 index.html
System.out.println(resolve);
}
URL的编码和解码
Java中提供两个类java.net.URLEncoder和java.net.URLDecoder分别用于URL的编码和解码,注意需要使用两个参数的静态方法encode(String value,String charset)和decode(String value,String charset),单参数的方法已经过期,不建议使用。
注意在使用java.net.URLEncoder和java.net.URLDecoder的时候,它们的API不会判断URL的什么部分需要编码和解码,什么部分不需要编码和解码,直接整个URL字符串丢进去编码一定会出现意料之外的结果。
因此正确的做法应该是对Path和Path之后的字符进行编码和解码
public static void main(String[] args) throws Exception {
String raw= "http://localhost:9090/index?name=张大doge";
String base = raw.substring(raw.lastIndexOf("//"));
String pathLeft = base.substring(base.lastIndexOf("/") + 1);
String[] array = pathLeft.split("\\?");
String path = array[0];
String query = array[1];
base = raw.substring(0,raw.lastIndexOf(path));
path = URLEncoder.encode(path, "UTF-8");
String[] queryResult = query.split("=");
String queryKey = URLEncoder.encode(queryResult[0], "UTF-8");
String queryValue = URLEncoder.encode(queryResult[1], "UTF-8");
System.out.println(base + path + "?" + queryKey + "=" + queryValue);
}
//输出结果:http://localhost:9090/index?name=%E5%BC%A0%E5%A4%A7doge
//其中UTF-8编码中张的十六进制表示为E5 BC A0,大的十六进制编码为E5 A4 A7
Proxy代理类使用
在Java中除了TCP连接使用传输层的SOCKET代理,其他应用层代理都不支持。
Java对于SOCKET没有提供禁用代理的选项,但是可以通过下面三个系统属性配置来开启和限制代理:
http.proxyHost:代理服务器的主机名,默认不设置此系统属性。
http.proxyPort:代理服务器的端口号,默认不设置此系统属性。
http.noneProxyHosts:不需要代理访问的主机名,多个用竖线|分隔,默认不设置此系统属性。
举个例子:
System.setProperty("http.proxyHost", "localhost");
System.setProperty("http.proxyPort", 1080);
System.setProperty("http.noneProxyHosts", "www.baidu.com|github.com");
java.net.Proxy类提供了对代理服务器更细粒度的控制,也就是说这个类允许在编程的使用使用不同的远程服务器作为代理服务器,而不是通过系统属性全局配置代理。Proxy目前支持三种代理类型,分别是:
- Proxy.Type.DIRECT:直连,也就是不使用代理。
- Proxy.Type.HTTP:HTTP代理。
- Proxy.Type.SOCKS:socket的V4或者V5版本的代理。
SocketAddress socketAddress = new InetSocketAddress("localhost", 80);
Proxy proxy = new Proxy(Proxy.Type.HTTP, socketAddress);
Socket socket = new Socket(proxy);
//...
每个运行中的Java虚拟机都会存在一个java.net.ProxySelector实例对象,用来确定不同连接使用的代理服务器。
默认的java.net.ProxySelector的实现是sun.net.spi.DefaultProxySelector的实例,它会检查各种系统属性和URL的协议,再决定如果连接到不同的远程代理服务器。
当然,开发者也可以继承和实现自定义的java.net.ProxySelector,从而可以根据协议、主机、路径日期等其他标准来选择不同的代理服务器。
java.net.ProxySelector的几个核心的抽象方法如下:
//获取默认的ProxySelector实例
public static ProxySelector getDefault()
//设置默认的ProxySelector实例
public static void setDefault(ProxySelector ps)
//通过URI获取可用的代理列表
public abstract List<Proxy> select(URI uri)
//告知ProxySelector不可用的代理和出现的异常
public abstract void connectFailed(URI uri, SocketAddress sa, IOException ioe)
如果需要扩展的话,最好加入缓存的功能,缓存可用的Proxy列表,一旦出现Proxy不可用,通过connectFailed进行清理和剔除不可用的代理节点即可。