Spring Boot REST API not reachable in GCP - spring

I am deploying the Spring Boot Rest API in Google Cloud Platform. The Rest API is running fine in my local while running the application as Spring boot application. But the same REST API URL is not recognized while deploying in GCP.
Project Structure:
app.yaml:
runtime: java
env: flex
runtime_config:
  jdk: openjdk8
handlers:
- url: /.*
script: this field is required, but ignored
GreetingController.java
package com.designdreamers.rest.controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import com.designdreamers.rest.vo.Greeting;
#RestController
public class GreetingController {
private int idValue;
#RequestMapping(value="/greet")
public Greeting greeting(#RequestParam(value="content", defaultValue = "Hello World!!")String content){
Greeting greeting=new Greeting();
greeting.setId(idValue++);
greeting.setContent(content);
return greeting;
}
}
Application.java
package com.designdreamers.rest.main;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.ComponentScan;
#SpringBootApplication
#ComponentScan(basePackages = "com.designdreamers.rest.controller")
public class Application {
public static void main(String args[]){
SpringApplication.run(Application.class,args);
}
}
Below is the REST API response that I get on running the application as Spring boot application in local.
URL :http://localhost:8080/greet
{"id":1,"content":"Hello World!!"}
I deployed the same application in GCP and on hitting the REST API URI, below is the error that we get.
URI: http://helloworld-001.appspot.com/greet
Error: Not Found
The requested URL /greet was not found on this server.
A helping hand on identifying the issue would be very helpful.

Related

How to deploy BPMN or DMN to Camunda Process Engine in Spring Boot + REST?

Assuming the Spring Boot app is connected to a database which has all the Camunda related tables created like dbo.ACT_HI_PROCINST, dbo.ACT_HI_TASKINST, dbo.ACT_RU_TASK, dbo.ACT_RU_EXECUTION, etc. There is a UI screen (Angular/React frontend) which accepts BPMN & DMN files to be deployed in Camunda database (could be any Relational Database). This (.bpmn/.dmn) should get deployed as the latest version of their file name.
Camunda Version:
7.13.0
User can select a file by browsing the file system from UI screen (assuming UI file browsing and selecting a valid .bpmn or .dmn is done). User has to send this selected file as part of request in REST call to Spring Boot app, and deploy the .bpmn/.dmn file in database.
A .bpmn or .dmn could be deployed in many ways:
via Camunda Modeler
via Spring Boot App startup auto-deploy from /src/main/resources/bpmn/* by using #EnableProcessApplication annotation along with #SpringBootApplication
via Manual deployment by sending .bpmn/.dmn file from frontend UI via REST call to Spring Boot controller and using Camunda's RepositoryService.createDeployment()
application.yaml:
Provide Camunda DB datasource configurations like url, username and password along with Camunda admin user credential like -
camunda.bpm.admin-user:
id: admin
password: admin
camunda:
bpm:
history-level: audit
job-execution:
core-pool-size: 3
max-pool-size: 10
jpa:
enabled: true
handle-transaction: true
server:
port: 8091
spring:
application:
name: camunda-bpm-workflow-app
datasource:
url: jdbc:sqlserver://ABC250.abc.xyz:10010;databaseName=DevCamunda
username: DevUser
password: DevPass
driver-class-name: com.microsoft.sqlserver.jdbc.SQLServerDriver
hikari:
maximum-pool-size: 200
jpa:
hibernate:
ddl-auto: create
show-sql: false
properties:
hibernate:
format_sql: true
dialect: org.hibernate.dialect.SQLServerDialect
servlet:
multipart:
max-file-size: 10MB
max-request-size: 10MB
pom.xml:
camunda-bpm-spring-boot-starter
camunda-bpm-spring-boot-starter-webapp
camunda-bpm-spring-boot-starter-rest
Auto-deployment: #EnableProcessApplication: Spring Boot App startup auto-deploy from /src/main/resources/bpmn/*
Turn your Spring Boot app into a Camunda process application using #EnableProcessApplication annotation without extending any super-classes.
In the Spring Boot app, this annotation helps to link a resource to the process engine.
Disables the SpringProcessEngineConfiguration auto-deploy feature
Enables auto-deployment from /src/main/resources/META-INF/processes.xml
processes.xml - is an indicator for resource scanning. This could be left empty.
We can have .bpmn and .dmn files inside bmpn folder, example - /src/main/resources/bpmn/xyz.bmpn or /src/main/resources/bpmn/abc.dmn
On startup of Spring Boot app, all .bpmn and .dmn files from location /src/main/resources/bpmn/* will be auto-deployed if any changes were done in them
Manual deployment: By sending .bpmn/.dmn file from fronend UI via REST call to Spring Boot controller and using Camunda's RepositoryService.createDeployment(). After deployment is successful, new deployment ID can be found in database using SQL:
SELECT
ID_,
NAME_,
DEPLOY_TIME_,
SOURCE_,
TENANT_ID_
FROM
dbo.ACT_RE_DEPLOYMENT
ORDER BY
DEPLOY_TIME_ DESC
Main Class:
import org.camunda.bpm.spring.boot.starter.annotation.EnableProcessApplication;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
#SpringBootApplication
#EnableProcessApplication
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class);
}
}
REST Controller endpoint:
A Camunda BPMN or DMN file to be deployed in database will be consumed by the rest endpoint as MediaType.MULTIPART_FORM_DATA_VALUE.
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile;
import com.abc.workflow.dto.CamundaDeployResponse;
import com.abc.workflow.service.CamundaDeployService;
import lombok.extern.slf4j.Slf4j;
#RestController("/camunda")
public class CamundaDeployController {
#Autowired
private CamundaDeployService camundaDeployService;
#PostMapping(value = "/deploy", consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
public ResponseEntity<CamundaDeployResponse> deploy(#RequestParam("file") MultipartFile file) {
return ResponseEntity.ok(camundaDeployService.deploy(file));
}
}
Service Interface:
import org.springframework.web.multipart.MultipartFile;
import com.abc.workflow.dto.CamundaDeployResponse;
public interface CamundaDeployService {
CamundaDeployResponse deploy(MultipartFile file);
}
Service Implementation Class:
RepositoryService - Service providing access to the repository of process definitions and deployments.
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import org.camunda.bpm.engine.RepositoryService;
import org.camunda.bpm.engine.repository.DeploymentWithDefinitions;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.web.multipart.MultipartFile;
import com.abc.workflow.dto.CamundaDeployRequestDto;
import com.abc.workflow.dto.CamundaDeployResponse;
import com.abc.workflow.service.CamundaDeployService;
import lombok.extern.slf4j.Slf4j;
#Slf4j
#Service
public class CamundaDeployServiceImpl implements CamundaDeployService {
#Autowired
private RepositoryService repositoryService;
#Override
public CamundaDeployResponse deploy(MultipartFile file) {
CamundaDeployResponse camundaDeployResponse = new CamundaDeployResponse();
String orgFileName = file.getOriginalFilename();
log.info("Camunda file to be deployed [{}]", orgFileName);
if(orgFileName.endsWith(".bpmn") || orgFileName.endsWith(".dmn")) {
try {
log.info("Camunda Deployment START : [{}]", orgFileName);
DeploymentWithDefinitions d = null;
d = repositoryService.createDeployment().addInputStream(orgFileName, file.getInputStream()).name(orgFileName).deployWithResult();
camundaDeployResponse.setSuccessMessage("Camunda Deployment SUCCESS ["+orgFileName+"] : Deployment ID ["+d.getId()+"]");
log.info("Camunda Deployment SUCCESS [{}] : Deployment ID [{}]", orgFileName, d.getId());
}
catch (IOException e) {
camundaDeployResponse.setErrorMessage("Camunda Deployment FAILED : "+e.getMessage());
log.error("Camunda Deployment FAILED [{}]: {}", orgFileName, e.getMessage());
e.printStackTrace();
}
}
else {
camundaDeployResponse.setErrorMessage("Not a valid Camunda file (BPMN/DMN)");
log.error("Not a valid Camunda file (BPMN/DMN)");
}
return camundaDeployResponse;
}
}

Spring Cloud APIGW and Open API Specification Integration issues

I am looking to develop a Spring Cloud APIGW and Open API Specification Integration example. I am following: https://piotrminkowski.com/2018/04/26/quick-guide-to-microservices-with-spring-boot-2-0-eureka-and-spring-cloud/, but use all latest dependency of Spring Boot which is 2.7.0
http://localhost:8011/swagger-ui.html is not working
gateway.properties
server.port=8011
spring.application.name=api-gateway
eureka.client.serviceUrl.defaultZone=http://localhost:8761/eureka
spring.config.import=configserver:http://localhost:8012/
spring.cloud.gateway.discovery.locator.enabled=true
spring.cloud.gateway.discovery.locator.lower-case-service-id=true
# Actuator
# https://docs.spring.io/spring-boot/docs/current/reference/html/actuator.html#actuator.enabling
management.endpoint.gateway.enabled=true
management.endpoints.web.exposure.include=beans, health, metrics, mappings, env, info,configprops, caches, gateway
# All Microservices Routes
spring.cloud.gateway.routes[0].id=organization-service
spring.cloud.gateway.routes[0].uri=lb://organization-service
spring.cloud.gateway.routes[0].predicates[0]=Path=/organization/**
spring.cloud.gateway.routes[0].predicates[1]=Method=GET,POST,PUT,DELETE
spring.cloud.gateway.routes[1].id=department-service
spring.cloud.gateway.routes[1].uri=lb://department-service
spring.cloud.gateway.routes[1].predicates[0]=Path=/department/**
spring.cloud.gateway.routes[1].predicates[1]=Method=GET,POST,PUT,DELETE
spring.cloud.gateway.routes[2].id=employee-service
spring.cloud.gateway.routes[2].uri=lb://employee-service
spring.cloud.gateway.routes[2].predicates[0]=Path=/employee/**
spring.cloud.gateway.routes[2].predicates[1]=Method=GET,POST,PUT,DELETE
spring.cloud.gateway.routes[3].id=openapi
spring.cloud.gateway.routes[3].uri=http://localhost:${server.port}
spring.cloud.gateway.routes[3].predicates[0]=Path=/v3/api-docs/**
spring.cloud.gateway.routes[3].predicates[1]=Method=GET,POST,PUT,DELETE
spring.cloud.gateway.routes[4].id=openapi2
spring.cloud.gateway.routes[4].uri=http://localhost:${server.port}
spring.cloud.gateway.routes[4].predicates[0]=Path=/webjars/**
spring.cloud.gateway.routes[4].predicates[1]=Method=GET,POST,PUT,DELETE
# Open API Specification
springdoc.swagger-ui.urls[0].name=employee
springdoc.swagger-ui.urls[0].url=/v3/api-docs/employee
springdoc.swagger-ui.urls[1].name=department
springdoc.swagger-ui.urls[1].url=/v3/api-docs/department
springdoc.swagger-ui.urls[2].name=organization
springdoc.swagger-ui.urls[2].url=/v3/api-docs/organization
server.max-http-header-size=2000000
MainApp.java
package com.example.demo;
import java.util.ArrayList;
import java.util.List;
import org.springdoc.core.GroupedOpenApi;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.gateway.route.RouteDefinition;
import org.springframework.cloud.gateway.route.RouteDefinitionLocator;
import org.springframework.cloud.netflix.eureka.EnableEurekaClient;
import org.springframework.context.annotation.Bean;
#EnableEurekaClient
#SpringBootApplication
public class GatewayServiceApplication {
public static void main(String[] args) {
SpringApplication.run(GatewayServiceApplication.class, args);
}
#Autowired
RouteDefinitionLocator locator;
#Bean
public List<GroupedOpenApi> apis() {
List<GroupedOpenApi> groups = new ArrayList<>();
List<RouteDefinition> definitions = locator.getRouteDefinitions().collectList().block();
assert definitions != null;
definitions.stream().filter(routeDefinition -> routeDefinition.getId().matches(".*-service")).forEach(routeDefinition -> {
String name = routeDefinition.getId().replaceAll("-service", "");
groups.add(GroupedOpenApi.builder().pathsToMatch("/" + name + "/**").group(name).build());
});
return groups;
}
}
Note: I am showing for 1 microservice, I've done almost same for others
application.properties
server.port=${PORT:0}
spring.application.name=employee-service
spring.config.import=configserver:http://localhost:8012/
eureka.client.fetch-registry=true
eureka.client.register-with-eureka=true
springdoc.packages-to-scan=com.example.demo
and Added below to entry service main app.
MainApp.java
#OpenAPIDefinition(info = #Info(title = "Employee API", version = "1.0", description = "Documentation Employee API v1.0"))
#EnableEurekaClient
#SpringBootApplication
public class EmployeeServiceApplication {
public static void main(String[] args) {
SpringApplication.run(EmployeeServiceApplication.class, args);
}
}

WebClient is incorrectly trying to start a web server

I am trying to migrate my REST client application to use WebClient instead of RestTemplate.
But, when I run my client code, it is apparently trying to start a web server and connect to port 8080, which fails because Tomcat is already running on the port.
I don't want to start a web server. I just want to connect to an external web server and pull back a response.
Here is the error I get:
***************************
APPLICATION FAILED TO START
***************************
Description:
Web server failed to start. Port 8080 was already in use.
Action:
Identify and stop the process that's listening on port 8080 or configure this application to listen on another port.
Here is my test code:
package test.rest.webClient;
import java.util.Map;
import org.apache.hc.core5.net.URIBuilder;
import org.slf4j.LoggerFactory;
import org.springframework.boot.CommandLineRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.core.ParameterizedTypeReference;
import org.springframework.http.MediaType;
import org.springframework.web.reactive.function.client.WebClient;
import org.springframework.web.reactive.function.client.WebClient.RequestHeadersUriSpec;
import org.springframework.web.reactive.function.client.WebClient.ResponseSpec;
import reactor.core.publisher.Mono;
import test.Path;
#SpringBootApplication
public class WebClientTest implements CommandLineRunner {
#Override
public void run(String... args)
throws Exception {
URIBuilder builder = new URIBuilder();
builder.setScheme("https");
builder.setHost("marketing.propfinancing.com");
builder.setPath("/caddata/TXCollin/getByIdAndYear");
builder.addParameter("id", "37");
builder.addParameter("year", "2022");
WebClient client = WebClient.create();
RequestHeadersUriSpec<?> uriSpec = client.get();
uriSpec.uri(builder.build());
uriSpec.header(Path.getApplicationProperties().getProperty("caddata.apiKey.header.name"),
Path.getApplicationProperties().getProperty("caddata.apiKey"));
uriSpec.accept(MediaType.APPLICATION_JSON);
ResponseSpec responseSpec = uriSpec.retrieve();
ParameterizedTypeReference<Map<String,Object>> typeReference = new ParameterizedTypeReference<Map<String,Object>>(){};
Mono<Map<String,Object>> mono = responseSpec.bodyToMono(typeReference);
Map<String,Object> response = mono.block();
for( Object key : response.keySet() ) {
Object value = response.get(key);
LoggerFactory.getLogger(getClass()).warn(key+":"+value);
}
}
public static void main(String[] args)
throws Exception {
SpringApplication.run(WebClientTest.class, args);
}
}
Any ideas?
Per default spring boot starts up either as a web application or a reactive application depending on what libraries you have on your classpath.
But you can also tell the framwork to not start up the webserver by explicitly setting the WebApplicationType to None
here is an example:
new SpringApplicationBuilder(MainApplication.class)
.web(WebApplicationType.NONE)
.run(args);
or you can set it in the application properties:
spring.main.web-application-type=none
You can read more about it here:
17.1.5. Create a Non-web Application
Spring Boot no web server

Spring Boot Actuator to show service start datetime

I am using spring-boot-starter-actuator in my project wanted to show service starts date time along with other information on /info end point.
Please guide how to achieve this.
Thanks in advance
/startup - is the Actuator Endpoint to see the startup information.
Sample URL syntax:
http://<HOST>:<port>/actuator/startup
For more info, visit here
--- Edit---
/startup endpoint does not get exposed by default, Hence need to enable explicitly by below property in application.properties :
management.endpoints.web.exposure.include=startup
BufferingApplicationStartup class is in-memory buffered implementation for capturing startup steps. Hence in the main class below changes are required:
import java.util.TimeZone;
import javax.annotation.PostConstruct;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.context.metrics.buffering.BufferingApplicationStartup;
#SpringBootApplication
public class ActuatorEndpointApplication {
public static void main(String[] args) {
// SpringApplication.run(ActuatorEndpointApplication.class, args);
SpringApplication app = new SpringApplication(ActuatorEndpointApplication.class);
app.setApplicationStartup(new BufferingApplicationStartup(2048));
app.run(args);
}
}
Now test the url:
http://<HOST>:<port>/actuator/startup in Chrome/Postman.
For more info, refer here

Hystrix Dashboard Issue in Spring Boot

I am new to Hystrix Dashboard. I have written sample application with Hystrix.
I want to see the Hystrix chart (command metric stream). But I am getting the below error:
Circuit: Unable to connect to Command Metric Stream
Thread Pools: Loading...
I am using STS with Maven.
Below is the code used:
Simple server microservice application (Spring boot web running in port 8085)
package hello;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.bind.annotation.RequestMapping;
#RestController
#SpringBootApplication
public class BookstoreApplication {
#RequestMapping(value = "/recommended")
public String readingList(){
return "Spring in Action (Manning), Cloud Native Java (O'Reilly), Learning Spring Boot (Packt)";
}
public static void main(String[] args) {
SpringApplication.run(BookstoreApplication.class, args);
}
}
Simple client microservice application (Spring boot web running in port 8095) I have included the dependency of Hystrix and Hystrix Dashboard along with Web, so all the Hystrix dependencies are in classpath
package hello;
import com.netflix.hystrix.contrib.javanica.annotation.HystrixCommand;
import org.springframework.stereotype.Service;
import org.springframework.web.client.RestTemplate;
import java.net.URI;
#Service
public class BookService {
private final RestTemplate restTemplate;
public BookService(RestTemplate rest) {
this.restTemplate = rest;
}
#HystrixCommand(fallbackMethod = "reliable")
public String readingList() {
URI uri = URI.create("http://localhost:8090/recommended");
return this.restTemplate.getForObject(uri, String.class);
}
public String reliable() {
return "Cloud Native Java (O'Reilly)";
}
}
package hello;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.web.client.RestTemplateBuilder;
import org.springframework.context.annotation.Bean;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.client.circuitbreaker.EnableCircuitBreaker;
import org.springframework.web.client.RestTemplate;
#EnableHystrixDashboard
#EnableHystrix
#EnableCircuitBreaker
#RestController
#SpringBootApplication
public class ReadingApplication {
#Autowired
private BookService bookService;
#Bean
public RestTemplate rest(RestTemplateBuilder builder) {
return builder.build();
}
#RequestMapping("/to-read")
public String toRead() {
return bookService.readingList();
}
public static void main(String[] args) {
SpringApplication.run(ReadingApplication.class, args);
}
}
By running the above code, the hystrix is working fine, when the BooKStoreApplication is down, it is going to fallback method.
Both the urls are working fine.
Normal Case:
http://localhost:8085/recommended
Output: Spring in Action (Manning), Cloud Native Java (O'Reilly), Learning Spring Boot (Packt)
http://localhost:8095/to-read
Output: Spring in Action (Manning), Cloud Native Java (O'Reilly), Learning Spring Boot (Packt)
When BookStoreApplication is down (http://localhost:8085/recommended) accessing http://localhost:8095/to-read returns "Cloud Native Java (O'Reilly)" as expected.
But when I tried to invoke this url http://localhost:8095/hystrix, I am getting the Hystrix DashBoard Page and asking for the stream value.
I have tried given http://localhost:8095/ or http://localhost:8095/to-read, and clicked "Monitor Stream" and it is going to next page with error:
Circuit: Unable to connect to Command Metric Stream
Thread Pools: Loading...
I've experienced the same. The main problem was, that I didn't have the actuator dependency in my maven pom. So I could not get the hystrix stream.
Include the spring-boot-actuator.
Check if localhost:8085/health is running.
Try to enter localhost:8085/hystrix.stream to stream value in Hystrix Dashboard.
Execute the service few times -> the dashboard should show the monitored method/command.

Resources