[Java SE/HTTP/网络] 核心源码精讲:java.net.URLConnection

概述: URLConnection

  • URLConnection是一个抽象类,表示指向URL【指定资源】的活动连接
  • URLConnection可以检查服务器发送的首部,并相应地做出响应。
  • 它可以设置客户端请求中使用的首部字段。
  • URLConnection可以用POST、PUT和其他HTTP请求方法向服务器发回数据;
  • URLConnection类是Java协议处理器机制的一部分
package java.net;

public abstract class URLConnection {
    ...
}

URLConnection 的7种URL协议实现

  • URL对象通过openConnection()可以获得的URLConnection的子类实现的连接对象

  • URL对象支持7种协议————子类实现:

  • FileURLConnection
public class sun.net.www.protocol.file.FileURLConnection extends URLConnection
    static String CONTENT_LENGTH = "content-length";
    static String CONTENT_TYPE = "content-type";
    static String TEXT_PLAIN = "text/plain";
    static String LAST_MODIFIED = "last-modified";
    ...

final class sun.net.www.protocol.file.UNCFileURLConnection extends FileURLConnection
  • FtpURLConnection
public class sun.net.www.protocol.ftp.FtpURLConnection extends URLConnection
  • HttpURLConnection
public abstract class java.net.HttpURLConnection extends URLConnection {

public class sun.net.www.protocol.http.HttpURLConnection extends java.net.HttpURLConnection {
  • HttpsURLConnection
public abstract class javax.net.ssl.HttpsURLConnection extends HttpURLConnection {
    private static HostnameVerifier defaultHostnameVerifier = new DefaultHostnameVerifier();
    protected HostnameVerifier hostnameVerifier;
    private static SSLSocketFactory defaultSSLSocketFactory = null;
    private SSLSocketFactory sslSocketFactory;
    ...


public class sun.net.www.protocol.https.HttpsURLConnectionImpl extends javax.net.ssl.HttpsURLConnection {
  • JarURLConnection
public abstract class java.net.JarURLConnection extends URLConnection
    private URL jarFileURL;
    private String entryName;
    protected URLConnection jarFileURLConnection;
    ...
  • MailToURLConnection
public class sun.net.www.protocol.mailto.MailToURLConnection extends URLConnection

这几种连接对象,都在包sun.net.www.protocol or java.net 包下面。

区别: URL vs. URLConnection

  • URL和URLConnection这两个类最大的不同在于:
  • URLConnection提供了对HTTP首部的访问;
  • URLConnection可以配置发送给服务器的请求参数;
  • URLConnection除了读取服务器数据外,还可以向服务器写入数据;
  • URL的构成:可由协议名、主机、端口和资源组成:
protocol://host:port/resourceName

例如:
file:///c:/Users/xxxxxx/Desktop/hosts.txt
https://www.baidu.com/index.html?param1=2222

URLConnection API

打开 URLConnection : openConnection()

  • 直接使用URLConnection类的程序遵循以下基本步骤:
  • 构造一个URL对象;
  • 调用这个URL对象的openConnection()获取一个对应该URL的URLConnection对象;
  • 配置这个URLConnection;
  • 读取首部字段;
  • 获得输入流并读取数据;
  • 获得输出流并写入数据;
  • 关闭连接;

注:并不一定执行所有这些步骤。看你需不需要!

  • URLConnection类仅有的一个构造函数protected类型:
protected URLConnection(URL url)
    String urlPath = "http://www.baidu.com"; //"file://d:/xx/yy/demo.txt" , "ftp://dd/gg/jh/demo.txt"
try {
	URL url = new URL(urlPaht);
	URLConnection connection = url.openConnection();
	//从URL读取...
} catch (Exception e) {
	// TODO: handle exception
}

读取服务器的数据

  • 下面是使用URLConnection对象从一个URL获取数据所需的最起码的步骤:
  • 构造一个URL对象;
  • 调用这个URL对象的openConnection()方法,获取对应该该URL的URLConnection对象;
  • 调用这个URLConnection的getInputStream()方法;
  • 使用通常的流API读取输入流;
  • getInputStream()方法返回一个通用InputStream,可以读取和解析服务器发送的数据:
public class Test {
	public static void main(String[] args) {
		try {
		    //打开URLConnection进行读取
			URL url = new URL("http://www.baidu.com");
			URLConnection connection = url.openConnection();
			try (InputStream in = connection.getInputStream()){    //带资源的try-catch语句,自动关闭
				InputStream buffer = new BufferedInputStream(in);
				//将InputStream串链到一个Reader
				Reader reader = new InputStreamReader(buffer);
				int c;
				while ((c = reader.read())!= -1) {
					System.out.print((char)c);
				}
			} catch (MalformedURLException e) {
				
			}
		} catch (IOException e) {
			
		}
	}	
}

读取指定的首部字段

  • 可以获取请求首部中特定的常用字段的方法:
  • Content-Type
  • Content-Length
  • Content-encoding
  • Date
  • Last-modified
  • Expires

public String getContentType()

  • getContentType()

返回响应主体的MIME内容类型。如果没有提供内容类型,它不会抛出异常,而是返回null;

public int getContentLength()

  • getContentLength()

告诉你内容中有多少字节。如果没有Content-Length首部,getContentLength()就返回-1;

public long getContentLengthLong()

Java7增加的
与getContentLength()类似,只不过它会返回一个long而不是int,这样就可以处理更大的资源;

public String getContentEncoding()

  • getContentEncoding()

返回一个String,指出内容是如何编码的。如果发送的内容没有编码,这个方法就返回null;

public long getDate()

  • getDate()方法

返回一个long,指出文档何时发送;

public long getExpiration()

  • 有些文档有基于服务器的过期日期,指示应当何时从缓存中删除文档,并从服务器重新下载。

  • 如果HTTP首部没有包括Expiration字段,getExpiration()就返回0,这表示文档不会过期,将永远保留在缓存中;

public long getLastModified()

返回文档的最后修改日期;

Test .java

import java.io.IOException;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLConnection;

public class Test {
    public static void main(String[] args) {
        try {
            URL url = new URL("http://www.baidu.com");
            URLConnection connection = url.openConnection();
            System.out.println("Content-Type: " + connection.getContentType());
            System.out.println("Content-Length: " + connection.getContentLength());
            System.out.println("Content-LengthLong: " + connection.getContentLengthLong());
            System.out.println("Content-encoding: " + connection.getContentEncoding());
            System.out.println("Date: " + connection.getDate());
            System.out.println("Expires: " + connection.getExpiration());
            System.out.println("Last-modified: " + connection.getLastModified());
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }
}

out.log

Content-Type: text/html
Content-Length: 2381
Content-LengthLong: 2381
Content-encoding: null
Date: 1735307608000
Expires: 0
Last-modified: 0

获取任意首部字段

public String getHeaderField(String name)

  • getHeaderField()

返回指定首部字段的值。首部的名不区分大小写,也不包含结束冒号;

URL url = new URL("http://www.baidu.com");
URLConnection connection = url.openConnection();
System.out.println(connection.getHeaderField("Content-Type"));
System.out.println(connection.getHeaderField("last-modified"));

//输出
text/html
Mon, 23 Jan 2017 13:27:36 GMT

public String getHeaderFieldKey(int n)

  • getHeaderFieldKey(int n)

返回第n个首部字段的键(即字段名)。请求方法本身是第0个首部,它的键为null。第一个首部即编号为1

System.out.println(connection.getHeaderFieldKey(5));    //输出Content-Type

public String getHeaderField(int n)

返回第n个首部字段的值,包含请求方法和路径的起始行是第0个首部字段,实际的第一个首部编号为1

Test.java : 循环显示整个HTTP首部

public class Test {

	public static void main(String[] args) {
		try {
			URL url = new URL("http://www.baidu.com");
			URLConnection connection = url.openConnection();
			for (int i = 1; ; i++) {
				String header = connection.getHeaderField(i);
				if (header == null) {
					break;
				}
			System.out.println(connection.getHeaderFieldKey(i)+": "+header);	
			}
		} catch (IOException e) {
			
		}
	}	
}

//输出
Accept-Ranges: bytes
Cache-Control: private, no-cache, no-store, proxy-revalidate, no-transform
Connection: Keep-Alive
Content-Length: 2381
Content-Type: text/html
Date: Thu, 04 Oct 2018 13:14:20 GMT
Etag: "588604ec-94d"
Last-Modified: Mon, 23 Jan 2017 13:28:12 GMT
Pragma: no-cache
Server: bfe/1.0.8.18
Set-Cookie: BDORZ=27315; max-age=86400; domain=.baidu.com; path=/

public long getHeaderFieldDate(String name, long Default)

这个方法首先获取由name参数指定的首部字段,然后尝试将这个字符串转换为一个long;

public long getHeaderFieldInt(String name, int Default)

这个方法获取首部字段name的值,尝试将其转换为int;

缓存

  • 默认情况下,一般认为使用GET通过HTTP访问的页面可以缓存,也应当缓存。使用HTTPS或POST访问的页面通常不应缓存。不过,HTTP首部可以对此做出调整:

  • Last-modified 首部指示资源最后一次修改的日期。

客户端可以使用一个HEAD请求来检查这个日期,只有当本地缓存的副本早于Last-modified日期时,它才会真正执行GET来获取资源;

配置连接

  • URLConnection类有7个保护的实例字段,定义了客户端如何向服务器做出请求:
protected URL url;
protected boolean doInput = true;
protected boolean doOutput = false;
protected boolean allowUserInteraction = defaultAllowUserInteraction;
protected boolean useCaches = defaultUseCaches;
protected long ifModifiedSince = 0;
protected boolean connected = false;

例如,如果doOutput为true,那么除了通过这个URLConnection读取数据外,还可以将数据写入到服务器。
如果useCaches为false,连接会绕过所有本地缓存,重新从服务器下载文件;

由于这些字段都是保护字段,所以它们的值要通过相应的set方法和get方法来访问和修改!
只能在URLConnection连接之前修改这些字段,对于设置字段的方法,如果调用这些方法时连接已经打开,大多数方法会抛出一个IllegalStateException异常

protected URL url

  • url字段指定了这个URLConnection连接的URL。可以通过getURL()方法获取这个字段的值
public class Test {

	public static void main(String[] args) {
		try {
			URL url = new URL("http://www.baidu.com");
			URLConnection connection = url.openConnection();
			System.out.println(connection.getURL());         //输出http://www.baidu.com
		} catch (IOException e) {			
		}
	}	
}

protected boolean connected

如果连接已经打开,boolean字段connected为true,如果连接关闭,这个字段则为false。由于创建一个新的URLConnection对象时连接尚未打开,所以其初始值为false。没有直接读取或改变connected值的方法。不过,任何导致URLConnection连接的方法都会将这个变量设置为true;

protected boolean allowUserInteraction

有些URLConnection需要与用户交互。allowUserInteraction字段指示了是否允许用户交互。默认值为false;

protected boolean doInput

URLConnection可以用于读取服务器、写入服务器,或者同时用于读/写服务器。如果URLConnection可以用来读取,保护类型boolen字段doInput就为true,否则为false;默认为true!

protected boolean doOutput

如果URLConnection可以用于写入,保护类型boolen字段doOutput就为true,否则为false;默认为false!

protected long ifModifiedSince

protected boolean useCaches

useCaches变量确定了是否可以使用缓存。默认值为true,表示将使用缓存;false表示不使用缓存 !!

超时

  • 有4个方法可以查询和修改连接的超时值。也就是说,底层socket等待远程服务器的响应时,等待多长时间后会抛出SocketTimeoutException异常:
//控制socket等待建立连接的时间
public void setConnectTimeout(int timeout)
public int getConnectTimeout()

//控制输入流等待数据到达的时间
public void setReadTimeout(int timeout)
public int getReadTimeout()

都以毫秒为单位。都将0解释为永远不超时。如果超时值为负数,两个设置方法都会抛出IllegalArgumentException异常;

配置客户端请求HTTP首部

  • 每个URLConnection会在首部默认设置一些不同的名–值对。打开连接前,可以使用setRequestProperty()方法为HTTP首部增加首部字段:
//只能在连接打开之前使用。如果连接已经打开,它将抛出一个IllegalArgumentException异常;getRequestProperty()方法返回这个
//URLConnection所用HTTP首部中指定字段的值
public void setRequestProperty(String key, String value)
  • HTTP允许一个指定名字的属性有多个值。在这种情况下,各个值用逗号隔开;
  • 要增加另外一个属性值,需要使用addRequestProperty()方法:
public void addRequestProperty(String key, String value)
  • 如果出于某种原因需要查看URLConnection中的首部,有一个标准的获取方法:
public String getRequestProperty(String key)
  • Java还提供了一个方法,可以获得连接的所有请求属性并作为一个Map返回:
public Map<String,List<String>> getRequestProperties()  //键是首部名,值是属性值列表

向服务器写入数据

  • 有时你需要向URLConnection写入数据,例如,使用POST向Web服务器提交表单,或者使用PUT上传文件。
  • getOutputStream()方法返回一个OutputStream,可以用来写入数据传送给服务器:
public OutputStream getOutputStream()
  • 由于URLConnection在默认的情况下不允许输出,所以在请求输出流之前必须调用setDoOutput(true)。为一个HTTP URL将doOutput设置为true时,请求方法将由GET变为POST;
public static void main(String[] args) {
	try {
		URL url = new URL("http://www.baidu.com");			
		URLConnection connection = url.openConnection();
		connection.setDoOutput(true);
		OutputStream out = connection.getOutputStream();
		OutputStream buff = new BufferedOutputStream(out);
		OutputStreamWriter writer = new OutputStreamWriter(buff);
		writer.write("name=yd&sex=man");
		writer.flush();
		writer.close();
	} catch (IOException e) {
		
	}
}	

关闭连接

  • 要关闭连接,请在InputStreamOutputStream对象上调用 close() 方法。这样做可以释放与URLConnection实例关联的网络资源

小结

  • URLConnectionconnect() 函数,实际上只是建立了一个与服务器的tcp连接,并没有实际发送http请求。

  • 无论是post还是gethttp请求实际上直到HttpURLConnectiongetInputStream()这个函数里面才正式发送出去。

  • 在用POST方式发送URL请求时,URL请求参数的设定顺序是重中之重。

connection对象的处理设置参数和一般请求属性和写入提交数据都必须要在connect()函数执行之前完成。
outputStream写提交数据操作,必须要在inputStream读操作之前。
这些顺序实际上是由http请求的格式决定的。

  • http请求实际上由两部分组成:http header 和 http body
  • http头,所有关于此次http请求的配置都在http头里面定义
  • http 正文content。connect()函数会根据HttpURLConnection对象的配置值生成http头部信息,因此在调用connect函数之前,就必须把所有的配置准备好。
  • http头后面紧跟着的是http请求的正文,正文的内容是通过outputStream流写入的。
  • 实际上outputStream不是一个网络流,充其量是个字符串流,往里面写入的东西不会立即发送到网络,而是存在于内存缓冲区中。
  • outputStream流关闭时,根据输入的内容生成http正文。至此,http请求的东西已经全部准备就绪。
  • getInputStream()函数调用的时候,就会把准备好的http请求正式发送到服务器了,然后返回一个输入流,用于读取服务器对于此次http请求的返回信息。
  • 由于http请求在getInputStream的时候已经发送出去了(包括http头和正文),因此在getInputStream()函数之后对connection对象进行设置(对http头的信息进行修改)或者写入outputStream(对正文进行修改)都是没有意义的了,执行这些操作会导致异常的发生。

Y 推荐文献

X 参考文献

posted @ 2024-12-28 10:56  千千寰宇  阅读(15)  评论(0编辑  收藏  举报