>>分享孙卫琴的Java技术专稿和著作 书籍支持  卫琴直播  品书摘要  在线测试  资源下载  联系我们
发表一个新主题 开启一个新投票 回复文章 您是本文章第 26508 个阅读者 刷新本主题
 * 贴子主题:  【Java网络编程专题】用java.net.URL类访问HTTP服务器读取网页数据 回复文章 点赞(0)  收藏  
作者:sunweiqin    发表时间:2020-01-10 10:28:02     消息  查看  搜索  好友  邮件  复制  引用

本文参考笔者所写的《Java网络编程核心技术详解》,电子工业出版社出版。
点击在新窗口中浏览原图
CTRL+鼠标滚轮放大或缩小

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方法:  
  • 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类利用URLConnection类来读取服务器的响应结果。

/* 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-object
0xCAFEBABE                                                   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请求中的书名,然后返回该书的相关信息。

点击在新窗口中浏览原图
CTRL+鼠标滚轮放大或缩小
图  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 重新编辑]
  Java面向对象编程-->对象的生命周期
  JavaWeb开发-->Servlet技术详解(Ⅱ)
  JSP与Hibernate开发-->映射一对多关联关系
  Java网络编程-->用Swing组件展示HTML文档
  精通Spring-->Vue简介
  Vue3开发-->虚拟DOM和render()函数
  【Vue.js技术专题】路由导航中抓取数据
  【Vue.js技术专题】自定义指令范例:v-drag指令
  【Java基础编程专题】浮点数的格式化以及运算精度
  【Spring专题】Spring MVC是否过时?
  【Spring专题】用Spring框架进行文件下载
  【持久化专题】映射对象标识符的基本原理
  【持久化专题】@Enumerated注解映射枚举类型
  【持久化专题】JPA的事件处理API的用法
  【Java网络编程专题】用Java套接字访问HTTP服务器读取网页数...
  【Java网络编程专题】创建基于SSL的安全服务器和安全客户的范...
  【Java网络编程专题】异步通道和异步运算结果
  【Java网络编程专题】优化访问数据库的程序代码的一些技巧
  【JavaWeb专题】在JavaWeb应用中对客户请求的异步处理
  【JavaWeb专题】Tomcat与IIS集成详解
  我的计算机书籍创作心得
  更多...
 IPIP: 已设置保密
楼主      
1页 0条记录 当前第1
发表一个新主题 开启一个新投票 回复文章


中文版权所有: JavaThinker技术网站 Copyright 2016-2026 沪ICP备16029593号-2
荟萃Java程序员智慧的结晶,分享交流Java前沿技术。  联系我们
如有技术文章涉及侵权,请与本站管理员联系。