基础知识
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
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
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
包含了如下标准:
- Query string 由一组键值对(
feild-value
)组成; - 每组键值对的
feild
和value
用=
分割; - 每组数据用
&
分割;
如果Content-Type为application/x-www-form-urlencoded ,且是 POST 请求,则会将数据放置在消息体(body)中,通过Header
中的Content-Type
来指定 MIME 类型。
如:
URI & URL & URN
URI 属于父类,而 URL 属于 子类。也就是说,URL是URI众多类型的中的一种。
URI = URL + URN
接下来,我们再来分别讨论这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.
在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);
}
}