|
本文参考笔者所写的《Java网络编程核心技术详解》,电子工业出版社出版。
![点击在新窗口中浏览原图
CTRL+鼠标滚轮放大或缩小](png/topadv-javanet.png)
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请求中的书名,然后返回该书的相关信息。
图 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 重新编辑]
|
|