17.2.3 URL URLConnection和URLPermission
URL
URL
(Uniform Resource Locator
)对象代表统一资源定位器,它是指向互联网“资源”的指针。资源可以是简单的文件或目录,也可以是对更为复杂对象的引用,例如对数据库或搜索引擎的查询。在通常情况下,URL
可以由协议名、主机、端口和资源组成,即满足如下格式:
URL格式
1
| protocol://host:port/resourceName
|
例如如下的URL
地址:
1
| https://lanlan2017.github.io/JavaReadingNotes/
|
URI
JDK
中还提供了一个URI
(Uniform Resource Identifiers
)类,其实例代表一个统一资源标识符,Java
的URI
不能用于定位任何资源,它的唯一作用就是解析。与此对应的是**URL
则包含一个可打开到达该资源
的输入流**,可以将URL
理解成URI
的特例.
URL方法
URL
类提供了多个构造器用于创建URL
对象,一旦获得了URL
对象之后,就可以调用如下方法来访问该URL
对应的资源。
方法 |
描述 |
String getFile() |
获取该URL 的资源名 |
String getHost() |
获取该URL 的主机名 |
String getPath() |
获取该URL 的路径部分 |
int getPort() |
获取该URL 的端口号 |
String getProtocol() |
获取该URL 的协议名称 |
String getQuery() |
获取该URL 的查询字符串部分 |
URLConnection openConnection() |
返回一个URLConnection 对象,它代表了与URL 所引用的远程对象的连接 |
InputStream openStream() |
打开与此URL 的连接,并返回一个用于读取该URL 资源的InputStream |
程序示例 多线程下载工具类
URL
对象中的前面几个方法都非常容易理解,而该对象提供的openStream
方法可以读取该URL
资源的InputStream
,通过该方法可以非常方便地读取远程资源—甚至实现多线程下载。如下程序实现了一个多线程下载工具类。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122
| import java.io.InputStream; import java.io.RandomAccessFile; import java.net.*;
public class DownUtil { private String path; private String targetFile; private int threadNum; private DownThread[] threads; private int fileSize;
public DownUtil(String path, String targetFile, int threadNum) { this.path = path; this.threadNum = threadNum; threads = new DownThread[threadNum]; this.targetFile = targetFile; }
public void download() throws Exception { URL url = new URL(path); HttpURLConnection conn = (HttpURLConnection) url.openConnection(); conn.setConnectTimeout(5 * 1000); conn.setRequestMethod("GET"); conn.setRequestProperty("Accept", "image/gif, image/jpeg, image/pjpeg, image/pjpeg, " + "application/x-shockwave-flash, application/xaml+xml, " + "application/vnd.ms-xpsdocument, application/x-ms-xbap, " + "application/x-ms-application, application/vnd.ms-excel, " + "application/vnd.ms-powerpoint, application/msword, */*"); conn.setRequestProperty("Accept-Language", "zh-CN"); conn.setRequestProperty("Charset", "UTF-8"); conn.setRequestProperty("Connection", "Keep-Alive"); fileSize = conn.getContentLength(); conn.disconnect(); int currentPartSize = fileSize / threadNum + 1; RandomAccessFile file = new RandomAccessFile(targetFile, "rw"); file.setLength(fileSize); file.close(); for (int i = 0; i < threadNum; i++) { int startPos = i * currentPartSize; RandomAccessFile currentPart = new RandomAccessFile(targetFile, "rw"); currentPart.seek(startPos); threads[i] = new DownThread(startPos, currentPartSize, currentPart); threads[i].start(); } }
public double getCompleteRate() { int sumSize = 0; for (int i = 0; i < threadNum; i++) { sumSize += threads[i].length; } return sumSize * 1.0 / fileSize; }
private class DownThread extends Thread { private int startPos; private int currentPartSize; private RandomAccessFile currentPart; public int length;
public DownThread(int startPos, int currentPartSize, RandomAccessFile currentPart) { this.startPos = startPos; this.currentPartSize = currentPartSize; this.currentPart = currentPart; }
@Override public void run() { try { URL url = new URL(path); HttpURLConnection conn = (HttpURLConnection) url.openConnection(); conn.setConnectTimeout(5 * 1000); conn.setRequestMethod("GET"); conn.setRequestProperty("Accept", "image/gif, image/jpeg, image/pjpeg, image/pjpeg, " + "application/x-shockwave-flash, application/xaml+xml, " + "application/vnd.ms-xpsdocument, application/x-ms-xbap, " + "application/x-ms-application, application/vnd.ms-excel, " + "application/vnd.ms-powerpoint, application/msword, */*"); conn.setRequestProperty("Accept-Language", "zh-CN"); conn.setRequestProperty("Charset", "UTF-8"); InputStream inStream = conn.getInputStream(); inStream.skip(this.startPos); byte[] buffer = new byte[1024]; int hasRead = 0; while (length < currentPartSize && (hasRead = inStream.read(buffer)) != -1) { currentPart.write(buffer, 0, hasRead); length += hasRead; } currentPart.close(); inStream.close(); } catch (Exception e) { e.printStackTrace(); } } } }
|
上面程序中定义了DownThread
线程类,该线程负责读取从start
开始,到end
结束的所有字节数据,并写入RandomAccessfile
对象。这个DownThread
线程类的run()
方法就是一个简单的输入、输出实现。
程序中DownUtils
类中的download
方法负责按如下步骤来实现多线程下载
- 创建
URL
对象。
- 获取指定
URL
对象所指向资源的大小(通过getContentLength()
方法获得),此处用到了URLConnection
类,该类代表Java
应用程序和URL
之间的通信链接。后面还有关于URLConnection
更详细的介绍。
- 在本地磁盘上创建一个与网络资源具有相同大小的空文件。
- 计算每个线程应该下载网络资源的哪个部分(从哪个字节开始,到哪个字节结束)
- 依次创建、启动多个线程来下载网络资源的指定部分。
如何实现断点下载
上面程序已经实现了多线程下载的核心代码,如果要实现断点下载,则需要额外増加个配置文件(读者可以发现,所有的断点下载工具都会在下载开始时生成两个文件:一个是与网络资源具有相同大小的空文件,一个是配置文件),该配置文件分别记录每个线程已经下载到哪个字节,当网络断开后再次开始下载时,每个线程根据配置文件里记录的位置向后下载即可。
有了上面的DownUtils
工具类之后,接下来就可以在主程序中调用该工具类的down()
方法执行下载,如下程序所示。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
| public class MultiThreadDown { public static void main(String[] args) throws Exception { String urlStr = "https://lanlan2017.github.io/download/TestJavaDownload_DoNotDelete/TestDownLoad.txt"; String targetFile = "TestDownLoad.md"; final DownUtil downUtil = new DownUtil(urlStr, targetFile, 4); downUtil.download(); new Thread(() -> { while (downUtil.getCompleteRate() < 1) { System.out.println("已完成:" + (downUtil.getCompleteRate() * 100) + "%"); try { Thread.sleep(1000); } catch (Exception ex) { } } System.out.println("已完成:" + "100%"); }).start();
} }
|
运行上面程序,即可看到程序从lanlan2017.github.io
下载得到一份名为TestDownLoad.md
的文本文件。
上面程序还用到URLConnection
和HttpURLConnection
对象,其中URLConnection
表示应用程序和URL
之间的通信连接,HttpURLConnection
表示与URL
之间的HTTP
连接。程序可以通过URLConnection
实例向该URL
发送请求、读取URL
引用的资源
URLPermission
Java8
新增了一个URLPermission
工具类,用于管理HttpURLConnection
的权限问题,如果在HttpURLConnection
安装了安全管理器,通过该对象打开连接时就需要先获得权限。
通常创建一个和URL
的连接,并发送请求、读取此URL
引用的资源需要如下几个步骤
- 通过调用
URL
对象的openConnection
方法来创建URLConnection
对象。
- 设置
URLConnection
的参数和普通请求属性。
- 如果只是发送
GET
方式请求,则使用connect()
方法建立和远程资源之间的实际连接即可;
- 如果需要发送
POST
方式的请求,则需要获取URLConnection
对象对应的输出流来发送请求参数。
- 远程资源变为可用,程序可以访问远程资源的头字段或通过输入流读取远程资源的数据
URLConnection
设置请求头方法
在建立和远程资源的实际连接之前,程序可以通过如下方法来设置请求头字段。
方法 |
描述 |
void setAllowUserInteraction(boolean allowuserinteraction) |
设置该URLConnection 的allowUserInteraction 请求头字段的值 |
void setDoInput(boolean doinput) |
设置该URLConnection 的doInput 请求头字段的值。 |
void setDoOutput(boolean dooutput) |
设置该URLConnection 的doOutput 请求头字段的值。 |
void setIfModifiedSince(long ifModifiedSince) |
设置该URLConnection 的ifModifiedSince 请求头字段的值 |
void setUseCaches(boolean usecaches) |
设置该URLConnection 的useCaches 请求头字段的值。 |
URLConnection
设置通用请求头
除此之外,还可以使用如下方法来设置或增加通用头字段。
方法 |
描述 |
void setRequestProperty(String key, String value) |
设置该URLConnection 的key 请求头字段的值为value |
void addRequestProperty(String key, String value) |
为该URLConnection 的key 请求头字段增加value 值,该方法并不会覆盖原请求头字段的值,而是将新值追加到原请求头字段中。 |
1
| urlConnection.setRequestProperty("accept","*/*");
|
访问头字段和内容
当远程资源可用之后,程序可以使用以下方法来访问头字段和内容。
方法 |
描述 |
String getContentEncoding() |
获取content-encoding 响应头字段的值。 |
int getContentLength() |
获取content-Length 响应头字段的值。 |
String getContentType() |
获取content-type 响应头字段的值。 |
long getDate() |
获取date 响应头字段的值。 |
long getExpiration() |
获取expires 响应头字段的值。 |
long getLastModified() |
获取last-modified 响应头字段的值。 |
如果既要使用输入流读取URLConnection
响应的内容,又要使用输出流发送请求参数,则一定要先使用输出流,再使用输入流。
程序示例 发送GET请求 发送POST请求
下面程序示范了如何向Web
站点发送GET
请求、POST
请求,并从Web
站点取得响应
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99
| import java.io.*; import java.net.*; import java.util.*;
public class GetPostTest {
public static String sendGet(String url, String param) { String result = ""; String urlName = url + "?" + param; try { URL realUrl = new URL(urlName); URLConnection conn = realUrl.openConnection(); conn.setRequestProperty("accept", "*/*"); conn.setRequestProperty("connection", "Keep-Alive"); conn.setRequestProperty("user-agent", "Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; SV1)"); conn.connect(); Map<String, List<String>> map = conn.getHeaderFields(); for (String key : map.keySet()) { System.out.println(key + "--->" + map.get(key)); } try ( BufferedReader in = new BufferedReader(new InputStreamReader(conn.getInputStream(), "utf-8"))) { String line; while ((line = in.readLine()) != null) { result += "\n" + line; } } } catch (Exception e) { System.out.println("发送GET请求出现异常!" + e); e.printStackTrace(); } return result; }
public static String sendPost(String url, String param) { String result = ""; try { URL realUrl = new URL(url); URLConnection conn = realUrl.openConnection(); conn.setRequestProperty("accept", "*/*"); conn.setRequestProperty("connection", "Keep-Alive"); conn.setRequestProperty("user-agent", "Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; SV1)"); conn.setDoOutput(true); conn.setDoInput(true); try ( PrintWriter out = new PrintWriter(conn.getOutputStream())) { out.print(param); out.flush(); } try ( BufferedReader in = new BufferedReader(new InputStreamReader(conn.getInputStream(), "utf-8"))) { String line; while ((line = in.readLine()) != null) { result += "\n" + line; } } } catch (Exception e) { System.out.println("发送POST请求出现异常!" + e); e.printStackTrace(); } return result; }
public static void main(String args[]) { String s = GetPostTest.sendGet("http://localhost:8080/abc/a.jsp", null); System.out.println(s); String s1 = GetPostTest.sendPost("http://localhost:8080/abc/login.jsp", "name=crazyit.org&pass=leegang"); System.out.println(s1); } }
|
如果发送GET
请求时,只需将请求参数放在URL
字符串之后,以?隔开,然后程序直接调用URLConnection
对象的connect()
方法即可发送Get请求;
如果程序要发送POST
请求,则需要先设置doIn
和doOut
两个请求头字段的值,再使用URLConnection
对应的输出流来发送请求参数:
1 2 3
| conn.setDoOutput(true); conn.setDoInput(true);
|
不管是发送GET
请求,还是发送POST
请求,程序获取URLConnection
响应的方式完全一样.如果程序可以确定远程响应是字符流,则可以使用字符流来读取;如果程序无法确定远程响应是字符流则使用字节流读取即可。
上面程序中发送请求的两个URL
是部署在本机的Web
应用abc
,
由于程序可以使用这种方式向服务器发送请求,相当于提交Web
应用中的登录表单页,这样就可以让程序不断地变换用户名、密码来提交登录请求,直到返回登录成功,这就是所谓的暴力破解。
Web应用abc
该Web应用的源代码如下:
展开/折叠
项目结构
1 2 3 4 5
| G:\Desktop\随书源码\疯狂Java讲义(第4版)光盘\codes\17\17.2\abc ├─a.jsp ├─login.jsp └─WEB-INF\ └─web.xml
|
a.jsp
1 2 3 4 5 6 7 8 9 10 11 12
| <%@ page contentType="text/html; charset=UTF-8" language="java" errorPage="" %> <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> <html xmlns="http://www.w3.org/1999/xhtml"> <head> <title> 测试页面 </title> <meta name="website" content="http://www.crazyit.org"/> </head> <body> 服务器时间为:<%=new java.util.Date()%> </body> </html>
|
login.jsp
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| <%@ page contentType="text/html; charset=UTF-8" language="java" errorPage="" %> <% request.setCharacterEncoding("UTF-8"); String name = request.getParameter("name"); String pass = request.getParameter("pass"); if(name.equals("crazyit.org") && pass.equals("leegang")) { out.println("登录成功!"); } else { out.println("登录失败!"); } %>
|
web.xml
1 2 3 4 5 6 7 8 9
| <?xml version="1.0" encoding="ISO-8859-1"?> <web-app xmlns="http://java.sun.com/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd" version="3.0" metadata-complete="true">
</web-app>
|