URLConnection-URL连接
Posted PacosonSWJTU
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了URLConnection-URL连接相关的知识,希望对你有一定的参考价值。
【README】
- 本文介绍了 URLConnection java类,通过 URLConnection 如何获取网络资源;
- 本文还梳理了涉及网络编程的java类的进化过程;从 URL -> URLConnection -> HttpURLConnection 或 HttpClient ;
- URL与URI的介绍, refer2 java URL和URI_PacosonSWJTU的博客-CSDN博客
1. 按理URL 有获取网络资源的接口(如getContent(), getFile()),为啥还要封装 URLConnection ? URL 获取资源方法如下:
2. 很显然, URL 只提供了客户端与服务器简单交互的功能,没有提供复杂的交互功能,比如请求头,缓存,字符编码,鉴权,等等,并根据请求头做出不同响应啊等等;所以引入 URLConnection来封装客户端与服务器间的复杂功能;
- 2.1 URL 与 URLConnection 两者间还有一个重要区别:
- 2.1.1 URL 只能读取网络资源内容(单向),而 URLConnection 提供了不仅从服务器读取数据,还有向服务器写入数据的功能,是双向交互;
3. 小结: URL 和 URLConnection的不同点
- URLConnection 提供了对http的首部访问接口;
- URLConnection 可以配置发送给服务器的请求参数;
- URLConnection除了可以读取数据之外,还可以向服务器写入数据; (数据流是双向)
4. 那为啥还要在 URLConnection的基础上 引入 HttpURLConnection ?
abstract public class HttpURLConnection extends URLConnection {
HttpURLConnection 继承了 URLConnection, 前者对后者的功能进行了扩展,以提供基于http协议的api,最显著的差别是,提供了获取错误输入流,响应码等方法,这是 URLConnection做不到的(根据下图的方法列表,HttpURLConnection也只是一点点扩展);
5. 我们再深入一下,既然有了 HttpURLConnection,那为啥 apache 还提供了 HttpClient-http客户端工具包进行网络编程?
在通常状况下,若是只是须要向Web站点的某个简单页面提交请求并获取服务器响应,HttpURLConnection彻底能够胜任。
但访问一些页面需要复制操作如鉴权,这就涉及Session、Cookie的处理了,若是打算使用HttpURLConnection来处理这些细节,固然也是可能实现的,只是处理起来难度就大了。
因此 为了简化复杂的http网络编程,apache提供了HttpClient工具包;
补充: 第5点并不是说不用 HttpURLConnection,全部用 HttpClient ;
要知道, 性能上 HttpURLConnection高于 HttpClient,简单网络请求用 HttpURLConnection更快,复杂请求如 保存会话session,cookie,使用缓存等 使用 HttpClient;可以理解为 HttpURLConnection 是 轻量级网络请求, HttpClient 封装了其他额外的功能,比较重量级
(但是我们写底层框架的时候,一般用的都是 HttpURLConnection,因为它是jdk自带的,HttpClient 需要额外引入apache依赖,不便于后期框架代码维护);
6. 下面给出 使用 URL, URLConnection, HttpURLConnection, HttpClient 读取网络资源的代码示例
- 6.1 根据 URL 获取资源
// 根据 URL 获取资源
@Test
public void f0 () throws Exception {
URL url = new URL("http://www.baidu.com");
try (InputStream inputStream = url.openStream()) {
BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(inputStream, StandardCharsets.UTF_8));
String result = "";
while((result = bufferedReader.readLine()) != null) {
System.out.println(result);
}
}
}
- 6.2 根据 URLConnection 获取资源
// 根据 URLConnection 获取资源
@Test
public void f1() throws Exception {
URL u = new URL("http://www.baidu.com");
// 打开连接获取 URLConnection对象
URLConnection uc = u.openConnection();
uc.setAllowUserInteraction(true);
// 读取资源信息
System.out.println("过期时间 " + uc.getExpiration());
System.out.println("报文长度 " + uc.getContentLength());
System.out.println("最后修改时间" + new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date(uc.getLastModified())));
// try资源块-自动关闭输入流
try (InputStream inputStream = uc.getInputStream()) {
BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(inputStream, StandardCharsets.UTF_8));
// 打印从服务器读取的报文
String line = "";
while ((line = bufferedReader.readLine()) != null) {
System.out.println(line);
}
}
}
- 6.3 根据 HttpURLConnection 获取资源
// 根据 HttpURLConnection 获取资源
@Test
public void f1_1() throws Exception {
URL u = new URL("http://www.baidu.com");
// 获取 HttpURLConnection 连接
HttpURLConnection httpUc = (HttpURLConnection) u.openConnection();
// try资源块-自动关闭输入流
try (InputStream inputStream = httpUc.getInputStream()) {
BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(inputStream, StandardCharsets.UTF_8));
// 打印从服务器读取的报文
String line = "";
while ((line = bufferedReader.readLine()) != null) {
System.out.println(line);
}
}
}
- 6.4 根据 apache HttpClient 获取资源
// 根据 apache HttpClient 获取资源
@Test
public void f1_2() {
//1.获得一个httpclient对象
CloseableHttpClient httpclient = HttpClients.createDefault();
//2.生成一个get请求
HttpGet httpget = new HttpGet("http://www.baidu.com");
//3.执行get请求并返回结果
try (CloseableHttpResponse response = httpclient.execute(httpget)) {
// 判断响应码
if (HttpStatus.SC_OK == response.getStatusLine().getStatusCode()) {
// 读取报文
BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(response.getEntity().getContent(), StandardCharsets.UTF_8));
String line = "";
while((line = bufferedReader.readLine()) != null) {
System.out.println(line);
}
}
} catch (Exception e) {
throw new RuntimeException("请求错误");
}
}
【1】URLConnection 简要介绍
1)URLConnection 是java的协议处理器机制的一部分,这个机制还包括 URLStatementHandler 类;
【1.1】打开 URLConnection
1)打开 URLConnection 与服务器交互步骤
- 构造 URL 对象;
- 调用 URL.openConnection() 获取 URLConnection 对象;
- 配置这个 URLConnection对象;
- 读取首部字段;(或有)
- 获取输入流并读取数据 调用 URLConnection.getInputStream() ; (或有)
- 获取输出流并写入数据; (或有)
- 关闭连接;
// 打开 URLConnection 与服务器交互
@Test
public void f1() throws Exception {
URL u = new URL("http://www.baidu.com");
// 打开连接获取 URLConnection对象
URLConnection uc = u.openConnection();
uc.setAllowUserInteraction(true);
try (InputStream inputStream = uc.getInputStream()) { // try资源块-自动关闭输入流
BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(inputStream, StandardCharsets.UTF_8));
// 打印从服务器读取的报文
String line = "";
while((line = bufferedReader.readLine()) != null) {
System.out.println(line);
}
}
}
【1.2】URLConnection 读取首部(请求头或响应头)
1)http首部包括 所请求网络资源的内容类型,长度,内容编码字符集,日期时间,内容过期时间,内容最后修改时间;
【1.2.0】常用首部包括:
- Content-type; 内容类型; MIME 内容类型; URLConnection.getContentType()
- Content-length;内容字节长度; URLConnection.getContentLength()
- Content-encoding,内容编码(非字符编码,字符编码格式在 content-type中的mime类型指定); URLConnection.getContentEncoding()
- Date, 内容产生时间; URLConnection.getDate()
- Last-modified, 最后修改时间, 以便于缓存; URLConnection.getLastModified()
- Expires, 到期时间, 以便于缓存; URLConnection.getExpiration()
【1.2.1】 getContentType 返回响应主体的 MIME内容类型 (多用途互联网邮件扩展类型)
1)常用的 mime类型包括
- text/html
- text/plain
- image/gif
- application/xml
- image/jpeg
- application/json
2)如果内容类型是某种类型文本,那这个首部可能还包含一个字符集部分来标识文档的字符编码格式,如:
Content-type: text/html; charset=UTF-8 或 Content-type: application/json; charset=UTF-8
【1.2.2】getContentLength 获取响应报文体字节个数
- 1)getContentLength 返回是 int 类型,最多标识 2gb(2^31=2g=20亿字节);
- 2)随着网络发展,实际上很有可能资源大小超过 2gb; 在这种情况下 getContentLength 返回-1;
java7 新增了 getContentLengthLong 方法 返回类型是long, 2^63 个字节,理论上可以接收 8000PB=800万TB 个字节,足够使用了;
【1.2.3】getContentEncoding() 获取内容编码格式
web上常用的内容编码格式 可能是 x-gzip 或 GZipInputStream 直接解码;
【1.2.4】getDate() 指出文档发送给客户端的时间;
【1.2.5】getExpiration() 文档在服务器的过期时间
- 1)提示客户端应该何时从缓存中删除文档,并从服务器重新下载;
- 2)如果http首部没有 expiration字段,getExpiration() 返回0, 表示文档不会过期,将永远保留在缓存中;
【1.2.6】getLastModified 返回文档的最后修改时间
例子1, 读取http常用响应头
// 读取http常用响应头
@Test
public void f0() throws Exception {
// 格式化器
SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
// 读取 URLConnection 的常用响应头
URL url = new URL("http://www.baidu.com");
URLConnection urlConnection = url.openConnection();
// 读取资源内容类型, mime 类型
System.out.println("内容类型 = " + urlConnection.getContentType());
// 读取资源内容长度
System.out.println("内容长度 = " + urlConnection.getContentLength());
// 读取资源内容编码(如gzip,不是字符编码,字符编码在 content-type中的mime类型指定)
System.out.println("内容编码 = " + urlConnection.getContentEncoding());
// 读取指出文档发送给客户端的时间
System.out.println("指出文档发送给客户端的时间 = " + simpleDateFormat.format(urlConnection.getDate()));
// 读取文档在服务器的过期时间
System.out.println("文档在服务器的过期时间 = " + simpleDateFormat.format(urlConnection.getExpiration()));
// 读取 文档的最后修改时间
System.out.println("文档的最后修改时间 = " + simpleDateFormat.format(urlConnection.getLastModified ()));
}
结果:
内容类型 = text/html
内容长度 = 2381
内容编码 = null
指出文档发送给客户端的时间 = 2021-11-06 12:02:00
文档在服务器的过期时间 = 1970-01-01 08:00:00
文档的最后修改时间 = 1970-01-01 08:00:00
例子2,读取二进制资源文件,如图片(百度上随便搜索一张),mime类型为 image/jpeg ;
@Test
public void f1() throws Exception {
URL url = new URL("https://gimg2.baidu.com/image_search/src=http%3A%2F%2Fbpic.588ku.com%2Felement_origin_min_pic%2F18%2F08%2F24%2F05dbcc82c8d3bd356e57436be0922357.jpg&refer=http%3A%2F%2Fbpic.588ku.com&app=2002&size=f9999,10000&q=a80&n=0&g=0n&fmt=jpeg?sec=1638764377&t=703444b029255bfa2ddba73484dd7c7c");
// 打开连接,判断是否为图片格式
URLConnection urlConnection = url.openConnection();
String contentType = urlConnection.getContentType();
// long contentLength = urlConnection.getContentLengthLong();
int contentLength = urlConnection.getContentLength();
System.out.println("文件内容类型=" + contentType + ", 内容长度=" + contentLength);
if (!contentType.startsWith("image") || contentLength < 1) {
throw new IOException("不是一个图片文件");
}
// 读取资源
try (BufferedInputStream bufferedInputStream = new BufferedInputStream(url.openStream())) {
byte[] data = new byte[contentLength];
int offset = 0;
int byteread = 0;
while(offset < contentLength) {
if ((byteread = bufferedInputStream.read(data, offset, data.length - offset)) == -1) {
break;
}
offset += byteread;
}
if (offset != contentLength) {
throw new IOException(MessageFormat.format("读取失败, 读了{0}个字节,期望读取{1}个字节", offset, contentLength));
}
// 写入文件,获取文件名
String filename = url.getFile();
System.out.println("资源文件名 = " + filename);
filename = "temp01." + contentType.split("[\\\\W+]")[1];// 非词字符正则分割
try (FileOutputStream fos = new FileOutputStream("D:/temp/" + filename)) {
fos.write(data);
fos.flush();
}
}
}
结果:
文件内容类型=image/jpeg, 内容长度=46417
【1.3】获取任意首部
// 获取任意首部
@Test
public void f1() throws Exception {
URL url = new URL(
"https://gimg2.baidu.com/image_search/src=http%3A%2F%2Fbpic.588ku.com%2Felement_origin_min_pic%2F18%2F08%2F24%2F05dbcc82c8d3bd356e57436be0922357.jpg&refer=http%3A%2F%2Fbpic.588ku.com&app=2002&size=f9999,10000&q=a80&n=0&g=0n&fmt=jpeg?sec=1638764377&t=703444b029255bfa2ddba73484dd7c7c");
URLConnection urlConnection = url.openConnection();
// 获取所有首部(响应头)的键值对
Map<String, List<String>> headerFileds = urlConnection.getHeaderFields();
Set<Map.Entry<String, List<String>>> entrySet = headerFileds.entrySet();
entrySet.forEach(entry -> {
String key = entry.getKey();
System.out.println("key = " + key + ", 值列表=" + entry.getValue());
});
// 获取单个首部
System.out.println("获取单个首部");
System.out.println("Content-Type = " + urlConnection.getHeaderField("Content-Type"));
}
结果:
key = null, 值列表=[HTTP/1.1 200 OK]
key = Server, 值列表=[JSP3/2.0.14]
key = Access-Control-Allow-Origin, 值列表=[*]
key = Ohc-Upstream-Trace, 值列表=[118.112.225.100]
key = Connection, 值列表=[keep-alive]
key = Last-Modified, 值列表=[Thu, 01 Jan 1970 00:00:00 GMT]
key = Ohc-File-Size, 值列表=[46417]
key = Date, 值列表=[Sat, 06 Nov 2021 04:42:29 GMT]
key = Accept-Ranges, 值列表=[bytes]
key = Ohc-Cache-HIT, 值列表=[cd6ct100 [4], bdix100 [4]]
key = Ohc-Response-Time, 值列表=[1 0 0 0 0 0]
key = ETag, 值列表=[927f0861741bd2135df3cbac979cdded]
key = Timing-Allow-Origin, 值列表=[*]
key = Expires, 值列表=[Sat, 04 Dec 2021 06:42:40 GMT]
key = Content-Length, 值列表=[46417]
key = Age, 值列表=[1287]
key = Content-Type, 值列表=[image/jpeg]
获取单个首部
Content-Type = image/jpeg
【2】 缓存
【2.1】缓存涉及的 http报文头
1)Expires 报文头指示这个资源可以缓存,期限是指定的时间为止;
2)Cache-control 首部提供了更加细粒度的缓存策略;
Cache-control 会覆盖 Expires ,即优先级 前者高于后者;服务器可以在一个首部中发送多个 Cache-control 首部,只要它们没有冲突;
3)Last-modified 指示资源最后一次修改日期。只有当本地缓存的副本早于这个日期,才会到服务器获取资源,否则客户端一直使用本地缓存;
4)Etag首部: 资源改变时的唯一标识; 当客户端资源副本的Etag 与 服务器不同时,才会到服务器获取资源,否则客户端一直使用本地缓存;
5)缓存策略(干货):
- 如果本地缓存中有这个资源的一个表示, 而且还没有到它的过期时间 Expires,那么可以直接使用这个资源,而无需请求服务器;
- 如果本地缓存中有这个资源的一个表示,但到了过期时间,在完成 get请求前,可以检查服务器首部的head首部 Etag,查看资源是否已经改变;
6)java自带的web缓存类
默认情况下,java并没有缓存。需要安装URL类使用的系统级缓存,需要有:
- ResponseCache的子类;
- CacheRequest的子类;
- CacheResponse的子类;
【3】配置 URLConnection连接
【3.1】配置属性及其方法
1)URLConnection定义了7个保护字段,定义了客户端如何向服务器发送请求;
- 1.1)protected URL url, 指定这个 URLConnection 连接 URL;初始化一次,不能修改;
- 1.2)protected boolean connected,是否连接;连接打开,该值为true,连接关闭,该值为false;connect(), getInputStream(), getOutputStream() 都会打开连接; disconnect() 关闭连接;
- 1.3)protected boolean allowUserInteraction, 指示了是否允许用户交互;只能在打开连接前设置,连接后设置抛出异常;
- 1.4)protected boolean doInput,是否可以从服务器服务器读取资源;补充 URConnection 可以用于读取服务器,写入服务器,或读写服务器;但响应字段要设置为true;
- 1.5)protected boolean doOutput,是否可以向服务器写入数据; 当为一个 http URL的doOutput设置为 true时,请求方法从 GET 修改为 POST;
- 1.6)protected boolean ifModifiedSince,设置为true,则客户端请求报文头包括一个首部 If-Modified-Since,值日期时间格式;如果服务器文档在这个时间之后修改,则发送该文档,否则不发送(而发送响应码 304 Not Modified),客户端使用本地缓存; URLConnection的 ifModifiedSince 字段指定了 放置在 If-Modified-Since 首部字段中的日期,调用 setIfModifiedSince(long ifModifiedSince毫秒数) 来设置;
- 1.7)protected boolean useCaches, 客户端是否使用本地缓存; URCConnection.setUseCahces(false) 用于禁用本地缓存;
2)URLConnection 连接属性设置例子
// URCConnection 连接属性设置例子
@Test
public void f3() throws Exception {
URL u = new URL("http://www.baidu.com");
// 打开连接获取 URLConnection对象
URLConnection uc = u.openConnection();
uc.setAllowUserInteraction(true); // 允许交互
uc.setDoInput(true); // 可以从服务器读取数据
uc.setDoOutput(true); // 可以向服务器写入数据
uc.setIfModifiedSince(new Date().getTime()); // 设置资源最后修改时间
uc.setUseCaches(false); // 禁用缓存
// try资源块-自动关闭输入流
try (InputStream inputStream = uc.getInputStream()) {
BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(inputStream, StandardCharsets.UTF_8));
// 打印从服务器读取的报文
String line = "";
while ((line = bufferedReader.readLine()) != null) {
System.out.println(line);
}
}
}
【3.2】超时
- setConnectionTimeout(int),设置连接获取超时时间;
- setReadTimeout(int) ,设置读取服务器资源超时时间;
【4】配置客户端请求http首部
【4.1】增加首部字段
1) URLConnection.setRequestProperty(String name, String value) 为http首部增加字段;
其允许一个name,有多个value,多个value通过逗号隔开; 该方法只能在 打开连接前使用;
【4.2】向服务器写入数据
// 向服务器写入数据
@Test
public void f4() throws Exception {
URL u = new URL("http://www.baidu.com");
// 打开连接,把请求方法从get变为post
URLConnection uc = u.openConnection();
uc.setDoOutput(true); // get 变为 post
uc.setRequestProperty("cookie", "username=zhangsan; password=123456; session=B2C7E8A1F2F5E8"); // 设置cookie请求头
// 写入数据到server
try(OutputStream outputStream = uc.getOutputStream()) {
outputStream.write("四川省成都市高新区".getBytes(StandardCharsets.UTF_8));
}
System.out.println("写入数据成功,bingo");
}
【5】HttpURLConnection
HttpURLConnection 是抽象类,构造函数是保护类型,所以不能直接创建;
URL.openConnection() 返回的就是一个 HttpURLConnection的一个实例; 如下:
URL u = new URL("http://www.baidu.com");
// 获取 HttpURLConnection 连接
HttpURLConnection httpUc = (HttpURLConnection) u.openConnection();
【5.1】 请求方法
1)改变请求方法
HttpURLConnection.setRequestMethod() 用于修改请求方法;
2)请求方法包括:
- GET,请求资源,但没有请求体;
- POST,请求资源, 有请求体;
- HEAD,告诉服务器只返回http首部,不用实际发送文件 ;常见用途是检查文件的最后修改时间;
- PUT,html编辑器或向上传文件到服务器使用put方法;
- DELETE,删除web服务器上的文件;
- OPTIONS,询问某个url 支持哪些选项;
- TRACE ,trace会发送http报文头,服务器接收这个报文头;可以查看 服务器和客户端之间的代理服务器做了哪些修改;
3)restful api中 方法类型与业务操作对应关系;
序号 | 方法类型 | 业务操作 |
1 | get | 查询数据 |
2 | post | 新增数据 |
3 | put | 修改或更新数据 |
4 | delete | 删除数据 |
【5.2】断开与服务器的连接
调用 HttpURLConnection.disconnect() 方法可以关闭连接,同时关闭流;但关闭流不会关闭连接;
【5.3】处理服务器响应
1)响应报文示例
(注意: 响应报文头 与 响应实体间 有一个空行(作为分隔符),图片中我忘记标识出来了,特此说明)
2)获取响应码和响应报文
// 获取响应码和响应报文
@Test
public void f6() throws Exception {
URL u = new URL("http://www.baidu.com");
// 打开连接,把请求方法从get变为post
HttpURLConnection httpURLConnection = (HttpURLConnection) u.openConnection();
System.out.println("响应码 = " + httpURLConnection.getResponseCode());
System.out.println("响应消息 = " + httpURLConnection.getResponseMessage());
// 获取所有响应头
for (int i = 1; ; i++) {
String header = httpURLConnection.getHeaderField(i);
String key = httpURLConnection.getHeaderFieldKey(i);
if (header == null || key == null) break;
System.out.println("key=" + key + ", value=" + header);
}
}
打印结果:
响应码 = 200
响应消息 = OK
key=Content-Length, value=2381
key=Content-Type, value=text/html
key=Server, value=bfe
key=Date, value=Sat, 06 Nov 2021 08:51:40 GMT
3)补充:HttpURLConnection 封装了很多常量响应码
以上是关于URLConnection-URL连接的主要内容,如果未能解决你的问题,请参考以下文章
使用实体框架迁移时 SQL Server 连接抛出异常 - 添加代码片段
错误:E/RecyclerView:未连接适配器;跳过片段上的布局
连接MySQL出现错误:ERROR 1045 (28000): Access denied for user ‘root‘@‘localhost‘ (using password: YES)(代码片段