选择显示字体大小

urls,uris,proxies和passwords 解析

类urlencoder 和 类urldecoder

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

版权声明:任何获得matrix授权的网站,转载时请务必保留以下作者信息和链接
作者:elliotte rusty;leo173(作者的blog:http://blog.matrix.org.cn/page/leo173)
原文:http://www.onjava.com/pub/a/onjava/excerpt/jvntwkprg_3e/index.html
译文:http://www.matrix.org.cn/resource/article/44/44187_urlencoder+urldecoder.html
关键字:urls,uris,proxies,passwords

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

编码过程非常简单,任何字符只要不是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=.net&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,都会被转换成它本身所表示的字符,比如符号&ldquo;<&rdquo;。如果你想得到未经解码的uri,这里有5个相并列的方法:
public string getrawauthority( )
public string getrawfragment( )
public string getrawpath( )
public string getrawquery( )
public string getrawuserinfo( )


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

事实上,具体的uri不会包括这些信息&mdash;例如,这个uri,http://www.example.com 就没有user info, path, port, 或 query string&mdash;相应的方法就返回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&#91;&#93;) {

    for (int i = 0; i < args.length; i++) {
      try {
        uri u = new uri(args&#91;i&#93;);
        system.out.println(&quot;the uri is &quot; + u);
        if (u.isopaque( )) {
          system.out.println(&quot;this is an opaque uri.&quot;);
          system.out.println(&quot;the scheme is &quot; + u.getscheme( ));        
          system.out.println(&quot;the scheme specific part is &quot;
           + u.getschemespecificpart( ));        
          system.out.println(&quot;the fragment id is &quot; + u.getfragment( ));        
        }
        else {
          system.out.println(&quot;this is a hierarchical uri.&quot;);
          system.out.println(&quot;the scheme is &quot; + u.getscheme( ));        
          try {      
            u = u.parseserverauthority( );
            system.out.println(&quot;the host is &quot; + u.getuserinfo( ));        
            system.out.println(&quot;the user info is &quot; + u.getuserinfo( ));        
            system.out.println(&quot;the port is &quot; + u.getport( ));        
          }
          catch (urisyntaxexception ex) {
            // must be a registry based authority
            system.out.println(&quot;the authority is &quot; + u.getauthority( ));        
          }
          system.out.println(&quot;the path is &quot; + u.getpath( ));        
          system.out.println(&quot;the query string is &quot; + u.getquery( ));        
          system.out.println(&quot;the fragment id is &quot; + u.getfragment( ));
        } // end else      
      }  // end try
      catch (urisyntaxexception ex) {
        system.err.println(args&#91;i&#93; + &quot; does not seem to be a uri.&quot;);
      }
      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(&quot;http://www.example.com/&quot;);
uri relative = new uri(&quot;images/logo.png&quot;);
uri resolved = absolute.resolve(relative);


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

如果这个被调用的uri 它本身就没有绝对的uri,那么方法resolve( )  会尽量的处理这个uri,返回一个新的相对的uri对象。比如,下面这三个语句:
uri top = new uri(&quot;javafaq/books/&quot;);
uri relative = new uri(&quot;jnp3/examples/07/index.html&quot;);
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(&quot;http://www.example.com/&quot;);
uri resolved = absolute.resolve(&quot;images/logo.png&quot;);
uri top = new uri(&quot;javafaq/books/&quot;);
resolved = top.resolve(&quot;jnp3/examples/07/index.html&quot;);
public uri relativize(uri uri)


反向操作这个过程也可以;也就是,从一个绝对的uri到一个相对的uri。方法relativize( )  用参数uri 创建一个新的uri 对象,这个uri 对被调用的uri 是相对的。参数没有改变。例如:
uri absolute = new uri(&quot;http://www.example.com/images/logo.png&quot;);
uri top = new uri(&quot;http://www.example.com/&quot;);
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是可以排序的。排序是基于对各个部分字符比较的结果上的,以下面的顺序:
&middot;        如果scheme是不同的,就比较scheme,不考虑大小写。
&middot;        否则,如果scheme是相同的,我们 就认为层次关系模糊的uri比有层次关系的uri优先级高。
&middot;        如果比较的两个uri都是层次关系模糊的,这时就以它们的scheme-specific进行排序。
&middot;        如果scheme和层次关系模糊的scheme-specific都相同了,就比较uri的fragment
&middot;        如果做比较的uri都是有层次关系的,就看它们的authority,比较其中的user info, host, port。
&middot;        如果scheme和authority都相同了,就看它们的path。
&middot;        如果path也相同了,接着比较query string。
&middot;        如果query string也相同了,就比较fragment

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

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

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


proxies(代理服务)

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

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


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


你也可以使用&ldquo;*&rdquo;作为一个通配符,表示对所有对所有有特殊域或子域的主机的访问不需要被代理。例如,对除了对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 型迭代器中的三个常量表示:
&middot;        proxy.type.direct
&middot;        proxy.type.http
&middot;        proxy.type.socks


除了代理的类型外,其他关于代理重要信息包括它的地址和端口,由socketaddress 的对象表示。例如,这个代码片段创建了一个代理对象,它表示proxy.example.com 端口80上的一个http代理服务器
socketaddress address = new .netsocketaddress(&quot;proxy.example.com&quot;, 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)
       &quot;http&quot;.equalsignorecase(uri.getscheme( ))) {
        result.add(proxy.no_proxy);
    }
    else {
        socketaddress proxyaddress
          = new .netsocketaddress( &quot;proxy.example.com&quot;, 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&mdash;被方法urlencoder.encode()  编码的,这在前面已经讨论过了。

对一个特定程序的查询string,这里有许多方法来决定它的语法规则。如果你自己已经写过服务器端程序,那么你就已经知道了满足这种程序的名字和值对。如果你已经在自己的服务器上安装了第三方的程序,这个程序的说明文档会告诉你它需要什么样的名字和值对。另一方面,如果你正在与第三方服务器上的程序进行对话,可能就得麻烦多了。你可以总是问在远程服务器上的管理员,让他给你提供怎样和他的站点进行对话的规范说明。尽管如此,即使他们不介意这样做,恐怕也没有谁的工作就是&ldquo;告诉与我们没有任何关系的黑客,怎样才能进入我们的服务器。&rdquo;因此,除非你恰巧碰到一个特别热心的或者一个无聊的人,他除了写一封长长的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=&quot;search&quot; action=&quot;http://www.google.com/search&quot; method=&quot;get&quot;>
  <input name=&quot;q&quot; />
  <input type=&quot;hidden&quot; value=&quot;cafeconleche.org&quot; name=&quot;domains&quot; />
  <input type=&quot;hidden&quot; name=&quot;sitesearch&quot; value=&quot;cafeconleche.org&quot; />
  <input type=&quot;hidden&quot; name=&quot;sitesearch2&quot; value=&quot;cafeconleche.org&quot; />
   <br />
   <input type=&quot;image&quot; height=&quot;22&quot; width=&quot;55&quot;
      src=&quot;images/search_blue.gif&quot; alt=&quot;search&quot; border=&quot;0&quot;
      name=&quot;search-image&quot; />
</form>


input 的类型并不重要&mdash;&mdash;例如,它是一个复选框的集合,一个下拉菜单条,或者是一个文本域都不重要&mdash;&mdash;只有你给的每个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所示,它有变得真正简单的优势。


图7-3。open directory的基本用户界面

open directory的基本用户界面是一个简单的表格,它拥有一个名为search 的输入框;其中的输入会发送到http://search.dmoz.org/cgi-bin/search 上的一个cgi程序,它执行真正的搜索。表格的html语句像下面这样:
<form accept-charset=&quot;utf-8&quot;
      action=&quot;http://search.dmoz.org/cgi-bin/search&quot; method=&quot;get&quot;>
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; <input size=30 name=search>

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


表格里只有两个input:一个submit按纽和一个名为search的文本框。因此,如果要向open directory发送一个搜索请求,你需要收集搜索string ,把它编码成一个查询string,然后发送到http://search.dmoz.org/cgi-bin/search。例如,为了搜索&ldquo;java&rdquo;,你需要打开一个连接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&#91;&#93; args) {
  
    string target = &quot;&quot;;
    
    for (int i = 0; i < args.length; i++) {
      target += args&#91;i&#93; + &quot; &quot;;
    }
    target = target.trim( );
    querystring query = new querystring(&quot;search&quot;, target);
    try {
      url u = new url(&quot;http://search.dmoz.org/cgi-bin/search?&quot; + query);
      inputstream in = new bufferedinputstream(u.openstream( ));
      inputstreamreader thehtml = new inputstreamreader(in);
      int c;
      while ((c = thehtml.read( )) != -1) {
        system.out.print((char) c);
      }
    }
    catch (malformedurlexception ex) {
      system.err.println(ex);
    }
    catch (ioexception ex) {
      system.err.println(ex);
    }
    
  }

}


当然,在解析和显示结果上需要更多的努力。但是请注意与服务器进行对话的代码是多么的简单呀!。除去看上有趣的url和组成它的一些部分需要被x-www-form-url-encoded的可能性更大以外,使用get 与服务器进行对话的程序不比检索任何其他html页面的工作困难。

accessing password-protected sites
(进入受密码保护的站点)

许多流行的站点,像thewall street journal,需要一个用户名和密码才能被访问。一些网站,像w3c的会员页面,通过http认证正规地执行这些。其他的像java developer connection,通过cookies 和 html非正规地执行这些认证。java的url类能访问使用http认证的站点,尽管你也需要告诉它你的用户名和密码。java对那些使用非标准的、基于cookie认证的站点是提供支持的。一部分原因是在java1.4和早期版本中,java实际上是不支持cookie的,还有部分原因是:如果这样做,需要解析和发送html表格,最后是因为cookie与web架构是完全抵触的。(java 1.5的确增加了对cookie的支持,我们将在下一章讨论它。尽管如此,但它没有对有认证功能的cookie与其他cookie区分开。)你可以自己提供这种支持,
使用urlconnection 类来读和写cookie被设置或被返回的http头文件。但是,要想这样做并不简单,经常需要你要连接站点的本地代码。这个难度不压于让web浏览器完全实现对html表格和cookie的支持。访问受标准的http认证保护的站点就简单多了。

authenticator 类
java.net 中有一个authenticator 类,你能用它给受http验证保护的站点提供一个用户名和密码:
public abstract class authenticator extends object // java 1.2

因为authenticator 是一个抽象类,你必须生成它的子类。不同的子类会用不同的方法检索信息。例如,一个字符模式的程序可能会叫用户输入用户名和密码,用system.in 接受它们。一个gui程序可能会显示一个对话框,像图7-4那样。一个自动机器会从一个加密文件中读出用户名。


图7-4。验证对话框

为了要让url 类使用authenticator 类的子类,就把它传递给静态方法authenticator.setdefault() ,这样就把它安装成了默认的authenticator:
public static void setdefault(authenticator a)


比如,如果你已经写好了一个叫dialogauthenticator 的authenticator 子类,你得这样安装它:
authenticator.setdefault(new dialogauthenticator( ));


你只需要这样做一次就行了。从这以后,当url 类需要用户名和密码时,它会叫dialogauthenticator 使用静态方法authenticator.requestpasswordauthentication()  :
public static passwordauthentication requestpasswordauthentication(
  .netaddress address, int port, string protocol, string prompt, string scheme)
  throws securityexception


参数address 就是需要进行验证的主机。参数port 就是那个主机上的端口,参数protocol 就是访问这个站点时所用的应用层协议。http服务器会提供参数prompt 。一般就是指需要被验证的访问域。(像www.ibiblio.org 这样的大网站会有许多的访问域,每个都要求用不同的用户名和密码。)参数scheme 是用来做验证的模式。(这里的单词scheme 与protocol (协议)不是同义词。而是一种基本的http验证模式。)

不被信任的applet不允许让用户提供用户名和密码。受信任的applet可以这样做,只要它们有requestpasswordauthenticatio.netpermission
否则的话,authenticator.requestpasswordauthentication( ) 抛出securityexception 异常。authenticator 的子类必须覆盖getpasswordauthentication( )  方法。这个方法里,你需要从用户或其他来源处取到用户名和密码,然后作为java.net.passwordauthentication 类的实例返回:
java.net.passwordauthentication class:
protected passwordauthentication getpasswordauthentication( )


如果你不想验证这个请求,就返回null,java回告诉服务器它不知道该怎样验证这个连接。如果你发送一个不正确的用户名或密码,java会再次调用getpasswordauthentication( ) ,再给你一次机会提交正确的数据。正常情况下,你有5次机会得到正确的用户名和密码;在那以后,openstream( )  会抛出protocolexception 异常。

用户名和密码被放在相同的虚拟机对话中。一旦你对一个访问域使用了正确的密码进行访问,你就不会被要求再次使用密码了,除非你已经很明确地清空了保存它的char 数组。你可以通过调用这些从超类authenticator 继承来的方法获取更多详细的信息:
protected final .netaddress getrequestingsite( )
protected final int         getrequestingport( )
protected final string      getrequestingprotocol( )
protected final string      getrequestingprompt( )
protected final string      getrequestingscheme( )
protected final string      getrequestinghost( )  // java 1.4


这些方法要么返回上次被requestpasswordauthentication( ) 调用后得到的信息,要么返回null,如果那些信息已经不能使用了。(getrequestingport( ) 返回-1,如果那个端口不能被使用了。)最后一个方法,getrequestinghost( ) ,只有在java1.4和更新的版本中才能被使用;在早期版本里,你可以调用getrequestingsite( ).gethostname( ) 代替。java1.5又增加了两个方法到这个类中:
protected final string getrequestingurl( )  // java 1.5
protected authenticator.requestortype getrequestortype( )


方法getrequestingurl( )  会返回一个被要求使用验证的完整的url&mdash;&mdash;这是个重要的 ,如果一个站点的不同文件需要使用不同的用户名和密码。方法getrequestortype( ) 会返回下面两个常量中的一个 authenticator.requestortype.proxy 或者 authenticator.requestortype.server  ,以此来区分是服务器还是代理服务器请求验证。

passwordauthentication 类

passwordauthentication 是一个非常简单的final类,它提供两个只读属性:用户名和密码。用户名是string存放的。密码被存放在char 数组中,所以当不需要时,可以擦除密码。一个string在它可以被擦除前,要等待垃圾回收器来收集,即使这时它仍有可能存在本地系统内存上的某个地方,也有可能在磁盘上,如果这时这个内存块已经被交换到作为虚拟内存的磁盘区域上。用户名和密码被设置在这个构造函数中:
public passwordauthentication(string username, char&#91;&#93; password)


每个都是通过getter 方法被访问的:
public string getusername( )
public char&#91;&#93; getpassword( )


jpasswordfield 类
swing中jpasswordfield 组件有个用来向用户获取密码的有用工具:
public class jpasswordfield extends jtextfield


这个轻量级组件和文本框的行为几乎是一样的。但是用户输入的会以&ldquo;*&rdquo;形式显示出来。这样的话,就可以防止其他人在后面看见用户输入的密码了。

jpasswordfield 也用char 数组来存放密码,这样当你不再需要是,你就可以清空它了。方法getpassword( )  用来返回密码:
public char&#91;&#93; getpassword( )


不然的话,大多数时候你得使用继承超类jtextfield 后得到的方法。例子7-13展示了一个来自swing的authenticator 的子类,它生成一个对话框向用户获取他的用户名和密码。下面的大多数代码都是用来生成gui。jpasswordfield 提取密码,jtextfield 提取用户名。图7-4就是下面代码所生成的一个简单的对话框。

example 7-13. a gui authenticator
package com.macfaq.net;

import java.net.*;
import javax.swing.*;
import java.awt.*;
import java.awt.event.*;

public class dialogauthenticator extends authenticator {

  private jdialog passworddialog;  
  private jlabel mainlabel
   = new jlabel(&quot;please enter username and password: &quot;);
  private jlabel userlabel = new jlabel(&quot;username: &quot;);
  private jlabel passwordlabel = new jlabel(&quot;password: &quot;);
  private jtextfield usernamefield = new jtextfield(20);
  private jpasswordfield passwordfield = new jpasswordfield(20);
  private jbutton okbutton = new jbutton(&quot;ok&quot;);
  private jbutton cancelbutton = new jbutton(&quot;cancel&quot;);
  
  public dialogauthenticator( ) {
    this(&quot;&quot;, new jframe( ));
  }
    
  public dialogauthenticator(string username) {
    this(username, new jframe( ));
  }
    
  public dialogauthenticator(jframe parent) {
    this(&quot;&quot;, parent);
  }
    
  public dialogauthenticator(string username, jframe parent) {
    
    this.passworddialog = new jdialog(parent, true);  
    container pane = passworddialog.getcontentpane( );
    pane.setlayout(new gridlayout(4, 1));
    pane.add(mainlabel);
    jpanel p2 = new jpanel( );
    p2.add(userlabel);
    p2.add(usernamefield);
    usernamefield.settext(username);
    pane.add(p2);
    jpanel p3 = new jpanel( );
    p3.add(passwordlabel);
    p3.add(passwordfield);
    pane.add(p3);
    jpanel p4 = new jpanel( );
    p4.add(okbutton);
    p4.add(cancelbutton);
    pane.add(p4);  
    passworddialog.pack( );
    
    actionlistener al = new okresponse( );
    okbutton.addactionlistener(al);
    usernamefield.addactionlistener(al);
    passwordfield.addactionlistener(al);
    cancelbutton.addactionlistener(new cancelresponse( ));
    
  }
  
  private void show( ) {
    
    string prompt = this.getrequestingprompt( );
    if (prompt == null) {
      string site     = this.getrequestingsite( ).gethostname( );
      string protocol = this.getrequestingprotocol( );
      int    port     = this.getrequestingport( );
      if (site != null & protocol != null) {
        prompt = protocol + &quot;://&quot; + site;
        if (port > 0) prompt += &quot;:&quot; + port;
      }
      else {
        prompt = &quot;&quot;;
      }
      
    }

    mainlabel.settext(&quot;please enter username and password for &quot;
     + prompt + &quot;: &quot;);
    passworddialog.pack( );
    passworddialog.show( );
    
  }
  
  passwordauthentication response = null;

  class okresponse implements actionlistener {
  
    public void actionperformed(actionevent e) {
      
      passworddialog.hide( );
      // the password is returned as an array of
      // chars for security reasons.
      char&#91;&#93; password = passwordfield.getpassword( );
      string username = usernamefield.gettext( );
      // erase the password in case this is used again.
      passwordfield.settext(&quot;&quot;);
      response = new passwordauthentication(username, password);
      
    }
    
  }

  class cancelresponse implements actionlistener {
  
    public void actionperformed(actionevent e) {
      
      passworddialog.hide( );
      // erase the password in case this is used again.
      passwordfield.settext(&quot;&quot;);
      response = null;
      
    }
    
  }

  public passwordauthentication getpasswordauthentication( ) {
    
    this.show( );    
    return this.response;
    
  }

}


例子7-14是经过修改后的sourceviewer 程序,通过dialogauthenticator 类向用户获取用户名和密码。
example 7-14. 用来下载受密码保护网页的程序
import java.net.*;
import java.io.*;
import com.macfaq.net.dialogauthenticator;

public class securesourceviewer {

  public static void main (string args&#91;&#93;) {

    authenticator.setdefault(new dialogauthenticator( ));

    for (int i = 0; i < args.length; i++) {
      
      try {
        //open the url for reading
        url u = new url(args&#91;i&#93;);
        inputstream in = u.openstream( );
        // buffer the input to increase performance
        in = new bufferedinputstream(in);      
        // chain the inputstream to a reader
        reader r = new inputstreamreader(in);
        int c;
        while ((c = r.read( )) != -1) {
          system.out.print((char) c);
        }
      }
      catch (malformedurlexception ex) {
        system.err.println(args&#91;0&#93; + &quot; is not a parseable url&quot;);
      }
      catch (ioexception ex) {
        system.err.println(ex);
      }
      
      // print a blank line to separate pages
      system.out.println( );
      
    } //  end for
  
    // since we used the awt, we have to explicitly exit.
    system.exit(0);

  } // end main

}  // end securesourceviewer


elliotte rusty harold 是《xml in a nutshell (o'reilly, 2002) and procesing xml with java》的合著作者,由addison-wesley 在11月份出版。


 


关键字 本文所属关键字

相关 与本文相关文章

分类 所有文章关键字导航

源码编程相关

Java   Asp   PHP   .Net   XML   C/C++   CGI   VB   Jsp   J2ee   J2se   J2me   EJB   Servlet   Tomcat   Resin   Struts   Weblogic   Eclipse   ANT   GUI   JMS   Web servise   IDEA   Webphere   Hibernate   Spring   Jboss   Applet   Swing   Socket   Javamail   Perl   Ajax   P2P   安全   模式   框架   测试   开源   游戏

SQL数据库相关

My-SQL   Ms-SQL   Access   DB2   Oracle   Sybase   SQLserver   索引   存储过程   加密   数据库   分页   视图  

手机无线相关

3G   Wap   CDMA   GRPS   GSM   IVR   彩信   短信   无线   增值业务

网页设计制作相关

HTML   CSS   网页配色   网页特效   Javascript   VBscript   Dreamweaver   Frontpage   JS   Web   网站设计

网站建设推广相关

建站经验   网站优化   网站排名   推广   Alexa

操作系统/服务器相关

Windows XP   Windows 2000   Windows 2003   Windows Me   Windows 9.x   Linux   UNIX   注册表   操作系统   服务器   应用服务器

图形图像多媒体相关

Photoshop   Fireworks   Flash   Coreldraw   Illustrator   Freehand   Photoimpact   多媒体   图形图像

标准 网站致力的规范

Valid CSS!

无不良内容,无不良广告,无恶意代码

Valid XHTML 1.0 Transitional

creativecommons