Skip to content

Commit

Permalink
Spring Boot 3.2 restClient
Browse files Browse the repository at this point in the history
  • Loading branch information
qihaiyan committed Dec 3, 2023
1 parent 6aa9f6e commit b6b1f4d
Show file tree
Hide file tree
Showing 12 changed files with 284 additions and 10 deletions.
6 changes: 3 additions & 3 deletions elasticsearch-javaclient/build.gradle
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
dependencies {
implementation 'co.elastic.clients:elasticsearch-java:8.5.0'
implementation 'com.fasterxml.jackson.core:jackson-databind:2.14.0'
implementation 'co.elastic.clients:elasticsearch-java:8.9.2'
implementation 'com.fasterxml.jackson.core:jackson-databind:2.16.0'

// Needed only if you use the spring-dependency-management
// and spring-boot Gradle plugins
implementation 'jakarta.json:jakarta.json-api:2.1.1'
// implementation 'jakarta.json:jakarta.json-api:2.1.3'
}

test {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,7 @@ public void findAndDelete() throws IOException {
));
SearchResponse<DemoDomain> search = elasticsearchClient.search(searchRequest, DemoDomain.class);
if (search != null) {
search.hits().hits().forEach(record -> {
search.hits().hits().stream().filter(record -> record.source() != null).forEach(record -> {
DeleteRequest deleteRequest = DeleteRequest.of(s -> s
.index(MY_INDEX)
.id(record.source().getId()));
Expand Down
2 changes: 1 addition & 1 deletion settings.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -27,4 +27,4 @@ include 'spring-rest-template-log'
include 'json-utils'
include 'spring-data-envers-conditional'
include 'spring-data-jdbc-client'

include 'spring-rest-client'
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
import javax.script.Invocable;
import javax.script.ScriptEngine;
import javax.script.ScriptEngineManager;
import java.lang.reflect.InvocationTargetException;

@SpringBootApplication
@ImportResource({"classpath:xml-bean-config.xml", "classpath:BeanBuilder.groovy"})
Expand All @@ -28,7 +29,7 @@ public static void main(String[] args) {
}

@Override
public void run(String... args) throws ScriptException, ResourceException, IllegalAccessException, InstantiationException, javax.script.ScriptException, NoSuchMethodException {
public void run(String... args) throws ScriptException, ResourceException, IllegalAccessException, InstantiationException, javax.script.ScriptException, NoSuchMethodException, InvocationTargetException {
MyDomain myDomain = new MyDomain();
myDomain.setName("test");
System.out.println(myServiceXml.fun(myDomain));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
import org.springframework.util.ResourceUtils;

import java.io.IOException;
import java.lang.reflect.InvocationTargetException;

@Slf4j
@Component
Expand All @@ -27,9 +28,9 @@ public MyEngine() throws IOException {
}

public void runScript(int x, int y) throws IllegalAccessException,
InstantiationException, ResourceException, ScriptException {
InstantiationException, ResourceException, ScriptException, NoSuchMethodException, InvocationTargetException {
Class<GroovyObject> calcClass = engine.loadScriptByName("CalcScript.groovy");
GroovyObject calc = calcClass.newInstance();
GroovyObject calc = calcClass.getDeclaredConstructor().newInstance();

Object result = calc.invokeMethod("calcSum", new Object[]{x, y});
System.out.println("Result of CalcScript.calcSum() method is " + result);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.web.client.RestTemplateBuilder;
import org.springframework.context.annotation.Bean;
import org.springframework.util.Assert;
import org.springframework.web.client.RestTemplate;

import java.util.function.Function;
Expand All @@ -23,7 +22,6 @@ public static void main(String[] args) {

@Bean
public Function<String, String> handle() {
Assert.hasText("11");
return String::toUpperCase;
}
}
6 changes: 6 additions & 0 deletions spring-rest-client/build.gradle
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
dependencies {
implementation 'org.springframework.boot:spring-boot-starter-web'
implementation 'org.apache.httpcomponents.client5:httpclient5'
testImplementation 'org.springframework.boot:spring-boot-starter-test'
testImplementation 'org.junit.vintage:junit-vintage-engine'
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
package cn.springcamp.spring.rest.client;

import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.core.ParameterizedTypeReference;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.client.RestClient;

@Slf4j
@SpringBootApplication
@RestController
public class Application {

@Autowired
private RestClient restClient;

@GetMapping("/demo/list")
public Object requestList() {
return restClient.get()
.uri("http://someservice/list")
.retrieve()
.body(new ParameterizedTypeReference<>() {});
}

public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
package cn.springcamp.spring.rest.client;

public record MyData(String origin, String url) {
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,140 @@
package cn.springcamp.spring.rest.client;

import lombok.extern.slf4j.Slf4j;
import org.apache.hc.client5.http.config.ConnectionConfig;
import org.apache.hc.client5.http.config.RequestConfig;
import org.apache.hc.client5.http.impl.DefaultConnectionKeepAliveStrategy;
import org.apache.hc.client5.http.impl.classic.CloseableHttpClient;
import org.apache.hc.client5.http.impl.classic.HttpClients;
import org.apache.hc.client5.http.impl.io.PoolingHttpClientConnectionManager;
import org.apache.hc.client5.http.socket.ConnectionSocketFactory;
import org.apache.hc.client5.http.socket.PlainConnectionSocketFactory;
import org.apache.hc.client5.http.ssl.SSLConnectionSocketFactory;
import org.apache.hc.core5.http.config.Registry;
import org.apache.hc.core5.http.config.RegistryBuilder;
import org.apache.hc.core5.http.io.SocketConfig;
import org.apache.hc.core5.util.TimeValue;
import org.apache.hc.core5.util.Timeout;
import org.springframework.boot.web.client.RestTemplateBuilder;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
import org.springframework.http.HttpRequest;
import org.springframework.http.HttpStatusCode;
import org.springframework.http.client.ClientHttpRequestExecution;
import org.springframework.http.client.ClientHttpRequestInterceptor;
import org.springframework.http.client.ClientHttpResponse;
import org.springframework.http.client.HttpComponentsClientHttpRequestFactory;
import org.springframework.lang.NonNull;
import org.springframework.util.StreamUtils;
import org.springframework.web.client.RestClient;
import org.springframework.web.client.RestTemplate;

import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.nio.charset.StandardCharsets;

@Configuration
public class RestClientConfig {
public CloseableHttpClient httpClient() {
Registry<ConnectionSocketFactory> registry =
RegistryBuilder.<ConnectionSocketFactory>create()
.register("http", PlainConnectionSocketFactory.getSocketFactory())
.register("https", SSLConnectionSocketFactory.getSocketFactory())
.build();
PoolingHttpClientConnectionManager poolingConnectionManager = new PoolingHttpClientConnectionManager(registry);

poolingConnectionManager.setDefaultSocketConfig(SocketConfig.custom().setSoTimeout(Timeout.ofSeconds(2)).build());
poolingConnectionManager.setDefaultConnectionConfig(ConnectionConfig.custom().setConnectTimeout(Timeout.ofSeconds(2)).build());

// set total amount of connections across all HTTP routes
poolingConnectionManager.setMaxTotal(200);
// set maximum amount of connections for each http route in pool
poolingConnectionManager.setDefaultMaxPerRoute(200);

RequestConfig requestConfig = RequestConfig.custom()
.setConnectionKeepAlive(TimeValue.ofSeconds(10))
.setConnectionRequestTimeout(Timeout.ofSeconds(2))
.setResponseTimeout(Timeout.ofSeconds(2))
.build();

return HttpClients.custom()
.setDefaultRequestConfig(requestConfig)
.setConnectionManager(poolingConnectionManager)
.setKeepAliveStrategy(new DefaultConnectionKeepAliveStrategy())
.build();
}

@Slf4j
static class CustomClientHttpRequestInterceptor implements ClientHttpRequestInterceptor {
@Override
@NonNull
public ClientHttpResponse intercept(HttpRequest request, @NonNull byte[] bytes, @NonNull ClientHttpRequestExecution execution) throws IOException {
log.info("HTTP Method: {}, URI: {}, Headers: {}", request.getMethod(), request.getURI(), request.getHeaders());
request.getMethod();
if (request.getMethod().equals(HttpMethod.POST)) {
log.info("HTTP body: {}", new String(bytes, StandardCharsets.UTF_8));
}

ClientHttpResponse response = execution.execute(request, bytes);
ClientHttpResponse responseWrapper = new BufferingClientHttpResponseWrapper(response);

String body = StreamUtils.copyToString(responseWrapper.getBody(), StandardCharsets.UTF_8);
log.info("RESPONSE body: {}", body);

return responseWrapper;
}
}

static class BufferingClientHttpResponseWrapper implements ClientHttpResponse {

private final ClientHttpResponse response;
private byte[] body;

BufferingClientHttpResponseWrapper(ClientHttpResponse response) {
this.response = response;
}

@NonNull
public HttpStatusCode getStatusCode() throws IOException {
return this.response.getStatusCode();
}

@NonNull
public String getStatusText() throws IOException {
return this.response.getStatusText();
}

@NonNull
public HttpHeaders getHeaders() {
return this.response.getHeaders();
}

@NonNull
public InputStream getBody() throws IOException {
if (this.body == null) {
this.body = StreamUtils.copyToByteArray(this.response.getBody());
}
return new ByteArrayInputStream(this.body);
}

public void close() {
this.response.close();
}
}

@Bean
public RestTemplate restTemplate(RestTemplateBuilder builder) {
return builder
.requestFactory(() -> new HttpComponentsClientHttpRequestFactory(httpClient()))
.interceptors(new CustomClientHttpRequestInterceptor())
.build();
}

@Bean
public RestClient restClient(RestTemplate restTemplate) {
return RestClient.builder(restTemplate).requestFactory(new HttpComponentsClientHttpRequestFactory(httpClient())).build();
}
}
1 change: 1 addition & 0 deletions spring-rest-client/src/main/resources/application.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@

Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
package cn.springcamp.spring.rest.client;

import lombok.extern.slf4j.Slf4j;
import org.hamcrest.Matchers;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.web.client.TestRestTemplate;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.test.context.junit4.SpringRunner;
import org.springframework.test.web.client.MockRestServiceServer;
import org.springframework.test.web.client.match.MockRestRequestMatchers;
import org.springframework.web.client.RestClient;
import org.springframework.web.client.RestTemplate;

import static org.springframework.test.web.client.ExpectedCount.manyTimes;
import static org.springframework.test.web.client.response.MockRestResponseCreators.withSuccess;

@Slf4j
@RunWith(SpringRunner.class)
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
public class DemoApplicationTest {

@Autowired
private TestRestTemplate testRestTemplate;
@Autowired
private RestTemplate restTemplate;
@Autowired
private RestClient restClient;
private MockRestServiceServer mockRestServiceServer;

@Before
public void before() {
mockRestServiceServer = MockRestServiceServer.bindTo(restTemplate).ignoreExpectOrder(true).build();

this.mockRestServiceServer.expect(manyTimes(), MockRestRequestMatchers.requestTo(Matchers.startsWithIgnoringCase("http://someservice/list")))
.andRespond(withSuccess("[{\"code\": \"200\"}]", MediaType.APPLICATION_JSON));

}

@Test
public void testGet() {
String resp = restClient.get()
.uri("https://httpbin.org/get")
.retrieve()
.body(String.class);
log.info("get: {}", resp);

MyData myData = restClient.get()
.uri("https://httpbin.org/get")
.retrieve()
.body(MyData.class);
log.info("get: {}", myData);

MyData postBody = new MyData("test", "test RestClient");
ResponseEntity<String> respObj = restClient.post()
.uri("https://httpbin.org/post")
.contentType(MediaType.APPLICATION_JSON)
.body(postBody)
.retrieve()
.toEntity(String.class);
log.info("post response: {}", respObj);

// Error handling
restClient.get()
.uri("https://httpbin.org/status/404")
.retrieve()
.onStatus(status -> status.value() == 404, (request, response) -> {
log.info("status 404");
})
.toBodilessEntity();

// Exchange
restClient.get()
.uri("https://httpbin.org/get")
.accept(MediaType.APPLICATION_JSON)
.exchange((request, response) -> {
if (response.getStatusCode().is4xxClientError()) {
log.info("status 4xx");
return "";
} else {
log.info("response: {}", response);
return response;
}
});

// testRestTemplate.getForObject("/demo/list", String.class);
}
}

0 comments on commit b6b1f4d

Please sign in to comment.