Administrator
发布于 2025-01-15 / 11 阅读
0
0

Jdk HttpClient

基础知识

refer to :

https://docs.oracle.com/en/java/javase/11/docs/api/java.net.http/java/net/http/HttpClient.html

https://openjdk.org/groups/net/httpclient/intro.html

https://docs.oracle.com/en/java/javase/11/docs/api/java.net.http/java/net/http/package-summary.html

HttpClient

主要涉及到以下几个对象:

  • HttpClient
  • HttpRequest
  • HttpResponse
  • BodyHandler

The BodyHandler determines how to handle the response body, if any.

其中,对于请求体和响应体,主要依赖于:

BodyPublishers

BodySubscribers

BodyPublishers

https://docs.oracle.com/en/java/javase/11/docs/api/java.net.http/java/net/http/HttpRequest.BodyPublishers.html

Implementations of BodyPublisher that implement various useful publishers, such as publishing the request body from a String, or from a file.

The following are examples of using the predefined body publishers to convert common high-level Java objects into a flow of data suitable for sending as a request body:

// Request body from a String
   HttpRequest request = HttpRequest.newBuilder()
        .uri(URI.create("https://foo.com/"))
        .header("Content-Type", "text/plain; charset=UTF-8")
        .POST(BodyPublishers.ofString("some body text"))
        .build();

   // Request body from a File
   HttpRequest request = HttpRequest.newBuilder()
        .uri(URI.create("https://foo.com/"))
        .header("Content-Type", "application/json")
        .POST(BodyPublishers.ofFile(Paths.get("file.json")))
        .build();

   // Request body from a byte array
   HttpRequest request = HttpRequest.newBuilder()
        .uri(URI.create("https://foo.com/"))
        .POST(BodyPublishers.ofByteArray(new byte[] { ... }))
        .build(); 

BodySubscribers

https://docs.oracle.com/en/java/javase/11/docs/api/java.net.http/java/net/http/HttpResponse.BodySubscribers.html

Implementations of BodySubscriber that implement various useful subscribers, such as converting the response body bytes into a String, or streaming the bytes to a file.

The following are examples of using the predefined body subscribers to convert a flow of response body data into common high-level Java objects:

 // Streams the response body to a File
   HttpResponse<byte[]> response = client
     .send(request, responseInfo -> BodySubscribers.ofByteArray());

   // Accumulates the response body and returns it as a byte[]
   HttpResponse<byte[]> response = client
     .send(request, responseInfo -> BodySubscribers.ofByteArray());

   // Discards the response body
   HttpResponse<Void> response = client
     .send(request, responseInfo -> BodySubscribers.discarding());

   // Accumulates the response body as a String then maps it to its bytes
   HttpResponse<byte[]> response = client
     .send(request, responseInfo ->
        BodySubscribers.mapping(BodySubscribers.ofString(UTF_8), String::getBytes));

Example

get请求:

public static void main(String[] args) throws IOException, InterruptedException {
        HttpClient httpClient = HttpClient.newBuilder().connectTimeout(Duration.ofSeconds(20)).build();
        HttpRequest httpRequest = HttpRequest.newBuilder()
                .uri(URI.create("http://www.baidu.com"))
                .timeout(Duration.ofMinutes(2))
                .header("Content-Type", "application/json")
                .GET().build();
        HttpResponse<String> httpResponse = httpClient.send(httpRequest, HttpResponse.BodyHandlers.ofString());
        System.out.println(httpResponse.body());

    }

JSON请求和响应:

https://golb.hplar.ch/2019/01/java-11-http-client.html

首先,自定义一个BodyHandler

package org.wp;

import com.alibaba.fastjson2.JSON;

import java.io.ByteArrayInputStream;
import java.net.http.HttpResponse;

public class JsonBodyHandler<T> implements HttpResponse.BodyHandler<T>{

    private Class<T> targetClass;

    public JsonBodyHandler(Class<T> clazz) {
        this.targetClass = clazz;
    }

    public static <T> JsonBodyHandler<T> build(Class<T> clazz) {
        return new JsonBodyHandler(clazz);
    }

    @Override
    public HttpResponse.BodySubscriber<T> apply(HttpResponse.ResponseInfo responseInfo) {
        return HttpResponse.BodySubscribers.mapping(HttpResponse.BodySubscribers.ofByteArray(),
                byteArray -> JSON.parseObject(new ByteArrayInputStream(byteArray), this.targetClass));
    }
}

测试如下:

public static void main(String[] args) throws IOException, InterruptedException {
        HttpClient httpClient = HttpClient.newBuilder().connectTimeout(Duration.ofSeconds(20)).build();

        MonQueryReq monQueryReq = buildMonQueryReq();

        HttpRequest httpRequest = HttpRequest.newBuilder()
                .uri(URI.create("http://218.94.128.34:7072/iot/mon/page/list"))
                .timeout(Duration.ofMinutes(2))
                .header("Content-Type", "application/json")
                .header("Authorization", "8bfa1bdf-c37e-488d-9be9-8a31fbc50baf")
                .POST(HttpRequest.BodyPublishers.ofString(JSON.toJSONString(monQueryReq)))
                .build();

        HttpResponse<MonQueryResp> httpResponse = httpClient.send(httpRequest, JsonBodyHandler.build(MonQueryResp.class));
        MonQueryResp monQueryResp = httpResponse.body();
        System.out.println(JSON.toJSONString(monQueryResp));

    }

    private static MonQueryReq buildMonQueryReq() {
        MonQueryReq monQueryReq = new MonQueryReq();
        PageParameter pageParameter = new PageParameter(500, 1);
        SearchBody searchBody = new SearchBody();
        searchBody.setBoxNo("1848652682395889664");

        monQueryReq.setPagedParameter(pageParameter);
        monQueryReq.setSearch(searchBody);
        return monQueryReq;
    }

form-urlencoded & Query Parameters

当我们在HTTP中使用 MIME 类型为x-www-form-urlencoded格式提交数据时,所使用的就是前文所介绍的编码方式。

默认情况下,Content-Type: application/x-www-form-urlencoded。

也就是说,如果我们不指定Content-Type,默认情况下,都是x-www-form-urlencoded,适用于Get和Post请求。

如果Content-Type为application/x-www-form-urlencoded ,且是 GET 请求,那么数据会拼接在 Query 中,也就是我们常说的Query Parameters;

如:field1=value1&field2=value2&field3=value3

包含了如下标准:

  1. Query string 由一组键值对(feild-value)组成;
  2. 每组键值对的 feildvalue=分割;
  3. 每组数据用&分割;

如果Content-Type为application/x-www-form-urlencoded ,且是 POST 请求,则会将数据放置在消息体(body)中,通过Header中的Content-Type 来指定 MIME 类型。

如:

image-20250115092853756

URI & URL & URN

URI 属于父类,而 URL 属于 子类。也就是说,URL是URI众多类型的中的一种。

URI = URL + URN

image-20250115095938511

接下来,我们再来分别讨论这3个术语,他们的概念和作用

The URI acronym stands for Uniform Resource Identifier

The URL is an acronym that stands for Uniform Resource Locator.

Both URLs and URIs follow the same specification: RFC 3986.

URL: allow you to locate a resource, is intended as an address to get a resource.

URI: simply identifies a resource,just as an identifier for resource,类似于数据库中的唯一id

Going back to the address example, if you say you live in the only yellow house in your town, you are not giving directions on how to get there. However, this information identifies your house among the others in your town.

URN stands for Uniform Resource Name, and its scope is to identify resources in a permanent way, even after that resource does not exist anymore.

In particular, a URN is a URI whose scheme is urn and has the following structure, as described by the RFC 2141:

urn:<NAMESPACE-IDENTIFIER>:<NAMESPACE-SPECIFIC-STRING>

The <NAMESPACE-IDENTIFIER> placeholder stands for a string representing the resource category you want to identify.

The <NAMESPACE-SPECIFIC-STRING> is the resource's specific identifier, and its format depends on the namespace identifier.

The following are examples of URNs:

urn:isbn:1234567890
urn:ISSN:0167-6423
urn:ietf:rfc:2648

Those URNs identify objects of different types.

For example, urn:isbn:1234567890 identifies a publication through the ISBN system;

urn:ISSN:0167-6423 identifies a publication through the ISSN system;

urn:ietf:rfc:2648 is an RFC issued by the IETF.

image-20250115100827157

在Java中的转换

java.net.URL
    
java.net.URI
    
convert between these two classes using URL.toURI() and URI.toURL().

工作中实际使用

提供了2个调用类:

  • DynamicGetCaller
  • DynamicPostCaller

代码如下:

package com.jshxhb.framework.feign.http;

import cn.hutool.core.net.url.UrlBuilder;
import com.jshxhb.framework.common.entity.exception.ThirdDynamicErrorException;
import org.apache.commons.collections4.MapUtils;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.stereotype.Component;

import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.time.Duration;
import java.util.Map;

@Component
public class DynamicGetCaller {

    private Duration defaultConnectTimeout = Duration.ofSeconds(20);

    private Duration defaultTimeout = Duration.ofSeconds(30);

    HttpClient httpClient = HttpClient.newBuilder().connectTimeout(defaultConnectTimeout).build();

    private Map<String,String> defaultHeaders = Map.of(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_FORM_URLENCODED_VALUE);

    public <R> R executeSimpleGet(Class<R> responseType, String url) throws Exception {
        return executeHeaderGet(responseType,url,null);
    }

    public <R> R executeHeaderGet(Class<R> responseType, String url, Map<String, String> headerMap) throws Exception {
        return executeHeaderParamGet(responseType,url,headerMap,null);
    }

    public <R> R executeHeaderParamGet(Class<R> responseType, String url,Map<String,String> headerMap,Map<String,Object> queryParamMap) throws Exception {
        URI uri = null;
        if (MapUtils.isEmpty(queryParamMap)) {
            uri = URI.create(url);
        } else {
            UrlBuilder urlBuilder = UrlBuilder.ofHttp(url);
            for (Map.Entry<String, Object> entry : queryParamMap.entrySet()) {
                urlBuilder.addQuery(entry.getKey(), entry.getValue());
            }
            uri = urlBuilder.toURI();
        }
        HttpRequest.Builder builder = HttpRequest.newBuilder().uri(uri).timeout(defaultTimeout);
        defaultHeaders.forEach(builder::header);
        if (MapUtils.isNotEmpty(headerMap)) {
            for (Map.Entry<String, String> entry : headerMap.entrySet()) {
                builder.setHeader(entry.getKey(), entry.getValue());
            }
        }
        HttpRequest httpRequest = builder.GET().build();
        HttpResponse<R> httpResponse = httpClient.send(httpRequest, JsonBodyHandler.build(responseType));
        if (httpResponse.statusCode() != HttpStatus.OK.value()) {
            throw new ThirdDynamicErrorException("调用第三方接口出现错误");
        } else {
            return httpResponse.body();
        }
    }
}

package com.jshxhb.framework.feign.http;

import com.alibaba.fastjson2.JSON;
import com.jshxhb.framework.common.entity.exception.ThirdDynamicErrorException;
import org.apache.commons.collections4.MapUtils;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.stereotype.Component;

import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.time.Duration;
import java.util.Map;
import java.util.Objects;

@Component
public class DynamicPostCaller {
    private Duration defaultConnectTimeout = Duration.ofSeconds(20);

    private Duration defaultTimeout = Duration.ofSeconds(30);

    HttpClient httpClient = HttpClient.newBuilder().connectTimeout(defaultConnectTimeout).build();

    private Map<String,String> defaultHeaders = Map.of(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_UTF8_VALUE);

    public <T, R> R executeSimplePost(Class<R> responseType, String url) throws Exception {
        return executeBodyPost(responseType,url,null);
    }

    public <T, R> R executeBodyPost(Class<R> responseType, String url, T requestBody) throws Exception {
        return executeHeaderBodyPost(responseType,url,null,requestBody);
    }

    public <T, R> R executeHeaderBodyPost(Class<R> responseType, String url, Map<String, String> headerMap, T requestBody) throws Exception {
        HttpRequest.Builder builder = HttpRequest.newBuilder().uri(URI.create(url)).timeout(defaultTimeout);
        defaultHeaders.forEach(builder::header);
        if (MapUtils.isNotEmpty(headerMap)) {
            for (Map.Entry<String, String> entry : headerMap.entrySet()) {
                builder.setHeader(entry.getKey(), entry.getValue());
            }
        }
        if (Objects.isNull(requestBody)) {
            builder.POST(HttpRequest.BodyPublishers.noBody());
        } else {
            builder.POST(HttpRequest.BodyPublishers.ofString(JSON.toJSONString(requestBody)));
        }
        HttpRequest httpRequest = builder.build();
        HttpResponse<R> httpResponse = httpClient.send(httpRequest, JsonBodyHandler.build(responseType));
        if (httpResponse.statusCode() != HttpStatus.OK.value()) {
            throw new ThirdDynamicErrorException("调用第三方接口出现错误");
        } else {
            return httpResponse.body();
        }
    }
}

测试demo


package com.jshxhb.module.box.mon.biz.controller.develop;

import com.alibaba.fastjson2.JSON;
import com.jshxhb.framework.common.entity.exception.BizServiceException;
import com.jshxhb.framework.feign.http.DynamicGetCaller;
import com.jshxhb.framework.feign.http.DynamicPostCaller;
import com.jshxhb.framework.redis.helper.RedisCacheHelper;
import com.jshxhb.framework.web.crud.dto.req.PageParamReqDTO;
import com.jshxhb.framework.web.crud.dto.req.PageQueryReqBodyDTO;
import com.jshxhb.framework.web.crud.dto.resp.CommonRespDTO;
import com.jshxhb.framework.web.crud.dto.resp.PageQueriedBizListResult;
import com.jshxhb.module.box.mon.entity.dto.mon.MonQueryReqDTO;
import com.jshxhb.module.box.mon.entity.dto.mon.MonQueryRespDTO;
import com.jshxhb.module.box.mon.entity.dto.project.ProjectTreeRespDTO;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.enums.ParameterIn;
import io.swagger.v3.oas.annotations.tags.Tag;
import org.apache.commons.lang3.exception.ExceptionUtils;
import org.apache.http.HttpHeaders;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import java.net.http.HttpTimeoutException;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.concurrent.TimeUnit;

@Tag(name = "动态http样例")
@RestController
@RequestMapping("/iot/dynamichttp")
public class DynamicHttpDemoController {
    private static final Logger LOGGER = LoggerFactory.getLogger(DynamicHttpDemoController.class);

    @Autowired
    private DynamicGetCaller dynamicGetCaller;

    @Autowired
    private DynamicPostCaller dynamicPostCaller;

    @Operation(parameters = {
            @Parameter(in= ParameterIn.HEADER,description = "认证token",name = HttpHeaders.AUTHORIZATION)
    },summary = "测试get请求", description = "测试get请求")
    @GetMapping("/testGet")
    public CommonRespDTO<Integer> testGet() throws Exception {

        try {
            CommonRespDTO commonRespDTO = dynamicGetCaller.executeHeaderParamGet(
                    CommonRespDTO.class,
                    "http://218.94.128.34:7072/iot/project/tree",
                    Map.of("Authorization", "34fb471a-bec1-4b49-8d5b-d26b3f5416af"),
                    Map.of("needBox", true));
            if (Objects.nonNull(commonRespDTO)) {
                CommonRespDTO<List<ProjectTreeRespDTO>> result = (CommonRespDTO<List<ProjectTreeRespDTO>>)commonRespDTO;
                LOGGER.info("testGet result:{}", JSON.toJSONString(result));
            }
        } catch (HttpTimeoutException httpTimeoutException) {
            LOGGER.error("testGet occur httpTimeoutException:{}", ExceptionUtils.getStackTrace(httpTimeoutException));
            throw httpTimeoutException;
        } catch (BizServiceException bizServiceException) {
            LOGGER.error("testGet occur bizServiceException:{}", ExceptionUtils.getStackTrace(bizServiceException));
            throw bizServiceException;
        } catch (Exception exception) {
            LOGGER.error("testGet occur exception:{}", ExceptionUtils.getStackTrace(exception));
            throw exception;
        }
        return CommonRespDTO.success(1);
    }

    @Operation(parameters = {
            @Parameter(in= ParameterIn.HEADER,description = "认证token",name = HttpHeaders.AUTHORIZATION)
    },summary = "测试post请求", description = "测试post请求")
    @GetMapping("/testPost")
    public CommonRespDTO<Integer> testPost() throws Exception {
        PageQueryReqBodyDTO<MonQueryReqDTO> queryReqBodyDTO = new PageQueryReqBodyDTO<>();
        PageParamReqDTO pageParamReqDTO = new PageParamReqDTO();
        pageParamReqDTO.setPageIndex(1);
        pageParamReqDTO.setPageSize(10);
        queryReqBodyDTO.setPagedParameter(pageParamReqDTO);
        MonQueryReqDTO monQueryReqDTO = new MonQueryReqDTO();
        monQueryReqDTO.setBoxNo("1848652682395889664");
        queryReqBodyDTO.setSearch(monQueryReqDTO);

        try {
            CommonRespDTO commonRespDTO = dynamicPostCaller.executeHeaderBodyPost(
                    CommonRespDTO.class,
                    "http://218.94.128.34:7072/iot/mon/page/list",
                    Map.of("Authorization", "34fb471a-bec1-4b49-8d5b-d26b3f5416af"),
                    queryReqBodyDTO);
            if (Objects.nonNull(commonRespDTO)) {
                CommonRespDTO<PageQueriedBizListResult<MonQueryRespDTO>> result = (CommonRespDTO<PageQueriedBizListResult<MonQueryRespDTO>>)commonRespDTO;
                LOGGER.info("testPost result:{}", JSON.toJSONString(result));
            }
        } catch (HttpTimeoutException httpTimeoutException) {
            LOGGER.error("testPost occur httpTimeoutException:{}", ExceptionUtils.getStackTrace(httpTimeoutException));
            throw httpTimeoutException;
        } catch (BizServiceException bizServiceException) {
            LOGGER.error("testPost occur bizServiceException:{}", ExceptionUtils.getStackTrace(bizServiceException));
            throw bizServiceException;
        } catch (Exception exception) {
            LOGGER.error("testPost occur exception:{}", ExceptionUtils.getStackTrace(exception));
            throw exception;
        }
        return CommonRespDTO.success(1);
    }
}


评论