【java学习笔记六十二】网络编程之URL

URL和URI

URL可以唯一地标识一个资源在Internet上的位置。URL是最常见的URI

URI

URI是采用一种特定的语法标识一个资源的字符串.例如https://www.myserver.com/a.png

URI的结构:
模式:模式特定部分

 

常见的模式有:

data file ftp http mailto 
magnet telnet urn

 

模式特定部分一般采用层次的结构,非ASCII字符要转换成UTF-8编码的十六进制码

URLs

URL是一个URI,除了标识一个资源,还会为资源提供一个特定的网络位置,客户端可以用它来获取这个资源。与之不同的时,通常URI可以告诉你一个资源是什么,但是无法告诉你在哪里,以及如何得到这个资源。

URL中的网络位置通常包括用来访问服务器的协议(FTP、HTTP)、服务器的主机或IP地址、以及文件在该服务器上的路径。

URL的语法为:

protocol://userInfo@host:port/path?query#fragment

 

这里的protocol就是URI的模式(scheme);userInfo可选;端口也是可选的;用户信息、主机和端口构成一个authority;fragment是片段,指向远程资源的某个特定的部分,比如某个HTML的标签的ID(这样的话,严格上说是URL引用,但是JAVA不做区分)。

相对URL

URL可以继承其父文档的协议、主机名和路径,因此可以使用相对URL,例如:

<a href="javafaq.html">

 

这个超链接在http://www.gg.com/gg/a.html中,那么这连接的地址是:http://www.gg.com/gg/javafaq.html

如果相对连接是以“/”开头,那么它相对于文档根目录,例如:

<a href="/projects/ipv6">

 

URL类

java的URL是一个final类,因此是线程安全的,并且使用了策略设计模式
创建URL的方法:

public URL(String url) throws MalformedURLException;
public URL(String protocol, String hostname, String file) throws MalformedURLException;
public URL(String protocol, String host, int prot, String file) throws MalformedURLEXception;
public URL(URL base, String relative) throws MalformedURLException

 

能否支持某个协议取决于虚拟机,如果不支持可以安装一个协议处理器,当然更好的方法就是使用一个库
除了验证能否识别URL模式外,java不会它构造的URL完成任何正确性检查。程序员要负责确保所创建的URL是合法的。

从组成部分创建URL

public URL(String protocol, String hostname, String file) throws MalformedURLException;

此方法等同于调用带四个参数的构造方法,四个参数为 protocol、host、-1(即采用默认端口) 和 file。 此构造方法不执行对输入的验证。注意file中不要忘了写“/”

其他方式获取URL
java.io.File类有一个toURL()方法,可以产生一个和平台有关的URL

ClassLoader.getSystemResource(String name)可以返回一个URL
ClassLoader.getSystemRecources(String name)返回包含URL的Enumeration
classloader的实例方法getResource(String name)会在所引用类加载器使用路径中搜索指定资源的URL

从URL获取数据

public InputStream openStream() throws IOException;

使用案例:

try{
    URL u = new URL("http://www.lolcats.com");
    try (InputStream in = u.openStream()){
        int c;
        while((c=in.read())!=-1) System.out.write(c);
    }
} catch(IOException ex){
    System.out.println(ex);
}

 

显示一个html页面(这里默认为ASCII码字符,实际上html是存在多种编码的)

public class SourceViewer {
    public static void main(String[] args) {
        if (args.length > 0) {
            InputStream in = null;
            try {
                URL u = new URL(args[0]);
                in = u.openStream();
                in = new BufferedInputStream(in);
                Reader r = new InputStreamReader(in);
                int c ;
                while ((c = r.read()) != -1) {
                    System.out.println((char) c);
                }
            } catch (MalformedURLException e) {
                e.printStackTrace();
            } catch (IOException e) {
                e.printStackTrace();
            } finally {
                if (in != null) {
                    try {
                        in.close();
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }
            }
        }
    }
}

 

public URLConnection openConnection() throws IOException

该方法为指定的URL打开一个socket,并返回一个URLConneciotn对象。该对象表示一个网络资源的打开的链接,如果失败将会抛出异常。如果希望与服务器直接通信,应当使用这个方法,除了访问原始的文档本身外,它还能访问这个协议指定的元数据,例如HTML的首部。
该方法用一个重载的版本:
可以指定哪个代理服务器传递连接

public URLConnection openConnection(Proxy proxy) throws IOException
public final Object getContent() throws IOException

通过URL数据建立对象,如果URL指示的是某中文本,返回的通常是某种InputStream。如果指示一个图像,返回一个java.awt.ImageProducer对象(getContent会从服务器获取的首部查找Content-type字段,如果服务器没有使用MIME首部,或者是不熟悉的Content-type就返回InputStream)

public final Object getContent(Class[] classes) throws IOException

上面方法的重载版本,允许选择返回的类(多个类,返回第一个匹配项),常配合instanceof来使用

分解URL

URL由以下5部分组成:模式;授权机构;路径;片段标识符;查询字符串
授权机构可以进一步分为:用户信息,主机和端口 例如:http://admin@www.blackstar.com:8080/
URL类提供了9个方法访问这些数据:

public String getProtocol();
public String getHost();
public int getPort();//没有指定端口,就返回-1
public int getDefaultPort();
public String getFile();//主机名后的都是file
public String getPath();//不包括查询字符串
public String getRef();//返回片段标识符
public String getQuery();
public String getUserInfo();//返回@符号之前的用户信息
public String getAuthority();

 

equals和hashcode

仅当两个URL都指向相同的主机、端口和路径上的相同资源,而且有相同的片段标识符和查询字符串,才认为两个URL是相等的。不过,equals会尝试用DNS解析主机,来判断连个主机是否相同。这说明URL是一个阻塞的IO操作,因此要避免在HashMap等依赖equals的数据结构中使用(一般情况下应该使用URI来存)。

public boolean sameFile(URL other)

也是比较URL,也会进行DNS查询,但不会比较片段标识符

转换

URL有三个方法可以将一个实例转换为另一种形式,分别是:

toString() 
toExternalForm() 可以在浏览器中打开
toURI() 

 

URI类

URI是对URL的抽象,不仅包括URL,还包括URN(统一资源名),实际上大多URI就是URL。两者的区别有

URI类只和资源的标识和URI的解析有关,没有提供获取RUI所标识资源的方法
相比URI类,URI与相关的规范更一致
URI对象可以表示相对URI,URL类在存储URI之前会将其绝对化

 

构造

与URL不同,URI类不依赖于底层协议处理器。只要URI语法正确,java就不需要为了创建URI对象而理解其协议。

public URI(String uri) throws URISyntaxException;
public URI(String scheme, String schemeSpecificPart, String fragment) throw...;
public URI(String shceme, String host, String path, String fragment) throws ...;
public URI(String scheme, String authority, String path, String query, String fragment) throws ...;
public URI(String scheme, String userInfo, String host, int port, String path,String query, String fragment) throws URISyntaxException;

 

上面方法中任何参数都可以省略,从而忽略该参数

URI的各部分

URI的引用主要包括三个部分:模式、模式特定部分、片段标识符
如果省略模式,表示这是一个相对URI,如果省略片段标识符,这个URI就是一个纯URI。
主要方法如下:

public Sting getScheme();

public String getSchemeSpecificPart();
public String getRawSchemeSpecificPart();

public String getFragment();
public String getRawFragment();

 

带raw的是编码形式,不带raw的是对所有用百分号转义的字符进行解码,然后返回。scheme只有ASCII码形式。
有模式的URI是绝对URI,没有模式的是相对URI

public boolean isAbsolute();//判断是不是绝对URI
public boolean isOpaque();//是否 不是有层次的URI

 

如果是有层次的,有相关的获取相应部分的方法:

public String getAuthority();
public String getFragment();
public String getHost();
public String getPath();
public String getPort();
public String getQuery();
public String getUserInfo();

 

这些都是解码后,也就是百分号转义会改为它们实际表示的字符,如果希望得到原始的部分,在get后面加上Raw,注意,host和Port(返回-1表示省略端口)没有raw方法,因为都是由ASCII码组成的

java并不是在开始就检测授权机构部分中的语法错误,所以无法返回授权机构的各个部分:端口、主机、用户信息。可以调用parseServerAuthority()强制重新解析授权机构。(注意URI是final类

解析相对URI

public URI resolve(URI uri);
public URI resolve(String uri);
public URI relative(String uri);

 

相对URI构造绝对URI:

URI absolute = new URI("http://www.example.com");
URI relative = new URI("images/logo.png");
URI resolved = absolute.resolve(relative);

 

当然也可以通过相对URI构造相对URI

绝对URI构造相对URI:

URI absolute = new URI("http;//www.example.com/images/logo.png");
URI top = new URI("http://www.example.com/");
URI relative = top.relativize(absolute);

 

equals

在进行equals比较时,相等的URI必须都是层次或非层次的,模式和授权机构不会区分大小写,转义字符在比较前不解码
URI实现了Comparable,一次URI可以排序。

字符串表示

toString() 未编码的字符串形式,更方便阅读,但显示结果不能保证是一个语法正确的URI
toASCIIString() 编码后的字符串形式,推荐使用

URLEncoder

对字符串完成URL编码
例如:

String encoded = URLEncoder.encode("this*string*has*asterisks","UTF-8");

注意这个方法对于/,&,=,: 都进行了编码,因此有时候需要分部分的编码(只编码需要编码的,而不是整个URL一起编码)

URLDEcoder

public static String decode(String s, String encoding) throws UnsupportedEncodingException;

该方法可以传入整个URL

代理

Proxy类有三种代理:

Proxy.Type.DIRECT //实际就是没代理
Proxy.Type.HTTP
Proxy.Type.SOCKS

 

代理信息可以用SocketAddress对象表示:

SocktAddress address - new InetSocketAddress("proxy.example.com",80);
Proxy proxy = new Proxy(Proxy.Type.HTTP, address);

 

每个运行中的虚拟机都有一个Java.net.ProxySelector对象,可以使用自己的ProxySelector子类来替代默认的选择器
ProxySelector类可以根据URI返回合适的代理:

public abstarct list<Proxy> select(URI uri);//返回代理
public void connectFailed(URI uri, SocketAddress sa, IOException ioe);//同时必须实现这个连接失败的方法

 

案例:

public class LocalProxySelector extends ProxySelector {
    private List<URI> failed = new ArrayList<>();
    @Override
    public List<Proxy> select(URI uri) {
        List<Proxy> result = new ArrayList<>();
        if (failed.contains(uri) || !"http".equalsIgnoreCase(uri.getScheme())) {
            result.add(Proxy.NO_PROXY);
        } else {
            SocketAddress proxyAddress = new InetSocketAddress("proxy.example.com", 80);
            Proxy proxy = new Proxy(Proxy.Type.HTTP, proxyAddress);
            result.add(proxy);
        }
        return result;
    }

    @Override
    public void connectFailed(URI uri, SocketAddress sa, IOException ioe) {
        failed.add(uri);
    }
}

 

改变虚拟机默认的选择器:

ProxySelector selector = new LocalProxySelector();
ProxySelector.setDefault(seletor);

 

因为虚拟机只有一个ProxySelector,因此不推荐在共享的环境下使用这个方法

 

发表评论

电子邮件地址不会被公开。 必填项已用*标注