Merge remote-tracking branch 'upstream/master'
This commit is contained in:
commit
36bab29858
|
@ -43,8 +43,9 @@ public class ControlSubThread implements Runnable {
|
||||||
try {
|
try {
|
||||||
Thread.sleep(interval);
|
Thread.sleep(interval);
|
||||||
} catch (InterruptedException e) {
|
} catch (InterruptedException e) {
|
||||||
// no-op, just loop again
|
Thread.currentThread().interrupt();
|
||||||
}
|
System.out.println("Thread was interrupted, Failed to complete operation");
|
||||||
|
}
|
||||||
// do something
|
// do something
|
||||||
}
|
}
|
||||||
stopped.set(true);
|
stopped.set(true);
|
||||||
|
|
|
@ -44,6 +44,11 @@
|
||||||
<artifactId>kotlin-stdlib</artifactId>
|
<artifactId>kotlin-stdlib</artifactId>
|
||||||
<version>${kotlin-stdlib.version}</version>
|
<version>${kotlin-stdlib.version}</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.jetbrains.kotlin</groupId>
|
||||||
|
<artifactId>kotlin-stdlib-jre8</artifactId>
|
||||||
|
<version>${kotlin-stdlib.version}</version>
|
||||||
|
</dependency>
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.jetbrains.kotlin</groupId>
|
<groupId>org.jetbrains.kotlin</groupId>
|
||||||
<artifactId>kotlin-test-junit</artifactId>
|
<artifactId>kotlin-test-junit</artifactId>
|
||||||
|
|
|
@ -0,0 +1,67 @@
|
||||||
|
package com.baeldung.kotlin
|
||||||
|
|
||||||
|
import org.junit.Test
|
||||||
|
import java.beans.ExceptionListener
|
||||||
|
import java.beans.XMLEncoder
|
||||||
|
import java.io.*
|
||||||
|
import java.lang.Exception
|
||||||
|
import kotlin.test.assertEquals
|
||||||
|
import kotlin.test.assertTrue
|
||||||
|
import kotlin.test.fail
|
||||||
|
|
||||||
|
class UseTest {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun givenCloseable_whenUseIsCalled_thenItIsClosed() {
|
||||||
|
val stringWriter = StringWriter()
|
||||||
|
val writer = BufferedWriter(stringWriter) //Using a BufferedWriter because after close() it throws.
|
||||||
|
writer.use {
|
||||||
|
assertEquals(writer, it)
|
||||||
|
|
||||||
|
it.write("something")
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
writer.write("something else")
|
||||||
|
|
||||||
|
fail("write() should have thrown an exception because the writer is closed.")
|
||||||
|
} catch (e: IOException) {
|
||||||
|
//Ok
|
||||||
|
}
|
||||||
|
|
||||||
|
assertEquals("something", stringWriter.toString())
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun givenAutoCloseable_whenUseIsCalled_thenItIsClosed() {
|
||||||
|
val baos = ByteArrayOutputStream()
|
||||||
|
val encoder = XMLEncoder(PrintStream(baos)) //XMLEncoder is AutoCloseable but not Closeable.
|
||||||
|
//Here, we use a PrintStream because after close() it throws.
|
||||||
|
encoder.exceptionListener = ThrowingExceptionListener()
|
||||||
|
encoder.use {
|
||||||
|
assertEquals(encoder, it)
|
||||||
|
|
||||||
|
it.writeObject("something")
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
encoder.writeObject("something else")
|
||||||
|
encoder.flush()
|
||||||
|
|
||||||
|
fail("write() should have thrown an exception because the encoder is closed.")
|
||||||
|
} catch (e: IOException) {
|
||||||
|
//Ok
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun whenSimpleFormIsUsed_thenItWorks() {
|
||||||
|
StringWriter().use { it.write("something") }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class ThrowingExceptionListener : ExceptionListener {
|
||||||
|
override fun exceptionThrown(e: Exception?) {
|
||||||
|
if(e != null) {
|
||||||
|
throw e
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -55,7 +55,7 @@ public class InfluxDBConnectionLiveTest {
|
||||||
|
|
||||||
InfluxDB connection = connectDatabase();
|
InfluxDB connection = connectDatabase();
|
||||||
|
|
||||||
// Create "baeldung and check for it
|
// Create "baeldung" and check for it
|
||||||
connection.createDatabase("baeldung");
|
connection.createDatabase("baeldung");
|
||||||
assertTrue(connection.databaseExists("baeldung"));
|
assertTrue(connection.databaseExists("baeldung"));
|
||||||
|
|
||||||
|
|
|
@ -1,3 +1,5 @@
|
||||||
### Relevant Articles:
|
### Relevant Articles:
|
||||||
- [Intro to Jedis – the Java Redis Client Library](http://www.baeldung.com/jedis-java-redis-client-library)
|
- [Intro to Jedis – the Java Redis Client Library](http://www.baeldung.com/jedis-java-redis-client-library)
|
||||||
- [A Guide to Redis with Redisson](http://www.baeldung.com/redis-redisson)
|
- [A Guide to Redis with Redisson](http://www.baeldung.com/redis-redisson)
|
||||||
|
- [Intro to Lettuce – the Java Redis Client Library](http://www.baeldung.com/lettuce-java-redis-client-library)
|
||||||
|
|
||||||
|
|
|
@ -36,6 +36,13 @@
|
||||||
<artifactId>redisson</artifactId>
|
<artifactId>redisson</artifactId>
|
||||||
<version>3.3.0</version>
|
<version>3.3.0</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
|
<dependency>
|
||||||
|
<groupId>io.lettuce</groupId>
|
||||||
|
<artifactId>lettuce-core</artifactId>
|
||||||
|
<version>5.0.1.RELEASE</version>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
</dependencies>
|
</dependencies>
|
||||||
|
|
||||||
<properties>
|
<properties>
|
||||||
|
|
|
@ -0,0 +1,312 @@
|
||||||
|
package com.baeldung;
|
||||||
|
|
||||||
|
import io.lettuce.core.LettuceFutures;
|
||||||
|
import io.lettuce.core.RedisClient;
|
||||||
|
import io.lettuce.core.RedisFuture;
|
||||||
|
import io.lettuce.core.TransactionResult;
|
||||||
|
import io.lettuce.core.api.StatefulRedisConnection;
|
||||||
|
import io.lettuce.core.api.async.RedisAsyncCommands;
|
||||||
|
import io.lettuce.core.api.sync.RedisCommands;
|
||||||
|
import io.lettuce.core.pubsub.RedisPubSubListener;
|
||||||
|
import io.lettuce.core.pubsub.StatefulRedisPubSubConnection;
|
||||||
|
import io.lettuce.core.pubsub.api.async.RedisPubSubAsyncCommands;
|
||||||
|
import org.junit.AfterClass;
|
||||||
|
import org.junit.Assert;
|
||||||
|
import org.junit.BeforeClass;
|
||||||
|
import org.junit.Test;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Set;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
|
import static org.junit.Assert.assertTrue;
|
||||||
|
|
||||||
|
public class LettuceIntegrationLiveTest {
|
||||||
|
|
||||||
|
private static Logger log = LoggerFactory.getLogger(LettuceIntegrationLiveTest.class);
|
||||||
|
|
||||||
|
private static StatefulRedisConnection<String, String> redisConnection;
|
||||||
|
|
||||||
|
private static RedisClient redisClient;
|
||||||
|
|
||||||
|
@BeforeClass
|
||||||
|
public static void setUp() {
|
||||||
|
// Docker defaults to mapping redis port to 32768
|
||||||
|
redisClient = RedisClient.create("redis://localhost:32768/");
|
||||||
|
redisConnection = redisClient.connect();
|
||||||
|
}
|
||||||
|
|
||||||
|
@AfterClass
|
||||||
|
public static void destroy() {
|
||||||
|
redisConnection.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void givenAString_thenSaveItAsRedisStringsSync() {
|
||||||
|
|
||||||
|
RedisCommands<String, String> syncCommands = redisConnection.sync();
|
||||||
|
|
||||||
|
String key = "key";
|
||||||
|
String value = "value";
|
||||||
|
|
||||||
|
syncCommands.set(key, value);
|
||||||
|
String response = syncCommands.get(key);
|
||||||
|
|
||||||
|
Assert.assertEquals(value, response);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void givenValues_thenSaveAsRedisHashSync() {
|
||||||
|
|
||||||
|
RedisCommands<String, String> syncCommands = redisConnection.sync();
|
||||||
|
|
||||||
|
String recordName = "record1";
|
||||||
|
String name = "FirstName";
|
||||||
|
String value = "John";
|
||||||
|
String surname = "LastName";
|
||||||
|
String value1 = "Smith";
|
||||||
|
|
||||||
|
syncCommands.hset(recordName, name, value);
|
||||||
|
syncCommands.hset(recordName, surname, value1);
|
||||||
|
Map<String, String> record = syncCommands.hgetall(recordName);
|
||||||
|
|
||||||
|
Assert.assertEquals(record.get(name), value);
|
||||||
|
Assert.assertEquals(record.get(surname), value1);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void givenAString_thenSaveItAsRedisStringsAsync() throws Exception {
|
||||||
|
|
||||||
|
RedisAsyncCommands<String, String> asyncCommands = redisConnection.async();
|
||||||
|
|
||||||
|
String key = "key";
|
||||||
|
String value = "value";
|
||||||
|
|
||||||
|
asyncCommands.set(key, value);
|
||||||
|
RedisFuture<String> redisFuture = asyncCommands.get(key);
|
||||||
|
|
||||||
|
String response = redisFuture.get();
|
||||||
|
|
||||||
|
Assert.assertEquals(value, response);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void givenValues_thenSaveAsRedisHashAsync() throws Exception {
|
||||||
|
|
||||||
|
RedisAsyncCommands<String, String> asyncCommands = redisConnection.async();
|
||||||
|
|
||||||
|
String recordName = "record1";
|
||||||
|
String name = "FirstName";
|
||||||
|
String value = "John";
|
||||||
|
String surname = "LastName";
|
||||||
|
String value1 = "Smith";
|
||||||
|
|
||||||
|
asyncCommands.hset(recordName, name, value);
|
||||||
|
asyncCommands.hset(recordName, surname, value1);
|
||||||
|
RedisFuture<Map<String, String>> redisFuture = asyncCommands.hgetall(recordName);
|
||||||
|
|
||||||
|
Map<String, String> record = redisFuture.get();
|
||||||
|
|
||||||
|
Assert.assertEquals(record.get(name), value);
|
||||||
|
Assert.assertEquals(record.get(surname), value1);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void givenValues_thenSaveAsRedisListAsync() throws Exception {
|
||||||
|
|
||||||
|
RedisAsyncCommands<String, String> asyncCommands = redisConnection.async();
|
||||||
|
|
||||||
|
String listName = "tasks";
|
||||||
|
String firstTask = "firstTask";
|
||||||
|
String secondTask = "secondTask";
|
||||||
|
|
||||||
|
asyncCommands.del(listName);
|
||||||
|
|
||||||
|
asyncCommands.lpush(listName, firstTask);
|
||||||
|
asyncCommands.lpush(listName, secondTask);
|
||||||
|
RedisFuture<String> redisFuture = asyncCommands.rpop(listName);
|
||||||
|
|
||||||
|
String nextTask = redisFuture.get();
|
||||||
|
|
||||||
|
Assert.assertEquals(firstTask, nextTask);
|
||||||
|
|
||||||
|
asyncCommands.del(listName);
|
||||||
|
|
||||||
|
asyncCommands.lpush(listName, firstTask);
|
||||||
|
asyncCommands.lpush(listName, secondTask);
|
||||||
|
|
||||||
|
redisFuture = asyncCommands.lpop(listName);
|
||||||
|
|
||||||
|
nextTask = redisFuture.get();
|
||||||
|
|
||||||
|
Assert.assertEquals(secondTask, nextTask);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void givenSetElements_thenSaveThemInRedisSetAsync() throws Exception {
|
||||||
|
|
||||||
|
RedisAsyncCommands<String, String> asyncCommands = redisConnection.async();
|
||||||
|
|
||||||
|
String countries = "countries";
|
||||||
|
|
||||||
|
String countryOne = "Spain";
|
||||||
|
String countryTwo = "Ireland";
|
||||||
|
String countryThree = "Ireland";
|
||||||
|
|
||||||
|
asyncCommands.sadd(countries, countryOne);
|
||||||
|
|
||||||
|
RedisFuture<Set<String>> countriesSetFuture = asyncCommands.smembers(countries);
|
||||||
|
Assert.assertEquals(2, countriesSetFuture.get().size());
|
||||||
|
|
||||||
|
asyncCommands.sadd(countries, countryTwo);
|
||||||
|
countriesSetFuture = asyncCommands.smembers(countries);
|
||||||
|
Assert.assertEquals(2, countriesSetFuture.get().size());
|
||||||
|
|
||||||
|
asyncCommands.sadd(countries, countryThree);
|
||||||
|
countriesSetFuture = asyncCommands.smembers(countries);
|
||||||
|
Assert.assertEquals(2, countriesSetFuture.get().size());
|
||||||
|
|
||||||
|
RedisFuture<Boolean> exists = asyncCommands.sismember(countries, countryThree);
|
||||||
|
assertTrue(exists.get());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void givenARanking_thenSaveItInRedisSortedSetAsync() throws Exception {
|
||||||
|
|
||||||
|
RedisAsyncCommands<String, String> asyncCommands = redisConnection.async();
|
||||||
|
|
||||||
|
String key = "sortedset";
|
||||||
|
|
||||||
|
asyncCommands.zadd(key, 1, "one");
|
||||||
|
asyncCommands.zadd(key, 4, "zero");
|
||||||
|
asyncCommands.zadd(key, 2, "two");
|
||||||
|
|
||||||
|
RedisFuture<List<String>> values = asyncCommands.zrevrange(key, 0, 3);
|
||||||
|
Assert.assertEquals("zero", values.get().get(0));
|
||||||
|
|
||||||
|
values = asyncCommands.zrange(key, 0, 3);
|
||||||
|
Assert.assertEquals("one", values.get().get(0));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void givenMultipleOperationsThatNeedToBeExecutedAtomically_thenWrapThemInATransaction() throws Exception {
|
||||||
|
|
||||||
|
RedisAsyncCommands<String, String> asyncCommands = redisConnection.async();
|
||||||
|
|
||||||
|
// Start a transaction
|
||||||
|
asyncCommands.multi();
|
||||||
|
|
||||||
|
// Add three sets to it, and save the future responses
|
||||||
|
RedisFuture<String> result1 = asyncCommands.set("key1", "value1");
|
||||||
|
RedisFuture<String> result2 = asyncCommands.set("key2", "value2");
|
||||||
|
RedisFuture<String> result3 = asyncCommands.set("key3", "value3");
|
||||||
|
|
||||||
|
// Execute it
|
||||||
|
RedisFuture<TransactionResult> execResult = asyncCommands.exec();
|
||||||
|
|
||||||
|
TransactionResult transactionResult = execResult.get();
|
||||||
|
|
||||||
|
// Get the three results in the transaction return
|
||||||
|
String firstResult = transactionResult.get(0);
|
||||||
|
String secondResult = transactionResult.get(0);
|
||||||
|
String thirdResult = transactionResult.get(0);
|
||||||
|
|
||||||
|
// Our results are in both!
|
||||||
|
assertTrue(firstResult.equals("OK"));
|
||||||
|
assertTrue(secondResult.equals("OK"));
|
||||||
|
assertTrue(thirdResult.equals("OK"));
|
||||||
|
|
||||||
|
assertTrue(result1.get().equals("OK"));
|
||||||
|
assertTrue(result2.get().equals("OK"));
|
||||||
|
assertTrue(result3.get().equals("OK"));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void givenMultipleIndependentOperations_whenNetworkOptimizationIsImportant_thenFlushManually() throws Exception {
|
||||||
|
|
||||||
|
int iterations = 50;
|
||||||
|
|
||||||
|
RedisAsyncCommands<String, String> asyncCommands = redisConnection.async();
|
||||||
|
|
||||||
|
asyncCommands.setAutoFlushCommands(false);
|
||||||
|
|
||||||
|
List<RedisFuture<?>> futures = new ArrayList<>();
|
||||||
|
for (int i = 0; i < iterations; i++) {
|
||||||
|
futures.add(asyncCommands.set("key" + i, "value" + i));
|
||||||
|
}
|
||||||
|
|
||||||
|
asyncCommands.flushCommands();
|
||||||
|
|
||||||
|
// Wait until all futures complete
|
||||||
|
boolean result = LettuceFutures.awaitAll(5, TimeUnit.SECONDS, futures.toArray(new RedisFuture[futures.size()]));
|
||||||
|
|
||||||
|
asyncCommands.setAutoFlushCommands(true);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void givenPubSubChannel_whenMessage_thenMessageReceived() throws Exception {
|
||||||
|
|
||||||
|
Listener listener = new Listener();
|
||||||
|
StatefulRedisPubSubConnection<String, String> connection = redisClient.connectPubSub();
|
||||||
|
StatefulRedisPubSubConnection<String, String> pubconnection = redisClient.connectPubSub();
|
||||||
|
connection.addListener(listener);
|
||||||
|
|
||||||
|
RedisPubSubAsyncCommands<String, String> async = connection.async();
|
||||||
|
async.subscribe("channel");
|
||||||
|
|
||||||
|
RedisPubSubAsyncCommands<String, String> pubasync = pubconnection.async();
|
||||||
|
RedisFuture<Long> result = pubasync.publish("channel", "hithere");
|
||||||
|
|
||||||
|
// Need a long wait for publish to complete, depending on system.
|
||||||
|
result.get(15, TimeUnit.SECONDS);
|
||||||
|
assertTrue(listener.getMessage().equals("hithere"));
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
private static class Listener implements RedisPubSubListener<String, String> {
|
||||||
|
|
||||||
|
private String message;
|
||||||
|
|
||||||
|
String getMessage() {
|
||||||
|
return message;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void message(String channel, String message) {
|
||||||
|
log.debug("Got {} on {}", message, channel);
|
||||||
|
this.message = message;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void message(String pattern, String channel, String message) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void subscribed(String channel, long count) {
|
||||||
|
log.debug("Subscribed to {}", channel);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void psubscribed(String pattern, long count) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void unsubscribed(String channel, long count) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void punsubscribed(String pattern, long count) {
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,3 @@
|
||||||
|
## Relevant articles:
|
||||||
|
|
||||||
|
- [Spring Security 5 -OAuth2 Login](http://www.baeldung.com/spring-security-5-oauth2-login)
|
|
@ -1,10 +1,9 @@
|
||||||
<?xml version="1.0" encoding="UTF-8"?>
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://java.sun.com/xml/ns/javaee" xmlns:web="http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd"
|
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
|
||||||
xsi:schemaLocation="
|
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||||
http://java.sun.com/xml/ns/javaee
|
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd"
|
||||||
http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd" id="WebApp_ID" version="3.0"
|
version="3.1"
|
||||||
>
|
>
|
||||||
|
|
||||||
<display-name>Spring MVC XML Application</display-name>
|
<display-name>Spring MVC XML Application</display-name>
|
||||||
|
|
||||||
<!-- Spring root -->
|
<!-- Spring root -->
|
||||||
|
@ -65,4 +64,4 @@
|
||||||
<error-page>
|
<error-page>
|
||||||
<location>/errors</location>
|
<location>/errors</location>
|
||||||
</error-page>
|
</error-page>
|
||||||
</web-app>
|
</web-app>
|
||||||
|
|
|
@ -0,0 +1,92 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||||
|
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||||
|
<modelVersion>4.0.0</modelVersion>
|
||||||
|
|
||||||
|
<artifactId>spring-reactive-websocket</artifactId>
|
||||||
|
<version>0.0.1-SNAPSHOT</version>
|
||||||
|
<packaging>jar</packaging>
|
||||||
|
|
||||||
|
<name>spring-reactive-websocket</name>
|
||||||
|
<description>Reactive WebSockets with Spring 5</description>
|
||||||
|
|
||||||
|
<parent>
|
||||||
|
<groupId>com.baeldung</groupId>
|
||||||
|
<artifactId>parent-modules</artifactId>
|
||||||
|
<version>1.0.0-SNAPSHOT</version>
|
||||||
|
</parent>
|
||||||
|
|
||||||
|
<properties>
|
||||||
|
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
||||||
|
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
|
||||||
|
<java.version>1.8</java.version>
|
||||||
|
</properties>
|
||||||
|
|
||||||
|
<dependencies>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.springframework.boot</groupId>
|
||||||
|
<artifactId>spring-boot-starter-integration</artifactId>
|
||||||
|
<version>2.0.0.M7</version>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.springframework.boot</groupId>
|
||||||
|
<artifactId>spring-boot-starter-webflux</artifactId>
|
||||||
|
<version>2.0.0.M7</version>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.projectlombok</groupId>
|
||||||
|
<artifactId>lombok</artifactId>
|
||||||
|
<scope>compile</scope>
|
||||||
|
<version>RELEASE</version>
|
||||||
|
</dependency>
|
||||||
|
</dependencies>
|
||||||
|
|
||||||
|
<build>
|
||||||
|
<plugins>
|
||||||
|
<plugin>
|
||||||
|
<groupId>org.springframework.boot</groupId>
|
||||||
|
<artifactId>spring-boot-maven-plugin</artifactId>
|
||||||
|
</plugin>
|
||||||
|
</plugins>
|
||||||
|
</build>
|
||||||
|
|
||||||
|
<repositories>
|
||||||
|
<repository>
|
||||||
|
<id>spring-snapshots</id>
|
||||||
|
<name>Spring Snapshots</name>
|
||||||
|
<url>https://repo.spring.io/snapshot</url>
|
||||||
|
<snapshots>
|
||||||
|
<enabled>true</enabled>
|
||||||
|
</snapshots>
|
||||||
|
</repository>
|
||||||
|
<repository>
|
||||||
|
<id>spring-milestones</id>
|
||||||
|
<name>Spring Milestones</name>
|
||||||
|
<url>https://repo.spring.io/milestone</url>
|
||||||
|
<snapshots>
|
||||||
|
<enabled>false</enabled>
|
||||||
|
</snapshots>
|
||||||
|
</repository>
|
||||||
|
</repositories>
|
||||||
|
|
||||||
|
<pluginRepositories>
|
||||||
|
<pluginRepository>
|
||||||
|
<id>spring-snapshots</id>
|
||||||
|
<name>Spring Snapshots</name>
|
||||||
|
<url>https://repo.spring.io/snapshot</url>
|
||||||
|
<snapshots>
|
||||||
|
<enabled>true</enabled>
|
||||||
|
</snapshots>
|
||||||
|
</pluginRepository>
|
||||||
|
<pluginRepository>
|
||||||
|
<id>spring-milestones</id>
|
||||||
|
<name>Spring Milestones</name>
|
||||||
|
<url>https://repo.spring.io/milestone</url>
|
||||||
|
<snapshots>
|
||||||
|
<enabled>false</enabled>
|
||||||
|
</snapshots>
|
||||||
|
</pluginRepository>
|
||||||
|
</pluginRepositories>
|
||||||
|
|
||||||
|
|
||||||
|
</project>
|
|
@ -0,0 +1,11 @@
|
||||||
|
package com.baeldung;
|
||||||
|
|
||||||
|
import lombok.AllArgsConstructor;
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
@Data
|
||||||
|
@AllArgsConstructor
|
||||||
|
public class Event {
|
||||||
|
private String eventId;
|
||||||
|
private String eventDt;
|
||||||
|
}
|
|
@ -0,0 +1,38 @@
|
||||||
|
package com.baeldung;
|
||||||
|
|
||||||
|
import java.net.URI;
|
||||||
|
import java.time.Duration;
|
||||||
|
|
||||||
|
import org.springframework.boot.CommandLineRunner;
|
||||||
|
import org.springframework.boot.SpringApplication;
|
||||||
|
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||||
|
import org.springframework.context.annotation.Bean;
|
||||||
|
import org.springframework.web.reactive.socket.WebSocketMessage;
|
||||||
|
import org.springframework.web.reactive.socket.client.ReactorNettyWebSocketClient;
|
||||||
|
import org.springframework.web.reactive.socket.client.WebSocketClient;
|
||||||
|
|
||||||
|
import reactor.core.publisher.Mono;
|
||||||
|
|
||||||
|
@SpringBootApplication
|
||||||
|
public class ReactiveWebSocketApplication {
|
||||||
|
public static void main(String[] args) {
|
||||||
|
SpringApplication.run(ReactiveWebSocketApplication.class, args);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Spring Reactive WebSocket Client
|
||||||
|
* **/
|
||||||
|
@Bean
|
||||||
|
CommandLineRunner runner() {
|
||||||
|
return run -> {
|
||||||
|
WebSocketClient client = new ReactorNettyWebSocketClient();
|
||||||
|
client.execute(URI.create("ws://localhost:8080/event-emitter"), session -> session.send(Mono.just(session.textMessage("event-me-from-spring-reactive-client")))
|
||||||
|
.thenMany(session.receive()
|
||||||
|
.map(WebSocketMessage::getPayloadAsText)
|
||||||
|
.log())
|
||||||
|
.then())
|
||||||
|
.block();
|
||||||
|
// .block(Duration.ofSeconds(10L));//force timeout after given duration
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,34 @@
|
||||||
|
package com.baeldung;
|
||||||
|
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.context.annotation.Bean;
|
||||||
|
import org.springframework.context.annotation.Configuration;
|
||||||
|
import org.springframework.web.reactive.HandlerMapping;
|
||||||
|
import org.springframework.web.reactive.handler.SimpleUrlHandlerMapping;
|
||||||
|
import org.springframework.web.reactive.socket.WebSocketHandler;
|
||||||
|
import org.springframework.web.reactive.socket.server.support.WebSocketHandlerAdapter;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
@Configuration
|
||||||
|
public class ReactiveWebSocketConfiguration {
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private WebSocketHandler webSocketHandler;
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
public HandlerMapping webSocketHandlerMapping() {
|
||||||
|
Map<String, WebSocketHandler> map = new HashMap<>();
|
||||||
|
map.put("/event-emitter", webSocketHandler);
|
||||||
|
|
||||||
|
SimpleUrlHandlerMapping handlerMapping = new SimpleUrlHandlerMapping();
|
||||||
|
handlerMapping.setOrder(1);
|
||||||
|
handlerMapping.setUrlMap(map);
|
||||||
|
return handlerMapping;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
public WebSocketHandlerAdapter handlerAdapter() {
|
||||||
|
return new WebSocketHandlerAdapter();
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,71 @@
|
||||||
|
package com.baeldung;
|
||||||
|
|
||||||
|
import org.springframework.web.reactive.socket.WebSocketSession;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.core.JsonProcessingException;
|
||||||
|
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||||
|
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
import org.springframework.web.reactive.socket.WebSocketHandler;
|
||||||
|
import org.springframework.web.reactive.socket.WebSocketMessage;
|
||||||
|
|
||||||
|
import reactor.core.publisher.Flux;
|
||||||
|
import reactor.core.publisher.Mono;
|
||||||
|
|
||||||
|
import javax.annotation.PostConstruct;
|
||||||
|
import java.time.Duration;
|
||||||
|
import java.time.LocalDateTime;
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
|
@Component
|
||||||
|
public class ReactiveWebSocketHandler implements WebSocketHandler {
|
||||||
|
|
||||||
|
private Flux<Event> eventFlux;
|
||||||
|
private Flux<Event> intervalFlux;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Here we prepare a Flux that will emit a message every second
|
||||||
|
*/
|
||||||
|
@PostConstruct
|
||||||
|
private void init() throws InterruptedException {
|
||||||
|
|
||||||
|
eventFlux = Flux.generate(e -> {
|
||||||
|
Event event = new Event(UUID.randomUUID()
|
||||||
|
.toString(),
|
||||||
|
LocalDateTime.now()
|
||||||
|
.toString());
|
||||||
|
e.next(event);
|
||||||
|
});
|
||||||
|
|
||||||
|
intervalFlux = Flux.interval(Duration.ofMillis(1000L))
|
||||||
|
.zipWith(eventFlux, (time, event) -> event);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* On each new client session, send the message flux to the client.
|
||||||
|
* Spring subscribes to the flux and send every new flux event to the WebSocketSession object
|
||||||
|
* @param session
|
||||||
|
* @return Mono<Void>
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public Mono<Void> handle(WebSocketSession webSocketSession) {
|
||||||
|
ObjectMapper json = new ObjectMapper();
|
||||||
|
return webSocketSession.send(intervalFlux.map(event -> {
|
||||||
|
try {
|
||||||
|
String jsonEvent = json.writeValueAsString(event);
|
||||||
|
System.out.println(jsonEvent);
|
||||||
|
return jsonEvent;
|
||||||
|
} catch (JsonProcessingException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.map(webSocketSession::textMessage))
|
||||||
|
|
||||||
|
.and(webSocketSession.receive()
|
||||||
|
.map(WebSocketMessage::getPayloadAsText)
|
||||||
|
.log());
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,34 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<title>Baeldung: Spring 5 Reactive Client WebSocket (Browser)</title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
|
||||||
|
<div class="events"></div>
|
||||||
|
<script>
|
||||||
|
var clientWebSocket = new WebSocket("ws://localhost:8080/event-emitter");
|
||||||
|
clientWebSocket.onopen = function() {
|
||||||
|
console.log("clientWebSocket.onopen", clientWebSocket);
|
||||||
|
console.log("clientWebSocket.readyState", "websocketstatus");
|
||||||
|
clientWebSocket.send("event-me-from-browser");
|
||||||
|
}
|
||||||
|
clientWebSocket.onclose = function(error) {
|
||||||
|
console.log("clientWebSocket.onclose", clientWebSocket, error);
|
||||||
|
events("Closing connection");
|
||||||
|
}
|
||||||
|
clientWebSocket.onerror = function(error) {
|
||||||
|
console.log("clientWebSocket.onerror", clientWebSocket, error);
|
||||||
|
events("An error occured");
|
||||||
|
}
|
||||||
|
clientWebSocket.onmessage = function(error) {
|
||||||
|
console.log("clientWebSocket.onmessage", clientWebSocket, error);
|
||||||
|
events(error.data);
|
||||||
|
}
|
||||||
|
function events(responseEvent) {
|
||||||
|
document.querySelector(".events").innerHTML += responseEvent + "<br>";
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
Loading…
Reference in New Issue