在上个专题《java网络编程之uri、url研究(上)》中我们介绍了uri、url的慨念和体系结构,以及如何使用uri在本文中我将继续向大家介绍如何使用url和mime(多用途的网际邮件扩充协议)的概念以及它如何与url发生联系的。使用url
网络api通过提供url类让我们能在源代码层使用url。每一个url对象都封装了资源的标识符和协议处理程序。前面的技巧显示了获得url对象的途径之一是调用uri对象的tourl()方法。但是这种选择不一定方便(为什么在需要url对象的时候必须建立uri对象呢?)。作为代替,你可以调用url构造函数来建立url对象。你也可以调用url的方法来提取url的组件,打开一个输入流(input stream)从资源中读取信息,获得某个能方便检索资源数据的对象的引用,比较两个url对象中的url,获得到资源的连接对象,该连接对象允许代码了解(并写入)更多的资源的信息。
url类有六个构造函数。其中最简单的是url(string url),它有一个string类型的参数,把url分解为自己的组件,并把这些组件存储在一个新的url对象中。如果某个url没有包含协议处理程序或该url的协议是未知的,其它的五个构造函数会产生一个java.net.malformedurlexception对象。
下面的代码片断演示了使用url(string url)建立一个url对象,该对象封装了一个简单的url组件和http协议处理程序。
| url url = new url ("http://www.informit.com"); |
一旦拥有了url对象,你就可以使用getauthority()、getdefaultport()、 getfile()、 gethost()、 getpath()、getport()、 getprotocol()、getquery()、getref()和getuserinfo(). the getdefaultport()等方法提取各种组件。如果url中没有指定端口的部分,getdefaultport()方法返回url对象的协议处理程序使用(资源定位)的默认端口。getfile()方法返回路径和查询组件的结合体。getprotocol()方法返回决定资源的连接类型(例如http、mailto、ftp)的协议的名称。getref()方法返回url的部分片断(我们所知道的引用)。最后,getuserinfo()方法返回授权机构组件的用户信息部分。在这些url组件提取方法中,如果某些组件不存在(如果没有给url对象的协议处理程序指定默认的端口,它也返回-1),这些方法就返回null或-1。
作为这些组件提取方法的补充,你还可以调用openstream()方法检索java.io.inputstream引用。使用这种引用,你可以用面向字节的方式读取资源。
列表4是urldemo1的源代码。该程序从命令行参数建立了一个url对象,调用url组件提取方法来检索该url的组件,调用url的openstream()方法打开与资源的连接并返回一个用于从资源读取字节数据的inputstream引用,读取/打印这些字节,关闭输入流。
列表4: urldemo1.java
| // urldemo1.java import java.io.*; import java.net.*; class urldemo1 { public static void main (string [] args) throws ioexception { if (args.length != 1) { system.err.println ("usage: java urldemo1 url"); return; } url url = new url (args [0]); system.out.println ("authority = "+ url.getauthority ()); system.out.println ("default port = " +url.getdefaultport ()); system.out.println ("file = " +url.getfile ()); system.out.println ("host = " +url.gethost ()); system.out.println ("path = " +url.getpath ()); system.out.println ("port = " +url.getport ()); system.out.println ("protocol = " +url.getprotocol ()); system.out.println ("query = " +url.getquery ()); system.out.println ("ref = " +url.getref ()); system.out.println ("user info = " +url.getuserinfo ()); system.out.print ('/n'); inputstream is = url.openstream (); int ch; while ((ch = is.read ()) != -1) system.out.print ((char) ch); is.close (); } } |
在命令行输入java urldemo1 http://www.javajeff.com/articles/articles/html后,上面的代码的输出如下:
| authority = http://www.javajeff.com default port = 80 file = /articles/articles.html host = http://www.javajeff.com path = /articles/articles.html port = -1 protocol = http query = null ref = null user info = null <html> <head> <title> java jeff - articles </title> <meta http-equiv=content-type content="text/html; charset=iso-8859-1"> <meta name=author content="jeff friesen"> <meta name=keywords content="java, virtual machine"> <script language=javascript> if (navigator.appname == "netscape") document.write (" "); </script> </head> <body bgcolor=#000000> <center> <table border=1 cellpadding=5 cellspacing=0> <tr> <td> <table cellpadding=0 cellspacing=0> <tr> <td> <a href=informit/informit.html> <img alt=informit border=0 src=informit.gif></a> </td> </tr> </table> </td> <td align=middle> <img src=title.gif> <a href=../welcome/welcome.html> <img alt="welcome to java jeff!" border=0 src=jupiter.jpg> </a> <img src=../common/clear_dot.gif vspace=5> <a href=../ads/ads.html> <img alt="welcome to java jeff!" border=0 src=jupiter.jpg> </td> <td> <table cellpadding=0 cellspacing=0> <tr> <td> <a href=javaworld/javaworld.html> <img alt=javaworld border=0 src=javaworld.gif></a> </td> </tr> </table> </td> </tr> </table> </center> <font color=#ffffff> <center> best viewed at a resolution of 1024x768 or higher. <img src=../common/clear_dot.gif vspace=5> <i> copyright © 2001-2002, jeff friesen. all rights reserved. </i> <p> <a href=../index.html> <img alt=back border=0 src=../common/back.gif></a> </center> </font> </body> </html> |
在上面的信息中,输出标识符80是默认端口,http是协议。上面给出的是输出的html页面的源代码。
url的openstream()方法通常返回抽象的inputstream类的一个具体的子类所建立的对象的引用。这意味着你必须按字节次序读取资源数据,这种做法是恰当的,因为你不知道将要读取的数据是什么类型的。如果你事先知道要读取的数据是文本的,并且每一行以换行符(/n)结束,你就可以按行读取而不是按字节读取数据了。
下面的代码片断演示了把一个inputstream对象包装进java.io.inputstreamreader对象以从8位过渡到16位字符,把结果对象包装进java.io.bufferedreader对象以访问bufferedreader的readline()方法,并调用readline()方法从资源读取文本的所有行。
| inputstream is = url.openstream (); bufferedreader br = new bufferedreader (new inputstreamreader (is)); string line; while ((line = br.readline ()) != null) system.out.println (line); is.close (); |
有时候按字节的次序读取数据并不方便。例如,如果资源是jpeg文件,那么获取一个图像处理过程并向该过程注册一个用户使用数据的方法更好。当图像完整下载后立即显示它并不困难。如果出现这种情况,你就有必要使用getcontent()方法。
当调用getcontent()方法时,它会返回某种对象的object引用,而你可以调用该对象的方法(在转换成适当的类型后),采用更方便的方式检索数据。但是在调用该方法前,你必须使用instanceof验证对象的类型,防止类产生异常。
对于jpeg资源,getcontent()返回一个对象,该对象的类实现了java.awt.image.imageproducer接口。下面的代码片断演示了使用instanceof验证对象是imageproducer的,并进行了转换。接下来可以调用imageproducer方法注册一个用户并初始化图像的使用过程。
| url url = new url (args [0]); object o = url.getcontent (); if (o instanceof imageproducer) { imageproducer ip = (imageproducer) o; // ... } |
技巧
调用url的equals(object o)和samefile(object o)方法来决定两个url是否相同。第一个方法包含了比较的片断,而第二个方法没有包含。你可以参阅sdk文档查找更多信息。
查看一下getcontent()方法的源代码,你会找到openconnection().getcontent()。此外,查看一下openstream()方法的源代码,你会发现openconnection().getinputstream()。每个方法都首先调用url的openconnection()方法。这个方法返回抽象的java.net.urlconnection类(描述与某些资源的连接)的一个子类建立的对象的引用。urlconnection的方法反映了资源和连接的细节信息,使我们能编写代码向资源写入信息。
列表5的urldemo2源代码演示了openconnection(),以及调用一些urlconnection的方法。
列表5: urldemo2.java
| // urldemo2.java import java.io.*; import java.net.*; import java.util.*; class urldemo2 { public static void main (string [] args) throws ioexception { if (args.length != 1) { system.err.println ("usage: java urldemo2 url"); return; } url url = new url (args [0]); // 返回代表某个资源的连接的新的特定协议对象的引用 urlconnection uc = url.openconnection (); // 进行连接 uc.connect (); // 打印多种头部字段的内容 map m = uc.getheaderfields (); iterator i = m.entryset ().iterator (); while (i.hasnext ()) system.out.println (i.next ()); // 如果资源允许输入和输出操作就找出来 system.out.println ("input allowed = " +uc.getdoinput ()); system.out.println ("output allowed = " +uc.getdooutput ()); } } |
在对openconnection()的调用返回后,调用了connect()方法--用于建立某种资源的连接。(尽管openconnection()方法返回一个连接对象的引用,但是openconnection()不会连接到资源)。 urlconnection的getheaderfields()方法返回一个对象的应用,该对象的类实现了java.util.map接口。该图表(map)包含头部名称和值的集合。什么是头部(header)?头部是基于文本的名称/值对,它识别资源数据的类型、数据的长度等等。
在编译了urldemo2后,在命令行输入java urldemo2 http://www.javajeff.com,输出如下:
| date=[sun, 17 feb 2002 17:49:32 gmt] connection=[keep-alive] content-type=[text/html; charset=iso-8859-1] accept-ranges=[bytes] content-length=[7214] null=[http/1.1 200 ok] etag=["4470e-1c2e-3bf29d5a"] keep-alive=[timeout=15, max=100] server=[apache/1.3.19 (unix) debian/gnu] last-modified=[wed, 14 nov 2001 16:35:38 gmt] input allowed = true output allowed = false |
上面的输出识别了很多头部(包括date、null、content-length、 server、last-modified等等)和它们的值。输出也显示只允许从资源读取数据。
你对一个程序是如何识别资源数据的是否感到惊奇?仔细看一下前面的输出,你会看到叫做content-type的东西。content-type是一个头部,它识别了资源数据(内容)的类型是text/html。text部分就是我们所知道的类型,html部分是我们所知道的子类型。(如果内容是普通的文本,content-type的值可能是text/plain。上面的类型表明内容是文本的但不是没有格式的)。content-type头部是我们所知道的多用途internet邮件扩展(mime)的一部分。
mime是传统的传输消息的7位ascii标准的一种扩展。通过引入了多种头部,mime使视频、声音、图像、不同字符集的文本与7位ascii结合起来。有了content-type,mime可以识别content-length和其它标准的头部。当你使用urlconnection类的时候,你会遇到getcontenttype()和getcontentlength()。这些方法返回的值是content-type和content-length头部。
你也许听说过html窗体(<form>、 </form>)和其它的html标记。窗体使我们能够从某种资源得到(get)数据并按后来的处理把html窗体的字段数据发送(post)到某种资源。你能够使用urlconnection类和mime模拟可以得到和发送数据的html窗体。下面说明你怎样完成这种事务。
假设你想把窗体数据发送(post)到某个服务器程序。发送需要对窗体数据的操作。首先,窗体的数据必须组织为名称/值对(name/value pair),其次每个对必须指定为name=value格式,再次如果发送多个名称/值对,必须使用 & 符号把每对分开,最后的name内容和value的内容必须使用application/x-www-form-urlencoded mime类型编码。例如x=y&a=b表现了两个名称/值对--x/y和a/b。
为了辅助编码,java提供了java.net.urlencoder类,它声明了一对静态的encode()方法。每个方法有一个string参数并返回包含已编码的参数内容的string对象的引用。例如,如果encode()发现参数中有空格,它在结果中用加号代替空格。
下面的代码片断演示了调用urlencoder的encode(string s)方法,对a 空格 b字符串进行编码。结果a+b存储在一个新的string对象中,result引用它。
| string result = urlencoder.encode ("a b"); |
作为准备窗体数据的补充,必须告诉urlconnection对象数据已经被发送了,因为urlconnection默认的操作是获取数据。为了完成这种事务,你可以首先把openconnection()的返回值转换为httpurlconnection类型(在确保该返回值的类型正确后)。接着调用结果对象的setrequestmethod(string method)方法,把post作为method参数引用的对象的值。
另一个必须完成的事务是调用urlconnection的setdooutput(boolean dooutput)方法,其参数的值必须为true。这种事务是必要的,因为urlconnection对象在默认情况下不支持输出。(接着程序最终可以调用urlconnection的getoutputstream()方法,为发送的窗体数据返回一个资源的输出流的引用)。
列表6是urldemo3的源代码,它演示了把窗体数据发送给某个"了解"application/x-www-form-urlencoded内容类型的资源。它实现了前面提到的各种事务。
列表6: urldemo3.java
| // urldemo3.java import java.io.*; import java.net.*; class urldemo3 { public static void main (string [] args) throws ioexception { // 检查最后两个参数和参数的数量 if (args.length < 2 || args.length % 2 != 0) { system.err.println ("usage: java urldemo3 name value " + "[name value ...]"); return; } // 建立程序连接服务器程序资源的url对象,它返回一个窗体的名称/值对 url url; url = new url ("http://banshee.cs.uow.edu.au:2000/~nabg/echo.cgi"); // 向某个特定协议对象返回表现http资源连接的引用 urlconnection uc = url.openconnection (); // 验证连接的类型,必须是httpurlconnection的 if (!(uc instanceof httpurlconnection)) { system.err.println ("wrong connection type"); return; } // 表明程序必须把名称/值对输出到服务器程序资源 uc.setdooutput (true); // 表明只能返回有用的信息 uc.setusecaches (false); //设置content-type头部指示指定url已编码数据的窗体mime类型 uc.setrequestproperty ("content-type", "application/x-www-form-urlencoded"); // 建立名称/值对内容发送给服务器 string content = buildcontent (args); //设置content-type头部指示指定url已编码数据的窗体mime类型 uc.setrequestproperty ("content-length", "" + content.length ()); // 提取连接的适当的类型 httpurlconnection hc = (httpurlconnection) uc; // 把http请求方法设置为post(默认的是get) hc.setrequestmethod ("post"); // 输出内容 outputstream os = uc.getoutputstream (); dataoutputstream dos = new dataoutputstream (os); dos.writebytes (content); dos.flush (); dos.close (); // 从服务器程序资源输入和显示内容 inputstream is = uc.getinputstream (); int ch; while ((ch = is.read ()) != -1) system.out.print ((char) ch); is.close (); } static string buildcontent (string [] args) { stringbuffer sb = new stringbuffer (); for (int i = 0; i < args.length; i++) { // 为正确的传输对参数编码 string encodeditem = urlencoder.encode (args [i]); sb.append (encodeditem); if (i % 2 == 0) sb.append ("="); // 分离名称和值 else sb.append ("&"); // 分离名称/值对 } // 删除最后的 & 间隔符 sb.setlength (sb.length () - 1); return sb.tostring (); } } |
你可以会奇怪为什么urldemo3没有调用urlconnection的connect()的方法。这个方法没有被明显的调用,因为如果连向资源的连接没有建立的话,其它的urlconnection方法(例如getcontentlength())会明确的调用connect()方法。但是一旦连建立了接,调用这些方法(例如setdooutput(boolean dooutput))就是违反规定的。在connect()被(明确地或隐含地)调用后,这些方法会产生一个illegalstateexception对象。
在urldemo3编译后,在命令行输入java urldemo3 name1 value1 name2 value2 name3 value3,你可以看到下面的输出:
| <html> <head> <title>echoing your name value pairs</title> </head> <body> <ol> <li>name1 : value1 <li>name2 : value2 <li>name3 : value3 </ol> <hr> mon feb 18 08:58:45 2002 </body> </html> |
该服务器程序资源的输出由html组成,这些html回应的是name1、value1、name2、 value2、name3和value3。
技巧
如果你需要url对象的url的字符串表现形式,请调用toexternalform()或tostring()。两种方法的功能是相同的。
总结
本文研究了java的网络api,聚焦于uri、url和urn。你学习了这些概念,以及怎样使用uri和url(url相关)的类工作,同时你学习了mime的知识以及它与url的关系。现在你应该编写一些代码熟悉一下所学的内容了。
闽公网安备 35060202000074号