|  | 本文参考笔者所写的《Java网络编程核心技术详解》,电子工业出版社出版。 
  
 Oracle公司为协议处理框架提供了基于HTTP协议的实现,它们都位于JDK类库的sun.net.www包或者其子包中。本文以HTTP客户程序为例,介绍URL类和URLConnection类的用法。
 一、URL类的用法以下例程的HttpClient1类利用URL类创建了一个简单的HTTP客户程序。
 
 | /* HttpClient1.java (使用URL类的openStream()方法)*/ import java.net.*;
 import java.io.*;
 public class HttpClient1 {
 public static void main(String args[])throws IOException{
 //“http”是协议符号
 URL url=new URL("http://www.javathinker.net/hello.htm");
 
 //接收响应结果
 InputStream in=url.openStream();
 ByteArrayOutputStream buffer=new ByteArrayOutputStream();
 byte[] buff=new byte[1024];
 int len=-1;
 
 while((len=in.read(buff))!=-1){
 buffer.write(buff,0,len);
 }
 //把字节数组转换为字符串
 System.out.println(new String(buffer.toByteArray()));
 }
 }
 | 
 以上程序先创建了一个URL对象,然后通过它的openStream()方法获得一个输入流,接下来就从这个输入流中读取服务器发送的响应结果。HttpClient1最后会打印如下内容:
 
 
 | <html > <head >
 <title >helloapp </title >
 </head >
 <body  >
 <h1 >hello </h1 >
 </body >
 </html >
 | 
 在通过URL类的构造方法URL(String url)创建一个URL对象时,构造方法会根据参数url中的协议符号,来创建一个与协议匹配的URLStreamHandler实例。在本例中,协议符号为“http”。URL类的构造方法创建URLStreamHandler实例的流程如下:
 (1)如果在URL缓存中已经存在这样的URLStreamHandler实例,则无需再创建新的URLStreamHandler实例。否则继续执行下一步。
 (2)如果程序已经通过URL类的静态setURLStreamHandlerFactory()方法设置了URLStreamHandlerFactory接口的具体实现类,那么就通过这个工厂类的createURLStreamHandler()方法来构造一个URLStreamHandler实例。否则继续执行下一步。
 (3)根据系统属性java.protocol.handler.pkgs来决定URLStreamHandler具体子类的名字,然后对其实例化。假定运行HttpClient1的命令为:
 
 
 | java -Djava.protocol.handler.pkgs=com.abc.net.www | net.javathinker.protocols
 HttpClient1
 | 
 以上命令中的“-D”选项设定系统属性,URL构造方法会先查找并试图实例化com.abc.net.www.http.Handler类,如果失败,再试图实例化net.javathinker.protocols.http.Handler类。在设置java.protocol.handler.pkgs属性时,多个包名以“|”隔开。对于一个基于FTP协议的客户程序,那么URL构造方法会先后查找并试图实例化 com.abc.net.www.ftp.Handler 类和net.javathinker.protocols.ftp.Handler类。
 
 如果以上操作都失败,那么继续执行下一步。
 (4)试图实例化位于 sun.net.www.protocol 包中的 sun.net.[url]www.protocol.[/url] 协议名.Handler类,如果失败,URL构造方法就会抛出MalformedURLException。对于一个基于HTTP协议的客户程序,那么URL构造方法会试图实例化 sun.net.www.protocol.http.Handler 类。对于一个基于FTP协议的客户程序,那么URL构造方法会试图实例化 sun.net.www.protocol.ftp.Handler 类。
 从上述流程可以推断出,如果运行命令“java HttpClient1”,那么URL构造方法实际上会实例化 sun.net.www.protocol.http.Handler 类,它是URLStreamHandler类的一个具体子类。
 URL类具有以下方法:
   	openConnection()方法:创建并返回一个URLConnection对象,这个openConnection()方法实际上是通过调用URLStreamHandler类的openConnection()方法,来创建URLConnection对象的。  	openStream()方法:返回用于读取服务器发送数据的输入流,该方法实际上通过调用URLConnection类的getInputStream()方法来获得输入流。  	getContent()方法:返回包装了服务器发送数据的Java对象,该方法实际上调用URLConnection类的getContent()方法,而URLConnection类的getContent()方法又调用了ContentHandler类的getContent()方法。
 二、  URLConnection类的用法URLConnection类表示客户程序与远程服务器的连接。URLConnection有两个boolean类型的属性以及相应的get和set方法:以下例程的HttpClient2类利用URLConnection类来读取服务器的响应结果。	doInput属性:如果取值为true,表示允许获得输入流,读取远程服务器发送的数据。该属性的默认值为true。程序可通过getDoInput()和setDoInput()方法来读取和设置该属性。  	doOutput属性:如果取值为true,表示允许获得输出流,向远程服务器发送数据。该属性的默认值为false。程序可通过getDoOutput()和setDoOutput()方法来读取和设置该属性。URLConnection类提供了读取远程服务器的响应数据的一系列方法:
	getHeaderField(String name):返回响应头中参数name指定的属性的值。  	getContentType():返回响应正文的类型。如果无法获取响应正文的类型,就返回null。对于HTTP响应结果,在响应头中可能会包含响应正文的类型信息。  	getContentLength():返回响应正文的长度。如果无法获取响应正文的长度,就返回-1。对于HTTP响应结果,在响应头中可能会包含响应正文的长度信息。  	getContentEncoding():返回响应正文的编码类型。如果无法获取响应正文的编码类型,就返回null。对于HTTP响应结果,在响应头中可能会包含响应正文的编码类型信息。   
 
 
 | /* HttpClient2.java (使用URLConnection类)*/ import java.net.*;
 import java.io.*;
 public class HttpClient2{
 public static void main(String args[])throws IOException{
 URL url=new URL("http://www.javathinker.net/hello.htm");
 URLConnection connection=url.openConnection();
 //接收响应结果
 System.out.println("正文类型:"+connection.getContentType());
 System.out.println("正文长度:"+connection.getContentLength());
 InputStream in=connection.getInputStream(); //读取响应正文
 
 ByteArrayOutputStream buffer=new ByteArrayOutputStream();
 byte[] buff=new byte[1024];
 int len=-1;
 
 while((len=in.read(buff))!=-1){
 buffer.write(buff,0,len);
 }
 
 //把字节数组转换为字符串
 System.out.println(new String(buffer.toByteArray()));
 }
 }
 | 
 运行“java HttpClient2”命令,将得到如下打印结果:
 
 
 | 正文类型:text/html 正文长度:97
 <html >
 <head >
 <title >helloapp </title >
 </head >
 <body  >
 <h1 >hello </h1 >
 </body >
 </html >
 | 
 HTTP响应结果包括HTTP响应代码、响应头和响应正文这三部分。而Oracle公司在对HTTP协议的框架实现中,HttpURLConnection类作为URLConnection的具体子类,它的getInputStream()方法仅仅返回响应正文部分的输入流。所以本范例的HttpClient2实际上仅仅读取了HTTP响应结果中的正文部分内容。
 
 客户程序调用URLConnection的getInputStream()方法得到输入流后,就能读取服务器发送的响应正文。响应正文有可能是图片文件,也有可能是文本文件,还有可能是压缩文件或可运行文件等。客户程序可以采用以下几种方式来判断响应正文的类型:
 (1)调用URLConnection类的getContentType()方法。
 (2)调用URLConnection类的静态guessContentTypeFromName(String fname)方法,参数fname表示URL地址中的文件名部分。该方法根据文件的扩展名来猜测响应正文的类型。表6-1列出了文件扩展名与响应正文类型的对应关系。从该表可以推断出,执行URLConnection.guessContentTypeFromName("hello.htm")方法,其返回结果应该是“text/html”。
 
 正文类型也称为MIME(Multipurpose Internet Mail Extensions)类型,它规定了在Internet网上传送的常见数据类型的格式。RFC2045文档详细描述了MIME规范,网址为:http://www.ietf.org/rfc/rfc2045.txt。
 
 (3)调用URLConnection类的静态guessContentTypeFromStream (InputStream in)方法,参数in表示输入流。该方法根据输入流中的前几个字节来猜测响应正文的类型。以下表列出了输入流中的前几个字节与响应正文类型的对应关系。
 
 输入流中的前几个字节(十六进制)	对应的ASCII字符	响应正文类型0xACED		                                                                   application/x-java-serialized-object0xCAFEBABE		                                                  application/java-vm
 0x47494638	                                   GIF8	                image/gif
 0x23646566	                                   #def	                image/gif
 0x2E736E64		                                                  audio/basic
 0x3C3F786D6C	                                   <?xml	               application/xml
 0x3C21	                                                    <!	               text/html
 0x3C68746D6C	                                   <html	               text/html
 0x3C626F6497	                                   <body	               text/html
 0x3C68656164	                                   <head	               text/html
 0xFFD8FFE0		                                                  image/jpeg
 0x89504E470D0A1A0A		                                 image/png
 
 客户程序只要先调用了URLConnection类的setDoOutput(true)方法,就能通过URLConnection类的getOutputStream()方法获得输出流,然后向服务器发送数据。对于HTTP客户程序,在POST方式下可以发送大量表单数据。以下例程的HttpClient3类访问的URL地址为:http://www.javathinker.net/aboutBook.jsp。用户在HttpClient3的图形界面上选择一个书名,然后按下【查看】按钮,HttpClient3就会发送一个HTTP请求,这个请求的正文部分的内容为“title=用户选择的书名”,服务器端的aboutBook.jsp读取HTTP请求中的书名,然后返回该书的相关信息。
 
 
   图  HttpClient3向服务器查询特定书的信息
 
 
 | /* 例程  HttpClient3.java */ import java.io.*;
 import java.net.*;
 import java.util.*;
 import java.awt.*;
 import java.awt.event.*;
 import javax.swing.*;
 
 public class HttpClient3{
 public static void main(String[] args){
 JFrame frame = new PostTestFrame();
 frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
 frame.setVisible(true);
 }
 }
 
 class PostTestFrame extends JFrame{
 /**  负责发送HTTP请求正文,以及接收HTTP响应正文 */
 public static String doPost(String urlString, Map<String, String>
 nameValuePairs)
 throws IOException{
 URL url = new URL(urlString);
 URLConnection connection = url.openConnection();
 connection.setDoOutput(true);  //允许输出数据
 
 //发送HTTP请求正文
 PrintWriter out = new PrintWriter(connection.getOutputStream());
 boolean first = true;
 for (Map.Entry<String, String> pair : nameValuePairs.entrySet()){
 if (first) first = false;
 else out.print('&');
 String name = pair.getKey();
 String value = pair.getValue();
 out.print(name);
 out.print('=');
 //请求正文采用GB2312编码
 out.print(URLEncoder.encode(value, "GB2312"));
 }
 out.close();
 
 //接收HTTP响应正文
 InputStream in=connection.getInputStream(); //读取响应正文
 ByteArrayOutputStream buffer=new ByteArrayOutputStream();
 byte[] buff=new byte[1024];
 int len=-1;
 
 while((len=in.read(buff))!=-1){
 buffer.write(buff,0,len);
 }
 
 in.close();
 return new String(buffer.toByteArray()); //把字节数组转换为字符串
 }
 
 public PostTestFrame(){
 setSize(DEFAULT_WIDTH, DEFAULT_HEIGHT);
 setTitle("卫琴书籍系列");
 
 JPanel northPanel = new JPanel();
 add(northPanel, BorderLayout.NORTH);
 
 final JComboBox<String> combo = new JcomboBox<String>();
 for (int i = 0; i < books.length; i++)
 combo.addItem(books[i]);
 northPanel.add(combo);
 
 final JTextArea result = new JTextArea();
 add(new JScrollPane(result));
 
 JButton getButton = new JButton("查看");
 northPanel.add(getButton);
 getButton.addActionListener(new ActionListener(){
 public void actionPerformed(ActionEvent event){
 new Thread(new Runnable(){
 public void run(){
 final String SERVER_URL =
 "http://www.javathinker.net/aboutBook.jsp";
 result.setText("");
 Map<String, String> post =
 new HashMap<String, String>();
 post.put("title",
 books[combo.getSelectedIndex()]);
 try{
 result.setText(doPost(SERVER_URL, post));
 }catch (IOException e){
 result.setText("" + e);
 }
 }
 }).start();
 }
 });
 }
 
 private static String[] books = {"Java面向对象编程",
 "Tomcat与JavaWeb开发技术详解",
 "精通Struts:基于MVC的JavaWeb设计与开发",
 "精通JPA与Hibernate:Java对象持久化技术详解",
 "Java2认证考试指南与试题解析"};
 
 public static final int DEFAULT_WIDTH = 400;
 public static final int DEFAULT_HEIGHT = 300;
 }
 | 
 作者:孙卫琴
 
 
 
 程序猿的技术大观园:www.javathinker.net
 
 
 
 [这个贴子最后由 admin 在 2021-10-09 10:59:53 重新编辑]
 |  |