一個Http客戶端

世面上有很多很多好用的客戶端,如HttpClient,OkHttp等,如果您已經用慣了其他的客戶端,那么可以繼續用,如果您在編寫Http請求的時候,感覺要寫的代碼很散,或者要寫的內容很多,很復雜,那么您可以嘗試一下RestBuilder,他設計的初衷就是通過傻瓜式鏈式調用,完成http請求。

  1. hello RestBuilder

發送一個非常簡單的http請求

String response = 
            RestBuilder
        .builder()
                .sendForObj(HttpMethod.GET, "http://www.baidu.com", String.class);
  1. get請求路徑url參數拼接
String response =
        RestBuilder
                .builder()
                .pathParam("age","10")
                .pathParam("name","xiaoming")
                .sendForObj(HttpMethod.GET, "http://www.baidu.com", String.class);

拼接后的url

http://www.baidu.com?name=xiaoming&age=10

  1. 基于restful風格的參數拼接
String response =
        RestBuilder
                .builder()
                .restfulPathParam("age","10")
                .restfulPathParam("name","xiaoming")
                .sendForObj(HttpMethod.GET, "http://www.baidu.com/${age}/${name}", String.class);

拼接后的url

http://www.baidu.com/10/xiaoming

  1. 設置Content-type &添加Header
String response =
        RestBuilder
                .builder()
                .contentType(MediaType.APPLICATION_JSON)
                .header("token","xxxxxxxxxxxxxxx")
                .sendForObj(HttpMethod.GET, "http://www.baidu.com", String.class);
  1. 表單請求
String response =
        RestBuilder
                .builder()
                .contentType(MediaType.APPLICATION_FORM_URLENCODED)
                .formData("username","xiaoming")
                .formData("password","123456")
                .sendForObj(HttpMethod.POST, "http://www.baidu.com", String.class);

http請求信息

HTTP POST http://www.baidu.com
Accept=[text/plain, application/json, application/*+json, /]
Writing [{username=[xiaoming], password=[123456]}] as "application/x-www-form-urlencoded"

  1. 添加body

    1. 方式一

      不斷添加k,v參數,最終會整合成一個json對象

    String response =
            RestBuilder
                    .builder()
                    .contentType(MediaType.APPLICATION_JSON)
                    .bodyParam("username","xiaoming")
                    .bodyParam("password","123456")
                    .sendForObj(HttpMethod.POST, "http://www.baidu.com", String.class);
    

    http請求信息

    HTTP POST http://www.baidu.com
    Accept=[text/plain, application/json, application/*+json, /]
    Writing [{"password":"123456","username":"xiaoming"}] as "application/json"

    1. 方式二

      直接傳入對象,如果是String類型,直接當做body,如果是其他類型,最終轉換成json形式

    String response =
            RestBuilder
                    .builder()
                    .contentType(MediaType.APPLICATION_JSON)
                    .bodyObj(() -> User.builder()
                                    .name("xiaoming")
                                    .password("123456")
                                    .build())
                    .sendForObj(HttpMethod.POST, "http://www.baidu.com", String.class);
    
    1. 添加監控日志

    調用鏈中加入monitor()方法

    String response =
            RestBuilder
                    .builder()
                    .contentType(MediaType.APPLICATION_JSON)
                    .bodyObj(() -> User.builder()
                                    .name("xiaoming")
                                    .password("123456")
                                    .build())
                    .monitor()
                    .sendForObj(HttpMethod.POST, "http://www.baidu.com", String.class);
    
    1. 設置readTimeout&connectTimeout
    String response =
            RestBuilder
                    .builder()
                    .timeout(2000,2000)
                    .sendForObj(HttpMethod.POST, "http://www.baidu.com", String.class);
    
    1. 獲取響應的Http狀態碼,響應頭等信息

      sendForObj換成sendForEntity

    ResponseEntity<String> responseEntity =
            RestBuilder
                    .builder()
                    .timeout(2000, 2000)
                    .sendForEntity(HttpMethod.POST, "http://www.baidu.com", String.class);
    
    1. 獲取響應流

      一般用于文件下載使用,將sendForObj換成sendForInputStream即可。

    InputStream inputStream = RestBuilder
            .builder()
            .timeout(2000, 2000)
            .sendForInputStream(HttpMethod.POST, "http://www.baidu.com");
    
    1. 復雜響應類型

      獲取的響應類型形如:Map<String,<List<String>>>形式,這時我們可以使用JDK提供的TypeToken設置響應類型

    Type type = new TypeToken<HashMap<String, List<String>>>() {}.getType();
    HashMap<String, List<String>> response =
            RestBuilder
                    .builder()
                    .timeout(2000,2000)
                    .sendForObj(HttpMethod.POST, "http://www.baidu.com", type);
    

[注]

  • 底層封裝的是RestTemplate,使用到的所有Http相關API均來自Spring
  • RestTemplate不會頻繁的被創建,如果不設置超時時間,那么使用默認的restTemplate對象,如果設置了超時時間會嘗試從緩存池中獲取restTemplate對象,如果不存在,才會創建restTempalte對象。
  1. 這里需要添加gson依賴,用于json處理,
        <dependency>
            <groupId>com.google.code.gson</groupId>
            <artifactId>gson</artifactId>
            <version>2.8.6</version>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>
  1. 代碼
@Slf4j
public class RestBuilder {

    private static final RestTemplate REST_TEMPLATE;

    private HttpHeaders httpHeaders = new HttpHeaders();

    private String postBodyStr;

    private Map<String, String> restfulPathParamMap;

    private Map<String, String> getParamMap;

    private Map<String, Object> bodyParam;

    private MultiValueMap<String, Object> formData;

    private HttpEntity<?> httpEntity;

    private String requestPath;

    private Integer readTimeout;

    private Integer connectTimeout;

    private Boolean setTimeout = false;

    private Boolean monitor = false;

    private static final Cache<String, RestTemplate> CACHE;

    private static final Object LOCK = new Object();

    static {
        REST_TEMPLATE = new RestTemplate();
        MappingJackson2HttpMessageConverter converter = new MappingJackson2HttpMessageConverter();
        converter.setSupportedMediaTypes(Collections.singletonList(MediaType.ALL));
        REST_TEMPLATE.getMessageConverters().add(converter);
        CACHE = CacheBuilder.newBuilder()
                .maximumSize(50)
                .expireAfterWrite(30, TimeUnit.MINUTES)
                .build();
    }

    private RestBuilder() {
    }

    public static RestBuilder builder() {
        return new RestBuilder();
    }

    /**
     * 添加header
     *
     * @param headerKey key
     * @param value     value
     * @return {@link RestBuilder}
     */
    public RestBuilder header(String headerKey, String value) {
        Assert.hasText(headerKey, "headerKey must not be null");
        httpHeaders.add(headerKey, value);
        return this;
    }

    /**
     * 設置Http ContentType
     *
     * @param mediaType {@link MediaType}
     * @return {@link RestBuilder}
     */
    public RestBuilder contentType(MediaType mediaType) {
        if (Objects.isNull(httpHeaders)) {
            httpHeaders = new HttpHeaders();
        }
        httpHeaders.setContentType(mediaType);
        return this;
    }

    /**
     * 設置GET請求參數,URL拼接形式
     *
     * @param pathParam key
     * @param value     value
     * @return {@link RestBuilder}
     */
    public RestBuilder pathParam(String pathParam, String value) {
        Assert.hasText(pathParam, "supplier must not be null");
        if (Objects.isNull(getParamMap)) {
            getParamMap = new HashMap<>();
        }
        getParamMap.put(pathParam, value);
        return this;
    }

    /**
     * 設置post表單
     *
     * @param param key
     * @param value value
     * @return {@link RestBuilder}
     */
    public RestBuilder formData(String param, String value) {
        Assert.hasText(param, "supplier not be null");
        if (Objects.isNull(this.formData)) {
            formData = new LinkedMultiValueMap<>();
        }
        this.formData.add(param, value);
        return this;
    }

    /**
     * 設置body參數,最終會被轉換成json
     *
     * @param key   key
     * @param value value
     * @return {@link RestBuilder}
     */
    public <T> RestBuilder bodyParam(String key, String value) {
        Assert.hasText(key, "function must not be null");
        if (Objects.isNull(bodyParam)) {
            this.bodyParam = new HashMap<>();
        }
        bodyParam.put(key, value);
        return this;
    }

    /**
     * 設置請求body,最終會轉換成json
     *
     * @param supplier {@link Object}
     * @return {@link RestBuilder}
     */
    public RestBuilder bodyObj(Supplier<Object> supplier) {
        Assert.notNull(supplier, "supplier must not be null");
        Object obj = supplier.get();
        this.postBodyStr = obj instanceof String ? (String) obj : GsonUtils.object2Json(obj);
        return this;
    }

    /**
     * 設置路徑參數
     *
     * @param key   key
     * @param value value
     * @return {@link RestBuilder}
     */
    public RestBuilder restfulPathParam(String key, String value) {
        Assert.hasText(key, "key must not be null");
        if (Objects.isNull(restfulPathParamMap)) {
            this.restfulPathParamMap = new HashMap<>();
        }
        restfulPathParamMap.put(key, value);
        return this;
    }

    /**
     * 發送請求并返回{@link ResponseEntity}
     *
     * @param requestPath  請求路徑/url
     * @param responseType 響應類型
     * @param <R>          ResponseType
     * @return response
     */
    public <R> ResponseEntity<R> sendForEntity(HttpMethod requestMethod, String requestPath, Type responseType) {
        build(requestPath);
        return requestForEntity(this.requestPath, this.httpEntity, responseType, requestMethod);
    }

    /**
     * 發送請求并直接返回響應體
     *
     * @param requestMethod {@link HttpMethod}
     * @param requestPath   請求路徑
     * @param responseType  響應類型
     * @param <R>           R
     * @return response Body
     */
    public <R> R sendForObj(HttpMethod requestMethod, String requestPath, Type responseType) {
        ResponseEntity<R> responseEntity = sendForEntity(requestMethod, requestPath, responseType);
        return responseEntity.getBody();
    }

    /**
     * 發送請求并返回{@link ResponseEntity}
     *
     * @param requestPath  請求路徑/url
     * @param responseType 響應類型
     * @param <R>          ResponseType
     * @return response
     */
    public <R> ResponseEntity<R> sendForEntity(HttpMethod requestMethod, String requestPath, Class<R> responseType) {
        build(requestPath);
        return requestForEntity(this.requestPath, this.httpEntity, responseType, requestMethod);
    }

    /**
     * 發送請求并直接返回響應體
     *
     * @param requestMethod {@link HttpMethod}
     * @param requestPath   請求路徑
     * @param responseType  響應類型
     * @param <R>           R
     * @return response Body
     */
    public <R> R sendForObj(HttpMethod requestMethod, String requestPath, Class<R> responseType) {
        ResponseEntity<R> responseEntity = sendForEntity(requestMethod, requestPath, responseType);
        return responseEntity.getBody();
    }

    /**
     * 發送GET請求并反返回響應流
     *
     * @param requestPath 請求路徑/URL
     * @return InputStream
     */
    public InputStream sendForInputStream(HttpMethod requestMethod, String requestPath) throws IOException {
        ResponseEntity<Resource> responseEntity = sendForEntity(requestMethod, requestPath, Resource.class);
        return Objects.requireNonNull(responseEntity.getBody()).getInputStream();
    }

    /**
     * 發送請求檢測
     *
     * @return this
     */
    public RestBuilder monitor() {
        this.monitor = true;
        return this;
    }

    /**
     * 設置readTimeOut
     *
     * @param connectTimeout connectTimeout
     * @param readTimeout    readTimeout
     * @return {@link RestBuilder}
     */
    public RestBuilder timeout(int readTimeout, int connectTimeout) {
        this.setTimeout = true;
        this.readTimeout = readTimeout;
        this.connectTimeout = connectTimeout;
        return this;
    }

    /**
     * 構造restful路徑
     *
     * @param path path
     * @return restful path
     */
    private String generatePath(String path) {
        if (Objects.nonNull(restfulPathParamMap) && !restfulPathParamMap.isEmpty()) {
            // 替換restful值
            Set<Map.Entry<String, String>> entrySet = restfulPathParamMap.entrySet();
            for (Map.Entry<String, String> entry : entrySet) {
                path = path.replace(String.format("${%s}", entry.getKey()), entry.getValue());
            }
        }
        if (Objects.nonNull(getParamMap) && !getParamMap.isEmpty()) {
            StringBuilder pathBuilder = new StringBuilder(path).append("?");
            // 拼接請求值
            getParamMap.forEach((k, v) -> pathBuilder.append(k).append("=").append(v).append("&"));
            // 最后一個&
            int length = pathBuilder.length();
            pathBuilder.delete(length - 1, length);
            path = pathBuilder.toString();
        }
        if (monitor) {
            log.info("PATH [ {} ]", path);
        }
        return path;
    }

    /**
     * 構造http的URL和body
     *
     * @param path 請求路徑
     */
    private void build(String path) {
        // 構造請求路徑
        this.requestPath = generatePath(path);
        Object body = null;

        // 表單和body只能選中一個
        Assert.isTrue(!(formData != null && (postBodyStr != null || bodyParam != null)),
                "body or form data only one can be selected");

        // 沒有指定contentType默認'application/json'
        MediaType contentType = httpHeaders.getContentType();
        if (Objects.isNull(formData)) {
            if (Objects.isNull(contentType)) {
                httpHeaders.setContentType(MediaType.APPLICATION_JSON);
            }
            body = Strings.isNullOrEmpty(postBodyStr) ? GsonUtils.object2Json(bodyParam) : postBodyStr;
        } else {
            httpHeaders.setContentType(MediaType.APPLICATION_FORM_URLENCODED);
            body = formData;
        }
        // body允許傳空body
        this.httpEntity = new HttpEntity<>(body, httpHeaders);
        if (this.monitor) {
            log.info("requestEntity [ {} ]", httpEntity.toString());
        }
    }

    private <R> ResponseEntity<R> requestForEntity(String url, @Nullable Object request, Type responseType, HttpMethod httpMethod, Object... uriVariables) throws RestClientException {
        long begin = 0;
        if (monitor) {
            begin = System.currentTimeMillis();
        }
        RestTemplate restTemplate = getRestTemplate();
        RequestCallback requestCallback = restTemplate.httpEntityCallback(request, responseType);
        ResponseExtractor<ResponseEntity<R>> responseExtractor = restTemplate.responseEntityExtractor(responseType);
        ResponseEntity<R> responseEntity = nonNull(restTemplate.execute(url, httpMethod, requestCallback, responseExtractor, uriVariables));
        if (monitor) {
            log.info("Response Message [{}]", responseEntity);
            log.info("cost time [{}] ms.", System.currentTimeMillis() - begin);
        }
        return responseEntity;
    }

    private <T> T nonNull(@Nullable T result) {
        Assert.state(result != null, "No result");
        return result;
    }

    /**
     * 獲取restTemplate
     *
     * @return RestTemplate
     */
    private RestTemplate getRestTemplate() {
        if (!setTimeout) {
            return RestBuilder.REST_TEMPLATE;
        }
        synchronized (LOCK) {
            // 先去查看是否已經緩存了相同設置超時時間的restTemplate
            // 拼接規則為 ${readTimeout.toString()}:${connectTimeout.toString()}
            String cacheKey = generateCacheKey();
            RestTemplate timoutRestTemplate = CACHE.getIfPresent(cacheKey);
            if (Objects.nonNull(timoutRestTemplate)) {
                // 重置超時時間
                CACHE.put(cacheKey, timoutRestTemplate);
                return timoutRestTemplate;
            }
            // 之前沒有緩存該restTemplate,生成好restTemplate,然后緩存起來
            SimpleClientHttpRequestFactory requestFactory = new SimpleClientHttpRequestFactory();
            if (Objects.nonNull(this.readTimeout)) {
                requestFactory.setReadTimeout(readTimeout);
            }
            if (Objects.nonNull(this.connectTimeout)) {
                requestFactory.setConnectTimeout(connectTimeout);
            }
            RestTemplate restTemplate = new RestTemplate(requestFactory);
            MappingJackson2HttpMessageConverter converter = new MappingJackson2HttpMessageConverter();
            converter.setSupportedMediaTypes(Collections.singletonList(MediaType.ALL));
            restTemplate.getMessageConverters().add(converter);
            CACHE.put(cacheKey, restTemplate);
            return restTemplate;
        }
    }

    private String generateCacheKey() {
        StringBuilder sb = new StringBuilder();
        if (Objects.nonNull(readTimeout)) {
            sb.append(readTimeout.toString());
        }
        if (Objects.nonNull(connectTimeout)) {
            sb.append(connectTimeout.toString());
        }
        return sb.toString();
    }
}

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。