Java HttpClient-Restful工具各種請求高度封裝提煉及總結(jié)
總思路
總的工具要求底層完全可復用的代碼全部提煉,也就是不通類型(GET, POST, DELETE, PUT 等等)請求的決定性公共步驟其實是可以提煉出來的。
即 一個請求,請求頭一定會有,請求路徑一定會有,發(fā)起請求一定會有,返回處理一定會有。
但同時由于請求頭內(nèi)容可能會有不同的要求或者加密方式,所以需要將相關(guān)加工過程放到基礎(chǔ)工具類之外,保證調(diào)用基礎(chǔ)工具類時只執(zhí)行所有請求都需要的的步驟,不帶有特殊處理。
這里主要使用的都是 org.apache.http 已包裝的 httpClient ,項目中進一步將各種類型的請求做進一步提煉和封裝。
從最底層開始說明
RestfulService
基礎(chǔ) RestfulService 工具代碼可以參考如下:
個別說明加入到注釋中或示例代碼結(jié)尾
...... import org.apache.http.HttpEntity; import org.apache.http.HttpHeaders; import org.apache.http.NameValuePair; import org.apache.http.client.methods.CloseableHttpResponse; import org.apache.http.client.methods.HttpDelete; import org.apache.http.client.methods.HttpGet; import org.apache.http.client.methods.HttpPost; import org.apache.http.client.methods.HttpPut; import org.apache.http.client.methods.HttpRequestBase; import org.apache.http.entity.ContentType; import org.apache.http.entity.StringEntity; import org.apache.http.impl.client.CloseableHttpClient; import java.io.BufferedReader; import java.io.IOException; import java.io.InputStreamReader; import java.io.OutputStream; import java.net.URI; import java.nio.charset.StandardCharsets; import java.util.Objects; public class MyRestfulService { private static xxxLogger = new xxxLog(MyRestfulService .class); // 所有請求都由 httpClient.execute() 方式發(fā)出 private CloseableHttpClient httpClient; // 由于 httpClient 也存在自定義各種屬性,所以這里也作為一個定義的參數(shù) // 通過構(gòu)造方法傳入外側(cè)定義的 CloseableHttpClient public MyRestfulService(CloseableHttpClient client) { this.httpClient = client; } // 一般的GET 請求 public String jsonGet(String url, final NameValuePair[] headerParams) throws IOException, XxxxException { URI uri = URI.create(url); HttpGet get = new HttpGet(uri); this.addHeaders(headerParams, get); CloseableHttpResponse response = httpClient.execute(get); return this.parseResponseData(response, url); } // Get請求 獲取文件某字符串開頭的 public String fileGetLine(String url, final NameValuePair[] headerParams, String head) throws IOException, XxxxException { URI uri = URI.create(url); HttpGet get = new HttpGet(uri); this.addHeaders(headerParams, get); CloseableHttpResponse response = httpClient.execute(get); BufferedReader br = null; try { br = new BufferedReader(new InputStreamReader(response.getEntity().getContent(), "UTF-8")); String output; while ((output = br.readLine()) != null) { if (output.contains(head) && Objects.equals(output.split("=")[0], head)) { return output; } } return null; } catch (Exception e) { logger.error("Failed to get rest response", e); // 為自定義異常類型 throw new XxxxException(ExceptionType.XXXXXX); } finally { if (br != null) { br.close(); } response.close(); } } // 攜帶請求體即 Body 的GET 請求 其中 HttpGetWithEntity 需要自定義到文件中 稍后給出示例 public String jsonGetWithBody(String url, final NameValuePair[] headerParams, String requestBody) throws IOException, XxxxException { URI uri = URI.create(url); HttpGetWithEntity get = new HttpGetWithEntity(uri); this.addHeaders(headerParams, get); StringEntity input = new StringEntity(requestBody, ContentType.APPLICATION_JSON); get.setEntity(input); CloseableHttpResponse response = httpClient.execute(get); return this.parseResponseData(response, url); } // 普通的POST 請求 public String jsonPost(String url, final NameValuePair[] headerParams, String requestBody) throws IOException, XxxxException { HttpPost post = new HttpPost(url); this.addHeaders(headerParams, post); if (requestBody != null) { StringEntity input = new StringEntity(requestBody, ContentType.APPLICATION_JSON); post.setEntity(input); } CloseableHttpResponse response = httpClient.execute(post); return this.parseResponseData(response, url); } // 普通 put 請求 public String jsonPut(String url, final NameValuePair[] headerParams, String requestBody) throws IOException, XxxxException { HttpPut put = new HttpPut(url); this.addHeaders(headerParams, put); StringEntity input = new StringEntity(requestBody, ContentType.APPLICATION_JSON); put.setEntity(input); CloseableHttpResponse response = httpClient.execute(put); return this.parseResponseData(response, url); } // 一般的DELETE 請求 public String jsonDelete(String url, final NameValuePair[] headerParams) throws IOException, XxxxException { HttpDelete delete = new HttpDelete(url); this.addHeaders(headerParams, delete); CloseableHttpResponse response = null; response = httpClient.execute(delete); return this.parseResponseData(response, url); } // 攜帶請求體的DELETE 請求 HttpDeleteWithBody public String jsonDeleteWithBody(String url, final NameValuePair[] headerParams, String requestBody) throws IOException, XxxxException { HttpDeleteWithBody delete = new HttpDeleteWithBody(url); this.addHeaders(headerParams, delete); StringEntity input = new StringEntity(requestBody, ContentType.APPLICATION_JSON); delete.setEntity(input); CloseableHttpResponse response = null; response = httpClient.execute(delete); return this.parseResponseData(response, url); } // 文件類傳入 上傳 public String uploadFile(String url, final NameValuePair[] headerParams, HttpEntity multipartEntity) throws IOException, XxxxException { HttpPost post = new HttpPost(url); post.setEntity(multipartEntity); post.addHeader(HttpHeaders.CONTENT_TYPE, post.getEntity().getContentType().getValue()); if (headerParams != null) { for (NameValuePair nameValuePair : headerParams) { post.addHeader(nameValuePair.getName(), nameValuePair.getValue()); } } return this.parseResponseData(httpClient.execute(post), url); } // 數(shù)據(jù)結(jié)果轉(zhuǎn)換 private String parseResponseData(CloseableHttpResponse response, String url) throws IOException, XxxxException { BufferedReader br = null; try { // 編碼轉(zhuǎn)義結(jié)果 br = new BufferedReader(new InputStreamReader(response.getEntity().getContent(), StandardCharsets.UTF_8)); StringBuilder sbuilder = new StringBuilder(); String output; while ((output = br.readLine()) != null) { sbuilder.append(output); } logger.debug("MyRestfulService Request-URL: " + url + "; response: " + response.toString() + "; data:" + sbuilder + "}"); int statusCode = response.getStatusLine().getStatusCode(); // 200 可用已有常量替代 HTTP 本身有提供 if (statusCode != 200) { logger.info("Failed to get restful response, http error code = " + statusCode); } return sbuilder.toString(); } catch (Exception e) { logger.error("Failed to get rest response", e); // 自定義異常 throw new XxxxException(ExceptionType.XXXXXXX); } finally { if (br != null) { br.close(); } response.close(); } } // 公用 添加自定義請求頭信息 private void addHeaders(final NameValuePair[] headerParams, HttpRequestBase requestBase) { if (headerParams != null) { for (int i = 0; i < headerParams.length; i++) { NameValuePair nameValuePair = headerParams[i]; requestBase.addHeader(nameValuePair.getName(), nameValuePair.getValue()); } } this.addCommonHeader(requestBase); } // 公用 添加公共請求頭或公共操作 private void addCommonHeader(HttpRequestBase method) { // 去除個別屬性 if (method.containsHeader("Content-Length")) { method.removeHeaders("Content-Length"); } // 沒有則添加 if (!method.containsHeader("Content-Type")) { method.addHeader("Content-Type", "application/json;charset=utf-8"); } // 強制更新或添加 method.setHeader("accept", "application/json,text/plain,text/html"); } // 文件下載 public String downloadFile(String url, OutputStream outputStream,NameValuePair[] headerParams) throws IOException, DXPException { HttpGet httpget = new HttpGet(url); this.addHeaders(headerParams, httpget); CloseableHttpResponse execute = httpClient.execute(httpget); if(null != execute && execute.containsHeader("Content-Disposition")){ HttpEntity entity = execute.getEntity(); if (entity != null ) { entity.writeTo(outputStream); } }else { return this.parseResponseData(execute, url); } return null; } // httpClient 對應的get set 方法 public CloseableHttpClient getHttpClient() { return this.httpClient; } public void setHttpClient(CloseableHttpClient httpClient) { this.httpClient = httpClient; } }
上面
HttpDeleteWithBody 定義:
import java.net.URI; import org.apache.http.client.methods.HttpEntityEnclosingRequestBase; public class HttpDeleteWithBody extends HttpEntityEnclosingRequestBase { public static final String METHOD_NAME = "DELETE"; public HttpDeleteWithBody(final String uri) { super(); setURI(URI.create(uri)); } public HttpDeleteWithBody(final URI uri) { super(); setURI(uri); } public HttpDeleteWithBody() { super(); } public String getMethod() { return METHOD_NAME; } }
上面 HttpGetWithBody:
import org.apache.http.client.methods.HttpEntityEnclosingRequestBase; public class HttpGetWithEntity extends HttpEntityEnclosingRequestBase { private static final String METHOD_NAME = "GET"; public HttpGetWithEntity() { super(); } public HttpGetWithEntity(final URI uri) { super(); setURI(uri); } HttpGetWithEntity(final String uri) { super(); setURI(URI.create(uri)); } @Override public String getMethod() { return METHOD_NAME; } }
具體來源其實就是照抄 源碼 httppost POST 的結(jié)構(gòu)
然后換個名字以及屬性名即可完成請求體的攜帶
NameValuePair[]
示例中用到了大量的 NameValuePair[] 其,內(nèi)部結(jié)構(gòu)類似于 Map 但內(nèi)部屬性為name, value。
實際也可以使用 Map 來替代這種存儲結(jié)構(gòu), NameValuePair 也是 org.apache.http 內(nèi)部提供的類。
工具類上一層
其他調(diào)用者。用于處理特殊的請求頭信息,拼接請求參數(shù)以及請求路徑等內(nèi)容
并構(gòu)建使用的 CloseableHttpClient 傳入工具類。
示例:
某一 上層 Service 的內(nèi)容示例:
...... ...... import org.apache.commons.lang.StringUtils; import org.apache.http.HttpHeaders; import org.apache.http.NameValuePair; import org.apache.http.client.config.AuthSchemes; import org.apache.http.client.config.CookieSpecs; import org.apache.http.client.config.RequestConfig; import org.apache.http.client.methods.HttpDelete; import org.apache.http.client.methods.HttpGet; import org.apache.http.client.methods.HttpPost; import org.apache.http.config.ConnectionConfig; import org.apache.http.config.SocketConfig; import org.apache.http.conn.ssl.NoopHostnameVerifier; import org.apache.http.impl.client.CloseableHttpClient; import org.apache.http.impl.client.HttpClientBuilder; import org.apache.http.message.BasicNameValuePair; import org.springframework.stereotype.Service; import javax.annotation.PostConstruct; import javax.annotation.PreDestroy; import java.io.IOException; import java.io.UnsupportedEncodingException; import java.net.URLEncoder; import java.nio.charset.StandardCharsets; import java.util.ArrayList; import java.util.Arrays; import java.util.List; import java.util.Map; @Service("XXXRestfulService") public class XXXRestfulService implements IXxxRestfulService { private static final XXXLog xxxLog = new XXXLog(XXXRestfulService .class); private static int maxConnPerHost = 20; private static int maxTotalConn = 20; /** * 數(shù)據(jù)讀取超時時間 */ private static int soTimeout = 30000; /** * http連接超時時間 */ private static int connectionTimeout = 10000; /** * 連接管理器超時時間 */ private static int connectionManagerTimeout = 10000; // 基礎(chǔ)工具類對象聲明 private MyRestfulService restService; private CloseableHttpClient createHttpClient() { CloseableHttpClient httpClient = null; try { @SuppressWarnings("deprecation") ConnectionConfig cConfig = ConnectionConfig.custom().setCharset(StandardCharsets.UTF_8).build(); SocketConfig config = SocketConfig.custom().setSoTimeout(soTimeout).build(); RequestConfig defaultRequestConfig = RequestConfig.custom().setExpectContinueEnabled(true).setCookieSpec(CookieSpecs.DEFAULT) .setTargetPreferredAuthSchemes(Arrays.asList(AuthSchemes.NTLM, AuthSchemes.DIGEST)) .setProxyPreferredAuthSchemes(Arrays.asList(AuthSchemes.BASIC)) .setConnectionRequestTimeout(connectionManagerTimeout).setConnectTimeout(connectionTimeout) .setSocketTimeout(soTimeout).build(); httpClient = HttpClientBuilder.create().setMaxConnPerRoute(maxConnPerHost).setMaxConnTotal(maxTotalConn) .setSSLHostnameVerifier(new NoopHostnameVerifier()) .setDefaultRequestConfig(RequestConfig.copy(defaultRequestConfig).build()) .setDefaultConnectionConfig(cConfig).setDefaultSocketConfig(config).build(); } catch (Exception e) { xxxLog.error("Create Http Client Failed", e); } return httpClient; } // 類初始化時執(zhí)行的方法 @PostConstruct public void initProperties() { try { CloseableHttpClient client = this.createHttpClient(); this.restService = new MyRestfulService(client); } catch (Exception e) { xxxLog.error("Failed To Init DataFillRestfulService", e); } } // 關(guān)閉資源,如果每次都重新請求則也可以放到工具類內(nèi)每次請求完成都關(guān)閉 @PreDestroy // @PreDestroy 實際 Servlet 被銷毀前調(diào)用的方法 public void destroy() { try { CloseableHttpClient httpclient = restService.getHttpClient(); httpclient.close(); } catch (IOException e) { xxxLog.error("Failed To Destroy HttpClient", e); } } // 對請求頭內(nèi)容的特殊處理 private NameValuePair[] getBaseHeaders(String methodName, String urlStr) { // 對請求頭內(nèi)容的特殊處理 若沒有則直接添加到 返回值 NameValuePair[] 數(shù)組即可 ............ } // 如果需要URL編碼則可以使用該方法 private String encodeHeader(String value) throws UnsupportedEncodingException { if (StringUtils.isEmpty(value)) { return value; } return URLEncoder.encode(value, "UTF-8"); } // 拼接實際請求路徑 // XXXXConfig 可以作為一個類專門加載配置文件中的一些有關(guān)的地址信息等 public String getRequestUrl(String actionUrl) { return XXXXConfig.getXxxxServer() + actionUrl; } @Override public String get(final String actionUrl, Map<String, String> map) { String requestUrl = getRequestUrl(actionUrl); // 傳入實際地址等 調(diào)用基礎(chǔ)工具類請求 ..... } @Override public String post(final String actionUrl, final String jsonBody) { String requestUrl = getRequestUrl(actionUrl); // 傳入實際地址等 調(diào)用基礎(chǔ)工具類請求 ..... } @Override public String delete(final String actionUrl) { String requestUrl = getRequestUrl(actionUrl); // 傳入實際地址等 調(diào)用基礎(chǔ)工具類請求 ..... } }
可以看的出,上層工具類先聲明了 工具類對象 然后在當前類初始化時完成了對當前Service請求自己 httpClient 的相關(guān)創(chuàng)建以及配置的賦值,并將該對象 傳遞給工具類,使得調(diào)用工具類時依舊使用的是自己創(chuàng)建的那一個對象。
并通過一般的工具類將需要的配置文件的信息加載后,直接在該類中引用。完成請求信息的拼接。
由于不同的產(chǎn)品間的請求頭要求信息可能不同,所以需要自己額外處理請求頭相關(guān)信息。
這里其實還是在封裝部分內(nèi)容,使得業(yè)務(wù)請求調(diào)用時進一步簡化。
一些工具類特殊的傳入如下示例:
文件上傳方式
public String uploadFile(String url, final NameValuePair[] headerParams, HttpEntity multipartEntity)
中 HttpEntity 中加入文件的方式
MultipartEntityBuilder builder = MultipartEntityBuilder.create(); builder.setMode(HttpMultipartMode.BROWSER_COMPATIBLE); builder.setCharset(StandardCharsets.UTF_8); builder.addBinaryBody("file", inputStream, ContentType.DEFAULT_BINARY, fileName); builder.build(); // 最終可以到的 想要的 HttpEntity 攜帶文件內(nèi)容
其中 inputStream 為 InputStream 類型的文件輸入流, fileName 為 String 文件名;
獲取文件流
public String downloadFile(String url, OutputStream outputStream,NameValuePair[] headerParams)
獲得請求中的文件流,則需要傳入 OutputStream 類型的 輸出流對象 outputStream。
再上一層的業(yè)務(wù)調(diào)用
業(yè)務(wù)層處則通過直接定義指定的方法和入?yún)?,在?nèi)部直接寫入請求映射路徑,并要求傳入指定參數(shù)完成業(yè)務(wù)封裝,類似于:
@Override public XxxxxxxDTO<XXX> getXxxxList(XxxxxListRequest request, String xxx, String xxxx) throws XXXException { String url = "/xxxx/api/xxxx/xxxx/list"; String result = XxxxxRestfulService.post(url, JsonUtil.getJsonStr(request), xxx, xxxx); // 對 result 進行轉(zhuǎn)化 轉(zhuǎn)為自定義類或者Map 等返回 return ......; }
這樣在外部調(diào)用該業(yè)務(wù)方法時需要感知的只有入?yún)ⅲ渌麕缀醺兄坏?,與一般的方法差別幾乎沒有
以上為個人經(jīng)驗,希望能給大家一個參考,也希望大家多多支持腳本之家。
- java?11新特性HttpClient主要組件及發(fā)送請求示例詳解
- Java通過httpclient比較重定向和請求轉(zhuǎn)發(fā)
- Java HttpClient執(zhí)行請求時配置cookie流程詳細講解
- java中httpclient封裝post請求和get的請求實例
- java爬蟲之使用HttpClient模擬瀏覽器發(fā)送請求方法詳解
- java發(fā)送form-data請求實現(xiàn)文件上傳的示例代碼
- Java請求調(diào)用參數(shù)格式為form-data類型的接口代碼示例
- Java后臺接收數(shù)據(jù)的三種方式(url、form-data與application/json)
- Java httpclient請求form-data格式并設(shè)置boundary代碼實現(xiàn)方法
相關(guān)文章
SpringBoot傳遞單一參數(shù)時@RequestParam和@RequestBody的區(qū)別小結(jié)
用SpringBoot框架做項目時,經(jīng)常需要前端給后端傳遞參數(shù),本文主要介紹了SpringBoot傳遞單一參數(shù)時@RequestParam和@RequestBody的區(qū)別,具有一定的參考價值,感興趣的可以了解一下2023-08-08淺談SpringBoot Bean加載優(yōu)先級的問題
這篇文章主要介紹了淺談SpringBoot Bean加載優(yōu)先級的問題,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2021-11-11java正則表達式實現(xiàn)提取需要的字符并放入數(shù)組【ArrayList數(shù)組去重復功能】
這篇文章主要介紹了java正則表達式實現(xiàn)提取需要的字符并放入數(shù)組,即基于正則的ArrayList數(shù)組去重復功能,具有一定參考借鑒價值,需要的朋友可以參考下2017-01-01基于Java實現(xiàn)一個簡單的單詞本Android App的實踐
本文基于Java實現(xiàn)了一個簡單的單詞本安卓app,用的是SQLite數(shù)據(jù)庫,文中通過示例代碼介紹的非常詳細,具有一定的參考價值,感興趣的小伙伴們可以參考一下2022-01-01java在集合遍歷過程中刪除元素5種方法對比、案例、常見的錯誤及其后果
這篇文章主要介紹了java在集合遍歷過程中刪除元素5種方法對比、案例、常見的錯誤及其后果的相關(guān)資料,介紹了五種不同的解決方案,包括使用Iterator.remove()、for-each+手動刪除、for循環(huán)反向遍歷、List.removeIf()和使用Stream.filter(),需要的朋友可以參考下2024-12-12