Working example of Spring boot reactive and EventSource - mongodb

I'm trying to have a working spring boot with reactive mongodb and EventSource.
However, I'm facing issues with the repetitive reopening of the connection because it's closed by the server. I even have some doubt if this could really work since I didn't find any working example with a reactive db and Event source...
Could you please point me to a working example or tell me what's wrong with my code?
Here the main parts of the code:
pom.xml
<properties>
<java.version>1.8</java.version>
<junit-jupiter.version>5.3.2</junit-jupiter.version>
</properties>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.3.5.RELEASE</version>
</parent>
<dependencies>
<!-- webflux reactive -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-webflux</artifactId>
</dependency>
<!-- thymeleaf -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-mongodb-reactive</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-tomcat</artifactId>
</dependency>
<!-- exclude junit 4, prefer junit 5 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
<exclusions>
<exclusion>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
</exclusion>
</exclusions>
</dependency>
<!-- junit 5 -->
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-engine</artifactId>
<version>${junit-jupiter.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
</dependencies>
As you see in the pom, I'm using the embedded tomcat (I already tried with Netty, the default spring boot server...).
Also, I'm deploying the app to any remote server but just trying on my local (windows 10).
Web:
let source = new EventSource("/comment/stream");
source.addEventListener("message", function (event) {
// These events are JSON, so parsing and DOM fiddling are needed
var comment = JSON.parse(event.data);
console.log(comment );
});
source.addEventListener("error", function (event) {
console.log("error", event);
this.close();
});
RestController:
#RestController
public class CommentController {
#Autowired
private CommentRepository commentRepository;
#PostMapping(path = "/comment")
public Mono<Comment> comment(#RequestBody Comment comment) {
return this.commentRepository.save(comment);
}
#GetMapping(path = "/comment/stream", produces = MediaType.TEXT_EVENT_STREAM_VALUE)
public Flux<Comment> feed() {
return this.commentRepository.findAll();
}
}
DB Repository:
#Repository
public interface CommentRepository extends ReactiveSortingRepository<Comment, String> {
Flux<Comment> findAll();
}
Again, the web client that uses EventSource, keeps reconnecting every second because the connection is closed by the server.
Thank you!

Im not really sure, you are giving us too little information as to why your connection is closing. No logs, and you are not disclosing anything about where it being deployed.
i will only answer this question based on personal experience. I deployed an application to heroku that uses event streams and they have a proxy/loadbalancer infront of every application that will kill any connection that does not send anything after up to 60 sec.
As mentioned here Why are event sources closed after 30-60 sec it confirmes what i have been noticing.
To work around this you can if using websockets implement ping/pong messages or if using ServerSentEvents as i did, i implemented keep alive messages.
.GET("", accept(TEXT_EVENT_STREAM), request -> ok()
.contentType(TEXT_EVENT_STREAM)
.header("Cache-Control", "no-transform")
.body(Flux.merge(myHandler.getEvents()),
Flux.interval(Duration.ofSeconds(15))
.map(aLong -> ServerSentEvent.builder()
.comment("keep alive")
.build())),
new ParameterizedTypeReference<List<MyClass>>() {}))
I have taken this code snippet from one of my projects. Here you can see that i merge with my current stream a flux that at given intervals (15 sec) will emit a ServerSentEvent with only a keep alive comment. Since it is a comment it will get ignored by the client.
Just need to mention, the regular stream myHandler.getEvents returns data wrapped in ServerSentEvents.

Related

Combine REST connection with embedded Mongo integration in test

I'm using SpringBoot 2.6.2 together with flapdoodle 3.2.3 for test.
If I just want to test DB stuff, then #DataMongoTest works finde, because for tests I want to test it with an in-memory database. If I want to test RestConnections I'm using #SpringBootTest and also everything is fine.
Now I just want to use both, I need to test to send something to an rest connection and at the method on the other side it needs to check the database if the data is already there or not.
My problem now is, that I cannot use both #DataMongoTest and #SpringBootTest, they are not combinable, but I still need the autowired of all my Services, Components, etc. why I need SpringBootTest and also I need my embedded database, because I don't want to prerequisite an installed mongoDB with service.
I already tried #AutoConfigureDataMongo together with #SpringBootTest, like it is mentioned here: enter link description here but it will not work.
Any suggestions how I can combine both?
Thanks and Best,
Lobo
PS: Here is my pom provide which libraries I'm using in which version
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.6.2</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<properties>
<java.version>17</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-mongodb</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger2</artifactId>
<version>3.0.0</version>
</dependency>
<dependency>
<groupId>com.github.openjson</groupId>
<artifactId>openjson</artifactId>
<version>1.0.11</version>
</dependency>
<dependency>
<groupId>com.github.erosb</groupId>
<artifactId>everit-json-schema</artifactId>
<version>1.14.0</version>
</dependency>
<dependency>
<groupId>de.flapdoodle.embed</groupId>
<artifactId>de.flapdoodle.embed.mongo</artifactId>
<version>3.2.3</version><!--$NO-MVN-MAN-VER$-->
<scope>test</scope>
</dependency>
<dependency>
<groupId>de.flapdoodle.embed</groupId>
<artifactId>de.flapdoodle.embed.process</artifactId>
<version>3.1.4</version>
<scope>test</scope>
</dependency>
</dependencies>
OK I created just my own database in a #beforeAll like it is described here:
https://github.com/flapdoodle-oss/de.flapdoodle.embed.mongo
MongodStarter starter = MongodStarter.getDefaultInstance();
int port = Network.getFreeServerPort();
MongodConfig mongodConfig = MongodConfig.builder()
.version(Version.Main.PRODUCTION)
.net(new Net(port, Network.localhostIsIPv6()))
.build();
MongodExecutable mongodExecutable = null;
try {
mongodExecutable = starter.prepare(mongodConfig);
MongodProcess mongod = mongodExecutable.start();
try (MongoClient mongo = new MongoClient("localhost", port)) {
DB db = mongo.getDB("test");
DBCollection col = db.createCollection("testCol", new BasicDBObject());
col.save(new BasicDBObject("testDoc", new Date()));
}
} finally {
if (mongodExecutable != null)
mongodExecutable.stop();
}
with this I can use #SpringBootTest and still have the connection to the embedded database.

#RefreshScope and /refresh not working

I have tried to implement spring external configurations using Config Server. It is working fine for the very first time when the application is started but any changes to the properties file are not being reflected. I tried to use /refresh endpoint to refresh my properties on the fly but it doesn't seem to be working. Any help on this would be greatly helpful.
I tried POSTing to localhost:8080/refresh but getting a 404 Error response.
Below is the code of my application class
#SpringBootApplication
public class Config1Application {
public static void main(String[] args) {
SpringApplication.run(Config1Application.class, args);
}
}
#RestController
#RefreshScope
class MessageRestController {
#Value("${message:Hello default}")
private String message;
#RequestMapping("/message")
String getMessage() {
return this.message;
}
}
and POM file is
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.0.0.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<java.version>1.8</java.version>
<spring-cloud.version>Finchley.M8</spring-cloud.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-config</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>${spring-cloud.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
<repositories>
<repository>
<id>spring-milestones</id>
<name>Spring Milestones</name>
<url>https://repo.spring.io/milestone</url>
<snapshots>
<enabled>false</enabled>
</snapshots>
</repository>
</repositories>
and bootstrap.properties
spring.application.name=xxx
spring.cloud.config.uri=https://xxxxxx.com
management.security.enabled=false
endpoints.actuator.enabled=true
The endpoint is now /actuator/refresh for Spring 2 and greater
From the comments:
You do need to have the management.endpoints.web.exposure.include=refresh set in the bootstrap.properties/bootstrap.yml
Note: If you're new to Spring-Cloud and not quite sure of what all keywords can go in web.exposure you can set it to * (management.endpoints.web.exposure.include=*) to have all exposed and you can get to know the endpoints and their restrictions later.
It worked for me after adding the property "management.endpoints.web.exposure.include=*" in bootstrap.properties and changing the url to /actuator/refresh for spring version above 2.0.0
For spring version 1.0.5 url is /refresh
For YAML files, the property's value needs to be wrapped inside double quotes :
# Spring Boot Actuator
management:
endpoints:
web:
exposure:
include: "*"
Note : Ensure you use the right endpoints keyword (with 's') for this property as long as it exists for another property without 's' : "management.endpoint.health.... " .
Note: - It's a POST request (not GET)
I've posted the solution here
[https://stackoverflow.com/a/74465108/2171938][1]
If you have issues with accepting formurlencoded in SPRING 2.0>, use :
curl -H "Content-Type: application/json" -d {} http://localhost:port/actuator/refresh
instead of:
curl -d {} http://localhost:port/refresh
which was accepted in SPRING 1.*

Spring cloud gateway hystrix not working

I am having issues getting hystrix to work with my route. things like re-write paths and load balancing are working but for some reason hystrix never trips. I am setting my timeouts very low and have a delay in a downstream service. Is there anything special you need to do to get hystrix working?
I am using spring-cloud-gateway with eureka and spring-cloud-config. Also, is there a way to debug when a route is not working? Like a log setting to see what is happening?
Here is my route:
spring:
cloud:
gateway:
routes:
# =====================================
- id: main-service
uri: lbl://main-service
predicates:
- Path=/main-service**
filters:
- Hystrix=mainservice
Here is my pom
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.0.0.M7</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-
8</project.reporting.outputEncoding>
<java.version>1.8</java.version>
<spring-cloud.version>Finchley.M5</spring-cloud.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-gateway</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-config</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-eureka</artifactId>
<version>1.4.0.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-hystrix</artifactId>
<version>2.0.0.M2</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>${spring-cloud.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
Here is my Application class:
#EnableDiscoveryClient
#EnableHystrix
#SpringBootApplication
public class GatewayApplication {
public static void main(String[] args) {
SpringApplication.run(GatewayApplication.class, args);
}
}
The only other class in the jar is this:
#Configuration
public class GatewayConfiguration {
#Bean
public DiscoveryClientRouteDefinitionLocator
discoveryClientRouteLocator(DiscoveryClient discoveryClient) {
return new DiscoveryClientRouteDefinitionLocator(discoveryClient);
}
}
I figured out the issue. It seems that if you add eureka discovery then it automatically add routes that match all the spring.application.names returned by your eureka server and these have the same order as the ones I defined using application name as the predicate. I was able to fix this by setting the order for my route to -1.
Not sure if there is a better way to do this but at least I know hystrix is working.
Thanks
Maybe you should use this dependency:
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
</dependency>

Unable to execute drools workbench rules from java application

Installed drools workbench 6.4.0 Final and defined guided rules in workbench.
Getting below exception if am trying to execute rules from java application.
Exception in thread "main" java.lang.RuntimeException: Cannot find KieModule: org.mydemo:myDemo:1.0
at org.drools.compiler.kie.builder.impl.KieServicesImpl.newKieContainer(KieServicesImpl.java:117)
at org.drools.compiler.kie.builder.impl.KieServicesImpl.newKieContainer(KieServicesImpl.java:111)
at com.test.Test.main(Test.java:51)
Followed online resources and related questions, but still am not able to solve the problem.
This is one of the related link.
My java project dependencies and profile.
<dependency>
<groupId>org.kie</groupId>
<artifactId>kie-ci</artifactId>
<version>6.4.0.Final</version>
</dependency>
<dependency>
<groupId>org.drools</groupId>
<artifactId>drools-compiler</artifactId>
<version>6.4.0.Final</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.drools</groupId>
<artifactId>drools-core</artifactId>
<version>6.4.0.Final</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.kie</groupId>
<artifactId>kie-internal</artifactId>
<version>6.4.0.Final</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.kie</groupId>
<artifactId>kie-api</artifactId>
<version>6.4.0.Final</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.drools</groupId>
<artifactId>drools-templates</artifactId>
<version>6.4.0.Final</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.drools</groupId>
<artifactId>drools-decisiontables</artifactId>
<version>6.4.0.Final</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>com.thoughtworks.xstream</groupId>
<artifactId>xstream</artifactId>
<version>1.4.3</version>
<scope>provided</scope>
</dependency>
<profile>
<activation>
<activeByDefault>true</activeByDefault>
</activation>
<repositories>
<repository>
<id>guvnor-m2-repo</id>
<name>Guvnor M2 Repo</name>
<url>http://ip:8080/drools-wb/maven2/</url>
</repository>
</repositories>
</profile>
my Java code here :
public static void main(String[] args) {
String url = "http://ip:8080/drools-wb/maven2wb/org/mydemo/myDemo/1.0/myDemo-1.0.jar";
// make sure you use "LATEST" here!
ReleaseIdImpl releaseId = new ReleaseIdImpl("org.mydemo", "myDemo", "1.0");
KieServices ks = KieServices.Factory.get();
ks.getResources().newUrlResource(url);
KieContainer kieContainer = ks.newKieContainer(releaseId);
// check every 5 seconds if there is a new version at the URL
KieScanner kieScanner = ks.newKieScanner(kieContainer);
kieScanner.start(5000L);
// alternatively:
// kieScanner.scanNow();
Scanner scanner = new Scanner(System.in);
while (true) {
runRule(kieContainer);
System.out.println("Press enter in order to run the test again....");
scanner.nextLine();
}
}
private static void runRule(KieContainer kieKontainer) {
StatelessKieSession kSession = kieKontainer.newStatelessKieSession("testSession");
kSession.setGlobal("out", System.out);
kSession.execute("testRuleAgain");
}
Can anyone please help me, i am new to drools and spent almost 2 days to solve this issue.
It might be the url you are using, try changing it to:
http://ip:8080/drools-wb/maven2/org/mydemo/myDemo/1.0/myDemo-1.0.jar
i.e. maven2 NOT maven2b
then try that in a web browser. If you need authentication then this works for me when Drools is hosted on Tomcat and has basic authentication turned on.
KieServices ks = KieServices.Factory.get();
KieResources resources = ks.getResources();
UrlResource urlResource = (UrlResource) resources.newUrlResource(url);
urlResource.setUsername("admin");
urlResource.setPassword("admin");
urlResource.setBasicAuthentication("enabled");
I found this blog post very helpful, it might help you even though you are not using Spring.
http://reypader.github.io/2016/01/06/spring-drools.html

SpringCloud Eureka - simple client doesnt register

I have an Eureka Server where I want to register a very basic SpringBoot service. Unfortunately the service doesnt register although I tried to follow all the articles I could find.
Moreover when I check description of the DiscoveryClient (that gets autowired), I see "Spring Cloud No-op DiscoveryClient" which suggests (as per NoopDiscoveryClient.java source) that Eureka client library isnt found.
In pom I have
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-netflix-eureka-client</artifactId>
</dependency>
which if I am right should make sure that proper netflix libraries are in place. #EnableEurekaClient annotation is present. No errors on the console when starting the client, nothing interesting in the Eureka Server console logs.
This is the configuration from the application.yml:
eureka:
client:
serviceUrl:
defaultZone: ${vcap.services.eureka-service.credentials.uri:http://127.0.0.1:8761}/eureka/
Any suggestions are really welcomed as I am running out of ideas :)
http://start.spring.io is your friend. You need to use the starters.
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>Brixton.M5</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
and
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-eureka</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
</dependencies>
Except #spencergibb's answer, in my case it also require <spring-cloud.version> inside the <properties>:
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<java.version>1.8</java.version>
<spring-cloud.version>Greenwich.M3</spring-cloud.version>
</properties>