远程过程调用是什么呢?通俗来说就是程序员在访问远程资源的时候,就跟在本地访问一样。实现远程过程调用远没有想象那么复杂,简单来说就是将放在业务里面的调用外部系统逻辑的代码独立开来,调用者不需要考虑远程调用的实现细节,与在本地调用一个服务层一样。很多厂家的sdk也都是这样的方式,就拿腾讯云的短信为例,看似只需要填写短信的相关配置,然后调用其给定的方法就行了,其实还内含了例如网络传输,序列化与反序列化的东西,把这些问题给屏蔽掉了。

当然,简单的对http接口调用进行封装,算不上是真正的远程过程调用,只是借用RPC的概念,通过对调用接口的封装,屏蔽掉不同接口调用者网络实现和序列化与反序列化的实现差异。减少不必要的调试。

我现在是按照下面的方式实现的。首先定义一个通用的网络请求方法,这是一个简单的工具类

public class NetWorkUtil{

public static final int HTTP_METHOD_GET = 1;
public static final int HTTP_METHOD_HEAD = 2;
public static final int HTTP_METHOD_POST = 3;
public static final int HTTP_METHOD_PATCH = 4;
public static final int HTTP_METHOD_PUT = 5;

public static final List<String[]> reqHeaderList = Arrays.asList(new String[][]{
{"Accept-Encoding", "gzip, deflate, sdch"},
{"Accept-Language", "zh-CN,zh;q=0.8"},
{"Connection", "keep-alive"},
{"User-Agent", "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/44.0.2403.130 Safari/537.36"}
});

/**
* 获取 http 请求头
*
* @param xAuth acunetix key
* @return http 请求头
*/
public static Map<String, String> getReqHeaders(String xAuth) {
Map<String, String> reqHeaders = new HashMap<>();
reqHeaderList.forEach(header -> {
reqHeaders.put(header[0], header[1]);
});
reqHeaders.put("X-Auth", xAuth);
return reqHeaders;
}

/**
* http 应答
*/
public static class HttpResponse {
public CloseableHttpResponse response;
public String body;
}

/**
* 获取应答
*
* @param httpClient http 客户端
* @param method 请求方法
* @param httpRequest http 请求
* @return http 响应
*/
public static HttpResponse getResponse(CloseableHttpClient httpClient, int method, HttpRequestBase httpRequest) {
CloseableHttpResponse httpResponse = null;
String body = null;
try {
httpResponse = httpClient.execute(httpRequest);
if (method != HTTP_METHOD_HEAD) {
HttpEntity httpEntity = httpResponse.getEntity();
if (Objects.nonNull(httpEntity)) {
BufferedReader reader = new BufferedReader(new InputStreamReader(httpEntity.getContent(), "utf-8"));
String str = null;
StringBuffer stringBuffer = new StringBuffer();
while (Objects.nonNull(str = reader.readLine())) {
if (StringUtils.isBlank(str)) {
continue;
}
stringBuffer.append(str);
}
body = stringBuffer.toString();
}
}
} catch (Exception e) {
e.printStackTrace();
httpResponse = null;
} finally {
try {
httpResponse.close();
} catch (Exception ex) {
httpResponse = null;
}
try {
httpClient.close();
} catch (Exception ex) {
httpResponse = null;
}
}
if (Objects.isNull(httpResponse)) {
return null;
}
HttpResponse response = new HttpResponse();
response.response = httpResponse;
response.body = body;
return response;
}

/**
* 获取 Http 请求对象
*
* @param url 要访问的 url
* @param method 访问的方式
* @param headers 请求头
* @param body 请求内容
* @return http 请求对象
*/
public static HttpRequestBase newHttpRequest(String url, int method, Map<String, String> headers, Object body) {
HttpRequestBase httpRequest = null;
switch (method) {
case HTTP_METHOD_GET:
httpRequest = new HttpGet(url);
break;
case HTTP_METHOD_HEAD:
httpRequest = new HttpHead(url);
break;
case HTTP_METHOD_POST:
httpRequest = new HttpPost(url);
break;
case HTTP_METHOD_PATCH:
httpRequest = new HttpPatch(url);
break;
case HTTP_METHOD_PUT:
httpRequest = new HttpPut(url);
break;
default:
return null;
}
if (httpRequest instanceof HttpPost || httpRequest instanceof HttpPatch || httpRequest instanceof HttpPut) {
try {
if (body instanceof String) {
((HttpEntityEnclosingRequest) httpRequest).setEntity(new StringEntity((String) body, ContentType.APPLICATION_JSON));
} else if (body instanceof Map) {
List<NameValuePair> nameValuePairs = new ArrayList<>();
((Map<String, String>) body).forEach((key, value) -> {
nameValuePairs.add(new BasicNameValuePair(key, value));
});
if (httpRequest instanceof HttpPost) {
((HttpPost) httpRequest).setEntity(new UrlEncodedFormEntity(nameValuePairs));
} else if (httpRequest instanceof HttpPut) {
((HttpPut) httpRequest).setEntity(new UrlEncodedFormEntity(nameValuePairs));
} else if (httpRequest instanceof HttpPatch) {
((HttpPatch) httpRequest).setEntity(new UrlEncodedFormEntity(nameValuePairs));
} else {
throw new RuntimeException("不支持的 httpRequest: "+httpRequest.getMethod());
}
}
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
return null;
}
}
httpRequest.setConfig(RequestConfig.custom().setConnectTimeout(5000).setSocketTimeout(5000).build());
final HttpRequestBase httpRequestBase = httpRequest;
if (Objects.nonNull(headers)) {
headers.forEach((key, value) -> httpRequestBase.addHeader(key, value));
}
return httpRequest;
}

/**
* 创建一个允许访问自签名站点的 http 客户端
*
* @return 一个允许访问自签名站点的 http 客户端
*/
public static CloseableHttpClient createAcceptSelfSignedCertificateClient() {
try {
// use the TrustSelfSignedStrategy to allow Self Signed Certificates
SSLContext sslContext = SSLContextBuilder
.create()
.loadTrustMaterial(new TrustSelfSignedStrategy())
.build();

// we can optionally disable hostname verification.
// if you don't want to further weaken the security, you don't have to include this.
HostnameVerifier allowAllHosts = new NoopHostnameVerifier();

// create an SSL Socket Factory to use the SSLContext with the trust self signed certificate strategy
// and allow all hosts verifier.
SSLConnectionSocketFactory connectionFactory = new SSLConnectionSocketFactory(sslContext, allowAllHosts);

// finally create the HttpClient using HttpClient factory methods and assign the ssl socket factory
return HttpClients
.custom()
.setSSLSocketFactory(connectionFactory)
.build();
} catch (Exception ex) {
ex.printStackTrace();
return null;
}
}



public static HttpResponse getResponseWithLine(CloseableHttpClient httpClient, int method, HttpRequestBase httpRequest) {
CloseableHttpResponse httpResponse = null;
String body = null;
try {
httpResponse = httpClient.execute(httpRequest);
if (method != HTTP_METHOD_HEAD) {
HttpEntity httpEntity = httpResponse.getEntity();
if (Objects.nonNull(httpEntity)) {
BufferedReader reader = new BufferedReader(new InputStreamReader(httpEntity.getContent(), "utf-8"));
String str = null;
StringBuffer stringBuffer = new StringBuffer();
while (Objects.nonNull(str = reader.readLine())) {
if (StringUtils.isBlank(str)) {
continue;
}
str = str + "\r\n";
stringBuffer.append(str);
}
body = stringBuffer.toString();
}
}
} catch (Exception e) {
e.printStackTrace();
httpResponse = null;
} finally {
try {
httpResponse.close();
} catch (Exception ex) {
httpResponse = null;
}
try {
httpClient.close();
} catch (Exception ex) {
httpResponse = null;
}
}
if (Objects.isNull(httpResponse)) {
return null;
}
HttpResponse response = new HttpResponse();
response.response = httpResponse;
response.body = body;
return response;
}
}

java工具类在使用的时候尽量不要使用静态引入 + 星号的方法,一旦用多了,可能就不知道某个属性、常量或者变量方法究竟是哪个工具类里面的了。

然后利用此网络工具类构建出网络请求就好了,具体的方案有很多。最后我给大家看看效果。

我们在封装的时候,将整个(也可分开,看架构)包装模块定义到一个服务中,在需要使用的地方直接注入该服务就行了

下面的代码中的业务模块是注册逻辑的一部分,用到了两个服务。

一次基于Http协议的远程过程调用的实现_请求头


你能看出那个服务是远程服务吗?看不出来?远程过程调用达到的就是这么个效果,减少调用代码对业务代码的侵入性。