Skip to content

Commit

Permalink
Make HTTP modules as auto-configuration
Browse files Browse the repository at this point in the history
* Fix all the Checkstyle violations and compiler warnings
  • Loading branch information
artembilan committed Jan 8, 2024
1 parent 5d4cee0 commit 090ca28
Show file tree
Hide file tree
Showing 13 changed files with 81 additions and 89 deletions.
4 changes: 2 additions & 2 deletions function/spring-http-request-function/README.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ Users have to subscribe to the returned `Flux` to receive the data.

## Beans for injection

You can import the `HttpRequestFunction` configuration in a Spring Boot application and then inject the following bean.
The `HttpRequestFunction` auto-configuration provides the following bean:

`httpRequestFunction`

Expand All @@ -25,7 +25,7 @@ For more information on the various options available, please see link:src/main/

## Examples

See this link:src/test/java/org/springframework/cloud/fn/http/request/HttpRequestFunctionApplicationTests.java[test suite] for examples of how this function is used.
See this link:src/test/java/org/springframework/cloud/fn/http/request/HttpRequestFunctionTests.java[test suite] for examples of how this function is used.

## Other usage

Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright 2018-2022 the original author or authors.
* Copyright 2018-2024 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
Expand All @@ -20,34 +20,37 @@
import java.util.Map;
import java.util.function.Function;

import org.springframework.boot.autoconfigure.AutoConfiguration;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.convert.converter.Converter;
import org.springframework.expression.Expression;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
import org.springframework.http.ResponseEntity;
import org.springframework.integration.config.IntegrationConverter;
import org.springframework.messaging.Message;
import org.springframework.util.CollectionUtils;
import org.springframework.web.reactive.function.client.WebClient;
import org.springframework.web.util.DefaultUriBuilderFactory;
import org.springframework.web.util.UriBuilderFactory;

/**
* Configuration for a {@link Function} that makes HTTP requests to a resource and for
* each request, returns a {@link ResponseEntity}.
* Auto-configuration for a {@link Function} that makes HTTP requests to a resource and
* for each request, returns a {@link ResponseEntity}.
*
* @author David Turanski
* @author Sunny Hemdev
* @author Corneil du Plessis
**/
@Configuration(proxyBeanMethods = false)
@AutoConfiguration
@EnableConfigurationProperties(HttpRequestFunctionProperties.class)
public class HttpRequestFunctionConfiguration {

@Bean
public HttpRequestFunction httpRequestFunction(WebClient.Builder webClientBuilder,
HttpRequestFunctionProperties properties) {

return new HttpRequestFunction(webClientBuilder.build(), properties);
}

Expand Down Expand Up @@ -77,37 +80,39 @@ public HttpRequestFunction(WebClient webClient, HttpRequestFunctionProperties pr
@Override
public Object apply(Message<?> message) {
return this.webClient.method(resolveHttpMethod(message))
.uri(uriBuilderFactory.uriString(resolveUrl(message)).build())
.uri(this.uriBuilderFactory.uriString(resolveUrl(message)).build())
.bodyValue(resolveBody(message))
.headers(httpHeaders -> httpHeaders.addAll(resolveHeaders(message)))
.headers((httpHeaders) -> httpHeaders.addAll(resolveHeaders(message)))
.retrieve()
.toEntity(properties.getExpectedResponseType())
.map(responseEntity -> properties.getReplyExpression().getValue(responseEntity))
.timeout(Duration.ofMillis(properties.getTimeout()))
.toEntity(this.properties.getExpectedResponseType())
.map((responseEntity) -> this.properties.getReplyExpression().getValue(responseEntity))
.timeout(Duration.ofMillis(this.properties.getTimeout()))
.block();
}

private String resolveUrl(Message<?> message) {
return properties.getUrlExpression().getValue(message, String.class);
return this.properties.getUrlExpression().getValue(message, String.class);
}

private HttpMethod resolveHttpMethod(Message<?> message) {
return properties.getHttpMethodExpression().getValue(message, HttpMethod.class);
return this.properties.getHttpMethodExpression().getValue(message, HttpMethod.class);
}

private Object resolveBody(Message<?> message) {
return properties.getBodyExpression() != null ? properties.getBodyExpression().getValue(message)
return (this.properties.getBodyExpression() != null) ? this.properties.getBodyExpression().getValue(message)
: message.getPayload();
}

private HttpHeaders resolveHeaders(Message<?> message) {
HttpHeaders headers = new HttpHeaders();
if (properties.getHeadersExpression() != null) {
Map<?, ?> headersMap = properties.getHeadersExpression().getValue(message, Map.class);
for (Map.Entry<?, ?> header : headersMap.entrySet()) {
if (header.getKey() != null && header.getValue() != null) {
headers.add(header.getKey().toString(), header.getValue().toString());
}
Expression headersExpression = this.properties.getHeadersExpression();
if (headersExpression != null) {
Map<?, ?> headersMap = headersExpression.getValue(message, Map.class);
if (!CollectionUtils.isEmpty(headersMap)) {
headersMap.entrySet()
.stream()
.filter((entry) -> entry.getKey() != null && entry.getValue() != null)
.forEach((entry) -> headers.add(entry.getKey().toString(), entry.getValue().toString()));
}
}
return headers;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright 2015-2022 the original author or authors.
* Copyright 2015-2024 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
Expand Down Expand Up @@ -60,7 +60,7 @@ public class HttpRequestFunctionProperties {
/**
* A SpEL expression to derive the request method from the incoming message.
*/
private Expression httpMethodExpression = new ValueExpression(HttpMethod.GET);
private Expression httpMethodExpression = new ValueExpression<>(HttpMethod.GET);

/**
* A SpEL expression to derive the request body from the incoming message.
Expand All @@ -74,21 +74,21 @@ public class HttpRequestFunctionProperties {

/**
* A SpEL expression used to compute the final result, applied against the whole http
* {@link org.springframework.http.ResponseEntity}.
* {@link ResponseEntity}.
*/
private Expression replyExpression = new FunctionExpression<ResponseEntity>(ResponseEntity::getBody);
private Expression replyExpression = new FunctionExpression<ResponseEntity<?>>(ResponseEntity::getBody);

@NotNull
public Expression getUrlExpression() {
return urlExpression;
return this.urlExpression;
}

public void setUrlExpression(Expression urlExpression) {
this.urlExpression = urlExpression;
}

public Expression getHttpMethodExpression() {
return httpMethodExpression;
return this.httpMethodExpression;
}

public void setHttpMethodExpression(Expression httpMethodExpression) {
Expand All @@ -97,7 +97,7 @@ public void setHttpMethodExpression(Expression httpMethodExpression) {

@NotNull
public Class<?> getExpectedResponseType() {
return expectedResponseType;
return this.expectedResponseType;
}

public void setExpectedResponseType(Class<?> expectedResponseType) {
Expand All @@ -113,15 +113,15 @@ public void setTimeout(long timeout) {
}

public Expression getBodyExpression() {
return bodyExpression;
return this.bodyExpression;
}

public void setBodyExpression(Expression bodyExpression) {
this.bodyExpression = bodyExpression;
}

public Expression getHeadersExpression() {
return headersExpression;
return this.headersExpression;
}

public void setHeadersExpression(Expression headersExpression) {
Expand All @@ -130,7 +130,7 @@ public void setHeadersExpression(Expression headersExpression) {

@NotNull
public Expression getReplyExpression() {
return replyExpression;
return this.replyExpression;
}

public void setReplyExpression(Expression replyExpression) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
/**
* The HTTP function auto-configuration.
*/
package org.springframework.cloud.fn.http.request;
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
org.springframework.cloud.fn.http.request.HttpRequestFunctionConfiguration
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright 2020-2020 the original author or authors.
* Copyright 2020-2024 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
Expand Down Expand Up @@ -40,11 +40,11 @@
import org.springframework.messaging.support.MessageBuilder;

import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.fail;
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;

public class HttpRequestFunctionTestApplicationTests {
public class HttpRequestFunctionTests {

private MockWebServer server = new MockWebServer();
private final MockWebServer server = new MockWebServer();

private ApplicationContextRunner runner;

Expand All @@ -56,21 +56,19 @@ void setup() {

@Test
void shouldReturnString() {

server.enqueue(new MockResponse().setResponseCode(HttpStatus.OK.value()).setBody("hello"));

runner.withPropertyValues("http.request.http-method-expression='POST'").run(context -> {
runner.withPropertyValues("http.request.http-method-expression='POST'").run((context) -> {
HttpRequestFunction httpRequestFunction = context.getBean(HttpRequestFunction.class);
Message<?> message = MessageBuilder.withPayload("").build();
ResponseEntity r = (ResponseEntity) httpRequestFunction.apply(message);
ResponseEntity<?> r = (ResponseEntity<?>) httpRequestFunction.apply(message);
assertThat(r.getBody()).isEqualTo("hello");
assertThat(r.getStatusCode().is2xxSuccessful()).isTrue();
});
}

@Test
void shouldPostJson() {

server.setDispatcher(new Dispatcher() {
@Override
public MockResponse dispatch(RecordedRequest recordedRequest) {
Expand All @@ -84,11 +82,11 @@ public MockResponse dispatch(RecordedRequest recordedRequest) {
runner
.withPropertyValues("http.request.http-method-expression='POST'",
"http.request.headers-expression={'Content-Type':'application/json'}")
.run(context -> {
.run((context) -> {
HttpRequestFunction httpRequestFunction = context.getBean(HttpRequestFunction.class);
String json = "{\"hello\":\"world\"}";
Message<?> message = MessageBuilder.withPayload(json).build();
ResponseEntity r = (ResponseEntity) httpRequestFunction.apply(message);
ResponseEntity<?> r = (ResponseEntity<?>) httpRequestFunction.apply(message);
assertThat(r.getBody()).isEqualTo(json);
assertThat(r.getStatusCode().is2xxSuccessful()).isTrue();
assertThat(r.getHeaders().getContentType()).isEqualTo(MediaType.APPLICATION_JSON);
Expand All @@ -113,11 +111,11 @@ public MockResponse dispatch(RecordedRequest recordedRequest) {
runner
.withPropertyValues("http.request.http-method-expression='POST'",
"http.request.expected-response-type=" + Map.class.getName())
.run(context -> {
.run((context) -> {
HttpRequestFunction httpRequestFunction = context.getBean(HttpRequestFunction.class);
Map<String, String> json = Collections.singletonMap("hello", "world");
Message<?> message = MessageBuilder.withPayload(json).build();
ResponseEntity r = (ResponseEntity) httpRequestFunction.apply(message);
ResponseEntity<?> r = (ResponseEntity<?>) httpRequestFunction.apply(message);
assertThat(r.getBody()).isEqualTo(json);
assertThat(r.getStatusCode().is2xxSuccessful()).isTrue();
assertThat(r.getHeaders().getContentType()).isEqualTo(MediaType.APPLICATION_JSON);
Expand All @@ -141,10 +139,10 @@ public MockResponse dispatch(RecordedRequest recordedRequest) {
runner
.withPropertyValues("http.request.http-method-expression='DELETE'",
"http.request.expected-response-type=" + Void.class.getName())
.run(context -> {
.run((context) -> {
HttpRequestFunction httpRequestFunction = context.getBean(HttpRequestFunction.class);
Message<?> message = MessageBuilder.withPayload("").build();
ResponseEntity r = (ResponseEntity) httpRequestFunction.apply(message);
ResponseEntity<?> r = (ResponseEntity<?>) httpRequestFunction.apply(message);
assertThat(r.getBody()).isNull();
assertThat(r.getStatusCode().is2xxSuccessful()).isTrue();
RecordedRequest request = server.takeRequest(100, TimeUnit.MILLISECONDS);
Expand All @@ -155,15 +153,11 @@ public MockResponse dispatch(RecordedRequest recordedRequest) {
@Test
void shouldThrowErrorIfCannotConnect() throws IOException {
server.shutdown();
runner.run(context -> {
runner.run((context) -> {
HttpRequestFunction httpRequestFunction = context.getBean(HttpRequestFunction.class);
try {
httpRequestFunction.apply(new GenericMessage(""));
fail("Expected exception");
}
catch (Throwable throwable) {
assertThat(throwable.getMessage()).contains("Connection refused");
}
assertThatExceptionOfType(Throwable.class)
.isThrownBy(() -> httpRequestFunction.apply(new GenericMessage<>("")))
.withMessageContaining("Connection refused");
});
}

Expand All @@ -182,15 +176,15 @@ public MockResponse dispatch(RecordedRequest recordedRequest) {
runner.withPropertyValues("http.request.url-expression=headers['url']",
"http.request.http-method-expression=headers['method']", "http.request.body-expression=headers['body']",
"http.request.headers-expression={Accept:'application/json'}")
.run(context -> {
.run((context) -> {
Message<?> message = MessageBuilder.withPayload("")
.setHeader("url", url())
.setHeader("method", "POST")
.setHeader("body", "{\"hello\":\"world\"}")
.build();

HttpRequestFunction httpRequestFunction = context.getBean(HttpRequestFunction.class);
ResponseEntity responseEntity = (ResponseEntity) httpRequestFunction.apply(message);
ResponseEntity<?> responseEntity = (ResponseEntity<?>) httpRequestFunction.apply(message);
assertThat(responseEntity.getStatusCode()).isEqualTo(HttpStatus.OK);
assertThat(responseEntity.getBody()).isEqualTo(message.getHeaders().get("body"));
assertThat(responseEntity.getHeaders().getContentType()).isEqualTo(MediaType.APPLICATION_JSON);
Expand All @@ -212,10 +206,10 @@ public MockResponse dispatch(RecordedRequest recordedRequest) {
.withPropertyValues("http.request..http-method-expression='POST'",
"http.request.headers-expression={'Content-Type':'application/octet-stream'}",
"http.request.expected-response-type=byte[]")
.run(context -> {
.run((context) -> {
Message<?> message = MessageBuilder.withPayload("hello").build();
HttpRequestFunction httpRequestFunction = context.getBean(HttpRequestFunction.class);
ResponseEntity responseEntity = (ResponseEntity) httpRequestFunction.apply(message);
ResponseEntity<?> responseEntity = (ResponseEntity<?>) httpRequestFunction.apply(message);
assertThat(responseEntity.getStatusCode()).isEqualTo(HttpStatus.OK);
assertThat(responseEntity.getBody()).isEqualTo("hello".getBytes());
assertThat(responseEntity.getHeaders().getContentType()).isEqualTo(MediaType.APPLICATION_OCTET_STREAM);
Expand All @@ -233,11 +227,11 @@ public MockResponse dispatch(RecordedRequest recordedRequest) {
}
});
runner.withPropertyValues("http.request.http-method-expression=#jsonPath(payload,'$.myMethod')")
.run(context -> {
.run((context) -> {
Message<?> message = MessageBuilder.withPayload("{\"name\":\"Fred\",\"age\":41, \"myMethod\":\"POST\"}")
.build();
HttpRequestFunction httpRequestFunction = context.getBean(HttpRequestFunction.class);
ResponseEntity responseEntity = (ResponseEntity) httpRequestFunction.apply(message);
ResponseEntity<?> responseEntity = (ResponseEntity<?>) httpRequestFunction.apply(message);
assertThat(responseEntity.getStatusCode()).isEqualTo(HttpStatus.OK);
assertThat(responseEntity.getBody()).isEqualTo(message.getPayload());
});
Expand Down
2 changes: 1 addition & 1 deletion supplier/spring-http-supplier/README.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ Users have to subscribe to this `Flux` and then receive the data.

## Beans for injection

You can import the `HttpSupplierConfiguration` in the application and then inject the following bean.
The `HttpSupplierConfiguration` auto-configuration provides the following bean:

`httpSupplier`

Expand Down
Loading

0 comments on commit 090ca28

Please sign in to comment.