Netty源码解析之 java网络编程篇(2)


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进行清理和剔除不可用的代理节点即可。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 217,734评论 6 505
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,931评论 3 394
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 164,133评论 0 354
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,532评论 1 293
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,585评论 6 392
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,462评论 1 302
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,262评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,153评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,587评论 1 314
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,792评论 3 336
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,919评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,635评论 5 345
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,237评论 3 329
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,855评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,983评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,048评论 3 370
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,864评论 2 354