[Java SE/JDK/网络] 核心源码精讲:java.net.HttpURLConnection
1 java.net.HttpURLConnection 概述
- JDK源码分析版本: JDK 1.8.0_261
rt.jar
1.1 HttpURLConnection 简述
HttpURLConnection
位于java.net
包中;- 它对外提供访问HTTP协议的基本功能;
- HttpURLConnection 是 Java 提供的发起 HTTP 请求的基础类库.
- 其继承自
URLConnection
,可用于向指定网站发送GET
、POST
请求。 - 其提供了 HTTP 请求的基本功能,不过封装的比较少,在使用时很多内容都需要自己设置,也需要【自己处理】请求流和响应流。
- 其继承自
1.2 URLConnection 类 API
URLConnection提供如下方法:
方法名 | 备注说明 |
---|---|
int getResponseCode(); | 获取服务器的响应代码 |
String getResponseMessage(); | 获取服务器的响应消息 |
String getResponseMethod(); | 获取发送请求的方法 |
void setRequestMethod(String method); | 设置发送请求的方法 |
1.3 HttpURLConnection 类 API
1.3.1 获取连接对象
【特别注意】 获取连接对象 ≠ 建立连接
// 定义 URL对象
final URL url = new URL("http(s)://ip:port/xxx");
// 获取 URL 链接
URLConnection urlConnection = url.openConnection();
// 因为 URL 是根据 url 中的协议(此处http)生成的 URLConnection 类的子类 HttpURLConnection, 故:
// 此处转换为 HttpURLConnection子类,方便使用子类中的更多的API
HttpURLConnection connection = (HttpURLConnection)urlConnection;
1.3.2 设置参数(请求时间)
在 Http 请求时防止对方长时间无法连接等问题,一般会设置超时时间
// 设置连接超时时间, 值必须大于0,设置为0表示不超时 单位为“毫秒”
connection.setConnectTimeout(30000);
// 设置读超时时间, 值必须大于0,设置为0表示不超时 单位毫秒
connection.setReadTimeout(60000);
1.3.3 设置参数(请求方法)
在 Http 请求中包括 GET、POST、PUT等方法,可以通过如下方法设置 HttpURLConnection的请求方法
// 设置为 GET 请求,
connection.setRequestMethod("GET");
注意:此处 方法必须设置为 大写,否则会报错误:
java.net.ProtocolException: Invalid HTTP method: get
也可以通过定义枚举类型设置,如下所示:
// 定义请求方法枚举类
public enum HttpMethod {
GET, HEAD, POST, PUT, DELETE, CONNECT, OPTIONS, TRACE, PATCH
}
// 在使用枚举设置请求方法
connection.setRequestMethod(HttpMethod.POST.name());
1.3.4 设置参数(请求头)
在请求时,经常会遇到设置自定义请求头,或者更改 Conent-Type 的值,可以通过如下方设置:
// 设置请求类型为 application/json
connection.setRequestProperty("Content-Type", "application/json; charset=UTF-8");
// 设置可接受的数据类型
connection.setRequestProperty("Accept", "*/*");
// 设置保持长链接
connection.setRequestProperty("Connection", "Keep-Alive");
// 设置自定义头 Token
connection.setRequestProperty("Token", "123456");
//设置身份认证参数
connection.setRequestProperty("Authorization", "Bearer <token>");
connection.setRequestProperty("Authorization", "Basic <token>");
推荐文献
1.3.5 设置参数(其他)
// 设置不使用缓存, 默认为 true 使用缓存
connection.setUseCaches(false);
// 设置单次请求是否支持重定向,默认为 setFollowRedirects 方法设置的值
connection.setInstanceFollowRedirects(false);
// 设置是否进行重定向,注意此处为 静态方法,表示所有的请求都不支持重定向,默认为true
HttpURLConnection.setFollowRedirects(false);
注意:所有的参数,必须在建立连接之前设置,否则会报错误
java.lang.IllegalStateException: Already connected
- 如果需要写入数据需要调用
setDoOutput(true)
打开输出流
connection.setDoOutput(true);
1.3.6 建立连接
【特别注意】在调用
connection()
、getOutputStream()
、getInputStream()
方法之前设置好请求参数。
- 建立连接(显式连接)
在设置完所有参数后,可以通过调用 connect 方法,进行显式建立连接。
// 调用打开连接, 调用此方法,只是建立一个连接,并不会发送数据。
connection.connect();
- 建立连接(隐式连接)
除了上面的调用 connect 显式建立连接外,在调用如下方法时,会隐式的调用此方法,建立连接。
// 获取输出流 | 如果调用 `getOutputStream()` 方法,则:会自动把把请求方法改为 `POST`
connection.getOutputStream();
// 获取输入流
connection.getInputStream();
由于,在网络请求时,一般都会获取请求结果,故在实际应用中,一般不调用 connect() 方法进行显式打开连接。
1.3.7 发送数据
【POST请求】
众所周知,HTTP 中的 POST 请求的数据是包含在请求体中的。在 HttpURLConnection 中 POST 请求发送数据时,需要获取 连接的输出流对象,然后往输出流中写数据即可,如下所示:
// 要发送的数据
String connect = "我是一个POST请求数据";
// 因为这个是post请求,参数要放在
// http正文内,因此需要设为true, 默认情况下是false;
connection.setDoOutput(true);
// 从连接中获取 输出流对象
OutputStream os = connection.getOutputStream();
// 往输出流中写数据
os.write(connect.getBytes(StandardCharsets.UTF_8));
// 冲刷 并 关闭输出流
os.flush();
os.close();
【注意】
1、 需要写数据时,必须调用connection.setDoOutput(true);
方法,并且参数为true
, 且需要在调用getOutputStream()
方法之前调用。
2、此时写的数据,只是写到了缓冲区中,并不会把数据真正地发送给资源方。即:建立连接和调用
getOutputStream()
方法写入数据并关闭连接后,也不会发送数据,只有调用getInputStream()
才会真正的发送数据。
【GET请求】
Http 中的 GET 请求的参数是拼接在 URL 后进行发送的。
所以 发送 GET 请求时,在创建 连接时把参数拼接在后面即可。
但是,有一点需要注意,如果在 GET 请求中 也调用了 getOutputStream() 方法,那么,自动就会把请求改为 POST 请求。如下源码所示:
// HttpURLConnection 中的 方法,
private synchronized OutputStream getOutputStream0() throws IOException {
try {
if (!this.doOutput) {
} else {
// 如果设置的 方法为 GET 则改为 POST
if (this.method.equals("GET")) {
this.method = "POST";
}
}
}catch(Exception e){
}
}
1.3.8 响应数据(【响应码/响应结果】)
在 HTTP 请求中一般是需要知道请求状态,在 HttpURLConnection 中可以通过如下方式获取请求状态
// 获取响应状态码,此状态即为 HTTP 请求的状态 200:成功,404:找不到资源 等
int responseCode = connection.getResponseCode();
# 注:底层会调用 getInputStream() => 彻底触发/发起 HTTP 请求
// 获取响应描述信息
String responseMessage = connection.getResponseMessage();
// 获取响应输入流
InputStream responseInputStream = connection.getInputStream();
sun.net.www.protocol.http.HttpURLConnection#getResponseCode
sun.net.www.protocol.http.HttpURLConnection#getResponseCode
public int getResponseCode() throws IOException {
if (this.responseCode != -1) {
return this.responseCode;
} else {
Exception var1 = null;
try {
this.getInputStream();
} catch (Exception var6) {
var1 = var6;
}
String var2 = this.getHeaderField(0);
if (var2 == null) {
if (var1 != null) {
if (var1 instanceof RuntimeException) {
throw (RuntimeException)var1;
} else {
throw (IOException)var1;
}
} else {
return -1;
}
} else {
if (var2.startsWith("HTTP/1.")) {
int var3 = var2.indexOf(32);
if (var3 > 0) {
int var4 = var2.indexOf(32, var3 + 1);
if (var4 > 0 && var4 < var2.length()) {
this.responseMessage = var2.substring(var4 + 1);
}
if (var4 < 0) {
var4 = var2.length();
}
try {
this.responseCode = Integer.parseInt(var2.substring(var3 + 1, var4));
return this.responseCode;
} catch (NumberFormatException var7) {
}
}
}
return -1;
}
}
}
sun.net.www.protocol.http.HttpURLConnection#getInputStream
sun.net.www.protocol.http.HttpURLConnection#getInputStream
//sun.net.www.protocol.http.HttpURLConnection#getInputStream
public synchronized InputStream getInputStream() throws IOException {
this.connecting = true;
SocketPermission var1 = this.URLtoSocketPermission(this.url);
if (var1 != null) {
try {
return (InputStream)AccessController.doPrivilegedWithCombiner(new PrivilegedExceptionAction<InputStream>() {
public InputStream run() throws IOException {
return HttpURLConnection.this.getInputStream0();
}
}, (AccessControlContext)null, var1);
} catch (PrivilegedActionException var3) {
throw (IOException)var3.getException();
}
} else {
return this.getInputStream0();
}
}
private synchronized InputStream getInputStream0() throws IOException {
if (!this.doInput) {
throw new ProtocolException("Cannot read from URLConnection if doInput=false (call setDoInput(true))");
} else if (this.rememberedException != null) {
if (this.rememberedException instanceof RuntimeException) {
throw new RuntimeException(this.rememberedException);
} else {
throw this.getChainedException((IOException)this.rememberedException);
}
} else if (this.inputStream != null) {
return this.inputStream;
} else {
if (this.streaming()) {
if (this.strOutputStream == null) {
this.getOutputStream();
}
this.strOutputStream.close();
if (!this.strOutputStream.writtenOK()) {
throw new IOException("Incomplete output stream");
}
}
int var1 = 0;
boolean var2 = false;
long var3 = -1L;
Object var5 = null;
AuthenticationInfo var6 = null;
AuthenticationHeader var7 = null;
boolean var8 = false;
boolean var9 = false;
this.isUserServerAuth = this.requests.getKey("Authorization") != -1;
this.isUserProxyAuth = this.requests.getKey("Proxy-Authorization") != -1;
InputStream var45;
try {
int var33;
while(true) {
if (!this.checkReuseConnection()) {
this.connect();
}
if (this.cachedInputStream != null) {
InputStream var34 = this.cachedInputStream;
return var34;
}
boolean var10 = ProgressMonitor.getDefault().shouldMeterInput(this.url, this.method);
if (var10) {
this.pi = new ProgressSource(this.url, this.method);
this.pi.beginTracking();
}
this.ps = (PrintStream)this.http.getOutputStream();
if (!this.streaming()) {
this.writeRequests();
}
this.http.parseHTTP(this.responses, this.pi, this);
if (logger.isLoggable(Level.FINE)) {
logger.fine(this.responses.toString());
}
boolean var35 = this.responses.filterNTLMResponses("WWW-Authenticate");
boolean var12 = this.responses.filterNTLMResponses("Proxy-Authenticate");
if ((var35 || var12) && logger.isLoggable(Level.FINE)) {
logger.fine(">>>> Headers are filtered");
logger.fine(this.responses.toString());
}
this.inputStream = this.http.getInputStream();
var33 = this.getResponseCode();
if (var33 == -1) {
this.disconnectInternal();
throw new IOException("Invalid Http response");
}
label763: {
boolean var13;
Iterator var14;
String var15;
if (var33 == 407) {
if (this.streaming()) {
this.disconnectInternal();
throw new HttpRetryException("cannot retry due to proxy authentication, in streaming mode", 407);
}
var13 = false;
var14 = this.responses.multiValueIterator("Proxy-Authenticate");
label711: {
do {
if (!var14.hasNext()) {
break label711;
}
var15 = ((String)var14.next()).trim();
} while(!var15.equalsIgnoreCase("Negotiate") && !var15.equalsIgnoreCase("Kerberos"));
if (!var9) {
var9 = true;
} else {
var13 = true;
this.doingNTLMp2ndStage = false;
var6 = null;
}
}
AuthenticationHeader var39 = new AuthenticationHeader("Proxy-Authenticate", this.responses, new HttpCallerInfo(this.url, this.http.getProxyHostUsed(), this.http.getProxyPortUsed()), var13, disabledProxyingSchemes);
if (this.doingNTLMp2ndStage) {
String var16 = this.responses.findValue("Proxy-Authenticate");
this.reset();
if (!var6.setHeaders(this, var39.headerParser(), var16)) {
this.disconnectInternal();
throw new IOException("Authentication failure");
}
if (var5 != null && var7 != null && !((AuthenticationInfo)var5).setHeaders(this, var7.headerParser(), var16)) {
this.disconnectInternal();
throw new IOException("Authentication failure");
}
this.authObj = null;
this.doingNTLMp2ndStage = false;
break label763;
}
var6 = this.resetProxyAuthentication(var6, var39);
if (var6 != null) {
++var1;
this.disconnectInternal();
break label763;
}
} else {
var9 = false;
this.doingNTLMp2ndStage = false;
if (!this.isUserProxyAuth) {
this.requests.remove("Proxy-Authorization");
}
}
if (var6 != null) {
var6.addToCache();
}
if (var33 == 401) {
if (this.streaming()) {
this.disconnectInternal();
throw new HttpRetryException("cannot retry due to server authentication, in streaming mode", 401);
}
var13 = false;
var14 = this.responses.multiValueIterator("WWW-Authenticate");
label693: {
do {
if (!var14.hasNext()) {
break label693;
}
var15 = ((String)var14.next()).trim();
} while(!var15.equalsIgnoreCase("Negotiate") && !var15.equalsIgnoreCase("Kerberos"));
if (!var8) {
var8 = true;
} else {
var13 = true;
this.doingNTLM2ndStage = false;
var5 = null;
}
}
var7 = new AuthenticationHeader("WWW-Authenticate", this.responses, new HttpCallerInfo(this.url), var13);
var15 = var7.raw();
if (this.doingNTLM2ndStage) {
this.reset();
if (!((AuthenticationInfo)var5).setHeaders(this, (HeaderParser)null, var15)) {
this.disconnectWeb();
throw new IOException("Authentication failure");
}
this.doingNTLM2ndStage = false;
this.authObj = null;
this.setCookieHeader();
break label763;
}
if (var5 != null && ((AuthenticationInfo)var5).getAuthScheme() != AuthScheme.NTLM) {
if (((AuthenticationInfo)var5).isAuthorizationStale(var15)) {
this.disconnectWeb();
++var1;
this.requests.set(((AuthenticationInfo)var5).getHeaderName(), ((AuthenticationInfo)var5).getHeaderValue(this.url, this.method));
this.currentServerCredentials = (AuthenticationInfo)var5;
this.setCookieHeader();
break label763;
}
((AuthenticationInfo)var5).removeFromCache();
}
var5 = this.getServerAuthentication(var7);
this.currentServerCredentials = (AuthenticationInfo)var5;
if (var5 != null) {
this.disconnectWeb();
++var1;
this.setCookieHeader();
break label763;
}
}
if (var5 != null) {
if (var5 instanceof DigestAuthentication && this.domain != null) {
DigestAuthentication var41 = (DigestAuthentication)var5;
StringTokenizer var37 = new StringTokenizer(this.domain, " ");
var15 = var41.realm;
PasswordAuthentication var42 = var41.pw;
this.digestparams = var41.params;
while(var37.hasMoreTokens()) {
String var17 = var37.nextToken();
try {
URL var18 = new URL(this.url, var17);
DigestAuthentication var19 = new DigestAuthentication(false, var18, var15, "Digest", var42, this.digestparams);
var19.addToCache();
} catch (Exception var29) {
}
}
} else {
if (var5 instanceof BasicAuthentication) {
String var38 = AuthenticationInfo.reducePath(this.url.getPath());
String var36 = ((AuthenticationInfo)var5).path;
if (!var36.startsWith(var38) || var38.length() >= var36.length()) {
var38 = BasicAuthentication.getRootPath(var36, var38);
}
BasicAuthentication var43 = (BasicAuthentication)((AuthenticationInfo)var5).clone();
((AuthenticationInfo)var5).removeFromCache();
var43.path = var38;
var5 = var43;
}
((AuthenticationInfo)var5).addToCache();
}
}
var8 = false;
var9 = false;
this.doingNTLMp2ndStage = false;
this.doingNTLM2ndStage = false;
if (!this.isUserServerAuth) {
this.requests.remove("Authorization");
}
if (!this.isUserProxyAuth) {
this.requests.remove("Proxy-Authorization");
}
if (var33 == 200) {
this.checkResponseCredentials(false);
} else {
this.needToCheck = false;
}
this.needToCheck = true;
if (!this.followRedirect()) {
try {
var3 = Long.parseLong(this.responses.findValue("content-length"));
} catch (Exception var28) {
}
break;
}
++var1;
this.setCookieHeader();
}
if (var1 >= maxRedirects) {
throw new ProtocolException("Server redirected too many times (" + var1 + ")");
}
}
if (this.method.equals("HEAD") || var3 == 0L || var33 == 304 || var33 == 204) {
if (this.pi != null) {
this.pi.finishTracking();
this.pi = null;
}
this.http.finished();
this.http = null;
this.inputStream = new EmptyInputStream();
this.connected = false;
}
if ((var33 == 200 || var33 == 203 || var33 == 206 || var33 == 300 || var33 == 301 || var33 == 410) && this.cacheHandler != null && this.getUseCaches()) {
URI var44 = ParseUtil.toURI(this.url);
if (var44 != null) {
Object var40 = this;
if ("https".equalsIgnoreCase(var44.getScheme())) {
try {
var40 = (URLConnection)this.getClass().getField("httpsURLConnection").get(this);
} catch (NoSuchFieldException | IllegalAccessException var27) {
}
}
CacheRequest var46 = this.cacheHandler.put(var44, (URLConnection)var40);
if (var46 != null && this.http != null) {
this.http.setCacheRequest(var46);
this.inputStream = new HttpInputStream(this.inputStream, var46);
}
}
}
if (!(this.inputStream instanceof HttpInputStream)) {
this.inputStream = new HttpInputStream(this.inputStream);
}
if (var33 >= 400) {
if (var33 != 404 && var33 != 410) {
throw new IOException("Server returned HTTP response code: " + var33 + " for URL: " + this.url.toString());
}
throw new FileNotFoundException(this.url.toString());
}
this.poster = null;
this.strOutputStream = null;
var45 = this.inputStream;
} catch (RuntimeException var30) {
this.disconnectInternal();
this.rememberedException = var30;
throw var30;
} catch (IOException var31) {
this.rememberedException = var31;
String var11 = this.responses.findValue("Transfer-Encoding");
if (this.http != null && this.http.isKeepingAlive() && enableESBuffer && (var3 > 0L || var11 != null && var11.equalsIgnoreCase("chunked"))) {
this.errorStream = HttpURLConnection.ErrorStream.getErrorStream(this.inputStream, var3, this.http);
}
throw var31;
} finally {
if (this.proxyAuthKey != null) {
AuthenticationInfo.endAuthRequest(this.proxyAuthKey);
}
if (this.serverAuthKey != null) {
AuthenticationInfo.endAuthRequest(this.serverAuthKey);
}
}
return var45;
}
}
1.3.9 响应数据(【获取头信息】)
获取响应头有如下几种方式:
// 1、获取所有的响应头信息
Map<String, List<String>> headerFields = connection.getHeaderFields();
// 2、根据头信息名称获取响应头信息
String connectionHeader = connection.getHeaderField("Connection");
// 3、根据头信息索引获取响应头信息, 此下标 必须大于 0。
String secHeader = connection.getHeaderField(2);
1.3.10 读取数据
读取响应数据也是比较简单的,可以首先通过 HttpURLConnection 中的
getInputStream()
方法 获取输入流,然后,通过输入流获取数据即可,如下所示:
// 获取输入流
InputStream inputStream = connection.getInputStream();
// 定义一个临时字节输出流
ByteArrayOutputStream baos = new ByteArrayOutputStream();
try {
// 开始读取数据
byte[] buffer = new byte[256];
int len = 0;
while ((len = inputStream.read(buffer)) > 0){
baos.write(buffer,0, len);
}
return new String(baos.toByteArray(), StandardCharsets.UTF_8);
} finally {
// 关闭输入、输出流
inputStream.close();
baos.close();
}
1.3.11 上传下载(上传)
-
在普通 Web 页面中上传文件是很简单的,只需要把 from 标签中加上 enctype="multipart/form-data" 即可,剩下的都交给浏览器去完成发送数据的收集并发送 Http 请求即可。
-
但是,在 HttpURLConnection 中脱离了浏览器,就需要我们自己去完成数据收集并发送请求了。那么我们首先看下浏览器是怎么收集上传数据,并发送请求的。
-
先看下浏览器发送上传时间的请求正文格式:
// 请求头中的 Content-Type 属性 其中定义了属性分割线
Content-Type: multipart/form-data; boundary=----WebKitFormBoundaryfdRf0g4cPSTVeLkJ
// 请求数据正文信息
------WebKitFormBoundaryfdRf0g4cPSTVeLkJ
Content-Disposition: form-data; name="images"; filename="20150703212056_Yxi4L.jpeg"
Content-Type: image/jpeg
------WebKitFormBoundaryfdRf0g4cPSTVeLkJ
Content-Disposition: form-data; name="checkRecord"
{"describe":"","rectify":"立即整改"}
------WebKitFormBoundaryfdRf0g4cPSTVeLkJ--
分析上面的的数据我们能够发下如下规则:
- 数据正文中的第一行 ------WebKitFormBoundaryfdRf0g4cPSTVeLkJ 作为分隔符,然后是 \r\n 回车换行符。
- 第二行 Content-Disposition: form-data; name="images"; filename="*****", 代表 form 表单数据域,其中 name 表示 接口属性值,filename 为文件名称。
- 第三行 Content-Type: image/jpeg 表示上传文件的类型。
- 第四行是一个 回车换行符。
- 第五行 是 数据内容,由于此处为 图片故没有显示出来。
- 后面的也是遵从上述规律。
- 最后一行表示结束行,注意后面多两个--。
根据以上规律,我们 在 使用 HttpURLConnection 进行上传时,就可以按照此规律拼接发送的数据流。实例如下所示:
public void upload(File file) throws Exception {
final URL url = new URL("http://localhost:10010/user/upload");
// 获取 URL 链接
URLConnection urlConnection = url.openConnection();
// 因为 URL 是根据 url 中的协议(此处http)生成的 URLConnection 类的子类
// HttpURLConnection, 故此处转换为 HttpURLConnection子类,方便使用子类
// 中的更多的API
HttpURLConnection connection = (HttpURLConnection)urlConnection;
// 自定义分割线,并设置请求头信息
String boundary = "------------" + System.currentTimeMillis();
connection.setRequestProperty("Content-Type", "multipart/form-data;boundary=" + boundary);
// 设置请求为 POST 请求
connection.setRequestMethod(METHOD.POST.name());
// 打开输出流
connection.setDoOutput(true);
// 获取上传文件的类型
MagicMatch magicMatch = Magic.getMagicMatch(file, false, true);
String mimeType = magicMatch.getMimeType();
// 获取输出流
OutputStream outputStream = connection.getOutputStream();
//拼接请求数据
StringBuilder builder = new StringBuilder();
// 第一行分割行
builder.append("\r\n").append("--" + BOUNDARY).append( "\r\n");
// 第二行form表单数据
builder.append("Content-Disposition: form-data; name=\"file\"; filename=\"").append(file.getName() ).append("\"\r\n");
// 第三行 上传数据类型
builder.append( "Content-Type:").append(mimeType).append("\r\n");
// 第四行一个空行
builder.append("\r\n");
outputStream.write(builder.toString().getBytes(StandardCharsets.UTF_8));
// 开始写文件数据
InputStream fileInput = new FileInputStream(file);
byte[] buffer = new byte[512];
int len = 0;
while ((len = fileInput.read(buffer)) > 0){
outputStream.write(buffer, 0, len);
}
// 开始写基本数据
StringBuilder textBuffer = new StringBuilder();
// 分隔符行
textBuffer.append("\r\n").append("--" + BOUNDARY).append("\r\n");
// form表单数据
textBuffer.append("Content-Disposition: form-data; name=\"name\"\r\n");
// 一个空行
textBuffer.append("\r\n");
// 数据值
textBuffer.append("张三");
outputStream.write(textBuffer.toString().getBytes(StandardCharsets.UTF_8));
// 写入结束行
outputStream.write(("\r\n--" + BOUNDARY + "--\r\n").getBytes(StandardCharsets.UTF_8));
outputStream.flush();
outputStream.close();
fileInput.close();
int responseCode = connection.getResponseCode();
printHeaders(connection.getHeaderFields());
if(responseCode != 200){
LOGGER.error("请求失败, code: {}, message: {}", responseCode, connection.getResponseMessage());
}else {
InputStream inputStream = connection.getInputStream();
String reader = reader(inputStream);
LOGGER.info("服务端返回数据为: \n {}", reader);
}
}
【注意】基本数据比 file 缺少 Content-Type: image/jpeg 行
1.3.12 上传下载(下载)
文件的下载就比较简单了,获取输入流,然后读取输入流,并把读到的数据保存到本地即可,一下是下载网络上的图片为例。
/**
* 下载
* @param url 下载文件路径
* @param distDir 保存的文件路径
*/
public void download(String url, String distDir) throws Exception {
// 获取连接
HttpURLConnection connection = (HttpURLConnection) new URL(url).openConnection();
// 设置请求方法
connection.setRequestMethod("GET");
connection.setRequestProperty("Charset", "UTF-8");
// 获取文件名
String fileUrl = connection.getURL().getFile();
String fileName = fileUrl.substring(fileUrl.lastIndexOf(File.separatorChar) + 1);
LOGGER.info("文件名:{} -- {}", fileName, File.separator);
String filePath = distDir + File.separatorChar + fileName;
File file = new File(filePath);
if(!file.getParentFile().exists()){
file.getParentFile().mkdirs();
}
// 获取输入流,并写入文件
try (InputStream inputStream = connection.getInputStream();
OutputStream os = new FileOutputStream(file)) {
byte[] buffer = new byte[256];
int len = 0;
while ((len = inputStream.read(buffer)) > 0) {
os.write(buffer, 0, len);
}
os.flush();
}
}
1.3.13 关闭连接
- 关闭连接 :connection.disconnect()
- 当HttpURLConnection 是 "Connection: close " 模式时,关闭 inputStream 后就会自动断开连接。
- 当HttpURLConnection 是 "Connection: Keep-Alive" 模式时,关闭 inputStream 后,并不会断开底层的 Socket 连接
- 采用此种模式的优点:当需要连接到同一服务器地址时,可以复用该 Socket,如果要求断开连接,可调用 connection.disconnect()
2 案例实践
2.1 案例集
2.1.1 案例1:GET方式请求HTTP资源
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.HttpURLConnection;
import java.net.URL;
/**
* GET请求示例*/
public class GetDemo {
public static void main(String[] args) {
try {
// 1. 得到访问地址的URL
URL url = new URL("http://localhost:8080/index.jsp");
// 2. 得到网络访问对象java.net.HttpURLConnection
HttpURLConnection connection = (HttpURLConnection) url.openConnection();
/* 3. 设置请求参数(过期时间,输入、输出流、访问方式),以流的形式进行连接 */
// 设置是否向HttpURLConnection输出
connection.setDoOutput(false);
// 设置是否从HttpUrlConnection读入
connection.setDoInput(true);
// 设置请求方式
connection.setRequestMethod("GET");
// 设置是否使用缓存
connection.setUseCaches(true);
// 设置此 HttpURLConnection 实例是否应该自动执行 HTTP 重定向
connection.setInstanceFollowRedirects(true);
// 设置超时时间
connection.setConnectTimeout(3000);
// 4.连接
connection.connect();
// 5. 得到响应状态码的返回值 responseCode
int code = connection.getResponseCode();
// 6. 如果返回值正常,数据在网络中是以流的形式得到服务端返回的数据
String responseContent = "";
if (code == 200) { // 正常响应
// 从流中读取响应信息
BufferedReader reader = new BufferedReader(new InputStreamReader(connection.getInputStream()));
String line = null;
while ((line = reader.readLine()) != null) { // 循环从流中读取
responseContent += line + "\n";
}
reader.close(); // 关闭流
}
// 7. 断开连接,释放资源
connection.disconnect();
// 8. 显示响应结果
System.out.println(responseContent);
} catch (IOException e) {
e.printStackTrace();
}
}
}
2.1.2 案例2:POST方式请求HTTP资源
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.net.HttpURLConnection;
import java.net.MalformedURLException;
import java.net.URL;
/**
* POST请求示例*/
public class PostDemo {
public static void main(String[] args) {
try {
// 1. 获取访问地址URL
URL url = new URL("http://localhost:8080/index.jsp");
// 2. 创建HttpURLConnection对象
HttpURLConnection connection = (HttpURLConnection) url.openConnection();
/* 3. 设置请求参数等 */
// 请求方式
connection.setRequestMethod("POST");
// 设置连接超时时间
connection.setConnectTimeout(3000);
// 设置是否向 HttpUrlConnection 输出,对于post请求,参数要放在 http 正文内,因此需要设为true,默认为false。
connection.setDoOutput(true);
// 设置是否从 HttpUrlConnection读入,默认为true
connection.setDoInput(true);
// 设置是否使用缓存
connection.setUseCaches(false);
// 设置此 HttpURLConnection 实例是否应该自动执行 HTTP 重定向
connection.setInstanceFollowRedirects(true);
// 设置使用标准编码格式编码参数的名-值对
connection.setRequestProperty("Content-Type", "application/x-www-form-urlencoded");
// 添加 HTTP HEAD 中的一些参数。
// JDK8中,HttpURLConnection默认开启Keep-Alive
// connection.setRequestProperty("Connection", "Keep-Alive");
// 4. 连接
connection.connect();
/* 5. 处理输入输出 */
// 写入参数到请求中
String params = "username=test&password=123456";
OutputStream out = connection.getOutputStream();
out.write(params.getBytes());
out.flush();
out.close();
// 从连接中读取响应信息
String responseContent = "";
int code = connection.getResponseCode();
if (code == 200) {
BufferedReader reader = new BufferedReader(new InputStreamReader(connection.getInputStream()));
String line;
while ((line = reader.readLine()) != null) {
responseContent += line + "\n";
}
reader.close();
}
// 6. 断开连接
connection.disconnect();
// 7. 处理结果
System.out.println(responseContent);
} catch (MalformedURLException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
}
Y 【FAQ】
Y.1 HttpURLConnection的注意事项
-
HttpURLConnection对象不能直接构造
- 需使用
URL
类中的 openConnection() 方法来创建实例
- 需使用
-
HttpURLConnection对象属性设置,需在connect()方法执行之前完成
-
HttpURLConnection的
connect()
函数,其本质是建立一个与服务器的TCP连接- 并未实际发送HTTP请求
- HTTP请求靠调用getInputStream()、getResponseCode()等方法触发
-
HttpURLConnection是基于HTTP协议的,其底层通过socket通信实现
-
不设置超时(timeout),当网络异常的情况下,可能会导致程序僵死而不继续往下执行
-
HTTP正文内容是通过OutputStream流写入,向流中写入的数据不会立即发送到网络,而是存在于内存缓冲区中,待流关闭时,根据写入的内容生成HTTP正文。
调用getInputStream()方法时,会返回一个输入流,用于从中读取服务器对于HTTP请求的返回信息 -
当获取HTTP响应的时候,请求就会自动的发起
如使用HttpURLConnection.getInputStream()方法的时系统会自动调用connect()方法 -
HttpURLConnection长连接(Keep-Alive)相关说明
- JDK8自带的HttpURLConnection
- 默认启用keepAlive,支持HTTP / 1.1和HTTP / 1.0持久连接,
- 使用后的HttpURLConnection会放入缓存中供以后的同host:port的请求重用底层socket在keepAlive超时之前不会关闭
-
HttpURLConnection受system properties影响
http.keepAlive=<boolean>(默认值:true),是否启用keepAlive,如果设置为false,则HttpURLConnection不会缓存,使用完后会关闭socket连接。
http.maxConnections=<int>(默认值:5),每个目标host缓存socket连接的最大数。
当在HttpURLConnection的header中加入Connection: close,则此连接不会启用keepAlive
如果想启用keepAlive,程序请求完毕后必须调用HttpURLConnection.getInputStream().close()
(此操作用于归还长连接给缓存,下次同host:port的请求重用底层socket连接),
而不调用HttpURLConnection.disconnect()(表示关闭底层socket连接,不会启用keepAlive)
- keepAliveTimeout属性的获取原理:
从http response header中获取,如果没有取到,则默认为5秒
sun.net.www.http.KeepAliveCache.java中有一个线程,每5秒执行一次
检查缓存的连接的空闲时间是否超过keepAliveTimeout,如果超过则关闭连接
从KeepAliveCache中获取缓存的连接时也会检查获取到的连接的空闲时间是否超过keepAliveTimeout
如果超过则关闭连接,并且获取下一个连接,再执行以上检查,直达获取到空闲时间在keepAliveTimeout以内的缓存连接为此。
Y.2 HttpURLConnection同HttpClient的区别?
- 由于HttpClient是进行封装的框架,使用起来更加便捷。所以:
- 在一些复杂url请求处理时,可使用HttpClient
- 在一些简单的场景下,可使用HttpURLConnection
- 我们可以理解为:HttpClient是HttpURLConnection的增强
Y 推荐文献
- [Java SE] 核心源码精讲:java.net.URLConnection - 博客园/千千寰宇 【推荐】
- [Java/网络/HTTP(S)] 基于
Http(s)URLConnection
的网络请求工具(HttpRequestUtils) - 博客园/千千寰宇 【推荐】
基于
Http(s)URLConnection
/ 自定义的轻量HTTP网络请求工具
类比: OkHttp / HttpClient 等 Java HTTP 网络请求工具
- [身份认证/JWT] 身份认证方案与HTTP请求中Authorization Header - 博客园/千千寰宇 【推荐】
- [网络/HTTPS/Java] PKI公钥基础设施体系:数字证书(X.509)、CA机构 | 含:证书管理工具(jdk keytool / openssl) - 博客园/千千寰宇 【推荐】
X 参考文献
本文链接: https://www.cnblogs.com/johnnyzen
关于博文:评论和私信会在第一时间回复,或直接私信我。
版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!
日常交流:大数据与软件开发-QQ交流群: 774386015 【入群二维码】参见左下角。您的支持、鼓励是博主技术写作的重要动力!