一次基於Http協議的遠程過程調用的實現

遠程過程調用是什麼呢?通俗來說就是程序員在訪問遠程資源的時候,就跟在本地訪問一樣。實現遠程過程調用遠沒有想象那麼複雜,簡單來說就是將放在業務裏面的調用外部系統邏輯的代碼獨立開來,調用者不需要考慮遠程調用的實現細節,與在本地調用一個服務層一樣。很多廠家的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工具類在使用的時候儘量不要使用靜態引入 + 星號的方法,一旦用多了,可能就不知道某個屬性、常量或者變量方法究竟是哪個工具類裏面的了。

然後利用此網絡工具類構建出網絡請求就好了,具體的方案有很多。最後我給大家看看效果。

我們在封裝的時候,將整個(也可分開,看架構)包裝模塊定義到一個服務中,在需要使用的地方直接注入該服務就行了
下面的代碼中的業務模塊是註冊邏輯的一部分,用到了兩個服務。
在這裏插入圖片描述
你能看出那個服務是遠程服務嗎?看不出來?遠程過程調用達到的就是這麼個效果,減少調用代碼對業務代碼的侵入性。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章