服务热线:13616026886

技术文档 欢迎使用技术文档,我们为你提供从新手到专业开发者的所有资源,你也可以通过它日益精进

位置:首页 > 技术文档 > JAVA > 新手入门 > 基础入门 > 查看文档

urls,uris,proxies和passwords 解析

类urlencoder 和 类urldecoder

web设计者面临的众多难题之一便是怎样处理不同操作系统间的差异性。这些差异性能引起url方面的问题:例如,一些操作系统允许文件名中含有空格符,有些又不允许。大多数操作系统不会认为文件名中含有符号“#”会有什么特殊含义;但是在一个url中,符号“#”表示该文件名已经结束,后面会紧跟一个fragment(部分)标识符。其他的特殊字符,非字母数字字符集,它们在url或另一个操作系统上都有其特殊的含义,表述着相似的问题。为了解决这些问题,我们在url中使用的字符就必须是一个ascii字符集的固定字集中的元素,具体如下:
1.大写字母a-z
2.小写字母a-z
3.数字 0-9
4.标点符 - _ . ! ~ * ' (和 ,)

诸如字符: / & ? @ # ; $ + = 和 %也可以被使用,但是它们各有其特殊的用途,如果一个文件名包括了这些字符( / & ? @ # ; $ + = %),这些字符和所有其他字符就应该被编码。

编码过程非常简单,任何字符只要不是ascii码数字,字母,或者前面提到的标点符,它们都将被转换成字节形式,每个字节都写成这种形式:一个“%”

后面跟着两位16进制的数值。空格是一个特殊情况,因为它们太平常了。它除了被编码成“%20”以外,还能编码为一个“+”。加号(+)本身被编码为%2b。当/ # = & 和?作为名字的一部分来使用时,而不是作为url部分之间的分隔符来使用时,它们都应该被编码。

warning这种策略在存在大量字符集的异构环境中效果不甚理想。例如:在u.s. windows 系统中, é 被编码为 %e9. 在 u.s. mac中被编码为%8e。这种不确定性的存在是现存的uri的一个明显的不足。所以在将来uri的规范当中应该通过国际资源标识符(iris)进行改善。

类url并不自动执行编码或解码工作。你能生成一个url对象,它可以包括非法的ascii和非ascii字符和/或%xx。当用方法getpath() 和toexternalform( ) 作为输出方法时,这种字符和转移符不会自动编码或解码。你应对被用来生成一个url对象的字符串繁荣对象负责,确保所有字符都会被恰当地编码。


幸运的是,java提供了一个类urlencoder把string编码成这种形式。java1.2增加了一个类urldecoder它能以这种形式解码string。这两个类都不用初始化

public class urldecoder extends object
public class urlencoder extends object



urlencoder

在java1.3和早期版本中,类java.net.urlencoder包括一个简单的静态方法encode( ) 它对string以如下规则进行编码:
public static string encode(string s)
这个方法总是用它所在平台的默认编码形式,所以在不同系统上,它就会产生不同的结果。结果java1.4中,这个方法被另一种方法取代了。该方法要求你自己指定编码形式:

public static string encode(string s, string encoding) 
  throws unsupportedencodingexception



两种关于编码的方法,都把任何非字母数字字符转换成%xx(除了空格,下划线(_),连字符(—),句号(。),和星号(*))。
两者也都编码所以的非ascii字符。空格被转换成一个加号。这些方法有一点过分累赘了;它们—也把“~”,“‘”,“()”转换成%xx,即使它们完全用不着这样做。尽管这样,但是这种转换并没被url规范所禁止。所以web浏览器会自然地处理这些被过分编码后的url。

两中关于编码的方法都返回一个新的被编码后的string,java1.3的方法encode( ) 使用了平台的默认编码形式,得到%xx。这些编码形式典型的有:在 u.s. unix 系统上的iso-8859-1, 在u.s. windows 系统上的cp1252,在u.s. macs上的macroman,和其他本地字符集等。因为编码解码过程都是与本地操作平台相关的,所以这些方法是另人不爽的,不能跨平台的。这就明确地回答了为什么在java1.4中这种方法被抛弃了,转而投向了要求以自己指定编码形式的这种方法。尽管如此,如果你执意要使用所在平台的默认编码形式,你的程序将会像在java1.3中的程序一样,是本地平台相关的。在另一种编码的方法中,你应该总是事业utf-8,而不是其他什么。utf-8比起你选的其他的编码形式来说,它能与新的web浏览器和更多的其他软件相兼容。

例子7-8是使用urlencoder.encode( ) 来打印输出各种被编码后的string。它需要在java1.4或更新的版本中编译和运行。
example 7-8. x-www-form-urlencoded strings

import java.net.urlencoder;
import java.io.unsupportedencodingexception;


public class encodertest {

  public static void main(string[] args) {

    try {
      system.out.println(urlencoder.encode("this string has spaces",  
                                              "utf-8"));
      system.out.println(urlencoder.encode("this*string*has*asterisks",  
                                              "utf-8"));
      system.out.println(urlencoder.encode("this%string%has%percent%signs",
                                              "utf-8"));
      system.out.println(urlencoder.encode("this+string+has+pluses",  
                                              "utf-8"));
      system.out.println(urlencoder.encode("this/string/has/slashes",  
                                              "utf-8"));
      system.out.println(urlencoder.encode("this/"string/"has/"quote/"marks",
                                              "utf-8"));
      system.out.println(urlencoder.encode("this:string:has:colons",  
                                              "utf-8"));
      system.out.println(urlencoder.encode("this~string~has~tildes",  
                                              "utf-8"));
      system.out.println(urlencoder.encode("this(string)has(parentheses)",
                                              "utf-8"));
      system.out.println(urlencoder.encode("this.string.has.periods",
                                              "utf-8"));
      system.out.println(urlencoder.encode("this=string=has=equals=signs",
                                              "utf-8"));
      system.out.println(urlencoder.encode("this&string&has&ersands",
                                              "utf-8"));
      system.out.println(urlencoder.encode("thiséstringéhasé
                                              non-ascii characters", "utf-8"));
    }
    catch (unsupportedencodingexception ex) {
      throw new runtimeexception("broken vm does not support utf-8");
    }

  }

}



下面就是它的输出。需要注意的是这些代码应该以其他编码形式被保存而不是以ascii码的形式,还有就是你选择的编码形式应该作为一个参数传给编译器,让编译器能据此对源代码中的非ascii字符作出正确的解释。

% javac -encoding utf8 encodertest
% java encodertest
this+string+has+spaces
this*string*has*asterisks
this%25string%25has%25percent%25signs
this%2bstring%2bhas%2bpluses
this%2fstring%2fhas%2fslashes
this%22string%22has%22quote%22marks
this%3astring%3ahas%3acolons
this%7estring%7ehas%7etildes
this%28string%29has%28parentheses%29
this.string.has.periods
this%3dstring%3dhas%3dequals%3dsigns
this%26string%26has%26ampersands
this%c3%a9string%c3%a9has%c3%a9non-ascii+characters



特别需要注意的是这个方法编码了符号,“/”
,&,=,和:。它不会尝试着去规定在一个url中这些字符怎样被使用。由此,所以你不得不分块编译你的url,而不是把整个url一次传给这个方法。这是很重要的,因为对类urlencoder最通常的用法就是查询string,为了和服务器端使用get方法的程序进行交互。例如,假设你想编码这个查询sting,它用来搜索altavista网站:

pg=q&kl=xx&stype=stext&q=+"java+i/o"&search.x=38&search.y=3



这段代码对其进行编码:

string query = urlencoder.encode(
"pg=q&kl=xx&stype=stext&q=+/"java+i/o/"&search.x=38&search.y=3");
system.out.println(query);



不幸的是,得到的输出是:

pg%3dq%26kl%3dxx%26stype%3dstext%26q%3d%2b%22java%2bi%2fo%22%26search
.x%3d38%26search.y%3d3



出现这个问题就是方法urlencoder.encode( ) 在进行盲目地编码。它不能区分在url或者查询string中被用到的特殊字符(象前面string中的“=”,和“&”)和确实需要被编码的字符。由此,所以url需要像下面这样一次只编码一块:

string query = urlencoder.encode("pg");
query += "=";
query += urlencoder.encode("q");
query += "&";
query += urlencoder.encode("kl");
query += "=";
query += urlencoder.encode("xx");
query += "&";
query += urlencoder.encode("stype");
query += "=";
query += urlencoder.encode("stext");
query += "&";
query += urlencoder.encode("q");
query += "=";
query += urlencoder.encode("/"java i/o/"");
query += "&";
query += urlencoder.encode("search.x");
query += "=";
query += urlencoder.encode("38");
query += "&";
query += urlencoder.encode("search.y");
query += "=";
query += urlencoder.encode("3");
system.out.println(query);



这才是你真正想得到的输出:

pg=q&kl=xx&stype=stext&q=%2b%22java+i%2fo%22&search.x=38&search.y=3



例子7-9是一个querystring 类。在一个java对象中,它使用了类urlencoder 来编码连续的属性名和属性值对,这个java对象全被用来发送数据到服务器端的程序。当你在创建一个querystring 对象时,你可以把查询string中的第一个属性对传递给类querystring 的构造函数,得到初始string 。如果要继续加入后面的属性对,就应调用方法add(),它也能接受两个string作为参数,能对它们进行编码。方法getquery( ) 返回一个属性对被逐个编码后得到的整个string。

example 7-9. -the querystring class

package com.macfaq.net;

import java.net.urlencoder;
import java.io.unsupportedencodingexception;

public class querystring {

  private stringbuffer query = new stringbuffer( );

  public querystring(string name, string value) {
    encode(name, value);
  }
  
  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 ex) {
      throw new runtimeexception("broken vm does not support utf-8");
    }
  }
  
  public string getquery( ) {
    return query.tostring( );
  }
  
  public string tostring( ) {
    return getquery( );
  }

}



利用这个类,现在我们就能对前面那个例子中的string进行编码了:

querystring qs = new querystring("pg", "q");
qs.add("kl", "xx");
qs.add("stype", "stext");
qs.add("q", "+/"java i/o/"");
qs.add("search.x", "38");
qs.add("search.y", "3");
string url = "http://www.altavista.com/cgi-bin/query?" + qs;
system.out.println(url);



urldecoder
与urlencoder 类相对应的urldecoder 类有两种静态方法。它们解码以x-www-form-url-encoded这种形式编码的string。也就是说,它们把所有的加号(+)转换成空格符,把所有的%xx分别转换成与之相对应的字符:

public static string decode(string s) throws exception
public static string decode(string s, string encoding)  // java 1.4
  throws unsupportedencodingexception



第一种解码方法在java1.3和java1.2中使用。第二种解码方法在java1.4和更新的版本中使用。如果你拿不定主意用哪种编码方式,那就选择utf-8吧。它比其他任何的编码形式更有可能得到正确的结果。

如果string包含了一个“%”,但紧跟其后的不是两位16进制的数或者被解码成非法序列,该方法就会抛出illegalargumentexception 异常。当下次再出现这种情况时,它可能就不会被抛出了。这是与运行环境相关的,当检查到有非法序列抛不抛出illegalargumentexception 异常,这时到底会发生什么是不确定的。在sun's jdk 1.4中,不会抛出什么异常,它会把一些莫名其妙的字节加进不能被顺利编码的string中。这的确令人头疼,可能就是一个安全漏洞。

由于这个方法没有触及到非转义字符,所以你可以把整个url作为参数传给该方法,不用像之前那样分块进行。例如:

string input = "http://www.altavista.com/cgi-bin/" + 
"query?pg=q&kl=xx&stype=stext&q=%2b%22java+i%2fo%22&search.x=38&search.y=3";
try {
  string output = urldecoder.decode(input, "utf-8");
  system.out.println(output);
}



the uri class

uri是url的一个抽象,它不仅包括了统一资源定位符(url),还包括了统一资源名(urn).大多数实际应用中使用的uri都是url,但是许多规范和标准像xml都是用uri来定义的.在java1.4和更新的版本中, uri被java.net.uri 类所表示.这个类与java.net.url 相比有如下3点重要的区别:
·        uri 类只关心资源的标识和对uri的解析.它没有方法来检索它的uri所标识的资源。
·        uri 类与url 类相比,它更能适应相关的规范。
·        一个uri 对象能表示一个相对uri 。url 类在存放之前,就已经对所有的uri进行了“绝对化”的处理。

简而言之,一个url 对象就是网络应用层协议进行网络检索的一个代理,而一个uri 对象就只纯粹地做string的解析和操作的工作。uri 类没有进行网络检索的能力。url 类有一些进行string解析的方法。比如getfile( ) 和 getref( ) 方法,但很多都是蹩脚的方法,总是不完全像有关的规范上所说的那样好用。假如你现在用的是java1.4版本或更新的版本,这时你就可以做出选择,如果你想下载一个url指示的内容时,你应该使用 url类;如果你想使用uri类来进行标识的工作而不是用来检索的时候, 你应该用uri类。例如,去标识一个xml namespace 的uri。在一些情况下,当你同时需要实现这两种功能时,你可以用方法tourl( ) 把一个uri 转换成一个 url 。在java1.5中,你还能用类url 中的方法touri( )  把一个url 转换成一个uri 。

构造一个 uri
uri是由string构成的。不像类url ,类uri

不依靠底层协议的处理程序。只要uri是语义上正确的,java就不需要知道它的什么协议,然后才创建其代表的uri对象。因此,不象类url ,类uri 可以被用到新的试验性的uri协议中去。

public uri(string uri) throws urisyntaxexception

这是一个简单的构造函数,它用一个合适的string创建了一个新的uri 对象。例如:

uri voice = new uri("tel:+1-800-9988-9938");
uri web   = new uri("http://www.xml.com/pub/a/2003/09/17/stax.html#id=_hbc");
uri book  = new uri("urn:isbn:1-565-92870-9");



如果参数string不符合uri的语法规则—比如,如果它以“:”开始—构造函数就会抛出urisyntaxexception 异常。这是一个需要被检察的异常,所以你要么捕获该异常,要么在构造函数会被执行的方法中,声明这个方法会抛出该异常。尽管如此,但是有一条语法规则是不能被检察到的。与uri规范相矛盾的是,在uri中使用的字符不会被限制在ascii字符集上。它们可以包含其它的unicode字符,比如ø 和 é。从语义上讲,对uri几乎就没有什么限制,特别是一旦不需要对非ascii字符进行编码时,一切uri都是被允许的。几乎任何的string都可以被看成是uri。

public uri(string scheme, string schemespecificpart, string fragment) throws urisyntaxexception

这个构造函数主要是用在不存在有层次关系的uri中。scheme是指uri的协议,比如http, urn, tel等。它必须专门有ascii字母,数字和标点符(+, -, 和.)组成。第一个位置上必须是一个字母。如果省略了参数scheme,就用null代替。这时创建的是一个相对uri。例如:

uri absolute = new uri("http", "//www.ibiblio.org" , null);
uri relative = new uri(null, "/javafaq/index.shtml", "today");



scheme-specific部分的内容遵从于参数scheme给出的uri协议;对一个http 协议的url来说,是一种形式,对于mail协议的 url来说又是另一种形式,对于tel协议的 uri又是其它的什么形式。因为类uri 会以%xx对非法字符进行编码,所以显而易见,你不会在这步犯语法方面的错误。最后,只要有需要的话,第三个参数可以是指一个部分标识符。同样地,在这个部分标识符中,字符的合法性也顺理成章地不需要检察。如果省略了参数fragment,就用null代替。

public uri(string scheme, string host, string path, string fragment) throws urisyntaxexception

这个构造函数被用在有层次关系的uri中,比如http 和 ftp 协议的url。主机名和路径名(用“/”分开)一起构成uri的scheme-specific部分。例如:

uri today= new uri("http", "www.ibiblio.org", "/javafaq/index.html", "today");
produces the uri http://www.ibiblio.org/javafaq/index.html#today



如果构造函数从得到的几个参数中不能获得一个有合法层次关系的uri—例如,假如给定了参数scheme,它要求这个uri是一个绝对的uri可是path参数却又不是一“/”开始的—它就会抛出异常urisyntaxexception

public uri(string scheme, string authority, string path, string query, string fragment) throws urisyntaxexception

这个构造函数和前边所讲的那个基本相同,除了它增加了一个进行查询的string 参数。例如:

uri today= new uri("http", "www.ibiblio.org", "/javafaq/index.html", 
                   "referrer=cnet&date=2004-08-23",  "today");



与通常一样,遇到任何的语法错误它都会抛出异常urisyntaxexception ,如果其中的哪个参数被省略,就用null代替。

public uri(string scheme, string userinfo, string host, int port, string path, string query, string fragment) throws urisyntaxexception

这是构造一个有层次关系的uri的父亲构造函数,也就是说,前面提到的两个构造函数都会调用它。它把认证信息(authority)分成了user info, host, 和 port几部分。它们中的每一个都具备其自身的语法规则。比如:

uri styles = new uri("ftp", "anonymous:elharo@metalab.unc.edu", 
  "ftp.oreilly.com",  21, "/pub/stylesheet", null, null);



尽管这样,但是最后得到的uri仍然得遵从uri的一般规则。同样地,如果任何一个参数被省略,就用null代替。
public static uri create(string uri)

它不是个构造函数,而是个静态factory方法。不象构造函数那样,它不会抛出异常urisyntaxexception 。如果你确信你的uri一定会是合法的,会遵从任一条规则,那么你就可以用该方法。比如,这次调用创建了一个用来匿名登陆一个ftp的uri ,它用一个邮箱地址当密码:

uri styles = uri.create(
  "ftp://anonymous:elharo%40metalab.unc.edu@ftp.oreilly.com:
                                         21/pub/stylesheet");



如果该uri确实存在问题,这个方法会抛出异常illegalargumentexception 。它是在运行时才抛出的异常,所以你不能声明或捕获它。

the parts of the uri
一个uri最多就三部分:scheme, scheme-specific , fragment identifier。一般形式:

scheme:scheme-specific-part:fragment



如果省略了scheme,这个uri就是相对的。如果fragment identifier被省略了,这个uri就很简单了。类uri有返回方法,它们能返回每个uri对象的这三部分。方法getrawfoo( ) 返回编码后的uri,与此类似的还有方法getfoo() ,它先解码%xx形式的字符,然后返回解码后的uri。

public string getscheme( )
public string getschemespecificpart( )
public string getrawschemespecificpart( )
public string getfragment( )
public string getrawfragment( )



注意: 这里没有方法getrawscheme( )  ,因为在uri规范中所有的协议名应专门由合法的ascii字符组成。不允许%xx在协议名中出现。
如果uri 对象不具备相关的内容,这些方法都返回null:例如,对一个没指定协议的相对uri,或一个没有fragment identifier的http 协议的uri。一个指定了协议(scheme)的uri是一个绝对的uri。没有指定协议的uri是相对的uri。方法isabsolute()  返回true如果这个uri是绝对的,如果是相对的就返回false:

public boolean isabsolute( )



scheme-specific在细节上的变化取决于scheme的类型。比如在一个tel url中,scheme-specific需要用类似电话号码的语法。尽管如此,但是在许多有用的uri中,包括很常见的file 和 http url里面,scheme-specific都有其独特的描述层次关系的模式,包括authority, path, query string。authority又被细分为user info, host,  port。方法isopaque()  返回false如果这是个有层次关系的uri,否则返回true—也就是说,如果是层次模糊的:

public boolean isopaque( )



如果uri是层次模糊的,所有你能得到的就只是scheme, scheme-specific ,  fragment identifier。尽管如此,但是如果uri是有层次关系的,下面的getter方法还是能获取该uri的所有内容:

public string getauthority( )
public string getfragment( )
public string gethost( )
public string getpath( )
public string getport( )
public string getquery( )
public string getuserinfo( )



所有这些方法返回的都是解码后的内容;换句话说,这种形式%xx,例如%3c,都会被转换成它本身所表示的字符,比如符号“<”。如果你想得到未经解码的uri,这里有5个相并列的方法:

public string getrawauthority( )
public string getrawfragment( )
public string getrawpath( )
public string getrawquery( )
public string getrawuserinfo( )



记住类uri  与uri规范是不同的。所以像é 和 ü 这种非ascii字符绝不会在第一位置以%xx形式出现,所以在用方法getrawfoo() 返回得到的string中,仍将回出现在其中;除非本来被用做构建uri对象的string已被编码。
注意: 这里没有方法getrawport( ) 和 getrawhost( )  ,因为这些部分总是被保证由ascii字符构成的,至少现在是这样的。国际化的域名时代即将来临,希望在java以后的版本中能重新思考这一做法。

事实上,具体的uri不会包括这些信息—例如,这个uri,http://www.example.com 就没有user info, path, port, 或 query string—相应的方法就返回null。方法getport( )  是唯一例外。因为它被声明为返回一个int ,所以它不能返回null。它就用返回-1来代替,来表示没有port的信息。
由于各种技术上的也不会有多大实际影响的原因,java不会一开始就在认证部分做语法错误检察。这样做的一个很自然的后果就是不会返回认证的各个部分:port, host,  user info。这种情况下,你可以调用parseserverauthority()  让认证(authority)被强制再解析:

public uri parseserverauthority( )  throws urisyntaxexception



这个最初的uri 不会发生变化(uri对象是永远不会变的),但是uri 的返回值将会是authority被分开后的部分:user info, host,  port。如果这个authority不能被解析,会抛出urisyntaxexception 异常。

例子7-10使用这些方法把输入在命令行上的uri分成了它的那几个组成部分。这与例子7-4相似,只是它处理的是语法上正确的uri,而不要求jdk必须提供一个合适的protocal 处理器。
example 7-10. the parts of a uri

import java.net.*;

public class urisplitter {

  public static void main(string args[]) {

    for (int i = 0; i < args.length; i++) {
      try {
        uri u = new uri(args[i]);
        system.out.println("the uri is " + u);
        if (u.isopaque( )) {
          system.out.println("this is an opaque uri.");
          system.out.println("the scheme is " + u.getscheme( ));        
          system.out.println("the scheme specific part is "
           + u.getschemespecificpart( ));        
          system.out.println("the fragment id is " + u.getfragment( ));        
        }
        else {
          system.out.println("this is a hierarchical uri.");
          system.out.println("the scheme is " + u.getscheme( ));        
          try {      
            u = u.parseserverauthority( );
            system.out.println("the host is " + u.getuserinfo( ));        
            system.out.println("the user info is " + u.getuserinfo( ));        
            system.out.println("the port is " + u.getport( ));        
          }
          catch (urisyntaxexception ex) {
            // must be a registry based authority
            system.out.println("the authority is " + u.getauthority( ));        
          }
          system.out.println("the path is " + u.getpath( ));        
          system.out.println("the query string is " + u.getquery( ));        
          system.out.println("the fragment id is " + u.getfragment( ));
        } // end else      
      }  // end try
      catch (urisyntaxexception ex) {
        system.err.println(args[i] + " does not seem to be a uri.");
      }
      system.out.println( );
    }  // end for

  }  // end main

}  // end urisplitter



下面就是运行这三个uri实例后的结果:

% java urisplitter tel:+1-800-9988-9938 
/http://www.xml.com/pub/a/2003/09/17/stax.html#id=_hbc /urn:isbn:1-565-92870-9
the uri is tel:+1-800-9988-9938
this is an opaque uri.
the scheme is tel
the scheme specific part is +1-800-9988-9938
the fragment id is null

the uri is http://www.xml.com/pub/a/2003/09/17/stax.html#id=_hbc
this is a hierarchical uri.
the scheme is http
the host is null
the user info is null
the port is -1
the path is /pub/a/2003/09/17/stax.html
the query string is null
the fragment id is id=_hbc

the uri is urn:isbn:1-565-92870-9
this is an opaque uri.
the scheme is urn
the scheme specific part is isbn:1-565-92870-9
the fragment id is null



处理相对的 uri
类uri 提供了三种方法进行相对的和绝对的uri之间的转换。

public uri resolve(uri uri)

它比较参数uri 和这个uri ,然后用它构成一个新的uri 对象,它会有一个绝对的uri。例如,思考这三行代码:

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



在它们执行后,resolved 会有一个绝对的uri
http://www.example.com/images/logo.png.

如果这个被调用的uri 它本身就没有绝对的uri,那么方法resolve( )  会尽量的处理这个uri,返回一个新的相对的uri对象。比如,下面这三个语句:

uri top = new uri("javafaq/books/");
uri relative = new uri("jnp3/examples/07/index.html");
uri resolved = top.resolve(relative);



在执行后,resolved 现在有一个相对的uri
javafaq/books/jnp3/examples/07/index.html
就没有scheme(协议)和authority(认证)

public uri resolve(string uri)
这是个简便的方法,它能方便地把参数string 转化成一个uri,然后与被调用的这个uri组合后,返回一个新的uri对象。也就是,它与resolve(newuri(str)) 是等价的。使用这个方法,前面的两个例子就可以重写如下:

uri absolute = new uri("http://www.example.com/");
uri resolved = absolute.resolve("images/logo.png");
uri top = new uri("javafaq/books/");
resolved = top.resolve("jnp3/examples/07/index.html");
public uri relativize(uri uri)



反向操作这个过程也可以;也就是,从一个绝对的uri到一个相对的uri。方法relativize( )  用参数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);



现在的 uri 对象包含了一个相对的 uri images/logo.png.

一些有用的方法
类uri 有不少普通的但很实用的方法:equals(), hashcode( ), tostring( ), 和 compareto( )
public boolean equals(object o)

uri之间的比较与你期待中的一样好。它不是string之间的直接做比较。相同的uri必须要么是有层次关系的要么就是层次关系模糊的。scheme和authority部分的比较是不区分大小写的。也就是http 和 http 都表示的是相同的协议,authority 的比较www.example.com 和 www.example.com 也是一样的。uri中剩下的部分就得区分大小写了,除了用来转义非法字符的16进制的数字以外。在做比较前,%xx还没有被解码。

http://www.example.com/a and http://www.example.com/%41 are unequal uris.
public int hashcode( )

方法hashcode( )  是个普通的hashcode( )  方法,没有什么特别的。相同的uri就会有相同的哈希码,不同的uri就很难拥有相同的哈希码了。

public int compareto(object o)
uri是可以排序的。排序是基于对各个部分字符比较的结果上的,以下面的顺序:
·        如果scheme是不同的,就比较scheme,不考虑大小写。
·        否则,如果scheme是相同的,我们 就认为层次关系模糊的uri比有层次关系的uri优先级高。
·        如果比较的两个uri都是层次关系模糊的,这时就以它们的scheme-specific进行排序。
·        如果scheme和层次关系模糊的scheme-specific都相同了,就比较uri的fragment
·        如果做比较的uri都是有层次关系的,就看它们的authority,比较其中的user info, host, port。
·        如果scheme和authority都相同了,就看它们的path。
·        如果path也相同了,接着比较query string。
·        如果query string也相同了,就比较fragment

uri除了和它们自身比较以外,不能和任何其他的类型做比较。把一个uri 和任何除了uri 以外的做比较会抛出异常classcastexception

public string tostring( )
方法tostring( )  返回一个uri 形式的未经编码的string。也就是说,像é 和 /这种字符就不会被转换成%xx,除非它们在用来生成uri 的string中已经被转换了。因此,调用这个方法后得到的结果不能保证它是一个语法正确的uri 。有时,这种形式有助于增强可读性,但是对检索来说就不一样了。

public string toasciistring( )
方法toasciistring( ) 返回一个编码后uri 形式的string。像é 和 /这样的字符总是是被转换成了%xx的,不管它们最初是否已经被转换。很多时间你就应该使用这种string形式 的uri。尽管tostring( ) 返回的形式对于人来说更易于理解,但是它们仍然可能被复制粘贴到一些期望得到合法uri的地方。而方法toasciistring( ) 返回语法正确的uri。


proxies(代理服务)

许多系统都通过proxy 服务器访问网站和其他internet上的不使用http的地方。代理服务器接受从本地客户机发向远程服务器的请求,再把这个结果发送给本地客户机。这样做有时出于安全的原因,比如阻止远程服务器获取关于本地客户机网络配置的信息。有时通过过滤发出的请求和限制被访问的网站,阻止用户去访问那些被禁止访问的的网站。比如像一所小学就会阻止对http://www.playboy.com 的访问。当然有时这样做是单纯地出于对效率的考虑,当大量用户需要下载一台远程服务器上的相同文件时,就让他们从一个本地cache上下载,而不是不停地从远程服务器上下载。java中基于url 类的程序可以在大多数代理服务器和协议工作。的确,这就是你宁愿想选择使用url 类而不愿意基于socket自己编写http包或者其他客户端程序的原因。

system properties(系统属性)
对于基本操作而言,你需要做的就是设置一些系统属性指定你的本地代理服务器的地址。如果你使用的是一个单纯的http代理,把http.proxyhost 设置为域名或者为你的代理服务器的ip地址,把http.proxyport 设置为代理服务器的端口号(默认为80)。这里有许多种方法可以实现这个操作。包括在你的java代码中调用system.setproperty() 或者当启动程序时,使用命令选项“-d”。下面这个例子就把代理服务器设置为192.168.254.254,端口设置为9000:

% java -dhttp.proxyhost=192.168.254.254  -dhttp.proxyport=9000  
com.domain.program



你如果不想让某个主机的访问通过代理服务,想直接进行连接,那么就把系统属性http.nonproxyhosts 设置为它的主机名或ip地址。如果你需要对多个主机都进行这种操作,就用“|”把它们分开。比如,这段代码片段表示它会代理所有的除去java.oreilly.com 和 xml.oreilly.com:

system.setproperty("http.proxyhost", "192.168.254.254");
system.setproperty("http.proxyport", "9000");
system.setproperty("http.nonproxyhosts", "java.oreilly.com|xml.oreilly.com");



你也可以使用“*”作为一个通配符,表示对所有对所有有特殊域或子域的主机的访问不需要被代理。例如,对除了对oreilly.com 域的主机不代理外,对其他的都可以:

% java -dhttp.proxyhost=192.168.254.254  -dhttp.nonproxyhosts=*.oreilly.com  
com.domain.program



如果你使用的是个ftp代理服务器,可以用相同的方法设置系统属性,像ftp.proxyhost, ftp.proxyport, 和 ftp.nonproxyhosts 。java不支持任何其他应用层的代理,除非如果你对所有的tcp连接都使用传输层的socks代理,你可以使用相关的socksproxyhost 和 socksproxyport 来设置系统属性。对于socks,java没有提供对是否需要代理的选择。你要么选择都需要代理,要么选择都不需要代理。

the proxy 类
java1.5允许java程序对代理服务器进行更精细的操作。特别的是,你可以对不同的远程主机选择不同的代理服务器。代理本身是由java.net.proxy 类的实例表示。这里仍然只有三种代理方式,http, socks, 和直接连接(也就是不需要代理),由proxy.type 型迭代器中的三个常量表示:
·        proxy.type.direct
·        proxy.type.http
·        proxy.type.socks


除了代理的类型外,其他关于代理重要信息包括它的地址和端口,由socketaddress 的对象表示。例如,这个代码片段创建了一个代理对象,它表示proxy.example.com 端口80上的一个http代理服务器:

socketaddress address = new inetsocketaddress("proxy.example.com", 80);
proxy proxy = new proxy(proxy.type.http, address);



尽管这里仅有三种代理对象,但是对于不同主机上的不同代理服务器,会有许多相同类型的代理。

the proxyselector 类
每个java1.5的jvm都有一个java.net.proxyselector 对象。它可以对不同的连接委派合适的代理服务器。默认的proxyselector 仅仅是检测一下各种系统属性和url协议,然后决定怎样连接不同的主机。尽管如此,但是你可以安装你自己的proxyselector 子类来代替默认的选择器(selector),让它基于协议,主机,路径,日期,或其它标准来选择不同的代理。

这个类的关键就是抽象方法select( ) :

public abstract list<proxy> select(uri uri)



java传一个表示需要被连接的主机的uri 对象给该方法。用类url生成的这个连接对象通常是这种形式:http://www.example.com/ 或者 ftp://ftp.example.com/pub/files/, 或者其他类似的。对于由socket类生成的单纯的tcp连接, uri会是这种形式:socket://host:port:,看一个实例,socket://www.example.com:80 。对象proxyselector 对这种类型的对象会选择合适的代理,然后再以形式list<proxy> 返回它们。这个类的第二个你必须实现的抽象方法是connectfailed( ):

public void connectfailed(uri uri, socketaddress address, ioexception ex)



这是个反馈方法,它警告程序代理服务器连接不成功。例子7-11表示一个proxyselector 试图对所有的http连接都使用在proxy.example.com 上的代理服务器,除非这个代理服务器在前面,对某个特殊的url的连接已经失败了。在这种情况下,它会建议用一个直接连接代替。

example 7-11. a proxyselector that remembers what it can connect to

import java.net.*;
import java.util.*;
import java.io.*;

public class localproxyselector extends proxyselector {
  
  private list failed = new arraylist( );
  
  public list<proxy> select(uri uri) {
    
    list<proxy> result = new arraylist<proxy>( );
    if (failed.contains(uri)
      || "http".equalsignorecase(uri.getscheme( ))) {
        result.add(proxy.no_proxy);
    }
    else {
        socketaddress proxyaddress
          = new inetsocketaddress( "proxy.example.com", 8000);
        proxy proxy = new proxy(proxy.type.http, proxyaddress);
        result.add(proxy);
    }
    
    return result;
    
  }
  
  public void connectfailed(uri uri, socketaddress address, ioexception ex) {
    failed.add(uri);
  }
  
}



我已经说过的,每个运行的jvm都肯定会有一个proxyselector 。为了改变这个proxyselector ,我们给静态方法proxyselector.setdefault( )  传一个新的选择器,像这样:

proxyselector selector = new localproxyselector( ):
proxyselector.setdefault(selector);



从这以后,这个jvm打开的所有连接都会询问这个proxyselector 该使用哪个合适的代理。正常情况下,你不要在共享环境中使用这段代码。例如,在一个servlet中,你就不要去改变这个proxyselector 了,因为对在相同容器中的所有servlet来说,它们的proxyselector 也都被改变了。

通过get与服务器端程序进行交互

类url 使java applet和application与服务器端程序比如cgis, servlets, php页面,和其他使用get 方法程序的交互,变得更容易了。(我们将在15章讨论服务器端使用post 方法会用到urlconnection 类的程序。)所有你需要知道的是程序希望接受名字和值之间采取怎样的连接,生成一个带有查询string的url,这个string会提供正需要的名字和值。所有的名字和值必须是这样x-www-form-url-encoded—被方法urlencoder.encode()  编码的,这在前面已经讨论过了。

对一个特定程序的查询string,这里有许多方法来决定它的语法规则。如果你自己已经写过服务器端程序,那么你就已经知道了满足这种程序的名字和值对。如果你已经在自己的服务器上安装了第三方的程序,这个程序的说明文档会告诉你它需要什么样的名字和值对。另一方面,如果你正在与第三方服务器上的程序进行对话,可能就得麻烦多了。你可以总是问在远程服务器上的管理员,让他给你提供怎样和他的站点进行对话的规范说明。尽管如此,即使他们不介意这样做,恐怕也没有谁的工作就是“告诉与我们没有任何关系的黑客,怎样才能进入我们的服务器。”因此,除非你恰巧碰到一个特别热心的或者一个无聊的人,他除了写一封长长的email邮件来告诉你如何进入他们的服务器以外,就没有什么其他事干,否则的话,你不得不做一些逆向工程。

注意:  这种情况正在得到改变。不少的网站开始认识到对第三方开发人员开放他们系统的重要性,以及发布一些开发人员需要的插件的重要性。这些插件提供了详细的信息,利用这些信息来构造url,对他们网站上的服务进行访问。像站点safari 和 amazon都提供了restful, url-based接口,通过url 类,它们都很容易被访问。像ebay和 google的 soap-based服务就很难使用了。

许多程序被设计用来处理表格输入,如果真是这种情况,就可以直截了当地指出程序需要的是什么输入就行了。表格使用的方法应该是form 元素的method 属性的值。这个值要么是get ,这时你可以使用这里所说的这种处理流程,要么是post ,这时你可以使用将在15章讲的处理流程。form 元素的action 属性的值给出了url查询string前面的内容。需要注意这可能是个相对url,这时你应该找到与之相对应的绝对url。最后,名字和值对(name-value pairs)就是input 元素的name 属性,type 属性的值是submit的input 元素例外。

比如,看一下我的cafe con leche站点上用于本地搜索引擎的html表格。你可以看见它使用了get 方法。通过url http://www.google.com/search ,这个处理表格的程序能被访问。它有四对名字和值对,它们中的三个有默认值:

<form name="search" action="http://www.google.com/search" method="get">
  <input name="q" />
  <input type="hidden" value="cafeconleche.org" name="domains" />
  <input type="hidden" name="sitesearch" value="cafeconleche.org" />
  <input type="hidden" name="sitesearch2" value="cafeconleche.org" />
   <br />
   <input type="image" height="22" width="55"
      src="images/search_blue.gif" alt="search" border="0"
      name="search-image" />
</form>



input 的类型并不重要——例如,它是一个复选框的集合,一个下拉菜单条,或者是一个文本域都不重要——只有你给的每个input 的名字和值才是重要的。只有submit型的input是个例外,它告知web浏览器什么时候发送数据,但不会给出服务器任何其他信息。在一些情况下,你可能会发现hidden型的input 必须有一个特定的要求的默认值。这个表格有三个hidden型的input 。在一些情形下,正在与你对话的程序不可能处理有歧义的文本型的string输入值。尽管如此,因为表格是拿来给大家看和填写的,所以它应该提供足够的线索来提示它需要得到什么样的输入;比如,某个域应该是一个两个字母的缩写或者是一个电话号码。

不返回表格的程序对于逆向工程来说会更难。例如,在http://www.ibiblio.org/nywc/bios.phtml,你会发现大量到php页面的链接,它们和数据库对话,用某个作曲家的名字来检索一堆音乐作品。尽管这样,这里没有与这个程序相关的表格。这些都被hardcoded的url处理了。这种情况下,你最多能做的就是尽可能多的查看url,看你通过他们能不能猜出服务器到底期望得到什么。如果这个设计者不想搞得太晦涩,这些信息是不难得到的。比如,看看在那个页面上所有能得到的url:

http://www.ibiblio.org/nywc/compositionsbycomposer.phtml?last=anderson  
    &first=beth&middle=
http://www.ibiblio.org/nywc/compositionsbycomposer.phtml?last=austin
    &first=dorothea&middle=
http://www.ibiblio.org/nywc/compositionsbycomposer.phtml?last=bliss
    &first=marilyn&middle=
http://www.ibiblio.org/nywc/compositionsbycomposer.phtml?last=hart
    &first=jane&middle=smith



看到这些,你可以猜出这个程序希望得到三个输入,分别是first, middle, 和 last,它们的值分别由一个作曲家的first, middle, 和 last名构成。有时的输入不会是那种让人一看就明白的名字。这时,你需要做些实验,首先复制一些已知的值,然后慢慢提炼出哪些值是能或者不能被接受的。在java程序中,你不需要这样做。你可以简单地在你的web浏览器窗口上的地址栏中编辑url。

注意: 由于其他的黑客可能也会对你的服务器端程序做这样的实验,所以你有理由让它们更健壮以应对那些意外的输入。

不管你怎样确定了这个服务器希望得到的名字和值对,一旦你知道了如何同他们进行交互,这就简单了。所有你需要做的就是创建一个包括必需的名字和值对的查询string,然后生成一个包含这个查询string的url。发送这个查询string到服务器和读取它的反馈的方法,与你连接服务器和检索一个静态html页面的方法是相同的。一旦url被创建,这里就没有什么特殊的协议需要被遵守了。(post 方法需要遵守一个特殊的协议,尽管这样,这就是为什么它需要在第15章才讨论。)为了表明这个过程,让我们写一个非常简单的命令行程序来看看netscape open directory (http://dmoz.org/)上的标题。站点如图7-3所示,它有变得真正简单的优势。

urls,uris,proxies和passwords 解析(图一)
图7-3。open directory的基本用户界面

open directory的基本用户界面是一个简单的表格,它拥有一个名为search 的输入框;其中的输入会发送到http://search.dmoz.org/cgi-bin/search 上的一个cgi程序,它执行真正的搜索。表格的html语句像下面这样:

<form accept-charset="utf-8"
      action="http://search.dmoz.org/cgi-bin/search" method="get">
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; <input size=30 name=search>

<input type=submit value="search">
<a href="http://search.dmoz.org/cgi-bin/search?a.x=0">
<small><i>advanced</i></small></a>
</form>



表格里只有两个input:一个submit按纽和一个名为search的文本框。因此,如果要向open directory发送一个搜索请求,你需要收集搜索string ,把它编码成一个查询string,然后发送到http://search.dmoz.org/cgi-bin/search。例如,为了搜索“java”,你需要打开一个连接url http://search.dmoz.org/cgi-bin/search?search=java 的连接,然后读取这个结果输入流。例子7-12就是按照这样来做的。

example 7-12. do an open directory search

import com.macfaq.net.*;
import java.net.*;
import java.io.*;

public class dmoz {

  public static void main(string[] args) {
  
    string target = "";
    
    for (int i = 0; i < args.length; i++) {
      target += args[i] + " ";