面試官:請用Java實現(xiàn)一個HTTP請求
大家好,我是指北君。
最近面試的時候,竟然有面試官提出這樣的要求:請用Java實現(xiàn)一個HTTP請求!當然不能慫! 雄起!!!
今天將介紹一種在 Java 中執(zhí)行 HTTP 請求的方法 -- 通過使用 Java 內(nèi)置的 HttpUrlConnection 類實現(xiàn)。
從 JDK 11 開始,Java 為執(zhí)行 HTTP 請求提供了一個新的 API,它是用來替代 HttpUrlConnection 的,即HttpClient API。
HttpUrlConnection
HttpUrlConnection 類允許我們執(zhí)行基本的 HTTP 請求,而無需使用任何額外的庫。我們需要的所有類都是 java.net 包的一部分。
使用這種方法的缺點是,代碼可能比其他的HTTP庫更繁瑣,而且它不提供更高級的功能,如添加頭文件或認證的專用方法。
創(chuàng)建一個請求
我們可以使用 URL 類的 openConnection() 方法創(chuàng)建一個 HttpUrlConnection 實例。注意,這個方法只是創(chuàng)建一個連接對象,但還沒有建立連接。
HttpUrlConnection 類通過將 requestMethod 屬性設(shè)置為get, post, head, options, put, delete, trace其中一個值。
讓我們使用GET方法創(chuàng)建一個與給定URL的連接:
URL url = new URL("https://www.javanorth.cn");
HttpURLConnection con = (HttpURLConnection) url.openConnection();
con.setRequestMethod("GET");
添加請求參數(shù)
如果我們想向一個請求添加參數(shù),我們必須將 doOutput 屬性設(shè)置為 true,然后向 HttpUrlConnection 實例的OutputStream 寫一個類似 param1=value&m2=value 的字符串。
Map<String, String> parameters = new HashMap<>();
parameters.put("param1", "val");
con.setDoOutput(true);
DataOutputStream out = new DataOutputStream(con.getOutputStream());
out.writeBytes(ParameterStringBuilder.getParamsString(parameters));
out.flush();
out.close();
為了方便參數(shù)Map的轉(zhuǎn)換,我們編寫了一個名為ParameterStringBuilder的實用類,其中包含一個靜態(tài)方法getParamsString(),可以將Map轉(zhuǎn)換為所需格式的字符串。
public class ParameterStringBuilder {
public static String getParamsString(Map<String, String> params)
throws UnsupportedEncodingException{
StringBuilder result = new StringBuilder();
for (Map.Entry<String, String> entry : params.entrySet()) {
result.append(URLEncoder.encode(entry.getKey(), "UTF-8"));
result.append("=");
result.append(URLEncoder.encode(entry.getValue(), "UTF-8"));
result.append("&");
}
String resultString = result.toString();
return resultString.length() > 0 ? resultString.substring(0, resultString.length() - 1) : resultString;
}
}
設(shè)置請求頭信息
通過使用 setRequestProperty() 方法可以實現(xiàn)在請求中添加頭信息。
con.setRequestProperty("Content-Type", "application/json");
要從一個連接中讀取一個頭的值,我們可以使用 getHeaderField() 方法。
String contentType = con.getHeaderField("Content-Type");
配置超時
HttpUrlConnection 類允許設(shè)置連接和讀取超時。這些值定義了等待與服務(wù)器的連接建立或數(shù)據(jù)可被讀取的時間間隔。
為了設(shè)置超時值,我們可以使用 setConnectTimeout()和 setReadTimeout()方法。
con.setConnectTimeout(5000);
con.setReadTimeout(5000);
在這個例子中,我們把兩個超時值都設(shè)為5秒。
處理Cookie
java.net 包包含了便于處理 cookie 的類,如 CookieManager 和 HttpCookie。
首先,為了從響應中讀取 cookie,我們可以檢索 Set-Cookie 頭的值,并將其解析為一個 HttpCookie 對象的列表。
String cookiesHeader = con.getHeaderField("Set-Cookie");
List<HttpCookie> cookies = HttpCookie.parse(cookiesHeader);
接下來,我們將把cookie添加到cookieStore。
cookies.forEach(cookie -> cookieManager.getCookieStore().add(null,
cookie));
讓我們檢查一下是否有一個叫做 username 的 cookie,如果沒有,我們將把它添加到cookieStore,其值為 "javanorth"。
Optional<HttpCookie> usernameCookie = cookies.stream().findAny().filter(cookie -> cookie.getName().equals("username"));
if (usernameCookie == null) {
cookieManager.getCookieStore().add(null, new HttpCookie("username", "javanorth"));
}
最后,為了在請求中加入 cookie,我們需要在關(guān)閉和重新打開連接后設(shè)置 Cookie 頭。
con.disconnect();
con = (HttpURLConnection) url.openConnection();
con.setRequestProperty("Cookie", StringUtils.join(cookieManager.getCookieStore().getCookies(), ";"));
處理重定向
我們可以通過使用參數(shù)為 true 或 false 的 setInstanceFollowRedirects() 方法,為一個特定的連接啟用或禁用自動跟蹤重定向。
con.setInstanceFollowRedirects(false);
也可以啟用或禁用所有連接的自動重定向。
HttpUrlConnection.setFollowRedirects(false);
默認情況下,該行為是啟用的。
當一個請求返回狀態(tài)代碼 301 或 302,表示重定向時,我們可以檢索位置頭并創(chuàng)建一個新的請求到新的URL。
if (status == HttpURLConnection.HTTP_MOVED_TEMP || status == HttpURLConnection.HTTP_MOVED_PERM) {
String location = con.getHeaderField("Location");
URL newUrl = new URL(location);
con = (HttpURLConnection) newUrl.openConnection();
}
讀取響應
讀取請求的響應可以通過解析 HttpUrlConnection 實例的 InputStream 來完成。
為了執(zhí)行請求,我們可以使用 getResponseCode()、connect()、getInputStream() 或 getOutputStream() 方法。
int status = con.getResponseCode();
最后,讓我們讀一下請求的響應,并把它放在一個內(nèi)容字符串中。
BufferedReader in = new BufferedReader(new InputStreamReader(con.getInputStream()));
String inputLine;
StringBuffer content = new StringBuffer();
while ((inputLine = in.readLine()) != null) {
content.append(inputLine);
}
in.close();
要關(guān)閉連接,我們可以使用 disconnect() 方法。
con.disconnect();
在失敗的請求中讀取響應
如果請求失敗了,我們從 HttpUrlConnection 實例的 InputStream 讀取是讀取不到數(shù)據(jù)的。我們可以從 HttpUrlConnection.getErrorStream() 提供的流讀取。
我們可以通過比較 HTTP 狀態(tài)碼來決定使用哪個 InputStream。
int status = con.getResponseCode();
Reader streamReader = null;
if (status > 299) {
streamReader = new InputStreamReader(con.getErrorStream());
} else {
streamReader = new InputStreamReader(con.getInputStream());
}
最后,我們可以用與上一節(jié)相同的方式讀取 streamReader。
總結(jié)在這篇文章中,我們展示了如何使用 HttpUrlConnection 類來執(zhí)行HTTP請求。