文章目錄
情景引入
旁白:今天興致極好,開開心心的與小白玩着“網頁小遊戲”
小白:真開心呀,好久沒有玩這些網頁遊戲了呢。
我:確實,工作一來,哪有這樣的閒情逸致。
小白:不過,你看,咱們玩的遊戲都是通過網頁鏈接請求,然後返回給咱們這麼多對應的響應內容,它這裏感覺好神奇。
我:當然,它裏面涉及到的內容還是非常多,可不是一下就能完整掌握的,你這思考問題的能力大大加強了嘛!
小白:這可不嘛,學習到老才行呢。不過,我經常聽到同事說,快給我一個接口地址,我要向你發出請求獲取信息啦,這裏面到底是怎麼實現的,一直都沒有機會去請教他們。
我:虛心求教,這是非常值得的,不要害怕,不懂就要問,這纔是應該有的態度。既然,這樣的話,那我就給你科普科普。這不,正好咱們也可以“中場休息”一會,再繼續遊戲呢。
小白:好呀好呀!學習,遊戲兩不誤,豈不是人生一大快事!
我:小白,長大了!
基礎知識
引言
當我們輸入一個URL然後回車,頁面會顯示出對應的功能信息,那麼它這個過程到底是如何的呢?其實,這裏面涉及到的內容會非常多,比如緩存,地址解析,http協議,網絡請求,OSI模型,服務器數據組裝,頁面渲染等等。這裏,就不會說得那麼詳細(PS:但是這個鏈路過程,建議各位看官可以好好的琢磨琢磨,分析分析),本文主要是針對,如果對一個已知URL發出請求並獲取到其實時的響應信息;
實際場景
我想,在實際開發中經常會碰到這樣的事情,就是:需要Http請求其他同事開發的接口,而獲取對應需要的內容,比如下載一個URL的文件或者獲取數據列表信息等等以及調用第三方公共接口信息。那麼,必不可少的就是通過代碼去發出一個http的請求。那麼,具體如何操作呢?客觀,慢慢往下看!
操作方法
現在的方法有很多,且不限於本文說到的幾種方式哦!本文主要採取循序漸進的方式,並沒有說哪一種方法就是最好的,具體問題具體分析,要以一種開放的心態看待這個問題。
HttpUrlConnection
請求步驟
PS:該方式是最原始的方式,所以值得學習其內部執行流程;
package com.hnu.scw.utils;
import java.io.*;
import java.net.HttpURLConnection;
import java.net.URL;
import java.net.URLEncoder;
import java.util.Map;
/**
* @ Author :scw
* @ Date :Created in 上午 10:34 2020/5/24 0024
* @ Description:請求URL的post方法工具類
* @ Modified By:
* @Version: $version$
*/
public class HttpPostUtils {
/**
* 請求URL的post請求
* @param url 請求url地址
* @param parameters 請求參數
* @return 響應結果字符串
* @throws Exception 異常信息
*/
public static String doRequestPostMethod(String url, Map<String, Object> parameters) throws Exception {
// 將參數轉爲String(PS:因爲如果URL需要參數的話,那麼都是採取字符串字節的形式)
String params = convertRequestParameter(parameters);
URL restURL = new URL(url);
// 請求失敗的次數(PS:便於減少由於某次的網絡抖動而影響請求)
int errorNumber = 0;
while( errorNumber < 3 ) {
try {
HttpURLConnection conn = (HttpURLConnection) restURL.openConnection();
// 設置請求超時時間(PS:可以不設置)
conn.setConnectTimeout(5000);
conn.setReadTimeout(5000);
// 請求方式(PS:這裏採取POST請求,如果需要GET請求,則爲GET)
conn.setRequestMethod("POST");
// 設置請求的數據格式爲JSON
conn.setRequestProperty("Content-Type", "application/json;charset=UTF-8");
conn.setRequestProperty("Connection", "Keep-Alive");
// 是否從httpUrlConnection讀入,默認情況下是true
conn.setDoOutput(true);
// 是否允許用戶交互,假設爲true,則允許用戶交互的上下文中對此URL進行檢查(比如對話框驗證交互)。
conn.setAllowUserInteraction(false);
// 獲取輸出流(PS:這裏方式很多,這裏提供幾種)
// 方式一
PrintStream ps = new PrintStream(conn.getOutputStream());
ps.print(params);
ps.close();
// 方式二
/* DataOutputStream out = new DataOutputStream(conn.getOutputStream());
out.write(params.getBytes("UTF-8"));
out.flush();
out.close();*/
// 建立連接
conn.connect();
// 獲取響應輸入流
BufferedReader bReader = new BufferedReader(new InputStreamReader(conn.getInputStream(), "UTF-8"));
String line;
String resultStr = "";
// 拼接響應內容
while (null != (line = bReader.readLine())) {
resultStr += line;
}
bReader.close();
// 返回相應結果(PS:爲字符串格式)
return resultStr;
} catch (Exception e) {
// 錯誤次數+1
errorNumber++;
}
}
throw new Exception("當前網絡異常存在抖動,請稍後再試");
}
/**
* 將map形式的參數轉爲字符串形式
* @param parameters 請求參數
* @return 請求參數字符串
*/
private static String convertRequestParameter(Map<String, Object> parameters) throws UnsupportedEncodingException {
StringBuilder sb = new StringBuilder();
// 遍歷接參數內容
for (Map.Entry<String,Object> param : parameters.entrySet()) {
if (sb.length() != 0) {
sb.append('&');
}
// 防止中文亂碼
sb.append(URLEncoder.encode(param.getKey(), "UTF-8"));
sb.append('=');
sb.append(URLEncoder.encode(String.valueOf(param.getValue()), "UTF-8"));
}
return sb.toString();
}
}
OkHttpClient
添加依賴
<dependency>
<groupId>com.squareup.okhttp3</groupId>
<artifactId>okhttp</artifactId>
<version>3.8.0</version>
</dependency>
請求步驟(GET和POST方式)
package com.hnu.scw.utils;
import okhttp3.*;
import javax.net.ssl.HostnameVerifier;
import javax.net.ssl.SSLSession;
import java.io.*;
import java.net.HttpURLConnection;
import java.net.URL;
import java.net.URLEncoder;
import java.util.Map;
/**
* @ Author :scw
* @ Date :Created in 上午 10:34 2020/5/24 0024
* @ Description:請求URL的post方法工具類
* @ Modified By:
* @Version: $version$
*/
public class HttpPostUtils {
/**
* 執行 POST 請求
* @param url 請求的URL
* @return 響應結果
*/
public String doRequestPostByOkHttpClient(String url) {
// ssl的認證重寫(PS:用於防止請求URL時有些系統會進行安全驗證而導致請求失敗)
OkHttpClient okHttpClient = new OkHttpClient.Builder().hostnameVerifier(
new HostnameVerifier() {
@Override
public boolean verify(String s, SSLSession sslSession) {
return true;
}
}
).build();
// 如果是JDK1.8以上的,那麼可以採取lambda重寫匿名函數的方式
/* OkHttpClient okHttpClient = new OkHttpClient.Builder().hostnameVerifier(
(s, sslSession) ->{
return true;
}
).build();*/
// 設置請求的參數信息
RequestBody requestBody = new FormBody.Builder()
.add("key1", "test")
.add("key2", "hello")
.build();
// 封裝請求頭信息
Request request = new Request.Builder()
.url(url)
.post(requestBody)
.addHeader("Cookie", "JSESSIONID=299571E0E40DA6E9962E41B87A669BBB")
.addHeader("content-type", "application/json")
.addHeader("cache-control", "no-cache")
.build();
Call call = okHttpClient.newCall(request);
try {
Response response = call.execute();
// 返回相應結果
return response.body().string();
} catch (IOException e) {
e.printStackTrace();
}
return null;
}
}
SpingRestTemplate
請求步驟(GET和POST方式)
方式一
package com.hnu.scw.utils;
import okhttp3.*;
import org.apache.poi.ss.formula.functions.T;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.web.client.RestTemplate;
import javax.net.ssl.HostnameVerifier;
import javax.net.ssl.SSLSession;
import java.io.*;
import java.net.HttpURLConnection;
import java.net.URL;
import java.net.URLEncoder;
import java.util.Map;
/**
* @ Author :scw
* @ Date :Created in 上午 10:34 2020/5/24 0024
* @ Description:請求URL的post方法工具類
* @ Modified By:
* @Version: $version$
*/
public class HttpPostUtils {
/**
* 執行 POST 請求 通過Spring 的restTemplate方式
* @param url 請求URL
* @param parameter 請求參數
* @return 響應結果字符串
*/
public String doRequestPostByRestTemplate(String url, Object parameter) {
RestTemplate template = new RestTemplate();
HttpHeaders headers = new HttpHeaders();
// 設置請求的格式爲:json
MediaType type = MediaType.parseMediaType("application/json;charset=UTF-8");
headers.setContentType(type);
HttpEntity entity = new HttpEntity(parameter,headers);
// POST方式,如果要用GET,則爲getForObject即可
String result = template.postForObject(url, entity, String.class);
return result;
}
/**
* 執行 POST 請求 通過Spring 的restTemplate方式二
* @param url 請求URL
* @param parameter 請求參數(PS:一般爲java實體)
* @param backDto 相應的結果實體
* @return 響應結果實體
*/
public <T> T doRequestPostByRestTemplate(String url, Object parameter, Class<T> backDto) {
RestTemplate template = new RestTemplate();
HttpHeaders headers = new HttpHeaders();
// 設置請求的格式爲:json
MediaType type = MediaType.parseMediaType("application/json;charset=UTF-8");
headers.setContentType(type);
HttpEntity entity = new HttpEntity(parameter,headers);
// POST方式,如果要用GET,則爲getForObject即可
String result = template.postForObject(url, entity, String.class);
// 將json字符串轉爲實體
return JSON.parseObject(result, backDto);
}
}
方式二:
package com.hnu.scw.utils;
import okhttp3.*;
import org.apache.poi.ss.formula.functions.T;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.web.client.RestTemplate;
import javax.net.ssl.HostnameVerifier;
import javax.net.ssl.SSLSession;
import java.io.*;
import java.net.HttpURLConnection;
import java.net.URL;
import java.net.URLEncoder;
import java.util.Map;
/**
* @ Author :scw
* @ Date :Created in 上午 10:34 2020/5/24 0024
* @ Description:請求URL的post方法工具類
* @ Modified By:
* @Version: $version$
*/
public class HttpPostUtils {
/**
* 執行 POST 請求 通過Spring 的restTemplate方式的方式二
* @param url 請求URL
* @param parameter 請求參數
* @return 響應結果實體類型
*/
public <T> T doRequestPostByRestTemplateEntity(String url, Object parameter, Class<T> backDto) {
RestTemplate template = new RestTemplate();
HttpHeaders headers = new HttpHeaders();
// 設置請求的格式爲:json
MediaType type = MediaType.parseMediaType("application/json;charset=UTF-8");
headers.setContentType(type);
HttpEntity entity = new HttpEntity(parameter,headers);
// POST方式,如果要用GET,則爲getForEntity即可
ResponseEntity<T> tResponseEntity = template.postForEntity(url, entity, backDto);
return tResponseEntity.getBody();
}
}
彩蛋
如何獲取請求URL的響應流
背景:假設我們請求的這個URL,其返回的結果是一個文件流,那麼我們就需要將這個文件流輸出給前端,以實現下載文件的功能;
實現步驟:先獲取請求URL的響應結果的輸入流,然後再通過最開始前端的HTTPServletResponse請求響應流outputStream輸出即可;
HttpUrlConnect
PS:如果採取原生的請求方式,那麼就比較簡單,直接獲取其對應的響應結果的輸入流,則可以返回。
/**
* 請求 URL 的post請求
* @param url 請求url地址
* @param parameters 請求參數
* @return 響應結果流
* @throws Exception 異常信息
*/
public static InputStream doRequestPostMethodInputStream(String url, Map<String, Object> parameters) throws Exception {
// 將參數轉爲String(PS:因爲如果URL需要參數的話,那麼都是採取字符串字節的形式)
String params = convertRequestParameter(parameters);
URL restURL = new URL(url);
// 請求失敗的次數(PS:便於減少由於某次的網絡抖動而影響請求)
int errorNumber = 0;
while( errorNumber < 3 ) {
try {
HttpURLConnection conn = (HttpURLConnection) restURL.openConnection();
// 設置請求超時時間(PS:可以不設置)
conn.setConnectTimeout(5000);
conn.setReadTimeout(5000);
// 請求方式(PS:這裏採取POST請求,如果需要GET請求,則爲GET)
conn.setRequestMethod("POST");
// 設置請求的數據格式爲JSON
conn.setRequestProperty("Content-Type", "application/json;charset=UTF-8");
conn.setRequestProperty("Connection", "Keep-Alive");
// 是否從httpUrlConnection讀入,默認情況下是true
conn.setDoOutput(true);
// 是否允許用戶交互,假設爲true,則允許用戶交互的上下文中對此URL進行檢查(比如對話框驗證交互)。
conn.setAllowUserInteraction(false);
// 獲取輸出流
DataOutputStream out = new DataOutputStream(conn.getOutputStream());
out.write(params.getBytes("UTF-8"));
out.flush();
out.close();
// 建立連接
conn.connect();
// 獲取響應輸入流
return conn.getInputStream();
} catch (Exception e) {
// 錯誤次數+1
errorNumber++;
}
}
return null;
}
/**
* 請求URL並且下載其返回的文件流內容
* @param httpServletResponse http響應
* @throws Exception
*/
public void downLoadFile(HttpServletResponse httpResponse) throws Exception {
InputStream inputStream = doRequestPostMethodInputStream("xxx", new HashMap<>());
// 採取緩存流的方式
OutputStream outputStream = new BufferedOutputStream(httpResponse.getOutputStream());
httpResponse.setContentType("application/octer-stream");
String fileName = URLEncoder.encode("測試","UTF-8");
httpResponse.setHeader("Content-disposition","attachment;filename=" + fileName + ";");
byte[] buff = new byte[1024];
int length = 0;
while((length = inputStream.read()) != -1 ){
outputStream.write(buff, 0, length);
}
outputStream.flush();
}
Spring的RestTemplate
package com.hnu.scw.utils;
import okhttp3.*;
import org.apache.poi.ss.formula.functions.T;
import org.springframework.core.io.Resource;
import org.springframework.http.*;
import org.springframework.http.MediaType;
import org.springframework.web.client.RestTemplate;
import javax.net.ssl.HostnameVerifier;
import javax.net.ssl.SSLSession;
import java.io.*;
import java.net.HttpURLConnection;
import java.net.URL;
import java.net.URLEncoder;
import java.util.Map;
/**
* @ Author :scw
* @ Date :Created in 上午 10:34 2020/5/24 0024
* @ Description:請求URL的post方法工具類
* @ Modified By:
* @Version: $version$
*/
public class HttpPostUtils {
/**
* 執行 POST 請求 獲取其返回的文件流
* @param url 請求URL
* @param parameter 請求參數
* @return 響應結果文件輸入流
* @throws IOException
*/
public static InputStream doRequestPostByRestTemplateEntity(String url, Object parameter) throws IOException {
RestTemplate template = new RestTemplate();
HttpHeaders headers = new HttpHeaders();
// 設置請求的格式爲:json
MediaType type = MediaType.parseMediaType("application/json;charset=UTF-8");
headers.setContentType(type);
HttpEntity entity = new HttpEntity(parameter,headers);
// POST方式,如果要用GET,則爲getForEntity即可
ResponseEntity<Resource> responseEntity = template.postForEntity(url, entity, Resource.class);
if(HttpStatus.OK.equals(responseEntity.getStatusCode())){
return responseEntity.getBody().getInputStream();
}
return null;
}
/**
* 請求URL並且下載其返回的文件流內容
* @param httpServletResponse http響應
* @throws Exception
*/
public void downLoadFile(HttpServletResponse httpResponse) throws Exception {
InputStream inputStream = doRequestPostByRestTemplateEntity("xxx", new HashMap<>());
// 採取緩存流的方式
OutputStream outputStream = new BufferedOutputStream(httpResponse.getOutputStream());
httpResponse.setContentType("application/octer-stream");
String fileName = URLEncoder.encode("測試","UTF-8");
httpResponse.setHeader("Content-disposition","attachment;filename=" + fileName + ";");
byte[] buff = new byte[1024];
int length = 0;
while((length = inputStream.read()) != -1 ){
outputStream.write(buff, 0, length);
}
outputStream.flush();
}
}
總結
- 本文講述的方式各自有各自的應用場景,並不存在哪一種就是最好的形式(PS:當然不僅僅只有這些方式,比如還有apache的httpClient,CloseableHttpClient以及springCloud中常見的Fegin);
- 實際工作中發出http的請求是相對的多,對於其內部的執行流程和實現原理要去掌握(PS:網絡協議+socket+netty都是需要掌握);
- 針對常用的場景和技術要學會去總結,分析各自的利弊,而不要只會用而不明白其內部;
- 通過閱讀本文內容之後,建議將“輸入一個URL地址,然後回車敲擊顯示結果”,這個鏈路的整個過程都去學習和掌握,你會發現更多的美哦!;