目录
② URLConnection openConnection()方法
Ⅲ. 通过HttpURLConnection写数据与发送数据问题
一、URL
URL是“统一资源定位符”,表示Internet上某一资源的地址。通过URL,开发人员可以访问Internet上的各种资源
完整的、带有授权部分的资源标志符语法为“协议://用户名:密码@子域名.域名.顶级域名:端口号/目录/路径/文件名.文件后缀?参数=值#标志”。
在Java中,java.net.URL类是对统一资源定位符的抽象。它扩展了java.lang.Object,是一个final类,不能对其派生子类。它不依赖于配置不同类型URL的实例,而使用了策略(strategy)设计模式。协议处理器就是策略,URL类构成上下文,通过它来选择不同的策略。
需要注意的是,URL是不可变的。构造URL对象之后,其字段不再改变,所以它们的线程是安全的。
1.1、创建URL对象
和InetAddress对象不同,我们可以构造java.net.URL的实例,这是因为IP地址是唯一的,而IP地址下的资源却是海量的。不同构造函数所需的信息有所不同,具体如下所示。
方法及描述 |
URL(String url) 通过给定的URL字符串创建URL |
URL(String protocol, String host, String file) |
URL(String protocol, String host, int port, String file) 通过给定的参数(协议、主机名、端口号、文件名)创建URL。 |
URL(URL context, String url) 使用基地址和相对URL创建 |
使用那个构造函数取决于你有那些信息,如果试图为一个不存在的URL创建对象,那么这些构造函数都会抛出一个MalformedURLException异常。
值得注意的是,Java除了验证能否识别URL模式之外,不会对它构造的URL完成任何正确性检查。所以,程序员应该要确保所创建的URL是合法的。
① 使用完整字符串构造URL对象
上述第一种构造函数URL(String url)只接受一个字符串形式的绝对URL作为唯一的参数。下面准备了一个很简单的程序,用来确定虚拟机支持那些协议。
public static void main(String[] args) {
// 超文本传输协议
test("http://www.baidu.com");
// 安全http
test("https://www.baidu.com");
// 文件传输协议
test("ftp://ibiblio.org/pub/languages/java/javafaq/");
// 简单邮件传输协议
test("mailto:963766197@qq.com");
// 本地文件访问
test("file:///tec/password");
}
private static void test(String url){
try{
URL u = new URL(url);
System.out.println("支持 "+u.getProtocol());
}catch(MalformedURLException ex){
String protocol = url.substring(0,url.indexOf(':'));
System.out.println("不支持 "+protocol);
}
}
输出结果如下所示
支持 http
支持 https
支持 ftp
支持 mailto
支持 file
② 由组成部分构造URL对象
通过指定协议、主机名和文件来创建URL对象。
URL(String protocol,String host,String file)
需要注意的是,file参数应当以斜线开头,包括路径、文件名和可选的片段标识符。下面准备了一个很简单的程序创建了URL对象。
try{
URL u = new URL("http","www.baidu.com","/index.html#intro");
}catch(MalformedURLException ex){
System.out.println("该URL不存在");
}
在默认端口不正确时,我们可以通过显式指定端口来创建URL对象
URL(String protocol,String host,int port,String file)
下面准备了一个很简单的程序创建了URL对象。
try{
URL u = new URL("http","www.baidu.com",8080,"/index.html");
}catch(MalformedURLException ex){
System.out.println("该URL不存在");
}
③ 构造相对URL对象
这个构造函数根据相对URL和基础URL构造一个绝对URL
URL(URL base,String relative)
例如,你可能正在解析HTML文档http://www.baidu.com/index.html,然后遇到一个名为login.html的文件连接,但没有进一步限定信息。这时,可以用包含该链接的文档的URL来提供缺少的信息。这个构造函数会计算出新的URL为http://www.baidu.com/login.html。下面准备了一个很简单的程序。
try{
URL u1 = new URL("http://www.baidu.com/index.html");
URL u2 = new URL(u1,"login.html");
}catch(MalformedURLException ex){
System.out.println("该URL不存在");
}
如果希望循环处理位于同一个目录下的一组文件,这个构造函数特别有用。
1.2、从URL中获取数据
获取到URL对象之后,我们就可以获取URL所指向文档中所包含的数据。URL类有以下几个方法可以从URL获取数据。
返回值类型 | 方法及说明 |
InputStream | openStream() 打开与此连接URL并返回一个InputStream以从该连接读取。 |
URLConnection | openConnection() 返回一个URLConnection实例,该实例表示与该引用的远程对象的连接URL。 |
URLConnection | openConnection(Proxy proxy) 相同openConnection(),不同之处在于连接将通过指定的代理建立;不支持代理的协议处理程序将忽略代理参数并进行正常连接。 |
Object | getContent() 获取此URL的内容 |
Object | getContent(Class[] classes) 获取此URL的内容 |
这些方法中,最常用的是openStream(),它返回一个inputStream,我们可以从这个流读取数据。如果需要更多地控制下载过程,应当调用openConnection方法,这会返回一个可以配置的URLConnection,再由它得到一个InputStream。最后,通过getContent()方法像URL请求其内容。下面我们介绍每个方法。
① InputStream openStream()方法
该方法链接到URL所引用的资源,在客户端和服务器之间完成必要的握手,然后返回一个InputStream对象,通过此对象可以使用InputStreamReader和BufferedReader来封装从而读取数据。
//获取URL对象
URL url = new URL("http://www.baidu.com");
//通过URL对象打开资源通道,返回InputStream原始资源字节流
InputStream is = url.openStream();
//InputStreamReader是字节流通向字符流的桥梁(也可以不用)
InputStreamReader isr = new InputStreamReader(is,"utf-8");
//BufferedReader从字符输入流中读取文本
BufferedReader br = new BufferedReader(isr);
String str;
while ((str = br.readLine()) != null) {
System.out.println(str);
}
//关闭流并释放与之关联的所有资源
br.close();
isr.close();
is.close();
从上述案例中,首先创建了URL对象,通过URL对象的openStream()方法打开输入流获取InputStream对象,再由此对象创建BufferedReader对象,通过此对象获取URL所指定资源文件的数据。
需要注意的是,从这个InputStream获取的数据是URL引用的原始数据(即未经解释的内容):如果读取ASCII文件,文件则为ASCII;如果读取HTML文件,则为原始HTML;如果读取图像文件,则为二进制图片数据等。它不包括任何HTTP首部或与协议有关的任何其他信息。
② URLConnection openConnection()方法
URL对象使用OpenConnection()方法打开指定的URL的Socket链接,并返回一个URLConnection对象。该对象表示URL所引用的远程对象的连接,然后再通过该对象的connect()方法进行链接。简单的范例代码如下所示。
//获取URL对象
URL url = new URL("http://www.baidu.com");
//调用URL对象openConnection()方法,获取URLConnection对象
URLConnection URLconnection = url.openConnection();
//将URLConnection对象转换成HttpURLConnection对象
HttpURLConnection httpConnection = (HttpURLConnection)URLconnection;
//获取请求响应码,判断访问是否成功
int responseCode = httpConnection.getResponseCode();
if (responseCode == HttpURLConnection.HTTP_OK) {
System.err.println("成功");
InputStream is = httpConnection.getInputStream();
InputStreamReader isr = new InputStreamReader(is,"utf-8");
BufferedReader bufr = new BufferedReader(isr);
String str;
while ((str = bufr.readLine()) != null) {
System.out.println(str);
}
bufr.close();
isr.close();
is.close();
} else {
System.err.println("失败");
}
如果希望与服务器直接通信,应当使用这个方法。通过URLConnection对象,可以访问服务器发送的所有数据;除了原始的文档本身之外(如HTML、纯文本、二进制图像数据等),它还允许你向URL写入数据,还可以访问这个协议指定的所有元数据。例如,访问HTTP首部以及原始HTML。
③ Object getContent()方法
getContent()方法获取由URL引用的数据,尝试由它建立某种类型的对象。如果URL指示某种文本(如ASCII或HTML文件),返回的文件通常是InputStream;如果URL指示一个图像,则返回一个java.awt.ImageProducer。它们本身并不是数据对象,而是一种途径,程序可以利用它们构造数据对象。
getContent()的做法是,从服务器获取的数据首部中查找Content-type字段。如果服务器没有使用MIME首部,或者发送了一个不熟悉的Content-type,getContent()会返回某种InputStream对象,如果无法获取这个对象,就会抛出一个IOException异常。
④ 使用URLConnectiond对象获取URL信息
Ⅰ. 创建URLConnection对象
URL url = new URL("http://www.baidu.com");
// HTTP协议生成的URLConnection类可以将其转换为HttpURLConnection子类,以便用到HttpURLConnection更多的API
URLConnection urlConnt = url.openConnection();
HttpURLConnection httpUrlConnt = (HttpURLConnection) urlConnt;
Ⅱ. 设置HttpURLConnection对象参数
// 设置是否向httpUrlConnection输出,因为这个是post请求,参数要放在
// http正文内,因此需要设为true, 默认情况下是false;
httpUrlConnection.setDoOutput(true);
// 设置是否从httpUrlConnection读入,默认情况下是true;
httpUrlConnection.setDoInput(true);
// Post 请求不能使用缓存
httpUrlConnection.setUseCaches(false);
// 设定传送的内容类型是可序列化的java对象
// (如果不设此项,在传送序列化对象时,当WEB服务默认的不是这种类型时可能抛java.io.EOFException)
httpUrlConnection.setRequestProperty("Content-type", "text/html");
// 设定请求的方法为"POST",默认是GET
httpUrlConnection.setRequestMethod("POST");
// 连接,从上述第2条中url.openConnection()至此的配置必须要在connect之前完成,
httpUrlConnection.connect();
Ⅲ. 通过HttpURLConnection写数据与发送数据问题
// 现在通过“输出流对象”构建“对象输出流对象”,以实现输出可序列化的对象。
ObjectOutputStream objOutputStrm = new ObjectOutputStream(outStream);
// 向对象输出流写出数据,这些数据将存到内存缓冲区中
objOutputStrm.writeObject(new String("我是测试数据"));
// 刷新对象输出流,将任何字节都写入潜在的流中(些处为ObjectOutputStream)
objOutputStm.flush();
// 关闭流对象。此时,不能再向对象输出流写入任何数据,先前写入的数据存在于内存缓冲区
// 在调用下边的getInputStream()函数时才把准备好的http请求正式发送到服务器
objOutputStm.close();
// 调用HttpURLConnection连接对象的getInputStream()函数,
// 将内存缓冲区中封装好的完整的HTTP请求电文发送到服务端。
InputStream inStrm = httpUrlConnt.getInputStream();
// 上边的httpConn.getInputStream()方法已调用,本次HTTP请求已结束,下边向对象输出流的输出已无意义,
// 既使对象输出流没有调用close()方法,下边的操作也不会向对象输出流写入任何数据.
// 因此,要重新发送数据时需要重新创建连接、重新设参数、重新创建流对象、重新写数据、
// 重新发送数据(至于是否不用重新这些操作需要再研究)
objOutputStm.writeObject(new String(""));
httpUrlConnt.getInputStream();
Socket通信实现步骤
1. 创建ServerSocekt和Socket
2. 打开连接到Socket的输入/输出流
3. 依照协议对Socket进行读写操作
4. 关闭输入输出流、关闭Socket
1.3、分解URL
URL由5部分组成:协议、授权机构、路径、查询字符串、片段标识符。
例如,在http://www.baidu.com/index.html?id=220308#toc中,协议是http;授权机构是www.baidu.com;路径是index.html;查询字符串是id=220308;片段标识符是toc。
授权机构还可以进一步划分为用户信息、主机和端口。例如在http://admin@www.baidu.com:8080/中,授权机构包含用户信息admin、主机www.baidu.com和端口8080
java提供了下面9个公共方法对上述URL组成部分进行只读访问
返回类型 | 描述 | 描述 |
String | getProtocal() | 返回URL的协议 |
String | getHost() | 返回URL的主机 |
int | getPort() | 返回URL端口部分 |
String | getFile() | 返回URL文件名部分 |
String | getQuery() | 返回URL查询部分。 |
String | getPath() | 返回URL路径部分。 |
String | getAuthority() | 获取此 URL 的授权部分。 |
int | getDefaultPort() | 协议的默认端口号。 |
String | getRef() | 返回URL片段标识符。 |
String | getUserInfo() | 返回用户名或口令信息。 |
下面通过简单程序演示上面的这个方法
try{
URL url = new URL("http://www.baidu.com/index.html?id=220308#Laoye");
System.out.println("URL 为:" + url.toString());
System.out.println("协议为:" + url.getProtocol());
System.out.println("验证信息:" + url.getAuthority());
System.out.println("文件名及请求参数:" + url.getFile());
System.out.println("主机名:" + url.getHost());
System.out.println("路径:" + url.getPath());
System.out.println("端口:" + url.getPort());
System.out.println("默认端口:" + url.getDefaultPort());
System.out.println("请求参数:" + url.getQuery());
System.out.println("定位位置:" + url.getRef());
}catch(IOException e){
e.printStackTrace();
}
输出结果如下所示。
[URL: http://www.baidu.com/index.html?id=220308#Laoye]
[协议: http]
[验证信息: www.baidu.com]
[文件名及参数: /index.html?id=220308]
[主机名: www.baidu.com]
[资源路径: /index.html]
[端口: -1]
[默认端口: 80]
[请求参数: id=220308]
[片段标识符: Laoye]
1.4、判断两个URL是否相等
URL类包含通常的equals()和hashCode()方法,当且仅当两个URL都指向相同主机、端口和路径上的相同资源,而且具有相同的片段标识符和查询字符串,才认为这两个URL是相等。实际上equals()方法会尝试使用DNS解析主机,来判断两个主机是否相同。
另一方面,equals()还不够深入,不会具体比较两个URL标识的资源。例如,http://www.baidu.com/不等于http://www.baidu.com/index.html。
下面我们通过简单的范例程序来演示。
URL url1 = new URL("http://www.baidu.com");
URL url2 = new URL("http://baidu.com");
if(url1.equals(url2)){
System.out.println("URL1 和 URL2 相等");
}else{
System.out.println("URL1 和 URL2 不相等");
}
除此之外,URL类还有一个sanmeFile()方法,可以检查两个URL是否指向相同的资源。
try{
URL url1 = new URL("http://www.baidu.com:80/index.html");
URL url2 = new URL("http://www.baidu.com/index.html");
if(url1.sameFile(url2)){
System.out.println("URL1 和 URL2 指向相同的资源");
}else{
System.out.println("URL1 和 URL2 指向不同的资源");
}
}catch(MalformedURLException ex){
System.err.println(ex);
}
需要注意的是,sameFile()只有在两个URL路径几乎一样才会返回true,即使前面的请求路径完全一样,但是请求参数不一样也会返回false,例如下面范例程序所示的情况。
URL url1 = new URL("http://www.baidu.com/index.html");
URL url2 = new URL("http://baidu.com/index.html");
URL url3 = new URL("http://www.baidu.com/index.html?id=308");
URL url4 = new URL("http://www.baidu.com/index.html?id=309");
上述的4个对象两两互不相等。
1.5、比较两个URL
URL有3个方法可以将一个实例转换为另外一种形式,分别是toString()、toExternalForm()和toURI()。
toString()返回的是String类型的绝对URL。显示调用toString()并不常见,通过显示(打印)语句会隐式调用toString()方法,除了打印语句以外,使用toExternalForm()方法更合适。
toExternalForm()方法将一个URL对象转换为一个字符串(拼接方式)。该方法返回表示这个URL的刻度String,它等同于toString()方法。事实上,URL的toString()方法就是返回该方法。
最后,toURI()方法将URL对象转换为URI对象。值得注意的是,URI类提供了比URL类更精确、更符合规范的行为。对于像绝对化和编码等操作,在选择时应当首选URI类。如果需要把URL存储在一个散列表或其他数据结构中,也应当首选URI类,因为它equals()方法不会阻塞。 ⬅ 重点理解