IT俱乐部 Java Java HttpClient-Restful工具各种请求高度封装提炼及总结

Java HttpClient-Restful工具各种请求高度封装提炼及总结

总思路

总的工具要求底层完全可复用的代码全部提炼,也就是不通类型(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 携带文件内容

其中 inputStreamInputStream 类型的文件输入流, 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俱乐部。

本文收集自网络,不代表IT俱乐部立场,转载请注明出处。https://www.2it.club/code/java/6637.html
上一篇
下一篇
联系我们

联系我们

在线咨询: QQ交谈

邮箱: 1120393934@qq.com

工作时间:周一至周五,9:00-17:30,节假日休息

关注微信
微信扫一扫关注我们

微信扫一扫关注我们

返回顶部