[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
orjava.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) {
}
}
关闭连接
- 要关闭连接,请在
InputStream
或OutputStream
对象上调用close()
方法。这样做可以释放与URLConnection
实例关联的网络资源。
小结
-
URLConnection
的connect()
函数,实际上只是建立了一个与服务器的tcp
连接,并没有实际发送http
请求。 -
无论是
post
还是get
,http
请求实际上直到HttpURLConnection
的getInputStream()
这个函数里面才正式发送出去。 -
在用
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 推荐文献
- [Java SE/JDK/网络] 核心源码精讲:java.net.HttpURLConnection - 博客园/千千寰宇
- [Java SE] Java-文件系统-常用文件路径的获取方法 - 博客园/千千寰宇
- [Java/网络/HTTP(S)] 基于
Http(s)URLConnection
的网络请求工具(HttpRequestUtils) - 博客园/千千寰宇
X 参考文献
本文链接: https://www.cnblogs.com/johnnyzen
关于博文:评论和私信会在第一时间回复,或直接私信我。
版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!
日常交流:大数据与软件开发-QQ交流群: 774386015 【入群二维码】参见左下角。您的支持、鼓励是博主技术写作的重要动力!