├── README.md ├── chat ├── .gitignore ├── build.gradle ├── gradle │ └── wrapper │ │ ├── gradle-wrapper.jar │ │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── settings.gradle └── src │ ├── main │ ├── java │ │ └── webSocket │ │ │ └── chat │ │ │ ├── ChatApplication.java │ │ │ ├── WebSocketConfig.java │ │ │ ├── controller │ │ │ ├── ChatController.java │ │ │ ├── RoomController.java │ │ │ └── RoomForm.java │ │ │ ├── domain │ │ │ ├── Chat.java │ │ │ └── Room.java │ │ │ ├── dto │ │ │ └── ChatMessage.java │ │ │ ├── repository │ │ │ ├── ChatRepository.java │ │ │ └── RoomRepository.java │ │ │ └── service │ │ │ └── ChatService.java │ └── resources │ │ ├── application.yml │ │ ├── static │ │ ├── app.js │ │ └── main.css │ │ └── templates │ │ └── chat │ │ ├── room.html │ │ ├── roomForm.html │ │ └── roomList.html │ └── test │ └── java │ └── webSocket │ └── chat │ ├── ChatApplicationTests.java │ └── service │ └── ChatServiceTest.java └── complete ├── .gitignore ├── .gradle ├── 6.5.1 │ ├── executionHistory │ │ ├── executionHistory.bin │ │ └── executionHistory.lock │ ├── fileChanges │ │ └── last-build.bin │ ├── fileContent │ │ └── fileContent.lock │ ├── fileHashes │ │ ├── fileHashes.bin │ │ └── fileHashes.lock │ ├── gc.properties │ └── javaCompile │ │ ├── classAnalysis.bin │ │ ├── javaCompile.lock │ │ └── taskHistory.bin ├── buildOutputCleanup │ ├── buildOutputCleanup.lock │ ├── cache.properties │ └── outputFiles.bin ├── checksums │ ├── checksums.lock │ ├── md5-checksums.bin │ └── sha1-checksums.bin └── vcs-1 │ └── gc.properties ├── .idea ├── .gitignore ├── .name ├── compiler.xml ├── gradle.xml ├── jarRepositories.xml ├── misc.xml └── vcs.xml ├── .mvn └── wrapper │ ├── maven-wrapper.jar │ └── maven-wrapper.properties ├── build.gradle ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── settings.gradle └── src ├── main ├── java │ └── com │ │ └── example │ │ └── messagingstompwebsocket │ │ ├── Greeting.java │ │ ├── GreetingController.java │ │ ├── HelloMessage.java │ │ ├── MessagingStompWebsocketApplication.java │ │ └── WebSocketConfig.java └── resources │ └── static │ ├── app.js │ ├── index.html │ └── main.css └── test └── java └── com └── example └── messagingstompwebsocket └── GreetingIntegrationTests.java /README.md: -------------------------------------------------------------------------------- 1 | ## 실시간 채팅 서비스 2 | 3 | ### STOMP 흐름 4 | 5 | 1. 클라이언트(Sender)가 메세지를 보내면 STOMP 통신으로 서버에 메세지가 전달된다. 6 | 7 | 2. Controller의 @MessageMapping에 의해 메세지를 받는다. 8 | 9 | 3. Controller의 @SendTo로 특정 topic을(/1) 구독(/room) 하는 클라이언트에게 메세지를 보낸다. 10 | (구독은 /room 으로 보면되고 특정 topic은 채팅방 id인 /1로 보면된다. -> /room/1) 11 | 12 | ### 내 블로그에서 상세 설명 보기(포스팅) 13 | https://blog.naver.com/qjawnswkd/222283176175 14 | 15 | ### 1. Gradle 의존성 설정 16 | ```java 17 | implementation 'org.springframework.boot:spring-boot-starter-websocket' 18 | implementation 'org.webjars:webjars-locator-core' 19 | implementation 'org.webjars:sockjs-client:1.0.2' 20 | implementation 'org.webjars:stomp-websocket:2.3.3' 21 | implementation 'org.webjars:bootstrap:3.3.7' 22 | implementation 'org.webjars:jquery:3.1.1-1' 23 | ``` 24 | 25 | ### 2. WebSocketConfig 설정 26 | ```java 27 | @Configuration 28 | @RequiredArgsConstructor 29 | @EnableWebSocketMessageBroker 30 | public class WebSocketConfig implements WebSocketMessageBrokerConfigurer { 31 | 32 | @Override 33 | public void configureMessageBroker(MessageBrokerRegistry registry) { 34 | registry.setApplicationDestinationPrefixes("/send"); //클라이언트에서 보낸 메세지를 받을 prefix 35 | registry.enableSimpleBroker("/room"); //해당 주소를 구독하고 있는 클라이언트들에게 메세지 전달 36 | } 37 | 38 | @Override 39 | public void registerStompEndpoints(StompEndpointRegistry registry) { 40 | registry.addEndpoint("/ws-stomp") //SockJS 연결 주소 41 | .withSockJS(); //버전 낮은 브라우저에서도 적용 가능 42 | // 주소 : ws://localhost:8080/ws-stomp 43 | } 44 | } 45 | ``` 46 | 47 | ### 3. 컨트롤러 48 | ```java 49 | @Controller 50 | @RequiredArgsConstructor 51 | public class ChatController { 52 | 53 | private final ChatService chatService; 54 | 55 | @MessageMapping("/{roomId}") //여기로 전송되면 메서드 호출 -> WebSocketConfig prefixes 에서 적용한건 앞에 생략 56 | @SendTo("/room/{roomId}") //구독하고 있는 장소로 메시지 전송 (목적지) -> WebSocketConfig Broker 에서 적용한건 앞에 붙어줘야됨 57 | public ChatMessage test(@DestinationVariable Long roomId, ChatMessage message) { 58 | 59 | //채팅 저장 60 | Chat chat = chatService.createChat(roomId, message.getSender(), message.getMessage()); 61 | return ChatMessage.builder() 62 | .roomId(roomId) 63 | .sender(chat.getSender()) 64 | .message(chat.getMessage()) 65 | .build(); 66 | } 67 | } 68 | ``` 69 | 70 | ### 4.클라이언트 71 | ```javascript 72 | 148 | 158 | ``` 159 | 160 | ### Redis 추가중 161 | 채팅 로그 Redis에 저장 162 | -------------------------------------------------------------------------------- /chat/.gitignore: -------------------------------------------------------------------------------- 1 | HELP.md 2 | .gradle 3 | build/ 4 | !gradle/wrapper/gradle-wrapper.jar 5 | !**/src/main/**/build/ 6 | !**/src/test/**/build/ 7 | 8 | ### STS ### 9 | .apt_generated 10 | .classpath 11 | .factorypath 12 | .project 13 | .settings 14 | .springBeans 15 | .sts4-cache 16 | bin/ 17 | !**/src/main/**/bin/ 18 | !**/src/test/**/bin/ 19 | 20 | ### IntelliJ IDEA ### 21 | .idea 22 | *.iws 23 | *.iml 24 | *.ipr 25 | out/ 26 | !**/src/main/**/out/ 27 | !**/src/test/**/out/ 28 | 29 | ### NetBeans ### 30 | /nbproject/private/ 31 | /nbbuild/ 32 | /dist/ 33 | /nbdist/ 34 | /.nb-gradle/ 35 | 36 | ### VS Code ### 37 | .vscode/ 38 | -------------------------------------------------------------------------------- /chat/build.gradle: -------------------------------------------------------------------------------- 1 | plugins { 2 | id 'org.springframework.boot' version '2.4.4' 3 | id 'io.spring.dependency-management' version '1.0.11.RELEASE' 4 | id 'java' 5 | } 6 | 7 | apply plugin: 'io.spring.dependency-management' 8 | 9 | group = 'webSocket' 10 | version = '0.0.1-SNAPSHOT' 11 | sourceCompatibility = '11' 12 | 13 | configurations { 14 | compileOnly { 15 | extendsFrom annotationProcessor 16 | } 17 | } 18 | 19 | repositories { 20 | mavenCentral() 21 | } 22 | 23 | dependencies { 24 | implementation 'org.springframework.boot:spring-boot-starter-data-jpa' 25 | implementation 'org.springframework.boot:spring-boot-starter-web' 26 | implementation 'org.springframework.boot:spring-boot-starter-websocket' 27 | implementation 'org.springframework.boot:spring-boot-starter-thymeleaf' 28 | 29 | implementation 'org.webjars:webjars-locator-core' 30 | implementation 'org.webjars:sockjs-client:1.0.2' 31 | implementation 'org.webjars:stomp-websocket:2.3.3' 32 | implementation 'org.webjars:bootstrap:3.3.7' 33 | implementation 'org.webjars:jquery:3.1.1-1' 34 | 35 | //redis 36 | implementation 'org.springframework.boot:spring-boot-starter-redis:1.4.6.RELEASE' 37 | 38 | //jpa query log 39 | implementation 'com.github.gavlyukovskiy:p6spy-spring-boot-starter:1.5.7' 40 | 41 | compileOnly 'org.projectlombok:lombok' 42 | developmentOnly 'org.springframework.boot:spring-boot-devtools' 43 | runtimeOnly 'com.h2database:h2' 44 | annotationProcessor 'org.projectlombok:lombok' 45 | testImplementation 'org.springframework.boot:spring-boot-starter-test' 46 | } 47 | 48 | test { 49 | useJUnitPlatform() 50 | } 51 | 52 | 53 | 54 | -------------------------------------------------------------------------------- /chat/gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BeomjunLee/webSocketChat/8ff4ff58759a6e89f13ac198f93614d2aa09c0b7/chat/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /chat/gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | distributionUrl=https\://services.gradle.org/distributions/gradle-6.8.3-bin.zip 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | -------------------------------------------------------------------------------- /chat/gradlew: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | 3 | # 4 | # Copyright 2015 the original author or authors. 5 | # 6 | # Licensed under the Apache License, Version 2.0 (the "License"); 7 | # you may not use this file except in compliance with the License. 8 | # You may obtain a copy of the License at 9 | # 10 | # https://www.apache.org/licenses/LICENSE-2.0 11 | # 12 | # Unless required by applicable law or agreed to in writing, software 13 | # distributed under the License is distributed on an "AS IS" BASIS, 14 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | # See the License for the specific language governing permissions and 16 | # limitations under the License. 17 | # 18 | 19 | ############################################################################## 20 | ## 21 | ## Gradle start up script for UN*X 22 | ## 23 | ############################################################################## 24 | 25 | # Attempt to set APP_HOME 26 | # Resolve links: $0 may be a link 27 | PRG="$0" 28 | # Need this for relative symlinks. 29 | while [ -h "$PRG" ] ; do 30 | ls=`ls -ld "$PRG"` 31 | link=`expr "$ls" : '.*-> \(.*\)$'` 32 | if expr "$link" : '/.*' > /dev/null; then 33 | PRG="$link" 34 | else 35 | PRG=`dirname "$PRG"`"/$link" 36 | fi 37 | done 38 | SAVED="`pwd`" 39 | cd "`dirname \"$PRG\"`/" >/dev/null 40 | APP_HOME="`pwd -P`" 41 | cd "$SAVED" >/dev/null 42 | 43 | APP_NAME="Gradle" 44 | APP_BASE_NAME=`basename "$0"` 45 | 46 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 47 | DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' 48 | 49 | # Use the maximum available, or set MAX_FD != -1 to use that value. 50 | MAX_FD="maximum" 51 | 52 | warn () { 53 | echo "$*" 54 | } 55 | 56 | die () { 57 | echo 58 | echo "$*" 59 | echo 60 | exit 1 61 | } 62 | 63 | # OS specific support (must be 'true' or 'false'). 64 | cygwin=false 65 | msys=false 66 | darwin=false 67 | nonstop=false 68 | case "`uname`" in 69 | CYGWIN* ) 70 | cygwin=true 71 | ;; 72 | Darwin* ) 73 | darwin=true 74 | ;; 75 | MINGW* ) 76 | msys=true 77 | ;; 78 | NONSTOP* ) 79 | nonstop=true 80 | ;; 81 | esac 82 | 83 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 84 | 85 | 86 | # Determine the Java command to use to start the JVM. 87 | if [ -n "$JAVA_HOME" ] ; then 88 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 89 | # IBM's JDK on AIX uses strange locations for the executables 90 | JAVACMD="$JAVA_HOME/jre/sh/java" 91 | else 92 | JAVACMD="$JAVA_HOME/bin/java" 93 | fi 94 | if [ ! -x "$JAVACMD" ] ; then 95 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 96 | 97 | Please set the JAVA_HOME variable in your environment to match the 98 | location of your Java installation." 99 | fi 100 | else 101 | JAVACMD="java" 102 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 103 | 104 | Please set the JAVA_HOME variable in your environment to match the 105 | location of your Java installation." 106 | fi 107 | 108 | # Increase the maximum file descriptors if we can. 109 | if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then 110 | MAX_FD_LIMIT=`ulimit -H -n` 111 | if [ $? -eq 0 ] ; then 112 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then 113 | MAX_FD="$MAX_FD_LIMIT" 114 | fi 115 | ulimit -n $MAX_FD 116 | if [ $? -ne 0 ] ; then 117 | warn "Could not set maximum file descriptor limit: $MAX_FD" 118 | fi 119 | else 120 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" 121 | fi 122 | fi 123 | 124 | # For Darwin, add options to specify how the application appears in the dock 125 | if $darwin; then 126 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" 127 | fi 128 | 129 | # For Cygwin or MSYS, switch paths to Windows format before running java 130 | if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then 131 | APP_HOME=`cygpath --path --mixed "$APP_HOME"` 132 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` 133 | 134 | JAVACMD=`cygpath --unix "$JAVACMD"` 135 | 136 | # We build the pattern for arguments to be converted via cygpath 137 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` 138 | SEP="" 139 | for dir in $ROOTDIRSRAW ; do 140 | ROOTDIRS="$ROOTDIRS$SEP$dir" 141 | SEP="|" 142 | done 143 | OURCYGPATTERN="(^($ROOTDIRS))" 144 | # Add a user-defined pattern to the cygpath arguments 145 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then 146 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" 147 | fi 148 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 149 | i=0 150 | for arg in "$@" ; do 151 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` 152 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option 153 | 154 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition 155 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` 156 | else 157 | eval `echo args$i`="\"$arg\"" 158 | fi 159 | i=`expr $i + 1` 160 | done 161 | case $i in 162 | 0) set -- ;; 163 | 1) set -- "$args0" ;; 164 | 2) set -- "$args0" "$args1" ;; 165 | 3) set -- "$args0" "$args1" "$args2" ;; 166 | 4) set -- "$args0" "$args1" "$args2" "$args3" ;; 167 | 5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; 168 | 6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; 169 | 7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; 170 | 8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; 171 | 9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; 172 | esac 173 | fi 174 | 175 | # Escape application args 176 | save () { 177 | for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done 178 | echo " " 179 | } 180 | APP_ARGS=`save "$@"` 181 | 182 | # Collect all arguments for the java command, following the shell quoting and substitution rules 183 | eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" 184 | 185 | exec "$JAVACMD" "$@" 186 | -------------------------------------------------------------------------------- /chat/gradlew.bat: -------------------------------------------------------------------------------- 1 | @rem 2 | @rem Copyright 2015 the original author or authors. 3 | @rem 4 | @rem Licensed under the Apache License, Version 2.0 (the "License"); 5 | @rem you may not use this file except in compliance with the License. 6 | @rem You may obtain a copy of the License at 7 | @rem 8 | @rem https://www.apache.org/licenses/LICENSE-2.0 9 | @rem 10 | @rem Unless required by applicable law or agreed to in writing, software 11 | @rem distributed under the License is distributed on an "AS IS" BASIS, 12 | @rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | @rem See the License for the specific language governing permissions and 14 | @rem limitations under the License. 15 | @rem 16 | 17 | @if "%DEBUG%" == "" @echo off 18 | @rem ########################################################################## 19 | @rem 20 | @rem Gradle startup script for Windows 21 | @rem 22 | @rem ########################################################################## 23 | 24 | @rem Set local scope for the variables with windows NT shell 25 | if "%OS%"=="Windows_NT" setlocal 26 | 27 | set DIRNAME=%~dp0 28 | if "%DIRNAME%" == "" set DIRNAME=. 29 | set APP_BASE_NAME=%~n0 30 | set APP_HOME=%DIRNAME% 31 | 32 | @rem Resolve any "." and ".." in APP_HOME to make it shorter. 33 | for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi 34 | 35 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 36 | set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" 37 | 38 | @rem Find java.exe 39 | if defined JAVA_HOME goto findJavaFromJavaHome 40 | 41 | set JAVA_EXE=java.exe 42 | %JAVA_EXE% -version >NUL 2>&1 43 | if "%ERRORLEVEL%" == "0" goto execute 44 | 45 | echo. 46 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 47 | echo. 48 | echo Please set the JAVA_HOME variable in your environment to match the 49 | echo location of your Java installation. 50 | 51 | goto fail 52 | 53 | :findJavaFromJavaHome 54 | set JAVA_HOME=%JAVA_HOME:"=% 55 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 56 | 57 | if exist "%JAVA_EXE%" goto execute 58 | 59 | echo. 60 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 61 | echo. 62 | echo Please set the JAVA_HOME variable in your environment to match the 63 | echo location of your Java installation. 64 | 65 | goto fail 66 | 67 | :execute 68 | @rem Setup the command line 69 | 70 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 71 | 72 | 73 | @rem Execute Gradle 74 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* 75 | 76 | :end 77 | @rem End local scope for the variables with windows NT shell 78 | if "%ERRORLEVEL%"=="0" goto mainEnd 79 | 80 | :fail 81 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 82 | rem the _cmd.exe /c_ return code! 83 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 84 | exit /b 1 85 | 86 | :mainEnd 87 | if "%OS%"=="Windows_NT" endlocal 88 | 89 | :omega 90 | -------------------------------------------------------------------------------- /chat/settings.gradle: -------------------------------------------------------------------------------- 1 | rootProject.name = 'chat' 2 | -------------------------------------------------------------------------------- /chat/src/main/java/webSocket/chat/ChatApplication.java: -------------------------------------------------------------------------------- 1 | package webSocket.chat; 2 | 3 | import org.springframework.boot.SpringApplication; 4 | import org.springframework.boot.autoconfigure.SpringBootApplication; 5 | 6 | @SpringBootApplication 7 | public class ChatApplication { 8 | 9 | public static void main(String[] args) { 10 | SpringApplication.run(ChatApplication.class, args); 11 | } 12 | 13 | } 14 | -------------------------------------------------------------------------------- /chat/src/main/java/webSocket/chat/WebSocketConfig.java: -------------------------------------------------------------------------------- 1 | package webSocket.chat; 2 | 3 | import lombok.RequiredArgsConstructor; 4 | import org.springframework.context.annotation.Configuration; 5 | import org.springframework.messaging.simp.config.MessageBrokerRegistry; 6 | import org.springframework.web.socket.config.annotation.*; 7 | 8 | @Configuration 9 | @RequiredArgsConstructor 10 | @EnableWebSocketMessageBroker 11 | public class WebSocketConfig implements WebSocketMessageBrokerConfigurer { 12 | 13 | @Override 14 | public void configureMessageBroker(MessageBrokerRegistry registry) { 15 | registry.setApplicationDestinationPrefixes("/send"); //클라이언트에서 보낸 메세지를 받을 prefix 16 | registry.enableSimpleBroker("/room"); //해당 주소를 구독하고 있는 클라이언트들에게 메세지 전달 17 | } 18 | 19 | @Override 20 | public void registerStompEndpoints(StompEndpointRegistry registry) { 21 | registry.addEndpoint("/ws-stomp") //SockJS 연결 주소 22 | .withSockJS(); //버전 낮은 브라우저에서도 적용 가능 23 | // 주소 : ws://localhost:8080/ws-stomp 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /chat/src/main/java/webSocket/chat/controller/ChatController.java: -------------------------------------------------------------------------------- 1 | package webSocket.chat.controller; 2 | import lombok.RequiredArgsConstructor; 3 | import org.springframework.messaging.handler.annotation.DestinationVariable; 4 | import org.springframework.messaging.handler.annotation.MessageMapping; 5 | import org.springframework.messaging.handler.annotation.SendTo; 6 | import org.springframework.messaging.simp.SimpMessageSendingOperations; 7 | import org.springframework.stereotype.Controller; 8 | 9 | import org.springframework.ui.Model; 10 | import org.springframework.web.bind.annotation.GetMapping; 11 | import org.springframework.web.bind.annotation.PathVariable; 12 | import webSocket.chat.domain.Chat; 13 | import webSocket.chat.dto.ChatMessage; 14 | import webSocket.chat.service.ChatService; 15 | 16 | 17 | @Controller 18 | @RequiredArgsConstructor 19 | public class ChatController { 20 | 21 | private final ChatService chatService; 22 | 23 | @MessageMapping("/{roomId}") //여기로 전송되면 메서드 호출 -> WebSocketConfig prefixes 에서 적용한건 앞에 생략 24 | @SendTo("/room/{roomId}") //구독하고 있는 장소로 메시지 전송 (목적지) -> WebSocketConfig Broker 에서 적용한건 앞에 붙어줘야됨 25 | public ChatMessage test(@DestinationVariable Long roomId, ChatMessage message) { 26 | 27 | //채팅 저장 28 | Chat chat = chatService.createChat(roomId, message.getSender(), message.getMessage()); 29 | return ChatMessage.builder() 30 | .roomId(roomId) 31 | .sender(chat.getSender()) 32 | .message(chat.getMessage()) 33 | .build(); 34 | } 35 | 36 | } 37 | -------------------------------------------------------------------------------- /chat/src/main/java/webSocket/chat/controller/RoomController.java: -------------------------------------------------------------------------------- 1 | package webSocket.chat.controller; 2 | 3 | import lombok.RequiredArgsConstructor; 4 | import org.springframework.stereotype.Controller; 5 | import org.springframework.ui.Model; 6 | import org.springframework.web.bind.annotation.GetMapping; 7 | import org.springframework.web.bind.annotation.PathVariable; 8 | import org.springframework.web.bind.annotation.PostMapping; 9 | import webSocket.chat.domain.Chat; 10 | import webSocket.chat.domain.Room; 11 | import webSocket.chat.dto.ChatMessage; 12 | import webSocket.chat.service.ChatService; 13 | 14 | import java.util.List; 15 | 16 | @Controller 17 | @RequiredArgsConstructor 18 | public class RoomController { 19 | 20 | private final ChatService chatService; 21 | 22 | /** 23 | * 채팅방 참여하기 24 | * @param roomId 채팅방 id 25 | */ 26 | @GetMapping("/{roomId}") 27 | public String joinRoom(@PathVariable(required = false) Long roomId, Model model) { 28 | List chatList = chatService.findAllChatByRoomId(roomId); 29 | 30 | model.addAttribute("roomId", roomId); 31 | model.addAttribute("chatList", chatList); 32 | return "chat/room"; 33 | } 34 | 35 | /** 36 | * 채팅방 등록 37 | * @param form 38 | */ 39 | @PostMapping("/room") 40 | public String createRoom(RoomForm form) { 41 | chatService.createRoom(form.getName()); 42 | return "redirect:/roomList"; 43 | } 44 | 45 | /** 46 | * 채팅방 리스트 보기 47 | */ 48 | @GetMapping("/roomList") 49 | public String roomList(Model model) { 50 | List roomList = chatService.findAllRoom(); 51 | model.addAttribute("roomList", roomList); 52 | return "chat/roomList"; 53 | } 54 | 55 | /** 56 | * 방만들기 폼 57 | */ 58 | @GetMapping("/roomForm") 59 | public String roomForm() { 60 | return "chat/roomForm"; 61 | } 62 | 63 | } 64 | -------------------------------------------------------------------------------- /chat/src/main/java/webSocket/chat/controller/RoomForm.java: -------------------------------------------------------------------------------- 1 | package webSocket.chat.controller; 2 | 3 | import lombok.Data; 4 | 5 | @Data 6 | public class RoomForm { 7 | private String name; 8 | } 9 | -------------------------------------------------------------------------------- /chat/src/main/java/webSocket/chat/domain/Chat.java: -------------------------------------------------------------------------------- 1 | package webSocket.chat.domain; 2 | 3 | import lombok.*; 4 | import org.springframework.data.annotation.CreatedDate; 5 | import org.springframework.data.redis.core.RedisHash; 6 | 7 | import javax.persistence.*; 8 | import java.time.LocalDateTime; 9 | 10 | @RedisHash 11 | @Getter 12 | @NoArgsConstructor(access = AccessLevel.PROTECTED) 13 | @ToString 14 | public class Chat { 15 | 16 | @Id @GeneratedValue 17 | @Column(name = "chat_id") 18 | private Long id; 19 | 20 | @ManyToOne(fetch = FetchType.EAGER) 21 | @JoinColumn(name = "room_id") 22 | private Room room; 23 | 24 | private String sender; 25 | 26 | @Column(columnDefinition = "TEXT") 27 | private String message; 28 | 29 | @CreatedDate 30 | @Column(updatable = false) 31 | private LocalDateTime sendDate; 32 | 33 | @Builder 34 | public Chat(Room room, String sender, String message) { 35 | this.room = room; 36 | this.sender = sender; 37 | this.message = message; 38 | this.sendDate = LocalDateTime.now(); 39 | } 40 | 41 | /** 42 | * 채팅 생성 43 | * @param room 채팅 방 44 | * @param sender 보낸이 45 | * @param message 내용 46 | * @return Chat Entity 47 | */ 48 | public static Chat createChat(Room room, String sender, String message) { 49 | return Chat.builder() 50 | .room(room) 51 | .sender(sender) 52 | .message(message) 53 | .build(); 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /chat/src/main/java/webSocket/chat/domain/Room.java: -------------------------------------------------------------------------------- 1 | package webSocket.chat.domain; 2 | 3 | import lombok.*; 4 | import org.springframework.web.socket.WebSocketSession; 5 | import webSocket.chat.dto.ChatMessage; 6 | 7 | import javax.persistence.Column; 8 | import javax.persistence.Entity; 9 | import javax.persistence.GeneratedValue; 10 | import javax.persistence.Id; 11 | 12 | @Entity 13 | @Getter 14 | @NoArgsConstructor(access = AccessLevel.PROTECTED) 15 | @ToString 16 | public class Room { 17 | @Id 18 | @GeneratedValue 19 | @Column(name = "room_id") 20 | private Long id; 21 | private String name; 22 | 23 | @Builder 24 | public Room(String name) { 25 | this.name = name; 26 | } 27 | 28 | /** 29 | * 채팅방 생성 30 | * @param name 방 이름 31 | * @return Room Entity 32 | */ 33 | public static Room createRoom(String name) { 34 | return Room.builder() 35 | .name(name) 36 | .build(); 37 | } 38 | 39 | } -------------------------------------------------------------------------------- /chat/src/main/java/webSocket/chat/dto/ChatMessage.java: -------------------------------------------------------------------------------- 1 | package webSocket.chat.dto; 2 | 3 | import lombok.AllArgsConstructor; 4 | import lombok.Builder; 5 | import lombok.Data; 6 | import lombok.NoArgsConstructor; 7 | 8 | import java.time.LocalDateTime; 9 | 10 | @Data 11 | @NoArgsConstructor 12 | @AllArgsConstructor 13 | @Builder 14 | public class ChatMessage { 15 | 16 | private Long roomId; 17 | private String sender; 18 | private String message; 19 | private LocalDateTime sendDate; 20 | 21 | } 22 | -------------------------------------------------------------------------------- /chat/src/main/java/webSocket/chat/repository/ChatRepository.java: -------------------------------------------------------------------------------- 1 | package webSocket.chat.repository; 2 | 3 | import org.springframework.data.jpa.repository.JpaRepository; 4 | import org.springframework.data.jpa.repository.Query; 5 | import org.springframework.data.repository.CrudRepository; 6 | import webSocket.chat.domain.Chat; 7 | 8 | import java.util.List; 9 | 10 | public interface ChatRepository extends CrudRepository { 11 | 12 | List findAllByRoomId(Long roomId); 13 | } 14 | -------------------------------------------------------------------------------- /chat/src/main/java/webSocket/chat/repository/RoomRepository.java: -------------------------------------------------------------------------------- 1 | package webSocket.chat.repository; 2 | 3 | import org.springframework.data.jpa.repository.JpaRepository; 4 | import webSocket.chat.domain.Room; 5 | 6 | public interface RoomRepository extends JpaRepository { 7 | } 8 | -------------------------------------------------------------------------------- /chat/src/main/java/webSocket/chat/service/ChatService.java: -------------------------------------------------------------------------------- 1 | package webSocket.chat.service; 2 | 3 | import com.fasterxml.jackson.databind.ObjectMapper; 4 | import lombok.RequiredArgsConstructor; 5 | import org.springframework.stereotype.Service; 6 | import webSocket.chat.domain.Chat; 7 | import webSocket.chat.domain.Room; 8 | import webSocket.chat.repository.ChatRepository; 9 | import webSocket.chat.repository.RoomRepository; 10 | 11 | import java.util.List; 12 | 13 | @Service 14 | @RequiredArgsConstructor 15 | public class ChatService { 16 | private final RoomRepository roomRepository; 17 | private final ChatRepository chatRepository; 18 | 19 | /** 20 | * 모든 채팅방 찾기 21 | */ 22 | public List findAllRoom() { 23 | return roomRepository.findAll(); 24 | } 25 | 26 | /** 27 | * 특정 채팅방 찾기 28 | * @param id room_id 29 | */ 30 | public Room findRoomById(Long id) { 31 | return roomRepository.findById(id).orElseThrow(); 32 | } 33 | 34 | /** 35 | * 채팅방 만들기 36 | * @param name 방 이름 37 | */ 38 | public Room createRoom(String name) { 39 | return roomRepository.save(Room.createRoom(name)); 40 | } 41 | 42 | ///////////////// 43 | 44 | /** 45 | * 채팅 생성 46 | * @param roomId 채팅방 id 47 | * @param sender 보낸이 48 | * @param message 내용 49 | */ 50 | public Chat createChat(Long roomId, String sender, String message) { 51 | Room room = roomRepository.findById(roomId).orElseThrow(); //방 찾기 -> 없는 방일 경우 여기서 예외처리 52 | return chatRepository.save(Chat.createChat(room, sender, message)); 53 | } 54 | 55 | /** 56 | * 채팅방 채팅내용 불러오기 57 | * @param roomId 채팅방 id 58 | */ 59 | public List findAllChatByRoomId(Long roomId) { 60 | return chatRepository.findAllByRoomId(roomId); 61 | } 62 | 63 | 64 | } 65 | -------------------------------------------------------------------------------- /chat/src/main/resources/application.yml: -------------------------------------------------------------------------------- 1 | spring: 2 | datasource: 3 | driver-class-name: org.h2.Driver 4 | url: jdbc:h2:tcp://localhost/~/test; 5 | username: sa 6 | password: 7 | 8 | jpa: 9 | hibernate: 10 | ddl-auto: create 11 | properties: 12 | hibernate: 13 | #show_sql: true //운영할 때는 system.out로 sql를 보여주기때문에 쓰지 말아야한다 14 | format_sql: true //sql 이쁘게 보이게 15 | default_batch_fetch_size: 100 16 | -------------------------------------------------------------------------------- /chat/src/main/resources/static/app.js: -------------------------------------------------------------------------------- 1 | var stompClient = null; 2 | var roomId = [[${roomId}]]; 3 | 4 | function setConnected(connected) { 5 | $("#connect").prop("disabled", connected); 6 | $("#disconnect").prop("disabled", !connected); 7 | if (connected) { 8 | $("#conversation").show(); 9 | } 10 | else { 11 | $("#conversation").hide(); 12 | } 13 | $("#greetings").html(""); 14 | } 15 | 16 | function connect() { 17 | var socket = new SockJS('/ws-stomp'); 18 | stompClient = Stomp.over(socket); 19 | stompClient.connect({}, function (frame) { 20 | setConnected(true); 21 | console.log('Connected: ' + frame); 22 | stompClient.subscribe('/room/'+roomId, function (chatMessage) { 23 | showGreeting(JSON.parse(chatMessage.body)); 24 | }); 25 | }); 26 | } 27 | 28 | function disconnect() { 29 | if (stompClient !== null) { 30 | stompClient.disconnect(); 31 | } 32 | setConnected(false); 33 | console.log("Disconnected"); 34 | } 35 | 36 | function sendName() { 37 | stompClient.send("/chat/"+roomId, {}, 38 | JSON.stringify({ 39 | 'roomId' : roomId, 40 | 'name': $("#name").val(), 41 | 'message' : $("#message").val() 42 | })); 43 | } 44 | 45 | function showGreeting(chatMessage) { 46 | console.log(chatMessage.name) 47 | $("#chatting").append("" + "[" + chatMessage.name + "]" + chatMessage.message + ""); 48 | } 49 | 50 | $(function () { 51 | $("form").on('submit', function (e) { 52 | e.preventDefault(); 53 | }); 54 | $( "#connect" ).click(function() { connect(); }); 55 | $( "#disconnect" ).click(function() { disconnect(); }); 56 | $( "#send" ).click(function() { sendName(); }); 57 | }); 58 | 59 | -------------------------------------------------------------------------------- /chat/src/main/resources/static/main.css: -------------------------------------------------------------------------------- 1 | body { 2 | background-color: #f5f5f5; 3 | } 4 | 5 | #main-content { 6 | max-width: 940px; 7 | padding: 2em 3em; 8 | margin: 0 auto 20px; 9 | background-color: #fff; 10 | border: 1px solid #e5e5e5; 11 | -webkit-border-radius: 5px; 12 | -moz-border-radius: 5px; 13 | border-radius: 5px; 14 | } -------------------------------------------------------------------------------- /chat/src/main/resources/templates/chat/room.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Hello WebSocket 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | Seems your browser doesn't support Javascript! Websocket relies on Javascript being 13 | enabled. Please enable 14 | Javascript and reload this page! 15 | 16 | 17 | 18 | 19 | 20 | WebSocket connection: 21 | Connect 22 | Disconnect 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 대화명 31 | 32 | 33 | 34 | 내용 35 | 36 | 37 | Send 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | Chat 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 134 | 144 | 145 | -------------------------------------------------------------------------------- /chat/src/main/resources/templates/chat/roomForm.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Title 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /chat/src/main/resources/templates/chat/roomList.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Title 6 | 7 | 8 | 방만들기 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /chat/src/test/java/webSocket/chat/ChatApplicationTests.java: -------------------------------------------------------------------------------- 1 | package webSocket.chat; 2 | 3 | import org.junit.jupiter.api.Test; 4 | import org.springframework.boot.test.context.SpringBootTest; 5 | 6 | @SpringBootTest 7 | class ChatApplicationTests { 8 | 9 | @Test 10 | void contextLoads() { 11 | } 12 | 13 | } 14 | -------------------------------------------------------------------------------- /chat/src/test/java/webSocket/chat/service/ChatServiceTest.java: -------------------------------------------------------------------------------- 1 | package webSocket.chat.service; 2 | 3 | import org.assertj.core.api.Assertions; 4 | import org.junit.jupiter.api.Test; 5 | import org.springframework.beans.factory.annotation.Autowired; 6 | import org.springframework.boot.test.context.SpringBootTest; 7 | import webSocket.chat.domain.Chat; 8 | import webSocket.chat.domain.Room; 9 | import webSocket.chat.repository.ChatRepository; 10 | import webSocket.chat.repository.RoomRepository; 11 | 12 | import java.util.List; 13 | 14 | import static org.junit.jupiter.api.Assertions.*; 15 | @SpringBootTest 16 | class ChatServiceTest { 17 | @Autowired 18 | ChatRepository chatRepository; 19 | 20 | @Autowired 21 | RoomRepository roomRepository; 22 | 23 | @Test 24 | void redisTest() { 25 | Room room = Room.createRoom("room1"); 26 | roomRepository.save(room); 27 | Chat chat = Chat.createChat(room, "범준", "안녕하세요"); 28 | chatRepository.save(chat); 29 | 30 | Chat findChat = chatRepository.findById(chat.getId()).get(); 31 | 32 | System.out.println("findChat = " + findChat); 33 | System.out.println("findChat.getRoom() = " + findChat.getRoom()); 34 | 35 | List rooms = chatRepository.findAllByRoomId(room.getId()); 36 | System.out.println("rooms = " + rooms); 37 | } 38 | 39 | } -------------------------------------------------------------------------------- /complete/.gitignore: -------------------------------------------------------------------------------- 1 | build 2 | target 3 | -------------------------------------------------------------------------------- /complete/.gradle/6.5.1/executionHistory/executionHistory.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BeomjunLee/webSocketChat/8ff4ff58759a6e89f13ac198f93614d2aa09c0b7/complete/.gradle/6.5.1/executionHistory/executionHistory.bin -------------------------------------------------------------------------------- /complete/.gradle/6.5.1/executionHistory/executionHistory.lock: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BeomjunLee/webSocketChat/8ff4ff58759a6e89f13ac198f93614d2aa09c0b7/complete/.gradle/6.5.1/executionHistory/executionHistory.lock -------------------------------------------------------------------------------- /complete/.gradle/6.5.1/fileChanges/last-build.bin: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /complete/.gradle/6.5.1/fileContent/fileContent.lock: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BeomjunLee/webSocketChat/8ff4ff58759a6e89f13ac198f93614d2aa09c0b7/complete/.gradle/6.5.1/fileContent/fileContent.lock -------------------------------------------------------------------------------- /complete/.gradle/6.5.1/fileHashes/fileHashes.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BeomjunLee/webSocketChat/8ff4ff58759a6e89f13ac198f93614d2aa09c0b7/complete/.gradle/6.5.1/fileHashes/fileHashes.bin -------------------------------------------------------------------------------- /complete/.gradle/6.5.1/fileHashes/fileHashes.lock: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BeomjunLee/webSocketChat/8ff4ff58759a6e89f13ac198f93614d2aa09c0b7/complete/.gradle/6.5.1/fileHashes/fileHashes.lock -------------------------------------------------------------------------------- /complete/.gradle/6.5.1/gc.properties: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BeomjunLee/webSocketChat/8ff4ff58759a6e89f13ac198f93614d2aa09c0b7/complete/.gradle/6.5.1/gc.properties -------------------------------------------------------------------------------- /complete/.gradle/6.5.1/javaCompile/classAnalysis.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BeomjunLee/webSocketChat/8ff4ff58759a6e89f13ac198f93614d2aa09c0b7/complete/.gradle/6.5.1/javaCompile/classAnalysis.bin -------------------------------------------------------------------------------- /complete/.gradle/6.5.1/javaCompile/javaCompile.lock: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BeomjunLee/webSocketChat/8ff4ff58759a6e89f13ac198f93614d2aa09c0b7/complete/.gradle/6.5.1/javaCompile/javaCompile.lock -------------------------------------------------------------------------------- /complete/.gradle/6.5.1/javaCompile/taskHistory.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BeomjunLee/webSocketChat/8ff4ff58759a6e89f13ac198f93614d2aa09c0b7/complete/.gradle/6.5.1/javaCompile/taskHistory.bin -------------------------------------------------------------------------------- /complete/.gradle/buildOutputCleanup/buildOutputCleanup.lock: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BeomjunLee/webSocketChat/8ff4ff58759a6e89f13ac198f93614d2aa09c0b7/complete/.gradle/buildOutputCleanup/buildOutputCleanup.lock -------------------------------------------------------------------------------- /complete/.gradle/buildOutputCleanup/cache.properties: -------------------------------------------------------------------------------- 1 | #Sat Mar 20 20:31:14 KST 2021 2 | gradle.version=6.5.1 3 | -------------------------------------------------------------------------------- /complete/.gradle/buildOutputCleanup/outputFiles.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BeomjunLee/webSocketChat/8ff4ff58759a6e89f13ac198f93614d2aa09c0b7/complete/.gradle/buildOutputCleanup/outputFiles.bin -------------------------------------------------------------------------------- /complete/.gradle/checksums/checksums.lock: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BeomjunLee/webSocketChat/8ff4ff58759a6e89f13ac198f93614d2aa09c0b7/complete/.gradle/checksums/checksums.lock -------------------------------------------------------------------------------- /complete/.gradle/checksums/md5-checksums.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BeomjunLee/webSocketChat/8ff4ff58759a6e89f13ac198f93614d2aa09c0b7/complete/.gradle/checksums/md5-checksums.bin -------------------------------------------------------------------------------- /complete/.gradle/checksums/sha1-checksums.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BeomjunLee/webSocketChat/8ff4ff58759a6e89f13ac198f93614d2aa09c0b7/complete/.gradle/checksums/sha1-checksums.bin -------------------------------------------------------------------------------- /complete/.gradle/vcs-1/gc.properties: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BeomjunLee/webSocketChat/8ff4ff58759a6e89f13ac198f93614d2aa09c0b7/complete/.gradle/vcs-1/gc.properties -------------------------------------------------------------------------------- /complete/.idea/.gitignore: -------------------------------------------------------------------------------- 1 | # Default ignored files 2 | /shelf/ 3 | /workspace.xml 4 | # Datasource local storage ignored files 5 | /dataSources/ 6 | /dataSources.local.xml 7 | # Editor-based HTTP Client requests 8 | /httpRequests/ 9 | -------------------------------------------------------------------------------- /complete/.idea/.name: -------------------------------------------------------------------------------- 1 | messaging-stomp-websocket -------------------------------------------------------------------------------- /complete/.idea/compiler.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /complete/.idea/gradle.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /complete/.idea/jarRepositories.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /complete/.idea/misc.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /complete/.idea/vcs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /complete/.mvn/wrapper/maven-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BeomjunLee/webSocketChat/8ff4ff58759a6e89f13ac198f93614d2aa09c0b7/complete/.mvn/wrapper/maven-wrapper.jar -------------------------------------------------------------------------------- /complete/.mvn/wrapper/maven-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.6.3/apache-maven-3.6.3-bin.zip 2 | wrapperUrl=https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.5.2/maven-wrapper-0.5.2.tar.gz 3 | -------------------------------------------------------------------------------- /complete/build.gradle: -------------------------------------------------------------------------------- 1 | plugins { 2 | id 'org.springframework.boot' version '2.4.3' 3 | id 'io.spring.dependency-management' version '1.0.11.RELEASE' 4 | id 'java' 5 | } 6 | 7 | group = 'com.example' 8 | version = '0.0.1-SNAPSHOT' 9 | sourceCompatibility = '1.8' 10 | 11 | repositories { 12 | mavenCentral() 13 | } 14 | 15 | dependencies { 16 | implementation 'org.springframework.boot:spring-boot-starter-websocket' 17 | implementation 'org.webjars:webjars-locator-core' 18 | implementation 'org.webjars:sockjs-client:1.0.2' 19 | implementation 'org.webjars:stomp-websocket:2.3.3' 20 | implementation 'org.webjars:bootstrap:3.3.7' 21 | implementation 'org.webjars:jquery:3.1.1-1' 22 | testImplementation 'org.springframework.boot:spring-boot-starter-test' 23 | } 24 | 25 | test { 26 | useJUnitPlatform() 27 | } 28 | -------------------------------------------------------------------------------- /complete/gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BeomjunLee/webSocketChat/8ff4ff58759a6e89f13ac198f93614d2aa09c0b7/complete/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /complete/gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | distributionUrl=https\://services.gradle.org/distributions/gradle-6.5.1-bin.zip 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | -------------------------------------------------------------------------------- /complete/gradlew: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | 3 | # 4 | # Copyright 2015 the original author or authors. 5 | # 6 | # Licensed under the Apache License, Version 2.0 (the "License"); 7 | # you may not use this file except in compliance with the License. 8 | # You may obtain a copy of the License at 9 | # 10 | # https://www.apache.org/licenses/LICENSE-2.0 11 | # 12 | # Unless required by applicable law or agreed to in writing, software 13 | # distributed under the License is distributed on an "AS IS" BASIS, 14 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | # See the License for the specific language governing permissions and 16 | # limitations under the License. 17 | # 18 | 19 | ############################################################################## 20 | ## 21 | ## Gradle start up script for UN*X 22 | ## 23 | ############################################################################## 24 | 25 | # Attempt to set APP_HOME 26 | # Resolve links: $0 may be a link 27 | PRG="$0" 28 | # Need this for relative symlinks. 29 | while [ -h "$PRG" ] ; do 30 | ls=`ls -ld "$PRG"` 31 | link=`expr "$ls" : '.*-> \(.*\)$'` 32 | if expr "$link" : '/.*' > /dev/null; then 33 | PRG="$link" 34 | else 35 | PRG=`dirname "$PRG"`"/$link" 36 | fi 37 | done 38 | SAVED="`pwd`" 39 | cd "`dirname \"$PRG\"`/" >/dev/null 40 | APP_HOME="`pwd -P`" 41 | cd "$SAVED" >/dev/null 42 | 43 | APP_NAME="Gradle" 44 | APP_BASE_NAME=`basename "$0"` 45 | 46 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 47 | DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' 48 | 49 | # Use the maximum available, or set MAX_FD != -1 to use that value. 50 | MAX_FD="maximum" 51 | 52 | warn () { 53 | echo "$*" 54 | } 55 | 56 | die () { 57 | echo 58 | echo "$*" 59 | echo 60 | exit 1 61 | } 62 | 63 | # OS specific support (must be 'true' or 'false'). 64 | cygwin=false 65 | msys=false 66 | darwin=false 67 | nonstop=false 68 | case "`uname`" in 69 | CYGWIN* ) 70 | cygwin=true 71 | ;; 72 | Darwin* ) 73 | darwin=true 74 | ;; 75 | MINGW* ) 76 | msys=true 77 | ;; 78 | NONSTOP* ) 79 | nonstop=true 80 | ;; 81 | esac 82 | 83 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 84 | 85 | # Determine the Java command to use to start the JVM. 86 | if [ -n "$JAVA_HOME" ] ; then 87 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 88 | # IBM's JDK on AIX uses strange locations for the executables 89 | JAVACMD="$JAVA_HOME/jre/sh/java" 90 | else 91 | JAVACMD="$JAVA_HOME/bin/java" 92 | fi 93 | if [ ! -x "$JAVACMD" ] ; then 94 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 95 | 96 | Please set the JAVA_HOME variable in your environment to match the 97 | location of your Java installation." 98 | fi 99 | else 100 | JAVACMD="java" 101 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 102 | 103 | Please set the JAVA_HOME variable in your environment to match the 104 | location of your Java installation." 105 | fi 106 | 107 | # Increase the maximum file descriptors if we can. 108 | if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then 109 | MAX_FD_LIMIT=`ulimit -H -n` 110 | if [ $? -eq 0 ] ; then 111 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then 112 | MAX_FD="$MAX_FD_LIMIT" 113 | fi 114 | ulimit -n $MAX_FD 115 | if [ $? -ne 0 ] ; then 116 | warn "Could not set maximum file descriptor limit: $MAX_FD" 117 | fi 118 | else 119 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" 120 | fi 121 | fi 122 | 123 | # For Darwin, add options to specify how the application appears in the dock 124 | if $darwin; then 125 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" 126 | fi 127 | 128 | # For Cygwin or MSYS, switch paths to Windows format before running java 129 | if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then 130 | APP_HOME=`cygpath --path --mixed "$APP_HOME"` 131 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` 132 | JAVACMD=`cygpath --unix "$JAVACMD"` 133 | 134 | # We build the pattern for arguments to be converted via cygpath 135 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` 136 | SEP="" 137 | for dir in $ROOTDIRSRAW ; do 138 | ROOTDIRS="$ROOTDIRS$SEP$dir" 139 | SEP="|" 140 | done 141 | OURCYGPATTERN="(^($ROOTDIRS))" 142 | # Add a user-defined pattern to the cygpath arguments 143 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then 144 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" 145 | fi 146 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 147 | i=0 148 | for arg in "$@" ; do 149 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` 150 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option 151 | 152 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition 153 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` 154 | else 155 | eval `echo args$i`="\"$arg\"" 156 | fi 157 | i=$((i+1)) 158 | done 159 | case $i in 160 | (0) set -- ;; 161 | (1) set -- "$args0" ;; 162 | (2) set -- "$args0" "$args1" ;; 163 | (3) set -- "$args0" "$args1" "$args2" ;; 164 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;; 165 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; 166 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; 167 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; 168 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; 169 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; 170 | esac 171 | fi 172 | 173 | # Escape application args 174 | save () { 175 | for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done 176 | echo " " 177 | } 178 | APP_ARGS=$(save "$@") 179 | 180 | # Collect all arguments for the java command, following the shell quoting and substitution rules 181 | eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" 182 | 183 | # by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong 184 | if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then 185 | cd "$(dirname "$0")" 186 | fi 187 | 188 | exec "$JAVACMD" "$@" 189 | -------------------------------------------------------------------------------- /complete/gradlew.bat: -------------------------------------------------------------------------------- 1 | @rem 2 | @rem Copyright 2015 the original author or authors. 3 | @rem 4 | @rem Licensed under the Apache License, Version 2.0 (the "License"); 5 | @rem you may not use this file except in compliance with the License. 6 | @rem You may obtain a copy of the License at 7 | @rem 8 | @rem https://www.apache.org/licenses/LICENSE-2.0 9 | @rem 10 | @rem Unless required by applicable law or agreed to in writing, software 11 | @rem distributed under the License is distributed on an "AS IS" BASIS, 12 | @rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | @rem See the License for the specific language governing permissions and 14 | @rem limitations under the License. 15 | @rem 16 | 17 | @if "%DEBUG%" == "" @echo off 18 | @rem ########################################################################## 19 | @rem 20 | @rem Gradle startup script for Windows 21 | @rem 22 | @rem ########################################################################## 23 | 24 | @rem Set local scope for the variables with windows NT shell 25 | if "%OS%"=="Windows_NT" setlocal 26 | 27 | set DIRNAME=%~dp0 28 | if "%DIRNAME%" == "" set DIRNAME=. 29 | set APP_BASE_NAME=%~n0 30 | set APP_HOME=%DIRNAME% 31 | 32 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 33 | set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" 34 | 35 | @rem Find java.exe 36 | if defined JAVA_HOME goto findJavaFromJavaHome 37 | 38 | set JAVA_EXE=java.exe 39 | %JAVA_EXE% -version >NUL 2>&1 40 | if "%ERRORLEVEL%" == "0" goto init 41 | 42 | echo. 43 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 44 | echo. 45 | echo Please set the JAVA_HOME variable in your environment to match the 46 | echo location of your Java installation. 47 | 48 | goto fail 49 | 50 | :findJavaFromJavaHome 51 | set JAVA_HOME=%JAVA_HOME:"=% 52 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 53 | 54 | if exist "%JAVA_EXE%" goto init 55 | 56 | echo. 57 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 58 | echo. 59 | echo Please set the JAVA_HOME variable in your environment to match the 60 | echo location of your Java installation. 61 | 62 | goto fail 63 | 64 | :init 65 | @rem Get command-line arguments, handling Windows variants 66 | 67 | if not "%OS%" == "Windows_NT" goto win9xME_args 68 | 69 | :win9xME_args 70 | @rem Slurp the command line arguments. 71 | set CMD_LINE_ARGS= 72 | set _SKIP=2 73 | 74 | :win9xME_args_slurp 75 | if "x%~1" == "x" goto execute 76 | 77 | set CMD_LINE_ARGS=%* 78 | 79 | :execute 80 | @rem Setup the command line 81 | 82 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 83 | 84 | @rem Execute Gradle 85 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% 86 | 87 | :end 88 | @rem End local scope for the variables with windows NT shell 89 | if "%ERRORLEVEL%"=="0" goto mainEnd 90 | 91 | :fail 92 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 93 | rem the _cmd.exe /c_ return code! 94 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 95 | exit /b 1 96 | 97 | :mainEnd 98 | if "%OS%"=="Windows_NT" endlocal 99 | 100 | :omega 101 | -------------------------------------------------------------------------------- /complete/settings.gradle: -------------------------------------------------------------------------------- 1 | rootProject.name = 'messaging-stomp-websocket' 2 | -------------------------------------------------------------------------------- /complete/src/main/java/com/example/messagingstompwebsocket/Greeting.java: -------------------------------------------------------------------------------- 1 | package com.example.messagingstompwebsocket; 2 | 3 | public class Greeting { 4 | 5 | private String content; 6 | 7 | public Greeting() { 8 | } 9 | public Greeting(String content) { 10 | this.content = content; 11 | } 12 | 13 | public String getContent() { 14 | return content; 15 | } 16 | 17 | } 18 | -------------------------------------------------------------------------------- /complete/src/main/java/com/example/messagingstompwebsocket/GreetingController.java: -------------------------------------------------------------------------------- 1 | package com.example.messagingstompwebsocket; 2 | 3 | import org.springframework.messaging.handler.annotation.MessageMapping; 4 | import org.springframework.messaging.handler.annotation.SendTo; 5 | import org.springframework.stereotype.Controller; 6 | import org.springframework.web.util.HtmlUtils; 7 | 8 | @Controller 9 | public class GreetingController { 10 | 11 | 12 | @MessageMapping("/hello") 13 | @SendTo("/topic/greetings") // topic의 greetings 라는 방 14 | public Greeting greeting(HelloMessage message) throws Exception { 15 | Thread.sleep(500); // simulated delay 16 | return new Greeting("Hello, " + message.getName() + "!"); 17 | } 18 | 19 | } 20 | -------------------------------------------------------------------------------- /complete/src/main/java/com/example/messagingstompwebsocket/HelloMessage.java: -------------------------------------------------------------------------------- 1 | package com.example.messagingstompwebsocket; 2 | 3 | public class HelloMessage { 4 | 5 | private String name; 6 | 7 | public HelloMessage() { 8 | } 9 | 10 | public HelloMessage(String name) { 11 | this.name = name; 12 | } 13 | 14 | public String getName() { 15 | return name; 16 | } 17 | 18 | public void setName(String name) { 19 | this.name = name; 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /complete/src/main/java/com/example/messagingstompwebsocket/MessagingStompWebsocketApplication.java: -------------------------------------------------------------------------------- 1 | package com.example.messagingstompwebsocket; 2 | 3 | import org.springframework.boot.SpringApplication; 4 | import org.springframework.boot.autoconfigure.SpringBootApplication; 5 | 6 | @SpringBootApplication 7 | public class MessagingStompWebsocketApplication { 8 | 9 | public static void main(String[] args) { 10 | SpringApplication.run(MessagingStompWebsocketApplication.class, args); 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /complete/src/main/java/com/example/messagingstompwebsocket/WebSocketConfig.java: -------------------------------------------------------------------------------- 1 | package com.example.messagingstompwebsocket; 2 | 3 | import org.springframework.context.annotation.Configuration; 4 | import org.springframework.messaging.simp.config.MessageBrokerRegistry; 5 | import org.springframework.web.socket.config.annotation.EnableWebSocketMessageBroker; 6 | import org.springframework.web.socket.config.annotation.StompEndpointRegistry; 7 | import org.springframework.web.socket.config.annotation.WebSocketMessageBrokerConfigurer; 8 | 9 | @Configuration 10 | @EnableWebSocketMessageBroker 11 | public class WebSocketConfig implements WebSocketMessageBrokerConfigurer { 12 | 13 | @Override 14 | public void configureMessageBroker(MessageBrokerRegistry config) { 15 | config.enableSimpleBroker("/topic"); //응답 prefix 16 | config.setApplicationDestinationPrefixes("/app"); //송신 prefix 17 | } 18 | 19 | @Override 20 | public void registerStompEndpoints(StompEndpointRegistry registry) { 21 | registry.addEndpoint("/ws-stomp").withSockJS(); //javascript SockJS 22 | } 23 | 24 | } 25 | -------------------------------------------------------------------------------- /complete/src/main/resources/static/app.js: -------------------------------------------------------------------------------- 1 | var stompClient = null; 2 | 3 | function setConnected(connected) { 4 | $("#connect").prop("disabled", connected); 5 | $("#disconnect").prop("disabled", !connected); 6 | if (connected) { 7 | $("#conversation").show(); 8 | } 9 | else { 10 | $("#conversation").hide(); 11 | } 12 | $("#greetings").html(""); 13 | } 14 | 15 | function connect() { 16 | var socket = new SockJS('/ws-stomp'); 17 | stompClient = Stomp.over(socket); 18 | stompClient.connect({}, function (frame) { 19 | setConnected(true); 20 | console.log('Connected: ' + frame); 21 | stompClient.subscribe('/topic/greetings', function (greeting) { 22 | showGreeting(JSON.parse(greeting.body).content); 23 | }); 24 | }); 25 | } 26 | 27 | function disconnect() { 28 | if (stompClient !== null) { 29 | stompClient.disconnect(); 30 | } 31 | setConnected(false); 32 | console.log("Disconnected"); 33 | } 34 | 35 | function sendName() { 36 | stompClient.send("/app/hello", {}, JSON.stringify({'name': $("#name").val()})); 37 | } 38 | 39 | function showGreeting(message) { 40 | $("#greetings").append("" + message + ""); 41 | } 42 | 43 | $(function () { 44 | $("form").on('submit', function (e) { 45 | e.preventDefault(); 46 | }); 47 | $( "#connect" ).click(function() { connect(); }); 48 | $( "#disconnect" ).click(function() { disconnect(); }); 49 | $( "#send" ).click(function() { sendName(); }); 50 | }); 51 | 52 | -------------------------------------------------------------------------------- /complete/src/main/resources/static/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Hello WebSocket 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | Seems your browser doesn't support Javascript! Websocket relies on Javascript being 14 | enabled. Please enable 15 | Javascript and reload this page! 16 | 17 | 18 | 19 | 20 | 21 | WebSocket connection: 22 | Connect 23 | Disconnect 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | What is your name? 32 | 33 | 34 | Send 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | Greetings 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | -------------------------------------------------------------------------------- /complete/src/main/resources/static/main.css: -------------------------------------------------------------------------------- 1 | body { 2 | background-color: #f5f5f5; 3 | } 4 | 5 | #main-content { 6 | max-width: 940px; 7 | padding: 2em 3em; 8 | margin: 0 auto 20px; 9 | background-color: #fff; 10 | border: 1px solid #e5e5e5; 11 | -webkit-border-radius: 5px; 12 | -moz-border-radius: 5px; 13 | border-radius: 5px; 14 | } -------------------------------------------------------------------------------- /complete/src/test/java/com/example/messagingstompwebsocket/GreetingIntegrationTests.java: -------------------------------------------------------------------------------- 1 | package com.example.messagingstompwebsocket; 2 | 3 | import static org.junit.jupiter.api.Assertions.*; 4 | 5 | import java.lang.reflect.Type; 6 | import java.util.ArrayList; 7 | import java.util.List; 8 | import java.util.concurrent.CountDownLatch; 9 | import java.util.concurrent.TimeUnit; 10 | import java.util.concurrent.atomic.AtomicReference; 11 | 12 | import org.junit.jupiter.api.BeforeEach; 13 | import org.junit.jupiter.api.Test; 14 | 15 | import org.springframework.boot.test.context.SpringBootTest; 16 | import org.springframework.boot.web.server.LocalServerPort; 17 | import org.springframework.messaging.converter.MappingJackson2MessageConverter; 18 | import org.springframework.messaging.simp.stomp.StompCommand; 19 | import org.springframework.messaging.simp.stomp.StompFrameHandler; 20 | import org.springframework.messaging.simp.stomp.StompHeaders; 21 | import org.springframework.messaging.simp.stomp.StompSession; 22 | import org.springframework.messaging.simp.stomp.StompSessionHandler; 23 | import org.springframework.messaging.simp.stomp.StompSessionHandlerAdapter; 24 | import org.springframework.web.socket.WebSocketHttpHeaders; 25 | import org.springframework.web.socket.client.standard.StandardWebSocketClient; 26 | import org.springframework.web.socket.messaging.WebSocketStompClient; 27 | import org.springframework.web.socket.sockjs.client.SockJsClient; 28 | import org.springframework.web.socket.sockjs.client.Transport; 29 | import org.springframework.web.socket.sockjs.client.WebSocketTransport; 30 | 31 | @SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) 32 | public class GreetingIntegrationTests { 33 | 34 | @LocalServerPort 35 | private int port; 36 | 37 | private SockJsClient sockJsClient; 38 | 39 | private WebSocketStompClient stompClient; 40 | 41 | private final WebSocketHttpHeaders headers = new WebSocketHttpHeaders(); 42 | 43 | @BeforeEach 44 | public void setup() { 45 | List transports = new ArrayList<>(); 46 | transports.add(new WebSocketTransport(new StandardWebSocketClient())); 47 | this.sockJsClient = new SockJsClient(transports); 48 | 49 | this.stompClient = new WebSocketStompClient(sockJsClient); 50 | this.stompClient.setMessageConverter(new MappingJackson2MessageConverter()); 51 | } 52 | 53 | @Test 54 | public void getGreeting() throws Exception { 55 | 56 | final CountDownLatch latch = new CountDownLatch(1); 57 | final AtomicReference failure = new AtomicReference<>(); 58 | 59 | StompSessionHandler handler = new TestSessionHandler(failure) { 60 | 61 | @Override 62 | public void afterConnected(final StompSession session, StompHeaders connectedHeaders) { 63 | session.subscribe("/topic/greetings", new StompFrameHandler() { 64 | @Override 65 | public Type getPayloadType(StompHeaders headers) { 66 | return Greeting.class; 67 | } 68 | 69 | @Override 70 | public void handleFrame(StompHeaders headers, Object payload) { 71 | Greeting greeting = (Greeting) payload; 72 | try { 73 | assertEquals("Hello, Spring!", greeting.getContent()); 74 | } catch (Throwable t) { 75 | failure.set(t); 76 | } finally { 77 | session.disconnect(); 78 | latch.countDown(); 79 | } 80 | } 81 | }); 82 | try { 83 | session.send("/app/hello", new HelloMessage("Spring")); 84 | } catch (Throwable t) { 85 | failure.set(t); 86 | latch.countDown(); 87 | } 88 | } 89 | }; 90 | 91 | this.stompClient.connect("ws://localhost:{port}/gs-guide-websocket", this.headers, handler, this.port); 92 | 93 | if (latch.await(3, TimeUnit.SECONDS)) { 94 | if (failure.get() != null) { 95 | throw new AssertionError("", failure.get()); 96 | } 97 | } 98 | else { 99 | fail("Greeting not received"); 100 | } 101 | 102 | } 103 | 104 | private class TestSessionHandler extends StompSessionHandlerAdapter { 105 | 106 | private final AtomicReference failure; 107 | 108 | public TestSessionHandler(AtomicReference failure) { 109 | this.failure = failure; 110 | } 111 | 112 | @Override 113 | public void handleFrame(StompHeaders headers, Object payload) { 114 | this.failure.set(new Exception(headers.toString())); 115 | } 116 | 117 | @Override 118 | public void handleException(StompSession s, StompCommand c, StompHeaders h, byte[] p, Throwable ex) { 119 | this.failure.set(ex); 120 | } 121 | 122 | @Override 123 | public void handleTransportError(StompSession session, Throwable ex) { 124 | this.failure.set(ex); 125 | } 126 | } 127 | } 128 | --------------------------------------------------------------------------------