总思路
总的工具要求底层完全可复用的代码全部提炼,也就是不通类型(GET, POST, DELETE, PUT 等等)请求的决定性公共步骤其实是可以提炼出来的。
即 一个请求,请求头一定会有,请求路径一定会有,发起请求一定会有,返回处理一定会有。
但同时由于请求头内容可能会有不同的要求或者加密方式,所以需要将相关加工过程放到基础工具类之外,保证调用基础工具类时只执行所有请求都需要的的步骤,不带有特殊处理。
这里主要使用的都是 org.apache.http 已包装的 httpClient ,项目中进一步将各种类型的请求做进一步提炼和封装。
从最底层开始说明
RestfulService
基础 RestfulService 工具代码可以参考如下:
个别说明加入到注释中或示例代码结尾
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 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 | ...... 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() 方式发出 private CloseableHttpClient httpClient; // 由于 httpClient 也存在自定义各种属性,所以这里也作为一个定义的参数 // 通过构造方法传入外侧定义的 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); } // 数据结果转换 private String parseResponseData(CloseableHttpResponse response, String url) throws IOException, XxxxException { BufferedReader br = null ; try { // 编码转义结果 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 |
上面
HttpDeleteWithBody 定义:
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 | 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:
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 | 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 的结构
然后换个名字以及属性名即可完成请求体的携带
NameValuePair[]
示例中用到了大量的 NameValuePair[] 其,内部结构类似于 Map 但内部属性为name, value。
实际也可以使用 Map 来替代这种存储结构, NameValuePair 也是 org.apache.http 内部提供的类。
工具类上一层
其他调用者。用于处理特殊的请求头信息,拼接请求参数以及请求路径等内容
并构建使用的 CloseableHttpClient 传入工具类。
示例:
某一 上层 Service 的内容示例:
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 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 | ...... ...... 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 ; /** * 数据读取超时时间 */ private static int soTimeout = 30000 ; /** * http连接超时时间 */ private static int connectionTimeout = 10000 ; /** * 连接管理器超时时间 */ private static int connectionManagerTimeout = 10000 ; // 基础工具类对象声明 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; } // 类初始化时执行的方法 @PostConstruct public void initProperties() { try { CloseableHttpClient client = this .createHttpClient(); this .restService = new MyRestfulService(client); } catch (Exception e) { xxxLog.error( "Failed To Init DataFillRestfulService" , e); } } // 关闭资源,如果每次都重新请求则也可以放到工具类内每次请求完成都关闭 @PreDestroy // @PreDestroy 实际 Servlet 被销毁前调用的方法 public void destroy() { try { CloseableHttpClient httpclient = restService.getHttpClient(); httpclient.close(); } catch (IOException e) { xxxLog.error( "Failed To Destroy HttpClient" , e); } } // 对请求头内容的特殊处理 private NameValuePair[] getBaseHeaders(String methodName, String urlStr) { // 对请求头内容的特殊处理 若没有则直接添加到 返回值 NameValuePair[] 数组即可 ............ } // 如果需要URL编码则可以使用该方法 private String encodeHeader(String value) throws UnsupportedEncodingException { if (StringUtils.isEmpty(value)) { return value; } return URLEncoder.encode(value, "UTF-8" ); } // 拼接实际请求路径 // XXXXConfig 可以作为一个类专门加载配置文件中的一些有关的地址信息等 public String getRequestUrl(String actionUrl) { return XXXXConfig.getXxxxServer() + actionUrl; } @Override public String get( final String actionUrl, Map map) { String requestUrl = getRequestUrl(actionUrl); // 传入实际地址等 调用基础工具类请求 ..... } @Override public String post( final String actionUrl, final String jsonBody) { String requestUrl = getRequestUrl(actionUrl); // 传入实际地址等 调用基础工具类请求 ..... } @Override public String delete( final String actionUrl) { String requestUrl = getRequestUrl(actionUrl); // 传入实际地址等 调用基础工具类请求 ..... } } |
可以看的出,上层工具类先声明了 工具类对象 然后在当前类初始化时完成了对当前Service请求自己 httpClient 的相关创建以及配置的赋值,并将该对象 传递给工具类,使得调用工具类时依旧使用的是自己创建的那一个对象。
并通过一般的工具类将需要的配置文件的信息加载后,直接在该类中引用。完成请求信息的拼接。
由于不同的产品间的请求头要求信息可能不同,所以需要自己额外处理请求头相关信息。
这里其实还是在封装部分内容,使得业务请求调用时进一步简化。
一些工具类特殊的传入如下示例:
文件上传方式
1 | public String uploadFile(String url, final NameValuePair[] headerParams, HttpEntity multipartEntity) |
中 HttpEntity 中加入文件的方式
1 2 3 4 5 6 | 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 携带文件内容 |
其中 inputStream 为 InputStream 类型的文件输入流, fileName 为 String 文件名;
获取文件流
1 | public String downloadFile(String url, OutputStream outputStream,NameValuePair[] headerParams) |
获得请求中的文件流,则需要传入 OutputStream 类型的 输出流对象 outputStream。
再上一层的业务调用
业务层处则通过直接定义指定的方法和入参,在内部直接写入请求映射路径,并要求传入指定参数完成业务封装,类似于:
1 2 3 4 5 6 7 8 | @Override public XxxxxxxDTO 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 进行转化 转为自定义类或者Map 等返回 return ......; } |
这样在外部调用该业务方法时需要感知的只有入参,其他几乎感知不到,与一般的方法差别几乎没有
以上为个人经验,希望能给大家一个参考,也希望大家多多支持IT俱乐部。