From 438bf4bd5c38fb96d21321aa9a011b2bdc995f8f Mon Sep 17 00:00:00 2001 From: Elex Date: Thu, 5 Aug 2021 11:46:27 +0900 Subject: [PATCH] 2021-08-05 --- README.md | 8 + build.gradle.kts | 7 + buildSrc/build.gradle.kts | 7 + .../main/kotlin/elex-application.gradle.kts | 7 + buildSrc/src/main/kotlin/elex-base.gradle.kts | 15 +- buildSrc/src/main/kotlin/elex-java.gradle.kts | 7 + .../src/main/kotlin/elex-library.gradle.kts | 7 + buildSrc/src/main/kotlin/elex-war.gradle.kts | 15 ++ gradle/wrapper/gradle-wrapper.properties | 7 + gradlew | 15 +- java-web-token/build.gradle.kts | 7 - .../kr/pe/elex/examples/package-info.java | 1 - json-web-token/build.gradle.kts | 19 ++ .../java/kr/pe/elex/examples/JwtSample.java | 66 +++++++ .../kr/pe/elex/examples/package-info.java | 8 + .../java/kr/pe/elex/examples/SampleTest.java | 22 +++ logback.xml | 7 + mockito/build.gradle.kts | 7 + .../main/java/kr/pe/elex/examples/Person.java | 7 + .../kr/pe/elex/examples/package-info.java | 7 + .../java/kr/pe/elex/examples/MockTest.java | 7 + .../kr/pe/elex/examples/PersonListTest.java | 7 + .../java/kr/pe/elex/examples/PersonTest.java | 7 + mosquitto/README.md | 33 ++++ mosquitto/build.gradle.kts | 13 ++ .../kr/pe/elex/examples/HelloMosquitto.java | 180 ++++++++++++++++++ .../java/kr/pe/elex/examples/TlsHelper.java | 114 +++++++++++ .../examples/TlsHelperWithBouncyCastle.java | 155 +++++++++++++++ .../kr/pe/elex/examples/package-info.java | 8 + rabbit-mq/README.md | 62 ++++++ rabbit-mq/build.gradle.kts | 13 ++ .../pe/elex/examples/fanout/RabbitClient.java | 118 ++++++++++++ .../pe/elex/examples/hello/HelloRabbit.java | 115 +++++++++++ .../pe/elex/examples/hello/HelloRabbit2.java | 105 ++++++++++ .../examples/loadbalance/RabbitClient.java | 136 +++++++++++++ .../kr/pe/elex/examples/package-info.java | 6 + .../kr/pe/elex/examples/rpc/HelloRabbit.java | 120 ++++++++++++ .../elex/examples/rpc/HelloRabbitServer.java | 76 ++++++++ .../kr/pe/elex/examples/tls/HelloRabbit.java | 106 +++++++++++ .../kr/pe/elex/examples/tls/TlsHelper.java | 93 +++++++++ .../tls/TlsHelperWithBouncyCastle.java | 129 +++++++++++++ .../pe/elex/examples/topic/RabbitClient.java | 104 ++++++++++ settings.gradle.kts | 14 +- ssh/build.gradle.kts | 10 +- .../java/kr/pe/elex/examples/SecureShell.java | 89 +++++++++ .../kr/pe/elex/examples/package-info.java | 8 + .../kr/pe/elex/examples/SecureShellTest.java | 67 +++++++ thread/build.gradle.kts | 14 ++ .../main/java/kr/pe/elex/examples/Sample.java | 38 ++++ .../kr/pe/elex/examples/ExecutorTest.java | 14 ++ web-socket-client/build.gradle.kts | 16 ++ .../kr/pe/elex/examples/WebSocketClient.java | 117 ++++++++++++ .../examples/WebSocketMessageHandler.java | 16 ++ .../kr/pe/elex/examples/package-info.java | 8 + web-socket-servlet/build.gradle.kts | 15 ++ .../kr/pe/elex/examples/WebSocketServlet.java | 57 ++++++ .../kr/pe/elex/examples/package-info.java | 8 + web-socket/build.gradle.kts | 7 - 58 files changed, 2418 insertions(+), 33 deletions(-) create mode 100644 README.md create mode 100644 buildSrc/src/main/kotlin/elex-war.gradle.kts delete mode 100644 java-web-token/build.gradle.kts delete mode 100644 java-web-token/src/main/java/kr/pe/elex/examples/package-info.java create mode 100644 json-web-token/build.gradle.kts create mode 100644 json-web-token/src/main/java/kr/pe/elex/examples/JwtSample.java create mode 100644 json-web-token/src/main/java/kr/pe/elex/examples/package-info.java create mode 100644 json-web-token/src/test/java/kr/pe/elex/examples/SampleTest.java create mode 100644 mosquitto/README.md create mode 100644 mosquitto/src/main/java/kr/pe/elex/examples/HelloMosquitto.java create mode 100644 mosquitto/src/main/java/kr/pe/elex/examples/TlsHelper.java create mode 100644 mosquitto/src/main/java/kr/pe/elex/examples/TlsHelperWithBouncyCastle.java create mode 100644 mosquitto/src/main/java/kr/pe/elex/examples/package-info.java create mode 100644 rabbit-mq/README.md create mode 100644 rabbit-mq/src/main/java/kr/pe/elex/examples/fanout/RabbitClient.java create mode 100644 rabbit-mq/src/main/java/kr/pe/elex/examples/hello/HelloRabbit.java create mode 100644 rabbit-mq/src/main/java/kr/pe/elex/examples/hello/HelloRabbit2.java create mode 100644 rabbit-mq/src/main/java/kr/pe/elex/examples/loadbalance/RabbitClient.java create mode 100644 rabbit-mq/src/main/java/kr/pe/elex/examples/package-info.java create mode 100644 rabbit-mq/src/main/java/kr/pe/elex/examples/rpc/HelloRabbit.java create mode 100644 rabbit-mq/src/main/java/kr/pe/elex/examples/rpc/HelloRabbitServer.java create mode 100644 rabbit-mq/src/main/java/kr/pe/elex/examples/tls/HelloRabbit.java create mode 100644 rabbit-mq/src/main/java/kr/pe/elex/examples/tls/TlsHelper.java create mode 100644 rabbit-mq/src/main/java/kr/pe/elex/examples/tls/TlsHelperWithBouncyCastle.java create mode 100644 rabbit-mq/src/main/java/kr/pe/elex/examples/topic/RabbitClient.java create mode 100644 ssh/src/main/java/kr/pe/elex/examples/SecureShell.java create mode 100644 ssh/src/main/java/kr/pe/elex/examples/package-info.java create mode 100644 ssh/src/test/java/kr/pe/elex/examples/SecureShellTest.java create mode 100644 thread/build.gradle.kts create mode 100644 thread/src/main/java/kr/pe/elex/examples/Sample.java create mode 100644 thread/src/test/java/kr/pe/elex/examples/ExecutorTest.java create mode 100644 web-socket-client/build.gradle.kts create mode 100644 web-socket-client/src/main/java/kr/pe/elex/examples/WebSocketClient.java create mode 100644 web-socket-client/src/main/java/kr/pe/elex/examples/WebSocketMessageHandler.java create mode 100644 web-socket-client/src/main/java/kr/pe/elex/examples/package-info.java create mode 100644 web-socket-servlet/build.gradle.kts create mode 100644 web-socket-servlet/src/main/java/kr/pe/elex/examples/WebSocketServlet.java create mode 100644 web-socket-servlet/src/main/java/kr/pe/elex/examples/package-info.java delete mode 100644 web-socket/build.gradle.kts diff --git a/README.md b/README.md new file mode 100644 index 0000000..0b94abe --- /dev/null +++ b/README.md @@ -0,0 +1,8 @@ +# Java Examples + + + +----- +programmed by Elex + +https://www.elex-project.com/ diff --git a/build.gradle.kts b/build.gradle.kts index e4f4545..6474e0b 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -1,3 +1,10 @@ +/* + * Examples for Java + * + * Copyright (c) 2021. Elex. All Rights Reserved. + * https://www.elex-project.com/ + */ + buildscript { repositories { maven { diff --git a/buildSrc/build.gradle.kts b/buildSrc/build.gradle.kts index d32bb9b..b663d12 100644 --- a/buildSrc/build.gradle.kts +++ b/buildSrc/build.gradle.kts @@ -1,3 +1,10 @@ +/* + * Examples for Java + * + * Copyright (c) 2021. Elex. All Rights Reserved. + * https://www.elex-project.com/ + */ + plugins{ `kotlin-dsl` } diff --git a/buildSrc/src/main/kotlin/elex-application.gradle.kts b/buildSrc/src/main/kotlin/elex-application.gradle.kts index 793b831..0b6844d 100644 --- a/buildSrc/src/main/kotlin/elex-application.gradle.kts +++ b/buildSrc/src/main/kotlin/elex-application.gradle.kts @@ -1,3 +1,10 @@ +/* + * Examples for Java + * + * Copyright (c) 2021. Elex. All Rights Reserved. + * https://www.elex-project.com/ + */ + plugins { id("elex-base") diff --git a/buildSrc/src/main/kotlin/elex-base.gradle.kts b/buildSrc/src/main/kotlin/elex-base.gradle.kts index cb5018f..faab476 100644 --- a/buildSrc/src/main/kotlin/elex-base.gradle.kts +++ b/buildSrc/src/main/kotlin/elex-base.gradle.kts @@ -1,3 +1,10 @@ +/* + * Examples for Java + * + * Copyright (c) 2021. Elex. All Rights Reserved. + * https://www.elex-project.com/ + */ + plugins { java } @@ -61,14 +68,16 @@ tasks.javadoc { } dependencies { //implementation(fileTree(mapOf("dir" to "libs", "include" to listOf("*.jar")))) - implementation("org.slf4j:slf4j-api:1.7.30") + implementation("org.slf4j:slf4j-api:1.7.32") implementation("org.jetbrains:annotations:21.0.1") + implementation("com.elex-project:abraxas:4.5.3") + compileOnly("org.projectlombok:lombok:1.18.20") annotationProcessor("org.projectlombok:lombok:1.18.20") testAnnotationProcessor("org.projectlombok:lombok:1.18.20") testImplementation("ch.qos.logback:logback-classic:1.2.3") - testImplementation("org.junit.jupiter:junit-jupiter:5.7.0") - testRuntimeOnly("org.junit.jupiter:junit-jupiter-engine:5.7.0") + testImplementation("org.junit.jupiter:junit-jupiter:5.7.2") + testRuntimeOnly("org.junit.jupiter:junit-jupiter-engine:5.7.2") } diff --git a/buildSrc/src/main/kotlin/elex-java.gradle.kts b/buildSrc/src/main/kotlin/elex-java.gradle.kts index 3af10c0..ba1b58f 100644 --- a/buildSrc/src/main/kotlin/elex-java.gradle.kts +++ b/buildSrc/src/main/kotlin/elex-java.gradle.kts @@ -1,3 +1,10 @@ +/* + * Examples for Java + * + * Copyright (c) 2021. Elex. All Rights Reserved. + * https://www.elex-project.com/ + */ + plugins { id ("elex-base") } diff --git a/buildSrc/src/main/kotlin/elex-library.gradle.kts b/buildSrc/src/main/kotlin/elex-library.gradle.kts index 860dd6a..5caf4d1 100644 --- a/buildSrc/src/main/kotlin/elex-library.gradle.kts +++ b/buildSrc/src/main/kotlin/elex-library.gradle.kts @@ -1,3 +1,10 @@ +/* + * Examples for Java + * + * Copyright (c) 2021. Elex. All Rights Reserved. + * https://www.elex-project.com/ + */ + plugins { id ("elex-base") `java-library` diff --git a/buildSrc/src/main/kotlin/elex-war.gradle.kts b/buildSrc/src/main/kotlin/elex-war.gradle.kts new file mode 100644 index 0000000..e35c938 --- /dev/null +++ b/buildSrc/src/main/kotlin/elex-war.gradle.kts @@ -0,0 +1,15 @@ +/* + * Examples for Java + * + * Copyright (c) 2021. Elex. All Rights Reserved. + * https://www.elex-project.com/ + */ + +plugins { + id ("elex-base") + war +} + +dependencies { + +} diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index af7be50..2677b68 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,3 +1,10 @@ +# +# Examples for Java +# +# Copyright (c) 2021. Elex. All Rights Reserved. +# https://www.elex-project.com/ +# + distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists distributionUrl=https\://services.gradle.org/distributions/gradle-7.1.1-all.zip diff --git a/gradlew b/gradlew index 4f906e0..6213d57 100755 --- a/gradlew +++ b/gradlew @@ -1,19 +1,10 @@ #!/usr/bin/env sh # -# Copyright 2015 the original author or authors. +# Examples for Java # -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# https://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. +# Copyright (c) 2021. Elex. All Rights Reserved. +# https://www.elex-project.com/ # ############################################################################## diff --git a/java-web-token/build.gradle.kts b/java-web-token/build.gradle.kts deleted file mode 100644 index 8367881..0000000 --- a/java-web-token/build.gradle.kts +++ /dev/null @@ -1,7 +0,0 @@ -plugins { - id("elex-library") -} - -dependencies { - -} diff --git a/java-web-token/src/main/java/kr/pe/elex/examples/package-info.java b/java-web-token/src/main/java/kr/pe/elex/examples/package-info.java deleted file mode 100644 index 18376b4..0000000 --- a/java-web-token/src/main/java/kr/pe/elex/examples/package-info.java +++ /dev/null @@ -1 +0,0 @@ -package kr.pe.elex.examples; diff --git a/json-web-token/build.gradle.kts b/json-web-token/build.gradle.kts new file mode 100644 index 0000000..773fd3e --- /dev/null +++ b/json-web-token/build.gradle.kts @@ -0,0 +1,19 @@ +/* + * Examples for Java + * + * Copyright (c) 2021. Elex. All Rights Reserved. + * https://www.elex-project.com/ + */ + +plugins { + id("elex-java") +} + +dependencies { + // https://mvnrepository.com/artifact/io.jsonwebtoken/jjwt-api + implementation("io.jsonwebtoken:jjwt-api:0.11.2") + + // https://mvnrepository.com/artifact/io.jsonwebtoken/jjwt-impl + runtimeOnly("io.jsonwebtoken:jjwt-impl:0.11.2") + runtimeOnly("io.jsonwebtoken:jjwt-jackson:0.11.2") +} diff --git a/json-web-token/src/main/java/kr/pe/elex/examples/JwtSample.java b/json-web-token/src/main/java/kr/pe/elex/examples/JwtSample.java new file mode 100644 index 0000000..757ffd5 --- /dev/null +++ b/json-web-token/src/main/java/kr/pe/elex/examples/JwtSample.java @@ -0,0 +1,66 @@ +/* + * Examples for Java + * + * Copyright (c) 2021. Elex. All Rights Reserved. + * https://www.elex-project.com/ + */ + +package kr.pe.elex.examples; + +import io.jsonwebtoken.*; +import io.jsonwebtoken.security.InvalidKeyException; +import io.jsonwebtoken.security.Keys; +import io.jsonwebtoken.security.SignatureException; + +import java.time.Instant; +import java.time.temporal.ChronoUnit; +import java.util.Date; +import java.util.Random; + +public class JwtSample { + private static final byte[] key; + + static { + key = new byte[32]; + new Random().nextBytes(key); + } + + public static String generateToken() throws InvalidKeyException { + return Jwts.builder() + .setHeaderParam(Header.TYPE, Header.JWT_TYPE) + .setIssuer("Elex") + .setExpiration(Date.from(Instant.now().plus(3, ChronoUnit.HOURS))) + .claim("userId", 3) + .signWith(Keys.hmacShaKeyFor(key)) + .compact(); + } + + public static Jws parseToken(final String token) + throws UnsupportedJwtException, MalformedJwtException, SignatureException, ExpiredJwtException { + + return Jwts.parserBuilder() + .setSigningKey(key) + .build() + .parseClaimsJws(parseHeader(token)); + } + + private static String parseHeader(final String authenticationHeader) { + final String[] authentication = authenticationHeader.split(" "); + if (authentication.length == 2 && authentication[0].matches("[bB]earer")) { + return authentication[1]; + } else if (authentication.length == 1) { + return authentication[0]; + } else { + throw new MalformedJwtException("Authentication Header param must be started with 'Bearer ': " + authenticationHeader); + } + } + + public static void main(String... args) { + String token = generateToken(); + System.out.println(token); + + String authHeader = "Bearer " + token; + Jws claims = parseToken(authHeader); + System.out.println(claims); + } +} diff --git a/json-web-token/src/main/java/kr/pe/elex/examples/package-info.java b/json-web-token/src/main/java/kr/pe/elex/examples/package-info.java new file mode 100644 index 0000000..bfb14b8 --- /dev/null +++ b/json-web-token/src/main/java/kr/pe/elex/examples/package-info.java @@ -0,0 +1,8 @@ +/* + * Examples for Java + * + * Copyright (c) 2021. Elex. All Rights Reserved. + * https://www.elex-project.com/ + */ + +package kr.pe.elex.examples; diff --git a/json-web-token/src/test/java/kr/pe/elex/examples/SampleTest.java b/json-web-token/src/test/java/kr/pe/elex/examples/SampleTest.java new file mode 100644 index 0000000..ae7e690 --- /dev/null +++ b/json-web-token/src/test/java/kr/pe/elex/examples/SampleTest.java @@ -0,0 +1,22 @@ +/* + * Examples for Java + * + * Copyright (c) 2021. Elex. All Rights Reserved. + * https://www.elex-project.com/ + */ + +package kr.pe.elex.examples; + +import org.junit.jupiter.api.Test; + +import java.util.Base64; + +import static org.junit.jupiter.api.Assertions.*; + +class SampleTest { + + @Test + void test(){ + System.out.println(new String()); + } +} diff --git a/logback.xml b/logback.xml index 17e6b52..294b921 100644 --- a/logback.xml +++ b/logback.xml @@ -1,4 +1,11 @@ + + diff --git a/mockito/build.gradle.kts b/mockito/build.gradle.kts index 5a37348..11b9990 100644 --- a/mockito/build.gradle.kts +++ b/mockito/build.gradle.kts @@ -1,3 +1,10 @@ +/* + * Examples for Java + * + * Copyright (c) 2021. Elex. All Rights Reserved. + * https://www.elex-project.com/ + */ + plugins { id("elex-java") } diff --git a/mockito/src/main/java/kr/pe/elex/examples/Person.java b/mockito/src/main/java/kr/pe/elex/examples/Person.java index 32076a7..4775120 100644 --- a/mockito/src/main/java/kr/pe/elex/examples/Person.java +++ b/mockito/src/main/java/kr/pe/elex/examples/Person.java @@ -1,3 +1,10 @@ +/* + * Examples for Java + * + * Copyright (c) 2021. Elex. All Rights Reserved. + * https://www.elex-project.com/ + */ + package kr.pe.elex.examples; import lombok.AllArgsConstructor; diff --git a/mockito/src/main/java/kr/pe/elex/examples/package-info.java b/mockito/src/main/java/kr/pe/elex/examples/package-info.java index 18376b4..bfb14b8 100644 --- a/mockito/src/main/java/kr/pe/elex/examples/package-info.java +++ b/mockito/src/main/java/kr/pe/elex/examples/package-info.java @@ -1 +1,8 @@ +/* + * Examples for Java + * + * Copyright (c) 2021. Elex. All Rights Reserved. + * https://www.elex-project.com/ + */ + package kr.pe.elex.examples; diff --git a/mockito/src/test/java/kr/pe/elex/examples/MockTest.java b/mockito/src/test/java/kr/pe/elex/examples/MockTest.java index b9d14b3..02a5d0b 100644 --- a/mockito/src/test/java/kr/pe/elex/examples/MockTest.java +++ b/mockito/src/test/java/kr/pe/elex/examples/MockTest.java @@ -1,3 +1,10 @@ +/* + * Examples for Java + * + * Copyright (c) 2021. Elex. All Rights Reserved. + * https://www.elex-project.com/ + */ + package kr.pe.elex.examples; import org.junit.jupiter.api.Test; diff --git a/mockito/src/test/java/kr/pe/elex/examples/PersonListTest.java b/mockito/src/test/java/kr/pe/elex/examples/PersonListTest.java index d3361fa..584b257 100644 --- a/mockito/src/test/java/kr/pe/elex/examples/PersonListTest.java +++ b/mockito/src/test/java/kr/pe/elex/examples/PersonListTest.java @@ -1,3 +1,10 @@ +/* + * Examples for Java + * + * Copyright (c) 2021. Elex. All Rights Reserved. + * https://www.elex-project.com/ + */ + package kr.pe.elex.examples; import org.junit.jupiter.api.BeforeAll; diff --git a/mockito/src/test/java/kr/pe/elex/examples/PersonTest.java b/mockito/src/test/java/kr/pe/elex/examples/PersonTest.java index 2c2b414..7d69a73 100644 --- a/mockito/src/test/java/kr/pe/elex/examples/PersonTest.java +++ b/mockito/src/test/java/kr/pe/elex/examples/PersonTest.java @@ -1,3 +1,10 @@ +/* + * Examples for Java + * + * Copyright (c) 2021. Elex. All Rights Reserved. + * https://www.elex-project.com/ + */ + package kr.pe.elex.examples; import org.junit.jupiter.api.BeforeEach; diff --git a/mosquitto/README.md b/mosquitto/README.md new file mode 100644 index 0000000..e4b6924 --- /dev/null +++ b/mosquitto/README.md @@ -0,0 +1,33 @@ +# Mosquitto Example + +```kotlin +implementation("org.eclipse.paho:org.eclipse.paho.client.mqttv3:1.2.5") +``` + +```java +MqttClient client = new MqttClient(BROKER_URI, clientId, persistence); +MqttConnectOptions connOpts = new MqttConnectOptions(); +connOpts.setUserName("elex"); +connOpts.setPassword("test".toCharArray()); +connOpts.setAutomaticReconnect(true); +connOpts.setCleanSession(true); + +client.connect(connOpts); +``` + +```java +MqttMessage msg = new MqttMessage(message.getBytes(StandardCharsets.UTF_8)); +msg.setQos(qos); +client.publish(topic, msg); +``` + +```java +client.subscribe(topic, qos, listener); +``` + +------ +Copyright (c) 2021. Elex. + +All Rights Reserved. + +https://www.elex-project.com/ diff --git a/mosquitto/build.gradle.kts b/mosquitto/build.gradle.kts index 5eee020..248c2fc 100644 --- a/mosquitto/build.gradle.kts +++ b/mosquitto/build.gradle.kts @@ -1,7 +1,20 @@ +/* + * Examples for Java + * + * Copyright (c) 2021. Elex. All Rights Reserved. + * https://www.elex-project.com/ + */ + plugins { id("elex-java") } dependencies { + //implementation("org.eclipse.paho:org.eclipse.paho.mqttv5.client:1.2.5") + implementation("org.eclipse.paho:org.eclipse.paho.client.mqttv3:1.2.5") + // https://mvnrepository.com/artifact/org.bouncycastle/bcprov-jdk15on + implementation("org.bouncycastle:bcprov-jdk15on:1.69") + // https://mvnrepository.com/artifact/org.bouncycastle/bcpkix-jdk15on + implementation("org.bouncycastle:bcpkix-jdk15on:1.69") } diff --git a/mosquitto/src/main/java/kr/pe/elex/examples/HelloMosquitto.java b/mosquitto/src/main/java/kr/pe/elex/examples/HelloMosquitto.java new file mode 100644 index 0000000..2a73256 --- /dev/null +++ b/mosquitto/src/main/java/kr/pe/elex/examples/HelloMosquitto.java @@ -0,0 +1,180 @@ +/* + * Examples for Java + * + * Copyright (c) 2021. Elex. All Rights Reserved. + * https://www.elex-project.com/ + */ + +package kr.pe.elex.examples; + +import lombok.extern.slf4j.Slf4j; +import org.eclipse.paho.client.mqttv3.*; +import org.eclipse.paho.client.mqttv3.persist.MemoryPersistence; + +import javax.net.ssl.SSLSocketFactory; +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.security.KeyManagementException; +import java.security.KeyStoreException; +import java.security.NoSuchAlgorithmException; +import java.security.UnrecoverableKeyException; +import java.security.cert.CertificateException; + +/** + * Mosquitto MQTT Example + * + * @author Elex + */ +@Slf4j +public final class HelloMosquitto { + static final String BROKER_URI = "tcp://127.0.0.1:1883"; + static final String BROKER_URI_TLS = "ssl://127.0.0.1:8883"; + static final String USER_NAME = "elex"; + static final String PASSWORD = "test"; + + private MqttClient client; + private MemoryPersistence persistence = new MemoryPersistence(); + + /** + * MQTT 연결 + * + * @param clientId 연결하는 클라이언트 마다 다르게 지정. + * @throws MqttException + */ + public HelloMosquitto(String clientId) throws MqttException { + client = new MqttClient(BROKER_URI, clientId, persistence); + MqttConnectOptions connOpts = new MqttConnectOptions(); + connOpts.setUserName(USER_NAME); + connOpts.setPassword(PASSWORD.toCharArray()); + connOpts.setAutomaticReconnect(true); + connOpts.setCleanSession(true); + + client.connect(connOpts); + } + + /** + * MQTT over TLS 연결 + * + * @param clientId 연결하는 클라이언트 마다 다르게 지정. + * @param socketFactory tls socket factory + * @throws MqttException + */ + public HelloMosquitto(String clientId, SSLSocketFactory socketFactory) throws MqttException { + client = new MqttClient(BROKER_URI_TLS, clientId, persistence); // 주소는 ssl로 시작하고, 포트는 8883을 사용합니다. + MqttConnectOptions connOpts = new MqttConnectOptions(); + connOpts.setUserName(USER_NAME); + connOpts.setPassword(PASSWORD.toCharArray()); + connOpts.setAutomaticReconnect(true); + connOpts.setCleanSession(true); + connOpts.setSocketFactory(socketFactory); + connOpts.setHttpsHostnameVerificationEnabled(false); + + client.connect(connOpts); + } + + + /** + * 발행 + * + * @param topic 토픽. Word separator: '/' + * @param message 메시지 + * @param qos 최대 전송 메시지 개수 + * @throws MqttException + */ + public void publish(String topic, String message, int qos) throws MqttException { + if (client.isConnected()) { + MqttMessage msg = new MqttMessage(message.getBytes(StandardCharsets.UTF_8)); + msg.setQos(qos); + + client.publish(topic, msg); + log.info("Tx: {} = {}", topic, new String(msg.getPayload(), StandardCharsets.UTF_8)); + } + } + + /** + * 구독 시작 + * + * @param topic 토픽 패턴. Wildcard char : '?' for 1 word, '#' for words + * @param qos 최대 전송 메시지 개수 + * @param listener 리스너 + * @throws MqttException + */ + public void subscribe(String topic, int qos, IMqttMessageListener listener) throws MqttException { + if (client.isConnected()) { + client.subscribe(topic, qos, listener); + } + } + + /** + * 구독 중단 + * + * @param topic 토픽 패턴 + * @throws MqttException + */ + public void unsubscribe(String topic) throws MqttException { + if (client.isConnected()) { + client.unsubscribe(topic); + } + } + + public void close() throws MqttException { + client.disconnect(); + } + + public static void main(String... args) + throws MqttException, CertificateException, UnrecoverableKeyException, NoSuchAlgorithmException, + KeyStoreException, KeyManagementException, IOException { + // TCP connection + HelloMosquitto client1 = new HelloMosquitto("Client_1"); + client1.subscribe("hello/#", 1, new IMqttMessageListener() { + @Override + public void messageArrived(String topic, MqttMessage message) throws Exception { + log.info("Rx: {} = {}", topic, new String(message.getPayload(), StandardCharsets.UTF_8)); + } + }); + client1.publish(String.join("/", "hello", "tcp"), "Hi!", 1); + + // TLS connection + HelloMosquitto client2 = new HelloMosquitto("Client_2", TlsHelper.socketFactory( + HelloMosquitto.class.getResourceAsStream("/clientstore.p12"), + "test".toCharArray(), + "test1".toCharArray(), + HelloMosquitto.class.getResourceAsStream("/truststore.p12"), + "test".toCharArray() + )); + client2.publish(String.join("/", "hello", "tls"), "Hahaha, ...", 1); + + // TLS with BC connection + HelloMosquitto client3 = new HelloMosquitto("Client_3", TlsHelperWithBouncyCastle.socketFactory( + HelloMosquitto.class.getResourceAsStream("/client.pem"), + HelloMosquitto.class.getResourceAsStream("/client.key.pem"), + "test1".toCharArray(), + HelloMosquitto.class.getResourceAsStream("/ca.pem") + )); + client3.publish(String.join("/", "hello", "tls", "bc"), "Oke, ...", 1); + + // TLS with BC connection, without client cert. + HelloMosquitto client4 = new HelloMosquitto("Client_4", TlsHelperWithBouncyCastle.socketFactory( + HelloMosquitto.class.getResourceAsStream("/ca.pem") + )); + client4.publish(String.join("/", "hello", "tls", "bc-ca-only"), "Oke, ...", 1); + + // // TLS connection, without client key store. + HelloMosquitto client5 = new HelloMosquitto("Client_4", TlsHelper.socketFactory( + HelloMosquitto.class.getResourceAsStream("/truststore.p12"), + "test".toCharArray()) + ); + client5.publish(String.join("/", "hello", "tls", "ca-only"), "Mmmm, ...", 1); + + try { + Thread.sleep(10000); + } catch (InterruptedException e) { + log.error("Interrupted ...", e); + } + client1.close(); + client2.close(); + client3.close(); + client4.close(); + client5.close(); + } +} diff --git a/mosquitto/src/main/java/kr/pe/elex/examples/TlsHelper.java b/mosquitto/src/main/java/kr/pe/elex/examples/TlsHelper.java new file mode 100644 index 0000000..7c181b1 --- /dev/null +++ b/mosquitto/src/main/java/kr/pe/elex/examples/TlsHelper.java @@ -0,0 +1,114 @@ +/* + * Examples for Java + * + * Copyright (c) 2021. Elex. All Rights Reserved. + * https://www.elex-project.com/ + */ + +package kr.pe.elex.examples; + +import javax.net.ssl.KeyManagerFactory; +import javax.net.ssl.SSLContext; +import javax.net.ssl.SSLSocketFactory; +import javax.net.ssl.TrustManagerFactory; +import java.io.IOException; +import java.io.InputStream; +import java.security.*; +import java.security.cert.CertificateException; + +/** + * @author Elex + */ +public final class TlsHelper { + private TlsHelper() { + } + + /** + * SSL Context + * + * @param keyStoreInputStream 키스토어 + * @param keyStorePassword 키스토어 비번 + * @param keyPassword 키 비번 + * @param trustStoreInputStream CA 인증서 키 스토어 + * @param trustStorePassword 트러스트 스토어 비번 + * @return + * @throws KeyStoreException + * @throws CertificateException + * @throws NoSuchAlgorithmException + * @throws IOException + * @throws UnrecoverableKeyException + * @throws KeyManagementException + */ + public static SSLContext context(InputStream keyStoreInputStream, char[] keyStorePassword, char[] keyPassword, + InputStream trustStoreInputStream, char[] trustStorePassword) + throws KeyStoreException, CertificateException, NoSuchAlgorithmException, IOException, UnrecoverableKeyException, + KeyManagementException { + // OpenSSL로 키와 인증서를 만들고, PKCS12 키 저장소에 넣었습니다. + KeyStore keyStore = KeyStore.getInstance("PKCS12"); + // 클라이언트 키와 인증서가 들어있습니다. + keyStore.load(keyStoreInputStream, + keyStorePassword); // 키스토어 비번 + KeyManagerFactory kmf = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm()); + kmf.init(keyStore, keyPassword); // 키 비번 + + KeyStore trustKeyStore = KeyStore.getInstance("PKCS12"); + // CA 인증서가 들어있습니다. + trustKeyStore.load(trustStoreInputStream, + trustStorePassword); // 키 스토어 비번 + TrustManagerFactory tmf = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm()); + tmf.init(trustKeyStore); + + SSLContext context = SSLContext.getInstance("TLSv1.3"); + context.init(kmf.getKeyManagers(), tmf.getTrustManagers(), null); + + return context; + } + + public static SSLSocketFactory socketFactory(InputStream keyStoreInputStream, char[] keyStorePassword, char[] keyPassword, + InputStream trustStoreInputStream, char[] trustStorePassword) + throws KeyStoreException, CertificateException, NoSuchAlgorithmException, IOException, UnrecoverableKeyException, + KeyManagementException { + return context(keyStoreInputStream, keyStorePassword, keyPassword, trustStoreInputStream, trustStorePassword) + .getSocketFactory(); + } + + /** + * @param trustStoreInputStream CA 인증서 키 스토어 + * @param trustStorePassword 트러스트 스토어 비번 + * @return + * @throws KeyStoreException + * @throws CertificateException + * @throws NoSuchAlgorithmException + * @throws IOException + * @throws UnrecoverableKeyException + * @throws KeyManagementException + */ + public static SSLContext context(InputStream trustStoreInputStream, char[] trustStorePassword) + throws KeyStoreException, CertificateException, NoSuchAlgorithmException, IOException, UnrecoverableKeyException, + KeyManagementException { + // OpenSSL로 키와 인증서를 만들고, PKCS12 키 저장소에 넣었습니다. + KeyStore keyStore = KeyStore.getInstance(KeyStore.getDefaultType()); + // 클라이언트 키와 인증서가 들어있습니다. + keyStore.load(null, null); // 키스토어 비번 + KeyManagerFactory kmf = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm()); + kmf.init(keyStore, null); // 키 비번 + + KeyStore trustKeyStore = KeyStore.getInstance("PKCS12"); + // CA 인증서가 들어있습니다. + trustKeyStore.load(trustStoreInputStream, + trustStorePassword); // 키 스토어 비번 + TrustManagerFactory tmf = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm()); + tmf.init(trustKeyStore); + + SSLContext context = SSLContext.getInstance("TLSv1.3"); + context.init(kmf.getKeyManagers(), tmf.getTrustManagers(), null); + + return context; + } + + public static SSLSocketFactory socketFactory(InputStream trustStoreInputStream, char[] trustStorePassword) + throws KeyStoreException, CertificateException, NoSuchAlgorithmException, IOException, UnrecoverableKeyException, + KeyManagementException { + return context(trustStoreInputStream, trustStorePassword).getSocketFactory(); + } +} diff --git a/mosquitto/src/main/java/kr/pe/elex/examples/TlsHelperWithBouncyCastle.java b/mosquitto/src/main/java/kr/pe/elex/examples/TlsHelperWithBouncyCastle.java new file mode 100644 index 0000000..f730b4f --- /dev/null +++ b/mosquitto/src/main/java/kr/pe/elex/examples/TlsHelperWithBouncyCastle.java @@ -0,0 +1,155 @@ +/* + * Examples for Java + * + * Copyright (c) 2021. Elex. All Rights Reserved. + * https://www.elex-project.com/ + */ + +package kr.pe.elex.examples; + +import org.bouncycastle.jce.provider.BouncyCastleProvider; +import org.bouncycastle.openssl.PEMDecryptorProvider; +import org.bouncycastle.openssl.PEMEncryptedKeyPair; +import org.bouncycastle.openssl.PEMKeyPair; +import org.bouncycastle.openssl.PEMParser; +import org.bouncycastle.openssl.jcajce.JcaPEMKeyConverter; +import org.bouncycastle.openssl.jcajce.JcePEMDecryptorProviderBuilder; + +import javax.net.ssl.KeyManagerFactory; +import javax.net.ssl.SSLContext; +import javax.net.ssl.SSLSocketFactory; +import javax.net.ssl.TrustManagerFactory; +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.security.*; +import java.security.cert.CertificateException; +import java.security.cert.CertificateFactory; +import java.security.cert.X509Certificate; + +/** + * @author Elex + */ +public final class TlsHelperWithBouncyCastle { + private TlsHelperWithBouncyCastle() { + } + + /** + * Bouncy Castle을 이용. + * + * @param clientCrtInputStream cert pem + * @param clientKeyInputStream key pem + * @param clientPassword key password + * @param caCrtInputStream ca cert pem + * @return + */ + public static SSLSocketFactory socketFactory(InputStream clientCrtInputStream, InputStream clientKeyInputStream, char[] clientPassword, + InputStream caCrtInputStream) + throws KeyStoreException, IOException, CertificateException, UnrecoverableKeyException, KeyManagementException, NoSuchAlgorithmException { + return context(clientCrtInputStream, clientKeyInputStream, clientPassword, caCrtInputStream).getSocketFactory(); + } + + public static SSLContext context(InputStream clientCrtInputStream, InputStream clientKeyInputStream, char[] clientPassword, + InputStream caCrtInputStream) + throws KeyStoreException, IOException, CertificateException, UnrecoverableKeyException, KeyManagementException, NoSuchAlgorithmException { + Security.addProvider(new BouncyCastleProvider()); + + // CA 인증서 불러오기 + CertificateFactory caCertFactory = CertificateFactory.getInstance("X.509"); + PEMParser parser = new PEMParser(new InputStreamReader(caCrtInputStream)); + X509Certificate caCert = (X509Certificate) caCertFactory + .generateCertificate(new ByteArrayInputStream(parser.readPemObject().getContent())); + parser.close(); + + // 클라이언트 인증서 불러오기 + CertificateFactory clientCertFactory = CertificateFactory.getInstance("X.509"); + parser = new PEMParser(new InputStreamReader(clientCrtInputStream)); + X509Certificate clientCert = (X509Certificate) clientCertFactory + .generateCertificate(new ByteArrayInputStream(parser.readPemObject().getContent())); + parser.close(); + + // 클라이언트 비밀키 불러오기 + parser = new PEMParser(new InputStreamReader(clientKeyInputStream)); + Object object = parser.readObject(); + JcaPEMKeyConverter converter = new JcaPEMKeyConverter().setProvider("BC"); + KeyPair keyPair; + if (object instanceof PEMEncryptedKeyPair) { + // 암호화된 키라면 패스워드가 필요하다. + PEMEncryptedKeyPair pemKeyPair = (PEMEncryptedKeyPair) object; + PEMDecryptorProvider decProv = new JcePEMDecryptorProviderBuilder().build(clientPassword); + keyPair = converter.getKeyPair(pemKeyPair.decryptKeyPair(decProv)); + } else { + // 암호화되지 않은 키라면 비밀번호가 필요없다. + PEMKeyPair pemKeyPair = (PEMKeyPair) object; + keyPair = converter.getKeyPair(pemKeyPair); + } + parser.close(); + PrivateKey clientKey = keyPair.getPrivate(); + + // CA 인증서를 사용해서 트러스트 스토어를 만든다. + KeyStore caKeyStore = KeyStore.getInstance(KeyStore.getDefaultType()); + caKeyStore.load(null, null); + caKeyStore.setCertificateEntry("ca-certificate", caCert); + TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm()); + trustManagerFactory.init(caKeyStore); + + // 클라이언트 인증서와 비밀키를 사용해서 키 스토어를 만든다. + KeyStore clientKeyStore = KeyStore.getInstance(KeyStore.getDefaultType()); + clientKeyStore.load(null, null); + clientKeyStore.setCertificateEntry("certificate", clientCert); + clientKeyStore.setKeyEntry("private-key", clientKey, clientPassword, new java.security.cert.Certificate[]{clientCert}); + KeyManagerFactory keyManagerFactory = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm()); + keyManagerFactory.init(clientKeyStore, clientPassword); + + SSLContext context = SSLContext.getInstance("TLSv1.3"); + context.init(keyManagerFactory.getKeyManagers(), trustManagerFactory.getTrustManagers(), null); + return context; + } + + /** + * BouncyCastle을 이용. + * + * @param caCrtInputStream ca cert pem + * @return + * @throws KeyStoreException + * @throws IOException + * @throws CertificateException + * @throws UnrecoverableKeyException + * @throws KeyManagementException + * @throws NoSuchAlgorithmException + */ + public static SSLContext context(InputStream caCrtInputStream) + throws KeyStoreException, IOException, CertificateException, UnrecoverableKeyException, KeyManagementException, NoSuchAlgorithmException { + Security.addProvider(new BouncyCastleProvider()); + + // CA 인증서 불러오기 + CertificateFactory caCertFactory = CertificateFactory.getInstance("X.509"); + PEMParser parser = new PEMParser(new InputStreamReader(caCrtInputStream)); + X509Certificate caCert = (X509Certificate) caCertFactory + .generateCertificate(new ByteArrayInputStream(parser.readPemObject().getContent())); + parser.close(); + + // CA 인증서를 사용해서 트러스트 스토어를 만든다. + KeyStore caKeyStore = KeyStore.getInstance(KeyStore.getDefaultType()); + caKeyStore.load(null, null); + caKeyStore.setCertificateEntry("ca-certificate", caCert); + TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm()); + trustManagerFactory.init(caKeyStore); + + // 클라이언트 인증서와 비밀키는 없으므로, 그냥 만든다. + KeyStore clientKeyStore = KeyStore.getInstance(KeyStore.getDefaultType()); + clientKeyStore.load(null, null); + KeyManagerFactory keyManagerFactory = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm()); + keyManagerFactory.init(clientKeyStore, null); + + SSLContext context = SSLContext.getInstance("TLSv1.3"); + context.init(keyManagerFactory.getKeyManagers(), trustManagerFactory.getTrustManagers(), null); + return context; + } + + public static SSLSocketFactory socketFactory(InputStream caCrtInputStream) + throws KeyStoreException, IOException, CertificateException, UnrecoverableKeyException, KeyManagementException, NoSuchAlgorithmException { + return context(caCrtInputStream).getSocketFactory(); + } +} diff --git a/mosquitto/src/main/java/kr/pe/elex/examples/package-info.java b/mosquitto/src/main/java/kr/pe/elex/examples/package-info.java new file mode 100644 index 0000000..bfb14b8 --- /dev/null +++ b/mosquitto/src/main/java/kr/pe/elex/examples/package-info.java @@ -0,0 +1,8 @@ +/* + * Examples for Java + * + * Copyright (c) 2021. Elex. All Rights Reserved. + * https://www.elex-project.com/ + */ + +package kr.pe.elex.examples; diff --git a/rabbit-mq/README.md b/rabbit-mq/README.md new file mode 100644 index 0000000..91fefb3 --- /dev/null +++ b/rabbit-mq/README.md @@ -0,0 +1,62 @@ +# RabbitMQ Client Example + +## Docker에 설치 + +```bash +#!/bin/bash +docker run -d --restart=no --name=rabbitmq \ +--hostname rabbit-mq \ +-p 5672:5672 \ +-p 15672:15672 \ +-v /docker/rabbitmq/rabbitmq.conf:/etc/rabbitmq/rabbitmq.conf \ +-v docker/rabbitmq/conf.d:/etc/rabbitmq/conf.d \ +-e TZ=Asia/Seoul -e LANG=ko_KR.UTF-8 \ +-e RABBITMQ_DEFAULT_USER=elex -e RABBITMQ_DEFAULT_PASS=test \ +rabbitmq:3.8.11-management +``` + +## Gradle 디펜던시 추가 +```kotlin +// https://mvnrepository.com/artifact/com.rabbitmq/amqp-client +implementation("com.rabbitmq:amqp-client:5.10.0") +``` + +## 다이렉트 익스체인지 +* 익스체인지는 브로커가 메시지를 받는 곳이다. 즉, 메시지를 보낼 때는 익스체인지로 보내야 한다. +* 큐는 브로커가 메시지를 보내는 곳이다. 즉, 메시지를 받을 때는 큐로부터 받는다. +* 익스체인지와 큐는 라우팅-키로 서로 바인딩된다. 여러 개의 라우팅-키로 여러 번 바인딩 할 수도 있다. +* 익스체인지에 메시지가 도착하면 메시지의 라우팅-키와 일치하는 큐로 메시지를 보낸다. + +## 팬아웃 익스체인지 +* 라우팅-키 규칙이 무시된다. + +## 토픽 익스체인지 +* 라우팅-키를 패턴으로 사용한다. + +## TLS +```bash +!/bin/bash +docker run -d --restart=always --name=rabbitmq \ +--hostname rabbitmq \ +-p 5671:5671 \ +-p 5672:5672 \ +-p 15671:15671 \ +-p 15672:15672 \ +-v /media/rabbitmq/certs:/etc/certs \ +-e TZ=Asia/Seoul -e LANG=ko_KR.UTF-8 \ +-e RABBITMQ_DEFAULT_USER=elex \ +-e RABBITMQ_DEFAULT_PASS=test \ +-e RABBITMQ_SSL_CACERTFILE=/etc/certs/ca.pem \ +-e RABBITMQ_SSL_CERTFILE=/etc/certs/server.pem \ +-e RABBITMQ_SSL_KEYFILE=/etc/certs/server.key.pem \ +-e RABBITMQ_SSL_FAIL_IF_NO_PEER_CERT=false \ +-e RABBITMQ_SSL_VERIFY=verify_none \ +rabbitmq:3.8.11-management +``` + +----- +Copyright (c) 2021 Elex. + +All Rights Reserved. + +https://www.elex-project.com/ diff --git a/rabbit-mq/build.gradle.kts b/rabbit-mq/build.gradle.kts index 5eee020..878560a 100644 --- a/rabbit-mq/build.gradle.kts +++ b/rabbit-mq/build.gradle.kts @@ -1,7 +1,20 @@ +/* + * Examples for Java + * + * Copyright (c) 2021. Elex. All Rights Reserved. + * https://www.elex-project.com/ + */ + plugins { id("elex-java") } dependencies { + // https://mvnrepository.com/artifact/com.rabbitmq/amqp-client + implementation("com.rabbitmq:amqp-client:5.13.0") + // https://mvnrepository.com/artifact/org.bouncycastle/bcprov-jdk15on + implementation("org.bouncycastle:bcprov-jdk15on:1.69") + // https://mvnrepository.com/artifact/org.bouncycastle/bcpkix-jdk15on + implementation("org.bouncycastle:bcpkix-jdk15on:1.69") } diff --git a/rabbit-mq/src/main/java/kr/pe/elex/examples/fanout/RabbitClient.java b/rabbit-mq/src/main/java/kr/pe/elex/examples/fanout/RabbitClient.java new file mode 100644 index 0000000..1788547 --- /dev/null +++ b/rabbit-mq/src/main/java/kr/pe/elex/examples/fanout/RabbitClient.java @@ -0,0 +1,118 @@ +/* + * Copyright (c) 2021. Elex. All Rights Reserved. + * https://www.elex-project.com/ + */ + +package kr.pe.elex.examples.fanout; + +import com.rabbitmq.client.*; +import lombok.extern.slf4j.Slf4j; + +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.util.concurrent.TimeoutException; + +/** + * 팬 아웃 익스체인지는 라우팅-키 규칙에 무관하게 메시지를 전달한다. + * @author Elex + * @see "https://www.rabbitmq.com/tutorials/tutorial-two-java.html" + */ +@Slf4j +public class RabbitClient { + private static final String EXCHANGE = "elex.fanout.exchange"; + + private Connection connection; + private Channel channel; + private String queue; + + RabbitClient() throws IOException, TimeoutException { + ConnectionFactory connectionFactory = new ConnectionFactory(); + connectionFactory.setHost("localhost"); + connectionFactory.setPort(5672); + connectionFactory.setUsername("elex"); + connectionFactory.setPassword("test"); + connectionFactory.setVirtualHost("/"); + + connection = connectionFactory.newConnection(); + channel = connection.createChannel(); + + // fanout은 routing-key 규칙을 무시하고, 모든 큐에 메시지를 전달합니다. + channel.exchangeDeclare(EXCHANGE, BuiltinExchangeType.FANOUT, false); + + // 큐 이름을 랜덤으로 생성합니다. + queue = channel.queueDeclare().getQueue(); + + // 익스체인지와 큐를 묶습니다. + channel.queueBind(queue, EXCHANGE, ""); + + } + + public void consume(String consumerTag) throws IOException { + // 큐로부터 메시지를 받습니다. + channel.basicConsume(queue, true, consumerTag, new DefaultConsumer(channel) { + @Override + public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException { + log.info("Rx: [{}] {}", consumerTag, new String(body, StandardCharsets.UTF_8)); + } + }); + } + + public void publish(String routingKey, String message) throws IOException { + // 익스체인지에 메시지를 보냅니다. + channel.basicPublish(EXCHANGE, routingKey, null, message.getBytes(StandardCharsets.UTF_8)); + log.info("Tx: [{}] {}", routingKey, message); + } + + public void close() throws IOException, TimeoutException { + channel.close(); + connection.close(); + } + + public static void main(String... args) throws IOException, TimeoutException { + RabbitClient producer = new RabbitClient(); + RabbitClient consumer1 = new RabbitClient(); + + consumer1.consume(""); + + for (int i = 0; i < 10; i++) { + producer.publish(String.valueOf(i), "Hello, " + i); + try { + Thread.sleep(100); + } catch (InterruptedException e) { + log.error("Interrupted..", e); + } + } + + try { + Thread.sleep(10000); + } catch (InterruptedException e) { + log.error("Interrupted..", e); + } + producer.close(); + consumer1.close(); + + /* +11:12:40.969 [main] INFO kr.pe.elex.rabbitmq.fanout.RabbitClient - Tx: [0] Hello, 0 +11:12:40.969 [pool-3-thread-4] INFO kr.pe.elex.rabbitmq.fanout.RabbitClient - Rx: [amq.ctag-NJH49xhJFHvgAo90c1qEiA] Hello, 0 +11:12:41.074 [main] INFO kr.pe.elex.rabbitmq.fanout.RabbitClient - Tx: [1] Hello, 1 +11:12:41.077 [pool-3-thread-5] INFO kr.pe.elex.rabbitmq.fanout.RabbitClient - Rx: [amq.ctag-NJH49xhJFHvgAo90c1qEiA] Hello, 1 +11:12:41.175 [main] INFO kr.pe.elex.rabbitmq.fanout.RabbitClient - Tx: [2] Hello, 2 +11:12:41.178 [pool-3-thread-6] INFO kr.pe.elex.rabbitmq.fanout.RabbitClient - Rx: [amq.ctag-NJH49xhJFHvgAo90c1qEiA] Hello, 2 +11:12:41.276 [main] INFO kr.pe.elex.rabbitmq.fanout.RabbitClient - Tx: [3] Hello, 3 +11:12:41.279 [pool-3-thread-7] INFO kr.pe.elex.rabbitmq.fanout.RabbitClient - Rx: [amq.ctag-NJH49xhJFHvgAo90c1qEiA] Hello, 3 +11:12:41.377 [main] INFO kr.pe.elex.rabbitmq.fanout.RabbitClient - Tx: [4] Hello, 4 +11:12:41.380 [pool-3-thread-8] INFO kr.pe.elex.rabbitmq.fanout.RabbitClient - Rx: [amq.ctag-NJH49xhJFHvgAo90c1qEiA] Hello, 4 +11:12:41.478 [main] INFO kr.pe.elex.rabbitmq.fanout.RabbitClient - Tx: [5] Hello, 5 +11:12:41.481 [pool-3-thread-9] INFO kr.pe.elex.rabbitmq.fanout.RabbitClient - Rx: [amq.ctag-NJH49xhJFHvgAo90c1qEiA] Hello, 5 +11:12:41.579 [main] INFO kr.pe.elex.rabbitmq.fanout.RabbitClient - Tx: [6] Hello, 6 +11:12:41.582 [pool-3-thread-10] INFO kr.pe.elex.rabbitmq.fanout.RabbitClient - Rx: [amq.ctag-NJH49xhJFHvgAo90c1qEiA] Hello, 6 +11:12:41.681 [main] INFO kr.pe.elex.rabbitmq.fanout.RabbitClient - Tx: [7] Hello, 7 +11:12:41.684 [pool-3-thread-11] INFO kr.pe.elex.rabbitmq.fanout.RabbitClient - Rx: [amq.ctag-NJH49xhJFHvgAo90c1qEiA] Hello, 7 +11:12:41.782 [main] INFO kr.pe.elex.rabbitmq.fanout.RabbitClient - Tx: [8] Hello, 8 +11:12:41.785 [pool-3-thread-12] INFO kr.pe.elex.rabbitmq.fanout.RabbitClient - Rx: [amq.ctag-NJH49xhJFHvgAo90c1qEiA] Hello, 8 +11:12:41.883 [main] INFO kr.pe.elex.rabbitmq.fanout.RabbitClient - Tx: [9] Hello, 9 +11:12:41.886 [pool-3-thread-13] INFO kr.pe.elex.rabbitmq.fanout.RabbitClient - Rx: [amq.ctag-NJH49xhJFHvgAo90c1qEiA] Hello, 9 + + */ + } +} diff --git a/rabbit-mq/src/main/java/kr/pe/elex/examples/hello/HelloRabbit.java b/rabbit-mq/src/main/java/kr/pe/elex/examples/hello/HelloRabbit.java new file mode 100644 index 0000000..15b88a5 --- /dev/null +++ b/rabbit-mq/src/main/java/kr/pe/elex/examples/hello/HelloRabbit.java @@ -0,0 +1,115 @@ +/* + * Copyright (c) 2021. Elex. All Rights Reserved. + * https://www.elex-project.com/ + */ + +package kr.pe.elex.examples.hello; + +import com.rabbitmq.client.*; +import lombok.extern.slf4j.Slf4j; + +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.util.concurrent.TimeoutException; + +/** + * 다이렉트 익스체인지 샘플 + *

+ * 익스체인지는 브로커가 메시지를 받는 곳이다. + * 큐는 브로커가 메시지를 보내는 곳이다. + * 익스체인지와 큐는 라우팅-키로 서로 바인딩된다. + *

+ * 익스체인지에 메시지가 도착하면 메시지의 라우팅-키와 일치하는 큐로 메시지를 보낸다. + * + * @author Elex + * @see "https://www.rabbitmq.com/tutorials/tutorial-one-java.html" + */ +@Slf4j +public class HelloRabbit { + private static final String EXCHANGE = "elex.direct.exchange"; + private static final String QUEUE = "elex.queue.01"; + private static final String ROUTING_KEY = "elex-routing-key"; + + private Connection connection; + private Channel channel; + + HelloRabbit() throws IOException, TimeoutException { + ConnectionFactory connectionFactory = new ConnectionFactory(); + connectionFactory.setHost("localhost"); + connectionFactory.setPort(5672); + connectionFactory.setUsername("elex"); + connectionFactory.setPassword("test"); + connectionFactory.setVirtualHost("/"); + + connection = connectionFactory.newConnection(); + channel = connection.createChannel(); + + // 익스체인지는 브로커가 메시지를 받는 곳입니다. + channel.exchangeDeclare(EXCHANGE, BuiltinExchangeType.DIRECT, true); + + // 큐는 브로커가 메시지를 보내는 곳입니다. + channel.queueDeclare(QUEUE, false, false, false, null); + + // 익스체인지와 큐를 묶습니다. + channel.queueBind(QUEUE, EXCHANGE, ROUTING_KEY); + + // 큐로부터 메시지를 받습니다. + channel.basicConsume(QUEUE, true, ROUTING_KEY, new DefaultConsumer(channel) { + @Override + public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException { + log.info("Rx: {}", new String(body, StandardCharsets.UTF_8)); + } + }); + } + + public void publish(String message) throws IOException { + // 익스체인지에 메시지를 보냅니다. + channel.basicPublish(EXCHANGE, ROUTING_KEY, null, message.getBytes(StandardCharsets.UTF_8)); + log.info("Tx: {}", message); + } + + public void close() throws IOException, TimeoutException { + channel.close(); + connection.close(); + } + + public static void main(String... args) throws IOException, TimeoutException { + HelloRabbit helloRabbit = new HelloRabbit(); + for (int i = 0; i < 10; i++) { + helloRabbit.publish("Hello, " + i); + try { + Thread.sleep(1000); + } catch (InterruptedException e) { + log.error("Interrupted..", e); + } + } + try { + Thread.sleep(10000); + } catch (InterruptedException e) { + log.error("Interrupted..", e); + } + helloRabbit.close(); + /* +11:01:31.502 [main] INFO kr.pe.elex.rabbitmq.hello.HelloRabbit - Tx: Hello, 0 +11:01:31.502 [pool-1-thread-4] INFO kr.pe.elex.rabbitmq.hello.HelloRabbit - Rx: Hello, 0 +11:01:32.505 [main] INFO kr.pe.elex.rabbitmq.hello.HelloRabbit - Tx: Hello, 1 +11:01:32.509 [pool-1-thread-5] INFO kr.pe.elex.rabbitmq.hello.HelloRabbit - Rx: Hello, 1 +11:01:33.507 [main] INFO kr.pe.elex.rabbitmq.hello.HelloRabbit - Tx: Hello, 2 +11:01:33.510 [pool-1-thread-6] INFO kr.pe.elex.rabbitmq.hello.HelloRabbit - Rx: Hello, 2 +11:01:34.508 [main] INFO kr.pe.elex.rabbitmq.hello.HelloRabbit - Tx: Hello, 3 +11:01:34.511 [pool-1-thread-7] INFO kr.pe.elex.rabbitmq.hello.HelloRabbit - Rx: Hello, 3 +11:01:35.509 [main] INFO kr.pe.elex.rabbitmq.hello.HelloRabbit - Tx: Hello, 4 +11:01:35.512 [pool-1-thread-8] INFO kr.pe.elex.rabbitmq.hello.HelloRabbit - Rx: Hello, 4 +11:01:36.511 [main] INFO kr.pe.elex.rabbitmq.hello.HelloRabbit - Tx: Hello, 5 +11:01:36.514 [pool-1-thread-9] INFO kr.pe.elex.rabbitmq.hello.HelloRabbit - Rx: Hello, 5 +11:01:37.512 [main] INFO kr.pe.elex.rabbitmq.hello.HelloRabbit - Tx: Hello, 6 +11:01:37.515 [pool-1-thread-10] INFO kr.pe.elex.rabbitmq.hello.HelloRabbit - Rx: Hello, 6 +11:01:38.513 [main] INFO kr.pe.elex.rabbitmq.hello.HelloRabbit - Tx: Hello, 7 +11:01:38.516 [pool-1-thread-11] INFO kr.pe.elex.rabbitmq.hello.HelloRabbit - Rx: Hello, 7 +11:01:39.514 [main] INFO kr.pe.elex.rabbitmq.hello.HelloRabbit - Tx: Hello, 8 +11:01:39.517 [pool-1-thread-12] INFO kr.pe.elex.rabbitmq.hello.HelloRabbit - Rx: Hello, 8 +11:01:40.515 [main] INFO kr.pe.elex.rabbitmq.hello.HelloRabbit - Tx: Hello, 9 +11:01:40.518 [pool-1-thread-13] INFO kr.pe.elex.rabbitmq.hello.HelloRabbit - Rx: Hello, 9 + */ + } +} diff --git a/rabbit-mq/src/main/java/kr/pe/elex/examples/hello/HelloRabbit2.java b/rabbit-mq/src/main/java/kr/pe/elex/examples/hello/HelloRabbit2.java new file mode 100644 index 0000000..3cdf9ae --- /dev/null +++ b/rabbit-mq/src/main/java/kr/pe/elex/examples/hello/HelloRabbit2.java @@ -0,0 +1,105 @@ +/* + * Copyright (c) 2021. Elex. All Rights Reserved. + * https://www.elex-project.com/ + */ + +package kr.pe.elex.examples.hello; + +import com.rabbitmq.client.*; +import lombok.extern.slf4j.Slf4j; + +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.util.concurrent.TimeoutException; + +/** + * 다이렉트 익스체인지 샘플 + *

+ * 메시지 소비자는 메시지 처리 후 ack를 보내야 한다. + * QoS를 지정해서 소비자에 전달할 메시지의 최대 개수를 지정할 수 있다. + * + * @author Elex + * @see "https://www.rabbitmq.com/tutorials/tutorial-two-java.html" + */ +@Slf4j +public class HelloRabbit2 { + private static final String EXCHANGE = "elex.direct.exchange"; + private static final String QUEUE = "elex.queue"; + private static final String ROUTING_KEY = "elex-routing-key"; + + private Connection connection; + private Channel channel; + + HelloRabbit2() throws IOException, TimeoutException { + ConnectionFactory connectionFactory = new ConnectionFactory(); + connectionFactory.setHost("localhost"); + connectionFactory.setPort(5672); + connectionFactory.setUsername("elex"); + connectionFactory.setPassword("test"); + connectionFactory.setVirtualHost("/"); + connectionFactory.setAutomaticRecoveryEnabled(true); + + connection = connectionFactory.newConnection(); + channel = connection.createChannel(); + + // 익스체인지는 브로커가 메시지를 받는 곳입니다. + channel.exchangeDeclare(EXCHANGE, BuiltinExchangeType.DIRECT, true); + + // 큐는 브로커가 메시지를 보내는 곳입니다. + channel.queueDeclare(QUEUE, false, false, false, null); + + // 익스체인지와 큐를 묶습니다. + channel.queueBind(QUEUE, EXCHANGE, ROUTING_KEY); + + // 메시지 소비자에 전달할 메시지의 최대 개수입니다. ack를 받을 때까지 메시지 전송을 미룰 수 있습니다. + channel.basicQos(1); + + // 큐로부터 메시지를 받습니다. + channel.basicConsume(QUEUE, false, ROUTING_KEY, new DefaultConsumer(channel) { + @Override + public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException { + log.info("Rx: {}", new String(body, StandardCharsets.UTF_8)); + + // 만일, 수신 확인을 하지 않으면, 브로커는 다시 전송을 시도할겁니다. + channel.basicAck(envelope.getDeliveryTag(), false); + } + }); + + } + + public void publish(String message) throws IOException, TimeoutException, InterruptedException { + // 익스체인지에 메시지를 보냅니다. + channel.basicPublish(EXCHANGE, ROUTING_KEY, + // 브로커가 메시지를 디스크에 저장해둠으로써, 오류 등으로 브로커가 종료되었을 경우에 + // 미처 전달되지 못한 메시지가 사라지는 것을 예방합니다. + MessageProperties.PERSISTENT_TEXT_PLAIN, + message.getBytes(StandardCharsets.UTF_8)); + + // 메시지가 전달되지 않으면 예외가 발생합니다. + channel.waitForConfirmsOrDie(1000); + + log.info("Tx: {}", message); + } + + public void close() throws IOException, TimeoutException { + channel.close(); + connection.close(); + } + + public static void main(String... args) throws IOException, TimeoutException { + HelloRabbit2 helloRabbit = new HelloRabbit2(); + for (int i = 0; i < 10; i++) { + try { + helloRabbit.publish("Hello, " + i); + } catch (TimeoutException | InterruptedException e) { + log.error("Publish fail..", e); + } + try { + Thread.sleep(1000); + } catch (InterruptedException e) { + log.error("Interrupted..", e); + } + } + helloRabbit.close(); + } +} diff --git a/rabbit-mq/src/main/java/kr/pe/elex/examples/loadbalance/RabbitClient.java b/rabbit-mq/src/main/java/kr/pe/elex/examples/loadbalance/RabbitClient.java new file mode 100644 index 0000000..e7f9739 --- /dev/null +++ b/rabbit-mq/src/main/java/kr/pe/elex/examples/loadbalance/RabbitClient.java @@ -0,0 +1,136 @@ +/* + * Copyright (c) 2021. Elex. All Rights Reserved. + * https://www.elex-project.com/ + */ + +package kr.pe.elex.examples.loadbalance; + +import com.rabbitmq.client.*; +import lombok.extern.slf4j.Slf4j; + +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.util.concurrent.TimeoutException; + +/** + * 하나의 큐에 여러 개의 컨슈머를 할당합니다. + * + * @author Elex + * @see "https://www.rabbitmq.com/tutorials/tutorial-two-java.html" + */ +@Slf4j +public class RabbitClient { + private static final String EXCHANGE = "elex.direct.exchange"; + private static final String QUEUE = "elex.queue"; + private static final String ROUTING_KEY = "elex-routing-key"; + + private String name; + + private Connection connection; + private Channel channel; + + RabbitClient(String name) throws IOException, TimeoutException { + this.name = name; + + ConnectionFactory connectionFactory = new ConnectionFactory(); + connectionFactory.setHost("localhost"); + connectionFactory.setPort(5672); + connectionFactory.setUsername("elex"); + connectionFactory.setPassword("test"); + connectionFactory.setVirtualHost("/"); + + connection = connectionFactory.newConnection(); + channel = connection.createChannel(); + + // 익스체인지는 브로커가 메시지를 받는 곳입니다. + channel.exchangeDeclare(EXCHANGE, BuiltinExchangeType.DIRECT, true); + + // 큐는 브로커가 메시지를 보내는 곳입니다. + channel.queueDeclare(QUEUE, false, false, false, null); + + // 익스체인지와 큐를 묶습니다. + channel.queueBind(QUEUE, EXCHANGE, ROUTING_KEY); + + // 메시지 소비자에 전달할 메시지의 최대 개수입니다. ack를 받을 때까지 메시지 전송을 미룰 수 있습니다. + channel.basicQos(1); + } + + public void consume(String consumerTag) throws IOException { + // 큐로부터 메시지를 받습니다. + channel.basicConsume(QUEUE, false, consumerTag, new DefaultConsumer(channel) { + @Override + public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException { + log.info("Rx: [{}] {}", name, new String(body, StandardCharsets.UTF_8)); + try { + // 메시지를 처리하는데 시간이 좀 걸린다고 가정합니다. + Thread.sleep(1000); + } catch (InterruptedException e) { + log.error("Interrupted..", e); + } + // 메시지 처리 후 ack를 보냅니다. + channel.basicAck(envelope.getDeliveryTag(), false); + } + }); + } + + public void publish(String message) throws IOException { + // 익스체인지에 메시지를 보냅니다. + channel.basicPublish(EXCHANGE, ROUTING_KEY, null, message.getBytes(StandardCharsets.UTF_8)); + log.info("Tx: [{}] {}", name, message); + } + + public void close() throws IOException, TimeoutException { + channel.close(); + connection.close(); + } + + public static void main(String... args) throws IOException, TimeoutException { + RabbitClient producer = new RabbitClient("Producer"); + RabbitClient consumer1 = new RabbitClient("Consumer1"); + RabbitClient consumer2 = new RabbitClient("Consumer2"); + RabbitClient consumer3 = new RabbitClient("Consumer3"); + consumer1.consume(ROUTING_KEY); + consumer2.consume(ROUTING_KEY); + consumer3.consume(ROUTING_KEY); + for (int i = 0; i < 10; i++) { + producer.publish("Hello, " + i); + try { + Thread.sleep(100); + } catch (InterruptedException e) { + log.error("Interrupted..", e); + } + } + + try { + Thread.sleep(10000); + } catch (InterruptedException e) { + log.error("Interrupted..", e); + } + producer.close(); + consumer1.close(); + consumer2.close(); + consumer3.close(); + /* +10:59:25.659 [main] INFO kr.pe.elex.rabbitmq.loadbalance.RabbitClient - Tx: [Producer] Hello, 0 +10:59:25.659 [pool-3-thread-4] INFO kr.pe.elex.rabbitmq.loadbalance.RabbitClient - Rx: [Consumer1] Hello, 0 +10:59:25.762 [main] INFO kr.pe.elex.rabbitmq.loadbalance.RabbitClient - Tx: [Producer] Hello, 1 +10:59:25.765 [pool-5-thread-4] INFO kr.pe.elex.rabbitmq.loadbalance.RabbitClient - Rx: [Consumer2] Hello, 1 +10:59:25.863 [main] INFO kr.pe.elex.rabbitmq.loadbalance.RabbitClient - Tx: [Producer] Hello, 2 +10:59:25.866 [pool-7-thread-4] INFO kr.pe.elex.rabbitmq.loadbalance.RabbitClient - Rx: [Consumer3] Hello, 2 +10:59:25.965 [main] INFO kr.pe.elex.rabbitmq.loadbalance.RabbitClient - Tx: [Producer] Hello, 3 +10:59:26.066 [main] INFO kr.pe.elex.rabbitmq.loadbalance.RabbitClient - Tx: [Producer] Hello, 4 +10:59:26.167 [main] INFO kr.pe.elex.rabbitmq.loadbalance.RabbitClient - Tx: [Producer] Hello, 5 +10:59:26.268 [main] INFO kr.pe.elex.rabbitmq.loadbalance.RabbitClient - Tx: [Producer] Hello, 6 +10:59:26.369 [main] INFO kr.pe.elex.rabbitmq.loadbalance.RabbitClient - Tx: [Producer] Hello, 7 +10:59:26.470 [main] INFO kr.pe.elex.rabbitmq.loadbalance.RabbitClient - Tx: [Producer] Hello, 8 +10:59:26.571 [main] INFO kr.pe.elex.rabbitmq.loadbalance.RabbitClient - Tx: [Producer] Hello, 9 +10:59:26.664 [pool-3-thread-5] INFO kr.pe.elex.rabbitmq.loadbalance.RabbitClient - Rx: [Consumer1] Hello, 3 +10:59:26.767 [pool-5-thread-5] INFO kr.pe.elex.rabbitmq.loadbalance.RabbitClient - Rx: [Consumer2] Hello, 4 +10:59:26.869 [pool-7-thread-5] INFO kr.pe.elex.rabbitmq.loadbalance.RabbitClient - Rx: [Consumer3] Hello, 5 +10:59:27.665 [pool-3-thread-5] INFO kr.pe.elex.rabbitmq.loadbalance.RabbitClient - Rx: [Consumer1] Hello, 6 +10:59:27.768 [pool-5-thread-5] INFO kr.pe.elex.rabbitmq.loadbalance.RabbitClient - Rx: [Consumer2] Hello, 7 +10:59:27.870 [pool-7-thread-5] INFO kr.pe.elex.rabbitmq.loadbalance.RabbitClient - Rx: [Consumer3] Hello, 8 +10:59:28.666 [pool-3-thread-5] INFO kr.pe.elex.rabbitmq.loadbalance.RabbitClient - Rx: [Consumer1] Hello, 9 +*/ + } +} diff --git a/rabbit-mq/src/main/java/kr/pe/elex/examples/package-info.java b/rabbit-mq/src/main/java/kr/pe/elex/examples/package-info.java new file mode 100644 index 0000000..62cdc15 --- /dev/null +++ b/rabbit-mq/src/main/java/kr/pe/elex/examples/package-info.java @@ -0,0 +1,6 @@ +/* + * Copyright (c) 2021. Elex. All Rights Reserved. + * https://www.elex-project.com/ + */ + +package kr.pe.elex.examples; diff --git a/rabbit-mq/src/main/java/kr/pe/elex/examples/rpc/HelloRabbit.java b/rabbit-mq/src/main/java/kr/pe/elex/examples/rpc/HelloRabbit.java new file mode 100644 index 0000000..545f920 --- /dev/null +++ b/rabbit-mq/src/main/java/kr/pe/elex/examples/rpc/HelloRabbit.java @@ -0,0 +1,120 @@ +/* + * Copyright (c) 2021. Elex. All Rights Reserved. + * https://www.elex-project.com/ + */ + +package kr.pe.elex.examples.rpc; + +import com.rabbitmq.client.*; +import lombok.extern.slf4j.Slf4j; + +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.util.HashMap; +import java.util.Map; +import java.util.UUID; +import java.util.concurrent.TimeoutException; + +import static kr.pe.elex.examples.rpc.HelloRabbitServer.EXCHANGE; +import static kr.pe.elex.examples.rpc.HelloRabbitServer.ROUTING_KEY; + +/** + * {@link AMQP.BasicProperties.Builder#replyTo(String)}를 사용해서 응답받을 라우팅-키를 전달할 수 있다. + * + * @author Elex + * @see "https://www.rabbitmq.com/tutorials/tutorial-six-java.html" + */ +@Slf4j +public class HelloRabbit { + private static final String CLIENT_ROUTING_KEY = "client-routing-key"; + private Connection connection; + private Channel channel; + private String queue; + + private Map handlers = new HashMap<>(); + + HelloRabbit() throws IOException, TimeoutException { + ConnectionFactory connectionFactory = new ConnectionFactory(); + connectionFactory.setHost("localhost"); + connectionFactory.setPort(5672); + connectionFactory.setUsername("elex"); + connectionFactory.setPassword("test"); + connectionFactory.setVirtualHost("/"); + + connection = connectionFactory.newConnection(); + channel = connection.createChannel(); + + // 익스체인지는 브로커가 메시지를 받는 곳입니다. + channel.exchangeDeclare(EXCHANGE, BuiltinExchangeType.DIRECT, true); + // 큐는 브로커가 메시지를 보내는 곳입니다. + queue = channel.queueDeclare().getQueue(); + // 익스체인지와 큐를 묶습니다. + channel.queueBind(queue, EXCHANGE, CLIENT_ROUTING_KEY); + + // 큐로부터 메시지를 받습니다. + channel.basicConsume(queue, false, queue, new DefaultConsumer(channel) { + @Override + public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException { + // 메시지 아이디로 핸들러를 가져옵니다. + Handler handler = handlers.remove(UUID.fromString(properties.getCorrelationId())); + if (null != handler) { + handler.onResponse(new String(body, StandardCharsets.UTF_8)); + channel.basicAck(envelope.getDeliveryTag(), false); + } + } + }); + } + + public void publish(String message, Handler handler) throws IOException { + UUID uuid = UUID.randomUUID(); // 메시지 아이디로 사용됩니다. + handlers.put(uuid, handler); // 응답 처리를 위해 핸들러를 저장해둡니다. + + // 익스체인지에 메시지를 보냅니다. + channel.basicPublish(EXCHANGE, ROUTING_KEY, + new AMQP.BasicProperties.Builder() + .replyTo(CLIENT_ROUTING_KEY) + .contentEncoding(StandardCharsets.UTF_8.name()) + .contentType("text/plain") + .correlationId(uuid.toString()) + .deliveryMode(MessageProperties.PERSISTENT_BASIC.getDeliveryMode()) + .build(), + message.getBytes(StandardCharsets.UTF_8)); + log.info("Tx: {}", message); + } + + public void close() throws IOException, TimeoutException { + channel.close(); + connection.close(); + handlers.clear(); + } + + interface Handler { + void onResponse(String message); + } + + public static void main(String... args) throws IOException, TimeoutException { + HelloRabbitServer server = new HelloRabbitServer(); + HelloRabbit client = new HelloRabbit(); + client.publish("Hello, there", new Handler() { + @Override + public void onResponse(String message) { + log.info("Rx: {}", message); + } + }); + client.publish("Lorem ipsum ...", new Handler() { + @Override + public void onResponse(String message) { + log.info("Rx: {}", message); + } + }); + + try { + Thread.sleep(5000); + } catch (InterruptedException e) { + log.error("Interrupted..", e); + } + client.close(); + server.close(); + } + +} diff --git a/rabbit-mq/src/main/java/kr/pe/elex/examples/rpc/HelloRabbitServer.java b/rabbit-mq/src/main/java/kr/pe/elex/examples/rpc/HelloRabbitServer.java new file mode 100644 index 0000000..691c0b0 --- /dev/null +++ b/rabbit-mq/src/main/java/kr/pe/elex/examples/rpc/HelloRabbitServer.java @@ -0,0 +1,76 @@ +/* + * Copyright (c) 2021. Elex. All Rights Reserved. + * https://www.elex-project.com/ + */ + +package kr.pe.elex.examples.rpc; + +import com.rabbitmq.client.*; +import lombok.extern.slf4j.Slf4j; + +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.util.concurrent.TimeoutException; + +/** + * @author Elex + * @see "https://www.rabbitmq.com/tutorials/tutorial-six-java.html" + */ +@Slf4j +public class HelloRabbitServer { + static final String EXCHANGE = "elex.rpc.exchange"; + static final String QUEUE = "elex.rpc.queue"; + static final String ROUTING_KEY = "elex-routing-key"; + + private Connection connection; + private Channel channel; + + HelloRabbitServer() throws IOException, TimeoutException { + ConnectionFactory connectionFactory = new ConnectionFactory(); + connectionFactory.setHost("localhost"); + connectionFactory.setPort(5672); + connectionFactory.setUsername("elex"); + connectionFactory.setPassword("test"); + connectionFactory.setVirtualHost("/"); + + connection = connectionFactory.newConnection(); + channel = connection.createChannel(); + + // 익스체인지는 브로커가 메시지를 받는 곳입니다. + channel.exchangeDeclare(EXCHANGE, BuiltinExchangeType.DIRECT, true); + // 큐는 브로커가 메시지를 보내는 곳입니다. + channel.queueDeclare(QUEUE, false, false, false, null); + // 익스체인지와 큐를 묶습니다. + channel.queueBind(QUEUE, EXCHANGE, ROUTING_KEY); + + // 큐로부터 메시지를 받습니다. + channel.basicConsume(QUEUE, true, ROUTING_KEY, new DefaultConsumer(channel) { + @Override + public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException { + log.info("Server Rx: {}", new String(body, StandardCharsets.UTF_8)); + + String queue = properties.getReplyTo(); + String messageId = properties.getCorrelationId(); + String message = new String(body, StandardCharsets.UTF_8).toUpperCase(); + + channel.basicPublish(EXCHANGE, queue, + new AMQP.BasicProperties.Builder() + .correlationId(messageId) + .build(), + message.getBytes(StandardCharsets.UTF_8)); + log.info("Server Tx: {}", message); + } + }); + } + + + public void close() throws IOException, TimeoutException { + channel.close(); + connection.close(); + } + + interface Handler { + void onResponse(String message); + } + +} diff --git a/rabbit-mq/src/main/java/kr/pe/elex/examples/tls/HelloRabbit.java b/rabbit-mq/src/main/java/kr/pe/elex/examples/tls/HelloRabbit.java new file mode 100644 index 0000000..694a322 --- /dev/null +++ b/rabbit-mq/src/main/java/kr/pe/elex/examples/tls/HelloRabbit.java @@ -0,0 +1,106 @@ +/* + * Copyright (c) 2021. Elex. All Rights Reserved. + * https://www.elex-project.com/ + */ + +package kr.pe.elex.examples.tls; + +import com.rabbitmq.client.*; +import lombok.extern.slf4j.Slf4j; + +import javax.net.ssl.SSLContext; +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.security.KeyManagementException; +import java.security.KeyStoreException; +import java.security.NoSuchAlgorithmException; +import java.security.UnrecoverableKeyException; +import java.security.cert.CertificateException; +import java.util.concurrent.TimeoutException; + +/** + * TLS 샘플 + * + * @author Elex + * @see "https://www.rabbitmq.com/tutorials/tutorial-one-java.html" + */ +@Slf4j +public class HelloRabbit { + private static final String EXCHANGE = "elex.direct.exchange"; + private static final String QUEUE = "elex.queue.01"; + private static final String ROUTING_KEY = "elex-routing-key"; + + private Connection connection; + private Channel channel; + + private SSLContext sslContext() + throws NoSuchAlgorithmException, KeyStoreException, UnrecoverableKeyException, KeyManagementException, IOException, CertificateException { + return TlsHelper.context(getClass().getResourceAsStream("/clientstore.p12"), + "test".toCharArray(), + "test1".toCharArray(), + getClass().getResourceAsStream("/truststore.p12"), + "test".toCharArray()); + } + + HelloRabbit() throws IOException, TimeoutException, KeyManagementException, NoSuchAlgorithmException, UnrecoverableKeyException, KeyStoreException, CertificateException { + ConnectionFactory connectionFactory = new ConnectionFactory(); + connectionFactory.setHost("localhost"); + connectionFactory.setPort(5671); // 포트 번호가 다릅니다. + connectionFactory.setUsername("elex"); + connectionFactory.setPassword("test"); + connectionFactory.setVirtualHost("/"); + //connectionFactory.useSslProtocol(); // 테스트 환경에서 사용됩니다. + connectionFactory.useSslProtocol(sslContext()); + //connectionFactory.enableHostnameVerification(); // 인증서 내용과 호스트네임을 검증합니다. + + connection = connectionFactory.newConnection(); + channel = connection.createChannel(); + + // 익스체인지는 브로커가 메시지를 받는 곳입니다. + channel.exchangeDeclare(EXCHANGE, BuiltinExchangeType.DIRECT, true); + + // 큐는 브로커가 메시지를 보내는 곳입니다. + channel.queueDeclare(QUEUE, false, false, false, null); + + // 익스체인지와 큐를 묶습니다. + channel.queueBind(QUEUE, EXCHANGE, ROUTING_KEY); + + // 큐로부터 메시지를 받습니다. + channel.basicConsume(QUEUE, true, ROUTING_KEY, new DefaultConsumer(channel) { + @Override + public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException { + log.info("Rx: {}", new String(body, StandardCharsets.UTF_8)); + } + }); + } + + public void publish(String message) throws IOException { + // 익스체인지에 메시지를 보냅니다. + channel.basicPublish(EXCHANGE, ROUTING_KEY, null, message.getBytes(StandardCharsets.UTF_8)); + log.info("Tx: {}", message); + } + + public void close() throws IOException, TimeoutException { + channel.close(); + connection.close(); + } + + public static void main(String... args) throws IOException, TimeoutException, UnrecoverableKeyException, CertificateException, NoSuchAlgorithmException, KeyStoreException, KeyManagementException { + HelloRabbit helloRabbit = new HelloRabbit(); + for (int i = 0; i < 10; i++) { + helloRabbit.publish("Hello, " + i); + try { + Thread.sleep(1000); + } catch (InterruptedException e) { + log.error("Interrupted..", e); + } + } + try { + Thread.sleep(10000); + } catch (InterruptedException e) { + log.error("Interrupted..", e); + } + helloRabbit.close(); + + } +} diff --git a/rabbit-mq/src/main/java/kr/pe/elex/examples/tls/TlsHelper.java b/rabbit-mq/src/main/java/kr/pe/elex/examples/tls/TlsHelper.java new file mode 100644 index 0000000..ec5dd31 --- /dev/null +++ b/rabbit-mq/src/main/java/kr/pe/elex/examples/tls/TlsHelper.java @@ -0,0 +1,93 @@ +/* + * Copyright (c) 2021. Elex. All Rights Reserved. + * https://www.elex-project.com/ + */ + +package kr.pe.elex.examples.tls; + +import javax.net.ssl.KeyManagerFactory; +import javax.net.ssl.SSLContext; +import javax.net.ssl.TrustManagerFactory; +import java.io.IOException; +import java.io.InputStream; +import java.security.*; +import java.security.cert.CertificateException; + +public final class TlsHelper { + private TlsHelper(){} + + /** + * SSL Context를 만든 다음, 소켓 팩토리를 가져옵니다. + * + * @param keyStoreInputStream 키스토어 + * @param keyStorePassword 키스토어 비번 + * @param keyPassword 키 비번 + * @param trustStoreInputStream CA 인증서 키 스토어 + * @param trustStorePassword 트러스트 스토어 비번 + * @return + * @throws KeyStoreException + * @throws CertificateException + * @throws NoSuchAlgorithmException + * @throws IOException + * @throws UnrecoverableKeyException + * @throws KeyManagementException + */ + public static SSLContext context(InputStream keyStoreInputStream, char[] keyStorePassword, char[] keyPassword, + InputStream trustStoreInputStream, char[] trustStorePassword) + throws KeyStoreException, CertificateException, NoSuchAlgorithmException, IOException, UnrecoverableKeyException, + KeyManagementException { + // OpenSSL로 키와 인증서를 만들고, PKCS12 키 저장소에 넣었습니다. + KeyStore keyStore = KeyStore.getInstance("PKCS12"); + // 클라이언트 키와 인증서가 들어있습니다. + keyStore.load(keyStoreInputStream, + keyStorePassword); // 키스토어 비번 + KeyManagerFactory kmf = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm()); + kmf.init(keyStore, keyPassword); // 키 비번 + + KeyStore trustKeyStore = KeyStore.getInstance("PKCS12"); + // CA 인증서가 들어있습니다. + trustKeyStore.load(trustStoreInputStream, + trustStorePassword); // 키 스토어 비번 + TrustManagerFactory tmf = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm()); + tmf.init(trustKeyStore); + + SSLContext context = SSLContext.getInstance("TLSv1.3"); + context.init(kmf.getKeyManagers(), tmf.getTrustManagers(), null); + + return context; + } + + /** + * @param trustStoreInputStream CA 인증서 키 스토어 + * @param trustStorePassword 트러스트 스토어 비번 + * @return + * @throws KeyStoreException + * @throws CertificateException + * @throws NoSuchAlgorithmException + * @throws IOException + * @throws UnrecoverableKeyException + * @throws KeyManagementException + */ + public static SSLContext context(InputStream trustStoreInputStream, char[] trustStorePassword) + throws KeyStoreException, CertificateException, NoSuchAlgorithmException, IOException, UnrecoverableKeyException, + KeyManagementException { + // OpenSSL로 키와 인증서를 만들고, PKCS12 키 저장소에 넣었습니다. + KeyStore keyStore = KeyStore.getInstance(KeyStore.getDefaultType()); + // 클라이언트 키와 인증서가 들어있습니다. + keyStore.load(null, null); // 키스토어 비번 + KeyManagerFactory kmf = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm()); + kmf.init(keyStore, null); // 키 비번 + + KeyStore trustKeyStore = KeyStore.getInstance("PKCS12"); + // CA 인증서가 들어있습니다. + trustKeyStore.load(trustStoreInputStream, + trustStorePassword); // 키 스토어 비번 + TrustManagerFactory tmf = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm()); + tmf.init(trustKeyStore); + + SSLContext context = SSLContext.getInstance("TLSv1.3"); + context.init(kmf.getKeyManagers(), tmf.getTrustManagers(), null); + + return context; + } +} diff --git a/rabbit-mq/src/main/java/kr/pe/elex/examples/tls/TlsHelperWithBouncyCastle.java b/rabbit-mq/src/main/java/kr/pe/elex/examples/tls/TlsHelperWithBouncyCastle.java new file mode 100644 index 0000000..e3076bb --- /dev/null +++ b/rabbit-mq/src/main/java/kr/pe/elex/examples/tls/TlsHelperWithBouncyCastle.java @@ -0,0 +1,129 @@ +/* + * Copyright (c) 2021. Elex. All Rights Reserved. + * https://www.elex-project.com/ + */ + +package kr.pe.elex.examples.tls; + +import org.bouncycastle.jce.provider.BouncyCastleProvider; +import org.bouncycastle.openssl.PEMDecryptorProvider; +import org.bouncycastle.openssl.PEMEncryptedKeyPair; +import org.bouncycastle.openssl.PEMKeyPair; +import org.bouncycastle.openssl.PEMParser; +import org.bouncycastle.openssl.jcajce.JcaPEMKeyConverter; +import org.bouncycastle.openssl.jcajce.JcePEMDecryptorProviderBuilder; + +import javax.net.ssl.KeyManagerFactory; +import javax.net.ssl.SSLContext; +import javax.net.ssl.TrustManagerFactory; +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.security.*; +import java.security.cert.CertificateException; +import java.security.cert.CertificateFactory; +import java.security.cert.X509Certificate; + +public final class TlsHelperWithBouncyCastle { + private TlsHelperWithBouncyCastle(){} + + public static SSLContext context(InputStream clientCrtInputStream, InputStream clientKeyInputStream, char[] clientPassword, + InputStream caCrtInputStream) + throws KeyStoreException, IOException, CertificateException, UnrecoverableKeyException, KeyManagementException, NoSuchAlgorithmException { + Security.addProvider(new BouncyCastleProvider()); + + // CA 인증서 불러오기 + CertificateFactory caCertFactory = CertificateFactory.getInstance("X.509"); + PEMParser parser = new PEMParser(new InputStreamReader(caCrtInputStream)); + X509Certificate caCert = (X509Certificate) caCertFactory + .generateCertificate(new ByteArrayInputStream(parser.readPemObject().getContent())); + parser.close(); + + // 클라이언트 인증서 불러오기 + CertificateFactory clientCertFactory = CertificateFactory.getInstance("X.509"); + parser = new PEMParser(new InputStreamReader(clientCrtInputStream)); + X509Certificate clientCert = (X509Certificate) clientCertFactory + .generateCertificate(new ByteArrayInputStream(parser.readPemObject().getContent())); + parser.close(); + + // 클라이언트 비밀키 불러오기 + parser = new PEMParser(new InputStreamReader(clientKeyInputStream)); + Object object = parser.readObject(); + JcaPEMKeyConverter converter = new JcaPEMKeyConverter().setProvider("BC"); + KeyPair keyPair; + if (object instanceof PEMEncryptedKeyPair) { + // 암호화된 키라면 패스워드가 필요하다. + PEMEncryptedKeyPair pemKeyPair = (PEMEncryptedKeyPair) object; + PEMDecryptorProvider decProv = new JcePEMDecryptorProviderBuilder().build(clientPassword); + keyPair = converter.getKeyPair(pemKeyPair.decryptKeyPair(decProv)); + } else { + // 암호화되지 않은 키라면 비밀번호가 필요없다. + PEMKeyPair pemKeyPair = (PEMKeyPair) object; + keyPair = converter.getKeyPair(pemKeyPair); + } + parser.close(); + PrivateKey clientKey = keyPair.getPrivate(); + + // CA 인증서를 사용해서 트러스트 스토어를 만든다. + KeyStore caKeyStore = KeyStore.getInstance(KeyStore.getDefaultType()); + caKeyStore.load(null, null); + caKeyStore.setCertificateEntry("ca-certificate", caCert); + TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm()); + trustManagerFactory.init(caKeyStore); + + // 클라이언트 인증서와 비밀키를 사용해서 키 스토어를 만든다. + KeyStore clientKeyStore = KeyStore.getInstance(KeyStore.getDefaultType()); + clientKeyStore.load(null, null); + clientKeyStore.setCertificateEntry("certificate", clientCert); + clientKeyStore.setKeyEntry("private-key", clientKey, clientPassword, new java.security.cert.Certificate[]{clientCert}); + KeyManagerFactory keyManagerFactory = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm()); + keyManagerFactory.init(clientKeyStore, clientPassword); + + SSLContext context = SSLContext.getInstance("TLSv1.3"); + context.init(keyManagerFactory.getKeyManagers(), trustManagerFactory.getTrustManagers(), null); + return context; + } + + /** + * BouncyCastle을 이용. + * + * @param caCrtInputStream ca cert pem + * @return + * @throws KeyStoreException + * @throws IOException + * @throws CertificateException + * @throws UnrecoverableKeyException + * @throws KeyManagementException + * @throws NoSuchAlgorithmException + */ + public static SSLContext context(InputStream caCrtInputStream) + throws KeyStoreException, IOException, CertificateException, UnrecoverableKeyException, KeyManagementException, NoSuchAlgorithmException { + Security.addProvider(new BouncyCastleProvider()); + + // CA 인증서 불러오기 + CertificateFactory caCertFactory = CertificateFactory.getInstance("X.509"); + PEMParser parser = new PEMParser(new InputStreamReader(caCrtInputStream)); + X509Certificate caCert = (X509Certificate) caCertFactory + .generateCertificate(new ByteArrayInputStream(parser.readPemObject().getContent())); + parser.close(); + + // CA 인증서를 사용해서 트러스트 스토어를 만든다. + KeyStore caKeyStore = KeyStore.getInstance(KeyStore.getDefaultType()); + caKeyStore.load(null, null); + caKeyStore.setCertificateEntry("ca-certificate", caCert); + TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm()); + trustManagerFactory.init(caKeyStore); + + // 클라이언트 인증서와 비밀키는 없으므로, 그냥 만든다. + KeyStore clientKeyStore = KeyStore.getInstance(KeyStore.getDefaultType()); + clientKeyStore.load(null, null); + KeyManagerFactory keyManagerFactory = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm()); + keyManagerFactory.init(clientKeyStore, null); + + SSLContext context = SSLContext.getInstance("TLSv1.3"); + context.init(keyManagerFactory.getKeyManagers(), trustManagerFactory.getTrustManagers(), null); + return context; + } + +} diff --git a/rabbit-mq/src/main/java/kr/pe/elex/examples/topic/RabbitClient.java b/rabbit-mq/src/main/java/kr/pe/elex/examples/topic/RabbitClient.java new file mode 100644 index 0000000..891fa0f --- /dev/null +++ b/rabbit-mq/src/main/java/kr/pe/elex/examples/topic/RabbitClient.java @@ -0,0 +1,104 @@ +/* + * Copyright (c) 2021. Elex. All Rights Reserved. + * https://www.elex-project.com/ + */ + +package kr.pe.elex.examples.topic; + +import com.rabbitmq.client.*; +import lombok.extern.slf4j.Slf4j; + +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.util.concurrent.TimeoutException; + +/** + * 토픽 익스체인지는 라우팅-키를 패턴으로 사용한다. + * + * @author Elex + * @see "https://www.rabbitmq.com/tutorials/tutorial-five-java.html" + */ +@Slf4j +public class RabbitClient { + private static final String EXCHANGE = "elex.topic.exchange"; + + private String name; + + private Connection connection; + private Channel channel; + private String queue; + + RabbitClient(String name) throws IOException, TimeoutException { + this.name = name; + + ConnectionFactory connectionFactory = new ConnectionFactory(); + connectionFactory.setHost("localhost"); + connectionFactory.setPort(5672); + connectionFactory.setUsername("elex"); + connectionFactory.setPassword("test"); + connectionFactory.setVirtualHost("/"); + + connection = connectionFactory.newConnection(); + channel = connection.createChannel(); + + // topic은 routing-key를 패턴으로 사용합니다. + channel.exchangeDeclare(EXCHANGE, BuiltinExchangeType.TOPIC, false); + + // 큐 이름을 랜덤으로 생성합니다. + queue = channel.queueDeclare().getQueue(); + + } + + public void consume(String topic) throws IOException { + // 익스체인지와 큐를 묶습니다. + channel.queueBind(queue, EXCHANGE, topic); + // 큐로부터 메시지를 받습니다. + channel.basicConsume(queue, true, queue, new DefaultConsumer(channel) { + @Override + public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException { + log.info("Rx: [{}] {} : {}", name, envelope.getRoutingKey(), new String(body, StandardCharsets.UTF_8)); + } + }); + } + + public void publish(String topic, String message) throws IOException { + // 익스체인지에 메시지를 보냅니다. + channel.basicPublish(EXCHANGE, topic, null, message.getBytes(StandardCharsets.UTF_8)); + log.info("Tx: [{}] {} : {}", name, topic, message); + } + + public void close() throws IOException, TimeoutException { + channel.close(); + connection.close(); + } + + public static void main(String... args) throws IOException, TimeoutException { + RabbitClient producer = new RabbitClient("Producer"); + RabbitClient consumer1 = new RabbitClient("Consumer1"); + RabbitClient consumer2 = new RabbitClient("Consumer2"); + consumer1.consume("message.apple.#"); + consumer2.consume("message.#"); + + producer.publish("message.hello", "Hello, there."); + producer.publish("message.apple", "Hello, apple."); + producer.publish("message.banana", "Hello, banana."); + + try { + Thread.sleep(5000); + } catch (InterruptedException e) { + log.error("Interrupted..", e); + } + producer.close(); + consumer1.close(); + consumer2.close(); + /* +11:29:47.699 [main] INFO kr.pe.elex.rabbitmq.topic.RabbitClient - Tx: [Producer] message.hello : Hello, there. +11:29:47.700 [pool-5-thread-4] INFO kr.pe.elex.rabbitmq.topic.RabbitClient - Rx: [Consumer2] message.hello : Hello, there. +11:29:47.703 [main] INFO kr.pe.elex.rabbitmq.topic.RabbitClient - Tx: [Producer] message.apple : Hello, apple. +11:29:47.703 [main] INFO kr.pe.elex.rabbitmq.topic.RabbitClient - Tx: [Producer] message.banana : Hello, banana. +11:29:47.704 [pool-5-thread-5] INFO kr.pe.elex.rabbitmq.topic.RabbitClient - Rx: [Consumer2] message.apple : Hello, apple. +11:29:47.704 [pool-5-thread-5] INFO kr.pe.elex.rabbitmq.topic.RabbitClient - Rx: [Consumer2] message.banana : Hello, banana. +11:29:47.704 [pool-3-thread-4] INFO kr.pe.elex.rabbitmq.topic.RabbitClient - Rx: [Consumer1] message.apple : Hello, apple. + */ + } +} diff --git a/settings.gradle.kts b/settings.gradle.kts index 2a064fd..c4b6e4a 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -1,3 +1,13 @@ +/* + * Examples for Java + * + * Copyright (c) 2021. Elex. All Rights Reserved. + * https://www.elex-project.com/ + */ + rootProject.name = "java-examples" -include("java-web-token", "mockito", -"mosquitto", "rabbit-mq", "ssh", "web-socket") +include("json-web-token", "mockito", +"mosquitto", "rabbit-mq", + "ssh", + "web-socket-servlet","web-socket-client", + "thread") diff --git a/ssh/build.gradle.kts b/ssh/build.gradle.kts index 5eee020..3b96e88 100644 --- a/ssh/build.gradle.kts +++ b/ssh/build.gradle.kts @@ -1,7 +1,15 @@ +/* + * Examples for Java + * + * Copyright (c) 2021. Elex. All Rights Reserved. + * https://www.elex-project.com/ + */ + plugins { id("elex-java") } dependencies { - + // https://mvnrepository.com/artifact/com.jcraft/jsch + implementation("com.jcraft:jsch:0.1.55") } diff --git a/ssh/src/main/java/kr/pe/elex/examples/SecureShell.java b/ssh/src/main/java/kr/pe/elex/examples/SecureShell.java new file mode 100644 index 0000000..6053411 --- /dev/null +++ b/ssh/src/main/java/kr/pe/elex/examples/SecureShell.java @@ -0,0 +1,89 @@ +/* + * Examples for Java + * + * Copyright (c) 2021. Elex. All Rights Reserved. + * https://www.elex-project.com/ + */ + +package kr.pe.elex.examples; + +import com.jcraft.jsch.*; + +import java.io.Closeable; +import java.io.InputStream; +import java.io.OutputStream; + +public class SecureShell implements Closeable { + + private final Session session; + + public SecureShell(String host, int port, String user, String password) throws JSchException { + JSch jSch = new JSch(); + session = jSch.getSession(user, host, port); + session.setPassword(password); + session.setUserInfo(new UserInfo() { + @Override + public String getPassphrase() { + return null; + } + + @Override + public String getPassword() { + return null; + } + + @Override + public boolean promptPassword(String message) { + return false; + } + + @Override + public boolean promptPassphrase(String message) { + return false; + } + + @Override + public boolean promptYesNo(String message) { + System.out.println(message); + // user type 'yes' + return true; + } + + @Override + public void showMessage(String message) { + System.out.println(message); + } + }); + session.connect(); + } + + @Override + public void close() { + session.disconnect(); + } + + public ChannelShell shell(InputStream is, OutputStream os) throws JSchException { + ChannelShell channel = (ChannelShell) session.openChannel("shell"); + channel.setInputStream(is); + channel.setOutputStream(os); + channel.setAgentForwarding(true); + channel.setPtyType("vt100"); + channel.connect(); + + return channel; + } + + public ChannelExec exec(String command) throws JSchException { + ChannelExec channel = (ChannelExec) session.openChannel("exec"); + channel.setCommand(command); + channel.setInputStream(null); + channel.setErrStream(System.err); + channel.connect(); + return channel; + } + + public void portForwardingL(int portL, String hostR, int portR) throws JSchException { + session.setPortForwardingL(portL, hostR, portR); + } + +} diff --git a/ssh/src/main/java/kr/pe/elex/examples/package-info.java b/ssh/src/main/java/kr/pe/elex/examples/package-info.java new file mode 100644 index 0000000..bfb14b8 --- /dev/null +++ b/ssh/src/main/java/kr/pe/elex/examples/package-info.java @@ -0,0 +1,8 @@ +/* + * Examples for Java + * + * Copyright (c) 2021. Elex. All Rights Reserved. + * https://www.elex-project.com/ + */ + +package kr.pe.elex.examples; diff --git a/ssh/src/test/java/kr/pe/elex/examples/SecureShellTest.java b/ssh/src/test/java/kr/pe/elex/examples/SecureShellTest.java new file mode 100644 index 0000000..9d64802 --- /dev/null +++ b/ssh/src/test/java/kr/pe/elex/examples/SecureShellTest.java @@ -0,0 +1,67 @@ +/* + * Examples for Java + * + * Copyright (c) 2021. Elex. All Rights Reserved. + * https://www.elex-project.com/ + */ + +package kr.pe.elex.examples; + +import com.elex_project.abraxas.IOz; +import com.jcraft.jsch.ChannelExec; +import com.jcraft.jsch.ChannelShell; +import com.jcraft.jsch.JSchException; +import jdk.nashorn.internal.ir.annotations.Ignore; +import lombok.extern.slf4j.Slf4j; +import org.junit.jupiter.api.Test; + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; + +import static org.junit.jupiter.api.Assertions.*; + +@Slf4j +class SecureShellTest { + + private static final String host = "192.168.0.1"; + private static final String user = "elex"; + private static final String password = "test"; + private static final int port = 22; + + @Test + @Ignore + void shell() throws JSchException { + SecureShell ssh = new SecureShell(host, port, user, password); + + ChannelShell shell = ssh.shell(System.in, System.out); + + // ??? + } + + @Test + void exec() throws JSchException, IOException { + SecureShell ssh = new SecureShell(host, port, user, password); + + ChannelExec channel = ssh.exec("ls -al"); + String out = IOz.readStringFrom(channel.getInputStream(),"\n"); + System.out.println(out); + + if (channel.isClosed()){ + channel.disconnect(); + } + ssh.close(); + } + + @Test + void scpTo() throws JSchException, IOException { + String filename = ""; + SecureShell ssh = new SecureShell(host, port, user, password); + + ChannelExec channel = ssh.exec("scp -p -t "+ filename); + InputStream is = channel.getInputStream(); + OutputStream os = channel.getOutputStream(); + + // ??? + } +} diff --git a/thread/build.gradle.kts b/thread/build.gradle.kts new file mode 100644 index 0000000..d2aab59 --- /dev/null +++ b/thread/build.gradle.kts @@ -0,0 +1,14 @@ +/* + * Examples for Java + * + * Copyright (c) 2021. Elex. All Rights Reserved. + * https://www.elex-project.com/ + */ + +plugins { + id("elex-java") +} + +dependencies { + +} diff --git a/thread/src/main/java/kr/pe/elex/examples/Sample.java b/thread/src/main/java/kr/pe/elex/examples/Sample.java new file mode 100644 index 0000000..4a3bbb9 --- /dev/null +++ b/thread/src/main/java/kr/pe/elex/examples/Sample.java @@ -0,0 +1,38 @@ +/* + * Examples for Java + * + * Copyright (c) 2021. Elex. All Rights Reserved. + * https://www.elex-project.com/ + */ + +package kr.pe.elex.examples; + +import lombok.extern.slf4j.Slf4j; + +import java.time.Duration; +import java.time.LocalDateTime; +import java.time.Period; +import java.time.temporal.ChronoUnit; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.Executors; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.TimeUnit; + +@Slf4j +public class Sample { + public static void main(String... args) throws ExecutionException, InterruptedException { + ScheduledExecutorService executor = Executors.newSingleThreadScheduledExecutor(); + + LocalDateTime startTime = LocalDateTime.of(2021, 12, 25, 0, 0); + executor.scheduleAtFixedRate(new Runnable() { + @Override + public void run() { + + } + }, + Duration.between(LocalDateTime.now(), startTime).abs().get(ChronoUnit.MILLIS), + Period.ofDays(1).get(ChronoUnit.MILLIS), + TimeUnit.MILLISECONDS); + + } +} diff --git a/thread/src/test/java/kr/pe/elex/examples/ExecutorTest.java b/thread/src/test/java/kr/pe/elex/examples/ExecutorTest.java new file mode 100644 index 0000000..64f4190 --- /dev/null +++ b/thread/src/test/java/kr/pe/elex/examples/ExecutorTest.java @@ -0,0 +1,14 @@ +/* + * Examples for Java + * + * Copyright (c) 2021. Elex. All Rights Reserved. + * https://www.elex-project.com/ + */ + +package kr.pe.elex.examples; + +import lombok.extern.slf4j.Slf4j; + +@Slf4j +public class ExecutorTest { +} diff --git a/web-socket-client/build.gradle.kts b/web-socket-client/build.gradle.kts new file mode 100644 index 0000000..672f0b0 --- /dev/null +++ b/web-socket-client/build.gradle.kts @@ -0,0 +1,16 @@ +/* + * Examples for Java + * + * Copyright (c) 2021. Elex. All Rights Reserved. + * https://www.elex-project.com/ + */ + +plugins { + id("elex-java") +} + +dependencies { + implementation("javax.websocket:javax.websocket-client-api:1.1") + implementation("org.eclipse.jetty.websocket:javax-websocket-client-impl:9.4.43.v20210629") + +} diff --git a/web-socket-client/src/main/java/kr/pe/elex/examples/WebSocketClient.java b/web-socket-client/src/main/java/kr/pe/elex/examples/WebSocketClient.java new file mode 100644 index 0000000..2b2236e --- /dev/null +++ b/web-socket-client/src/main/java/kr/pe/elex/examples/WebSocketClient.java @@ -0,0 +1,117 @@ +/* + * Examples for Java + * + * Copyright (c) 2021. Elex. All Rights Reserved. + * https://www.elex-project.com/ + */ + +package kr.pe.elex.examples; + +import lombok.extern.slf4j.Slf4j; +import org.jetbrains.annotations.NotNull; + +import javax.websocket.*; +import java.io.IOException; +import java.net.URI; +import java.net.URISyntaxException; +import java.nio.ByteBuffer; + +@Slf4j +@ClientEndpoint +public class WebSocketClient { + private Session session = null; + private WebSocketMessageHandler messageHandler; + + public WebSocketClient(URI endpointURI) { + try { + WebSocketContainer container = ContainerProvider.getWebSocketContainer(); + container.connectToServer(this, endpointURI); + } catch (Exception e) { + throw new RuntimeException(e); + } + } + + /** + * Callback hook for Connection open events. + * + * @param session the userSession which is opened. + */ + @OnOpen + public void onOpen(@NotNull Session session) throws IOException { + log.info("opening websocket"); + this.session = session; + //session.getBasicRemote().sendText("Hi~~"); + } + + /** + * Callback hook for Connection close events. + * + * @param session the userSession which is getting closed. + * @param reason the reason for connection close + */ + @OnClose + public void onClose(Session session, CloseReason reason) { + log.info("closing websocket"); + this.session = null; + } + + /** + * Callback hook for Message Events. This method will be invoked when a client send a message. + * + * @param message The text message + */ + @OnMessage + public void onMessage(String message) { + if (this.messageHandler != null) { + this.messageHandler.handleMessage(message); + } + } + + @OnMessage + public void onMessage(PongMessage message) { + if (this.messageHandler != null) { + this.messageHandler.handleMessage("pong: " + message.toString()); + } + } + + /** + * register message handler + * + * @param msgHandler + */ + public void setMessageHandler(WebSocketMessageHandler msgHandler) { + this.messageHandler = msgHandler; + } + + /** + * Send a message. + * + * @param message + */ + public void sendMessage(String message) throws IOException { + this.session.getBasicRemote().sendText(message); + } + + public void sendMessage(ByteBuffer message) throws IOException { + this.session.getBasicRemote().sendBinary(message); + } + + public void sendMessageAsync(String message) { + this.session.getAsyncRemote().sendText(message); + } + + public void sendMessageAsync(ByteBuffer message) { + this.session.getAsyncRemote().sendBinary(message); + } + + public static void main(String... args) throws URISyntaxException, IOException { + WebSocketClient client = new WebSocketClient(new URI("ws://localhost:8080/websocket")); + client.setMessageHandler(new WebSocketMessageHandler() { + @Override + public void handleMessage(final String message) { + log.info("Rx: {}", message); + } + }); + client.sendMessage("Hello"); + } +} diff --git a/web-socket-client/src/main/java/kr/pe/elex/examples/WebSocketMessageHandler.java b/web-socket-client/src/main/java/kr/pe/elex/examples/WebSocketMessageHandler.java new file mode 100644 index 0000000..7ccc530 --- /dev/null +++ b/web-socket-client/src/main/java/kr/pe/elex/examples/WebSocketMessageHandler.java @@ -0,0 +1,16 @@ +/* + * Examples for Java + * + * Copyright (c) 2021. Elex. All Rights Reserved. + * https://www.elex-project.com/ + */ + +package kr.pe.elex.examples; + +/** + * Message handler. + * + */ +public interface WebSocketMessageHandler { + public void handleMessage(String message); +} diff --git a/web-socket-client/src/main/java/kr/pe/elex/examples/package-info.java b/web-socket-client/src/main/java/kr/pe/elex/examples/package-info.java new file mode 100644 index 0000000..bfb14b8 --- /dev/null +++ b/web-socket-client/src/main/java/kr/pe/elex/examples/package-info.java @@ -0,0 +1,8 @@ +/* + * Examples for Java + * + * Copyright (c) 2021. Elex. All Rights Reserved. + * https://www.elex-project.com/ + */ + +package kr.pe.elex.examples; diff --git a/web-socket-servlet/build.gradle.kts b/web-socket-servlet/build.gradle.kts new file mode 100644 index 0000000..89045f3 --- /dev/null +++ b/web-socket-servlet/build.gradle.kts @@ -0,0 +1,15 @@ +/* + * Examples for Java + * + * Copyright (c) 2021. Elex. All Rights Reserved. + * https://www.elex-project.com/ + */ + +plugins { + id("elex-war") +} + +dependencies { + providedCompile ("javax.servlet:javax.servlet-api:4.0.1") + implementation("javax.websocket:javax.websocket-api:1.1") +} diff --git a/web-socket-servlet/src/main/java/kr/pe/elex/examples/WebSocketServlet.java b/web-socket-servlet/src/main/java/kr/pe/elex/examples/WebSocketServlet.java new file mode 100644 index 0000000..4ec8995 --- /dev/null +++ b/web-socket-servlet/src/main/java/kr/pe/elex/examples/WebSocketServlet.java @@ -0,0 +1,57 @@ +/* + * Examples for Java + * + * Copyright (c) 2021. Elex. All Rights Reserved. + * https://www.elex-project.com/ + */ + +package kr.pe.elex.examples; + +import org.jetbrains.annotations.NotNull; + +import javax.websocket.*; +import javax.websocket.server.ServerEndpoint; +import java.io.IOException; +import java.time.LocalDateTime; +import java.util.HashSet; +import java.util.Set; + +@ServerEndpoint("/") +public class WebSocketServlet { + private static final Set sessions; + + static { + sessions = new HashSet<>(); + } + + @OnOpen + public void onOpen(@NotNull Session session) throws IOException { + sessions.add(session);//session. + session.getBasicRemote().sendText("Hello"); + session.getUserProperties().put("createdOn", LocalDateTime.now()); + } + + @OnClose + public void onClose(Session session) { + sessions.remove(session); + session = null; + } + + @OnMessage + public void onMessage(@NotNull Session session, String message) throws IOException { + session.getBasicRemote().sendText("you said, " + message); + } + + @OnError + public void onError(Session session, Throwable e) { + + } + + public static void broadcast(final String message){ + sessions.forEach(session -> { + if (null!=session && session.isOpen()) { + session.getAsyncRemote().sendText(message); + } + }); + } +} diff --git a/web-socket-servlet/src/main/java/kr/pe/elex/examples/package-info.java b/web-socket-servlet/src/main/java/kr/pe/elex/examples/package-info.java new file mode 100644 index 0000000..bfb14b8 --- /dev/null +++ b/web-socket-servlet/src/main/java/kr/pe/elex/examples/package-info.java @@ -0,0 +1,8 @@ +/* + * Examples for Java + * + * Copyright (c) 2021. Elex. All Rights Reserved. + * https://www.elex-project.com/ + */ + +package kr.pe.elex.examples; diff --git a/web-socket/build.gradle.kts b/web-socket/build.gradle.kts deleted file mode 100644 index 5eee020..0000000 --- a/web-socket/build.gradle.kts +++ /dev/null @@ -1,7 +0,0 @@ -plugins { - id("elex-java") -} - -dependencies { - -}