I have the following Spring Boot controller:
#Controller
public class TestController {
#Autowired
private TestService service;
#GetMapping(path="/hello")
public ResponseEntity<String> handleGet() {
return service.getResponse();
}
#GetMapping(path="/hello/hystrix")
public Future<ResponseEntity<String>> handleGetAsync() {
return service.getResponseAsync();
}
#GetMapping(path="/hello/cf")
public Future<ResponseEntity<String>> handleGetCF() {
return service.getResponseCF();
}
}
and service:
#Service
public class TestService {
#HystrixCommand
public ResponseEntity<String> getResponse() {
ResponseEntity<String> response = ResponseEntity.status(HttpStatus.OK).body("Hello");
return response;
}
#HystrixCommand
public Future<ResponseEntity<String>> getResponseAsync() {
return new AsyncResult<ResponseEntity<String>>() {
#Override
public ResponseEntity<String> invoke() {
return getResponse();
}
};
}
public Future<ResponseEntity<String>> getResponseCF() {
return CompletableFuture.supplyAsync(() -> getResponse());
}
}
and application:
#EnableHystrix
#SpringBootApplication
#EnableAsync
public class HystrixApplication {
public static void main(String[] args) {
SpringApplication.run(HystrixApplication.class, args);
}
}
When I hit the /hello/cf endpoint, I get a response "Hello"
When I hit the /hello/hystrix endpoint, I get a 404 error.
Am I able to return an AsyncResult from a controller in this manner? If so, what am I doing wrong?
Thanks.
Your service class needs to return a CompletableFuture.
Also, unless you are using AspectJ, the circuit breaker will not work if the method with #HystrixCommand is called from within the same class.
Related
I am trying to write and test an application that used spring-cloud with azure functions following this tutorial.
https://github.com/markusgulden/aws-tutorials/tree/master/spring-cloud-function/spring-cloud-function-azure/src/main/java/de/margul/awstutorials/springcloudfunction/azure
I am tryign to write a testcase and override the bean.
Here is the application class having function and handler Bean function.
#SpringBootApplication
#ComponentScan(basePackages = { "com.package" })
public class DataFunctions extends AzureSpringBootRequestHandler<GenericMessage<Optional<String>>, Data> {
#FunctionName("addData")
public HttpResponseMessage addDataRun(
#HttpTrigger(name = "add", methods = {
HttpMethod.POST }, authLevel = AuthorizationLevel.FUNCTION) HttpRequestMessage<Optional<String>> request,
final ExecutionContext context) throws JsonParseException, JsonMappingException, IOException {
context.getLogger().info("Java HTTP trigger processed a POST request.");
try {
handleRequest(new GenericMessage<Optional<String>>(request.getBody()), context);
} catch (ServiceException ex) {
ErrorMessage em = new ErrorMessage();
return request.createResponseBuilder(handleException(ex, em)).body(em).build();
}
return request.createResponseBuilder(HttpStatus.CREATED).build();
}
#Autowired
MyService mService;
#Bean
public Consumer<GenericMessage<Optional<String>>> addData() {
ObjectMapper mapper = new ObjectMapper();
return req -> {
SomeModel fp = null;
try {
fp = mapper.readValue(req.getPayload().get(), SomeModel.class);
} catch (Exception e) {
throw new ServiceException(e);
}
mService.addData(fp);
};
}
}
I want to test by overriding the above bean.
Cosmosdb spring configuration
#Configuration
#EnableDocumentDbRepositories
public class CosmosDBConfig extends AbstractDocumentDbConfiguration {
#Value("${cosmosdb.collection.endpoint}")
private String uri;
#Value("${cosmosdb.collection.key}")
private String key;
#Value("${cosmosdb.collection.dbname}")
private String dbName;
#Value("${cosmosdb.connect.directly}")
private Boolean connectDirectly;
#Override
public DocumentDBConfig getConfig() {
ConnectionPolicy cp = ConnectionPolicy.GetDefault();
if (connectDirectly) {
cp.setConnectionMode(ConnectionMode.DirectHttps);
} else {
cp.setConnectionMode(ConnectionMode.Gateway);
}
return DocumentDBConfig.builder(uri, key, dbName).connectionPolicy(cp).build();
}
}
Here is the configuration
#TestConfiguration
#PropertySource(value = "classpath:application.properties", encoding = "UTF-8")
#Profile("test")
#Import({DataFunctions.class})
public class TestConfig {
#Bean(name="addData")
#Primary
public Consumer<GenericMessage<Optional<String>>> addData() {
return req -> {
System.out.println("data mock");
};
}
#Bean
#Primary
public DocumentDBConfig getConfig() {
return Mockito.mock(DocumentDBConfig.class);
}
}
Finally the test class
#RunWith(SpringRunner.class)
//#SpringBootTest //Enabling this gives initialization error.
#ActiveProfiles("test")
public class TempTest {
#InjectMocks
DataFunctions func;
#Mock
MyService mService;
#Before
public void setup() {
MockitoAnnotations.initMocks(this);
}
private Optional<String> createRequestString(final String res) throws IOException {
InputStream iStream = TempTest.class.getResourceAsStream(res);
String charset="UTF-8";
try (BufferedReader br = new BufferedReader(new InputStreamReader(iStream, charset))) {
return Optional.of(br.lines().collect(Collectors.joining(System.lineSeparator())));
}
}
#Test
public void testHttpPostTriggerJava() throws Exception {
#SuppressWarnings("unchecked")
final HttpRequestMessage<Optional<String>> req = mock(HttpRequestMessage.class);
final Optional<String> queryBody = createRequestString("/test-data.json");
doNothing().when(mService).addData(Mockito.any(SomeModel.class));
doReturn(queryBody).when(req).getBody();
doAnswer(new Answer<HttpResponseMessage.Builder>() {
#Override
public HttpResponseMessage.Builder answer(InvocationOnMock invocation) {
HttpStatus status = (HttpStatus) invocation.getArguments()[0];
return new HttpResponseMessageMock.HttpResponseMessageBuilderMock().status(status);
}
}).when(req).createResponseBuilder(any(HttpStatus.class));
final ExecutionContext context = mock(ExecutionContext.class);
doReturn(Logger.getGlobal()).when(context).getLogger();
doReturn("addData").when(context).getFunctionName();
// Invoke
final HttpResponseMessage ret = func.addDataRun(req, context);
// Verify
assertEquals(ret.getStatus(), HttpStatus.CREATED);
}
}
For this case instead of test configuration addData the actual bean is called from DataFunctions class. Also the database connection is also created when it should use the mocked bean from my test configuration. Can somebody please point out what is wrong in my test configuration?
I was able to resolve the first part of cosmos db config loading by marking it with
#Configuration
#EnableDocumentDbRepositories
#Profile("!test")
public class CosmosDBConfig extends AbstractDocumentDbConfiguration {
...
}
Also had to mark the repository bean as optional in the service.
public class MyService {
#Autowired(required = false)
private MyRepository myRepo;
}
Didn't use any spring boot configuration other than this.
#ActiveProfiles("test")
public class FunctionTest {
...
}
For the second part of providing mock version of Mock handlers, I simply made the test config file as spring application as below.
#SpringBootApplication
#ComponentScan(basePackages = { "com.boeing.da.helix.utm.traffic" })
#Profile("test")
public class TestConfiguration {
public static void main(final String[] args) {
SpringApplication.run(TestConfiguration.class, args);
}
#Bean(name="addData")
#Primary
public Consumer<GenericMessage<Optional<String>>> addData() {
return req -> {
System.out.println("data mock");
};
}
}
and made use of this constructor from azure functions library in spring cloud in my constructor
public class AppFunctions
extends AzureSpringBootRequestHandler<GenericMessage<Optional<String>>, List<Data>> {
public AppFunctions(Class<?> configurationClass) {
super(configurationClass);
}
}
public AzureSpringBootRequestHandler(Class<?> configurationClass) {
super(configurationClass);
}
Hope it helps someone.
Considering the following code:
#RestController
#RequestMapping("/timeout")
public class TestController {
#Autowired
private TestService service;
#GetMapping("/max10secs")
public String max10secs() {
//In some cases it can take more than 10 seconds
return service.call();
}
}
#Service
public class TestService {
public String call() {
//some business logic here
return response;
}
}
What I want to accomplish is that if the method call from the TestService takes more than 10 seconds I want to cancel it and generate a response with a HttpStatus.REQUEST_TIMEOUT code.
What I managed to do, but I don't know if there are any conceptual or practical flaws is what it follows...
First, the configuration of spring-async
#Configuration
#EnableAsync
public class AsyncConfig implements AsyncConfigurer {
#Bean(name = "threadPoolTaskExecutor")
public Executor threadPoolTaskExecutor() {
ThreadPoolTaskExecutor pool = new ThreadPoolTaskExecutor();
pool.setCorePoolSize(10);
pool.setMaxPoolSize(10);
pool.setWaitForTasksToCompleteOnShutdown(true);
return pool;
}
#Override
public Executor getAsyncExecutor() {
return new SimpleAsyncTaskExecutor();
}
}
And next, the Controller and Service modifications:
#RestController
#RequestMapping("/timeout")
public class TestController {
#Autowired
private TestService service;
#GetMapping("/max10secs")
public String max10secs() throws InterruptedException, ExecutionException {
Future<String> futureResponse = service.call();
try {
//gives 10 seconds to finish the methods execution
return futureResponse.get(10, TimeUnit.SECONDS);
} catch (TimeoutException te) {
//in case it takes longer we cancel the request and check if the method is not done
if (futureResponse.cancel(true) || !futureResponse.isDone())
throw new TestTimeoutException();
else {
return futureResponse.get();
}
}
}
}
#Service
public class TestService {
#Async("threadPoolTaskExecutor")
public Future<String> call() {
try{
//some business logic here
return new AsyncResult<>(response);
} catch (Exception e) {
//some cancel/rollback logic when the request is cancelled
return null;
}
}
}
And finally generate the TestTimeoutException:
#ResponseStatus(value = HttpStatus.REQUEST_TIMEOUT, reason = "too much time")
public class TestTimeoutException extends RuntimeException{ }
There is another solution via DeferredResult.
TestController.java
#RestController
#RequestMapping("/timeout")
public class TestController
{
#Autowired
private TestService service;
#GetMapping("/max10secs")
public DeferredResult<String> max10secs()
{
//In some cases it can take more than 10 seconds
return service.call();
}
}
TestService.java
#Service
public class TestService
{
public DeferredResult<String> call()
{
DeferredResult<String> result = new DeferredResult(10000L);
//some business logic here
result.onTimeout(()->{
// do whatever you want there
});
result.setResult("test");
return result;
}
}
This way, controller will return actual result only when you call result.setResult("test");.
As you can see, in case of timeout (value for timeout is defined in constructor of DeferredResult object in milliseconds) there will be a callback executed where you can throw any exception, or return another object(HttpStatus.REQUEST_TIMEOUT in your case).
You can read about the DeferredResult in Spring here.
I have a spring application and when i trigger the lambda function from API Gateway , "Endpoint request timed out" error is coming.The code is given below.
public class LambdaHandler implements RequestHandler<AwsProxyRequest, AwsProxyResponse> {
private static final Logger LOG = LoggerFactory.getLogger(LambdaHandler.class);
private SpringLambdaContainerHandler<AwsProxyRequest, AwsProxyResponse> handler;
#Override
public AwsProxyResponse handleRequest(AwsProxyRequest awsProxyRequest, Context context) {
try {
handler = SpringLambdaContainerHandler.getAwsProxyHandler(MvcConfig.class);
} catch (ContainerInitializationException e) {
LOG.warn("Unable to create handler", e);
return null;
}
return handler.proxy(awsProxyRequest, context);
}
}
The MvcConfig class is :-
#SpringBootApplication
#EnableWebMvc
#Configuration
#ComponentScan(basePackages = {"com.joeyvmason.serverless.spring"})
public class MvcConfig {
}
and HelloController class is :-
#RestController
#RequestMapping("/hello")
public class HelloController {
#RequestMapping(method = RequestMethod.GET)
public String hello(#PathVariable String id) {
return "Hello World!";
}
}
Thanks in advance for the help
As I know, feign include ribbon's function, and I prove it in my code.
When I use feign, the default rule is Round Robin Rule.
But how can I change the rule in my feign client code, is ribbon the only way?
Here is my code below, so please help.
ConsumerApplication.java
#SpringBootApplication
#EnableDiscoveryClient
#EnableFeignClients
#EnableCircuitBreaker
public class ConsumerApplication {
public static void main(String[] args) {
SpringApplication.run(ConsumerApplication.class, args);
}
}
UserFeignClient .java
#FeignClient(name = "cloud-provider", fallback = UserFeignClient.HystrixClientFallback.class)
public interface UserFeignClient {
#RequestMapping("/{id}")
BaseResponse findByIdFeign(#RequestParam("id") Long id);
#RequestMapping("/add")
BaseResponse addUserFeign(UserVo userVo);
#Component
class HystrixClientFallback implements UserFeignClient {
private static final Logger LOGGER = LoggerFactory.getLogger(HystrixClientFallback.class);
#Override
public BaseResponse findByIdFeign(#RequestParam("id") Long id) {
BaseResponse response = new BaseResponse();
response.setMessage("disable");
return response;
}
#Override
public BaseResponse addUserFeign(UserVo userVo) {
BaseResponse response = new BaseResponse();
response.setMessage("disable");
return response;
}
}
}
FeignController.java
#RestController
public class FeignController {
#Autowired
private UserFeignClient userFeignClient;
#GetMapping("feign/{id}")
public BaseResponse<Date> findByIdFeign(#PathVariable Long id) {
BaseResponse response = this.userFeignClient.findByIdFeign(id);
return response;
}
#GetMapping("feign/user/add")
public BaseResponse<Date> addUser() {
UserVo userVo = new UserVo();
userVo.setAge(19);
userVo.setId(12345L);
userVo.setUsername("nick name");
BaseResponse response = this.userFeignClient.addUserFeign(userVo);
return response;
}
}
From the documentation:
#RibbonClient(name = "cloud-provider", configuration = CloudProviderConfiguration.class)
public class ConsumerApplication {
/* ... */
}
class CloudProviderConfiguration {
#Bean
public IRule ribbonRule(IClientConfig config) {
return new RandomRule();
}
}
I'm trying to pass a protobuf parameter to a REST endpoint but I get
org.springframework.web.client.HttpServerErrorException: 500 null
each time I try. What I have now is something like this:
#RestController
public class TestTaskEndpoint {
#PostMapping(value = "/testTask", consumes = "application/x-protobuf", produces = "application/x-protobuf")
TestTaskComplete processTestTask(TestTask testTask) {
// TestTask is a generated protobuf class
return generateResult(testTask);
}
}
#Configuration
public class AppConfiguration {
#Bean
ProtobufHttpMessageConverter protobufHttpMessageConverter() {
return new ProtobufHttpMessageConverter();
}
}
#SpringBootApplication
public class JavaConnectorApplication {
public static void main(String[] args) {
SpringApplication.run(JavaConnectorApplication.class, args);
}
}
and my test looks like this:
#RunWith(SpringJUnit4ClassRunner.class)
#SpringBootTest
#WebAppConfiguration
public class JavaConnectorApplicationTest {
#Configuration
public static class RestClientConfiguration {
#Bean
RestTemplate restTemplate(ProtobufHttpMessageConverter hmc) {
return new RestTemplate(Arrays.asList(hmc));
}
#Bean
ProtobufHttpMessageConverter protobufHttpMessageConverter() {
return new ProtobufHttpMessageConverter();
}
}
#Autowired
private RestTemplate restTemplate;
private int port = 8081;
#Test
public void contextLoaded() {
TestTask testTask = generateTestTask();
final String url = "http://127.0.0.1:" + port + "/testTask/";
ResponseEntity<TestTaskComplete> customer = restTemplate.postForEntity(url, testTask, TestTaskComplete.class);
// ...
}
}
I'm sure that it is something with the parameters because if I create a variant which does not take a protobuf parameter but returns one it just works fine. I tried debugging the controller code but the execution does not reach the method so the problem is probably somewhere else. How do I correctly parametrize this REST method?
This is my first stack overflow answer but I was a lot to frustred from searching for working examples with protobuf over http and spring.
the answer https://stackoverflow.com/a/44592469/15705964 from Jorge is nearly correct.
Like the comments mention: "This won't work in itself. You need to add a converter somewhere at least."
Do it like this:
#Configuration
public class WebConfig implements WebMvcConfigurer {
#Autowired
ProtobufHttpMessageConverter protobufHttpMessageConverter;
#Override
public void extendMessageConverters(List<HttpMessageConverter<?>> converters) {
converters.add(protobufHttpMessageConverter);
}
}
The ProtobufHttpMessageConverter will do his job automatically and add the object to your controller methode
#RestController
public class ProtobufController {
#PostMapping(consumes = "application/x-protobuf", produces = "application/x-protobuf")
public ResponseEntity<TestMessage.Response> handlePost(#RequestBody TestMessage.Request protobuf) {
TestMessage.Response response = TestMessage.Response.newBuilder().setQuery("This is a protobuf server Response")
.build();
return ResponseEntity.ok(response);
}
Working example with send and reseive with rest take a look: https://github.com/Chriz42/spring-boot_protobuf_example
Here it's the complete answer
#SpringBootApplication
public class JavaConnectorApplication {
public static void main(String[] args) {
SpringApplication.run(JavaConnectorApplication.class, args);
}
}
Then you need to provide the right configuration.
#Configuration
public class AppConfiguration {
//You need to add in this list all the messageConverters you will use
#Bean
RestTemplate restTemplate(ProtobufHttpMessageConverter hmc) {
return new RestTemplate(Arrays.asList(hmc,smc));
}
#Bean
ProtobufHttpMessageConverter protobufHttpMessageConverter() {
return new ProtobufHttpMessageConverter();
}
}
And finally your RestController.
#RestController
public class TestTaskEndpoint {
#PostMapping(value = "/testTask")
TestTaskComplete processTestTask(#RequestBody TestTask testTask) {
// TestTask is a generated protobuf class
return generateResult(testTask);
}
}
The #RequestBody annotation: The body of the request is passed through an HttpMessageConverter (That you already defined) to resolve the method argument depending on the content type of the request
And your test class:
#RunWith(SpringRunner.class)
#SpringBootTest
#WebAppConfiguration
public class JavaConnectorApplicationTest {
#Autowired
private RestTemplate restTemplate;
private int port = 8081;
#Test
public void contextLoaded() {
TestTask testTask = generateTestTask();
final String url = "http://127.0.0.1:" + port + "/testTask/";
ResponseEntity<TestTaskComplete> customer = restTemplate.postForEntity(url, testTask, TestTaskComplete.class);
// Assert.assertEquals("dummyData", customer.getBody().getDummyData());
}
}