안녕하세요. 우선 좋은 강의 만들어주셔서 정말 감사드립니다. 한가지 질문이 있습니다. 만약 spring boot 이용해서 stomp 프로토콜을 이용해서 채팅 서비스를 구축 한다고 하였을 때 특정 사용자가 최초 웹소켓 connection 연결 후 해당 웹소켓 서버가 재배포 했을때 고려해야 될 상황이 있습니다. 그럼 프론트 서버에서는 기존에 연결되었던 프론트 서버 정보는 모두 삭제가 될텐데요. 이렇게 된다고 하면 사용자는 채팅 페이지에서 채팅 서비스가 멈춤 현상이 발생 할 것 같습니다. 이것을 해결하기 위해 특정 프론트 서버 정보는 백엔드 서버 메모리에 저장 하는 것보다 외부 서버에 (Ex> Redis) 관리 해야 할까요?
안녕하세요 강사님 제가 실전이라 생각하고 프론트랑 백엔드를 배포 했습니다. auto at org.springframework.boot.devtools.restart.RestartLauncher.run(RestartLauncher.java:50) ~[spring-boot-devtools-3.5.3.jar:3.5.3] Caused by: org.springframework.data.redis.listener.adapter.RedisListenerExecutionFailedException: org.springframework.data.redis.RedisConnectionFailureException: Unable to connect to Redis at org.springframework.data.redis.listener.RedisMessageListenerContainer.lazyListen(RedisMessageListenerContainer.java:383) ~[spring-data-redis-3.5.1.jar:3.5.1] at org.springframework.data.redis.listener.RedisMessageListenerContainer.start(RedisMessageListenerContainer.java:361) ~[spring-data-redis-3.5.1.jar:3.5.1] at org.springframework.context.support.DefaultLifecycleProcessor.doStart(DefaultLifecycleProcessor.java:405) ~[spring-context-6.2.8.jar:6.2.8] ... 19 common frames omitted Caused by: org.springframework.data.redis.RedisConnectionFailureException: Unable to connect to Redis at org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory$ExceptionTranslatingConnectionProvider.translateException(LettuceConnectionFactory.java:1858) ~[spring-data-redis-3.5.1.jar:3.5.1] at org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory$ExceptionTranslatingConnectionProvider.getConnection(LettuceConnectionFactory.java:1789) ~[spring-data-redis-3.5.1.jar:3.5.1] at org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory$SharedConnection.getNativeConnection(LettuceC Redis 에러 (백엔드 에러는 이렇게 뜨고) 프론트 엔드 (로그인 하고 들어가는 거 잘됩니다. 채팅만 치면 저렇게 뜹니다.) EC2에서 redis 접속은 잘 됩니다. 프론트는 S3+cloudfront (도메인 적용 안함)를 통해 배포를 했고 둘다 https 는 적용했습니다. 백엔드는 Redis(ElastiCache(실시간 PubSub 기능을 위해) + EC2+RDS +nginx(https)+ 깃헙 액션으 로 배포했고 몇시간 쨰 하는데 저 에러가 뜹니다. 백엔드 배포 자체는 /opt ~ 를 통해서 배포 했습니다. 혹시 프론트 배포가 잘 못 된건가요? S3+cloudfront 면 안될수도 있나요? StompWebSocketConfig package org.example.javademo.chatserver2.chat.config; import org.springframework.context.annotation.Configuration; import org.springframework.messaging.simp.config.ChannelRegistration; import org.springframework.messaging.simp.config.MessageBrokerRegistry; import org.springframework.web.socket.config.annotation.EnableWebSocketMessageBroker; import org.springframework.web.socket.config.annotation.StompEndpointRegistry; import org.springframework.web.socket.config.annotation.WebSocketMessageBrokerConfigurer; @Configuration @EnableWebSocketMessageBroker public class StompWebSocketConfig implements WebSocketMessageBrokerConfigurer { private final StompHandler stompHandler; public StompWebSocketConfig(StompHandler stompHandler) { this.stompHandler = stompHandler; } /** * 클라이언트가 WebSocket으로 접속할 수 있는 endpoint 설정 */ @Override public void registerStompEndpoints(StompEndpointRegistry registry) { registry.addEndpoint("/connect") .setAllowedOrigins( ) // SockJS를 통해 ws가 아닌 환경에서도 연결 가능하게 설정 .withSockJS(); } /** * STOMP 메시지 브로커 설정 */ @Override public void configureMessageBroker(MessageBrokerRegistry registry) { // /publish 로 시작하는 메시지는 @MessageMapping 메서드로 라우팅 registry.setApplicationDestinationPrefixes("/publish"); // /topic 으로 시작하는 메시지는 메시지 브로커를 통해 구독자에게 전달 registry.enableSimpleBroker("/topic"); } /** * 클라이언트 → 서버 (inbound) 메시지 처리 쓰레드 풀 설정 */ @Override public void configureClientInboundChannel(ChannelRegistration registration) { registration.taskExecutor() .corePoolSize(10) .maxPoolSize(20) .keepAliveSeconds(60); } /** * 서버 → 클라이언트 (outbound) 메시지 처리 쓰레드 풀 설정 */ @Override public void configureClientOutboundChannel(ChannelRegistration registration) { registration.taskExecutor() .corePoolSize(10) .maxPoolSize(20) .keepAliveSeconds(60); } // 인증 등을 처리하고 싶을 경우 stompHandler 등록 (현재 주석처리) // @Override // public void configureClientInboundChannel(ChannelRegistration registration) { // registration.interceptors(stompHandler);... // } } 이거는 조금 수정했습니다. RedisConfig package org.example.javademo.chatserver2.common.configs; import org.example.javademo.chatserver2.chat.service.RedisPubSubService; import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.beans.factory.annotation.Value; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.data.redis.connection.RedisConnectionFactory; import org.springframework.data.redis.connection.RedisStandaloneConfiguration; import org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory; import org.springframework.data.redis.core.RedisTemplate; import org.springframework.data.redis.core.StringRedisTemplate; import org.springframework.data.redis.listener.PatternTopic; import org.springframework.data.redis.listener.RedisMessageListenerContainer; import org.springframework.data.redis.listener.adapter.MessageListenerAdapter; import org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer; import org.springframework.data.redis.serializer.StringRedisSerializer; @Configuration public class RedisConfig { @Value("${spring.data.redis.host}") private String host; @Value("${spring.data.redis.port}") private int port; // 연결 팩토리 생성 @Bean @Qualifier("chatPubSub") public RedisConnectionFactory chatPubSubFactory() { RedisStandaloneConfiguration config = new RedisStandaloneConfiguration(); config.setHostName(host); config.setPort(port); return new LettuceConnectionFactory(config); } // String 기반 Redis 템플릿 @Bean @Qualifier("chatPubSub") public StringRedisTemplate stringRedisTemplate(@Qualifier("chatPubSub") RedisConnectionFactory connectionFactory) { return new StringRedisTemplate(connectionFactory); } // 메시지 수신 처리 리스너 어댑터 @Bean public MessageListenerAdapter messageListenerAdapter(RedisPubSubService redisPubSubService) { return new MessageListenerAdapter(redisPubSubService, "onMessage"); } // 수신 채널 주제 설정 @Bean public PatternTopic chatTopic() { return new PatternTopic("chat"); } // 메시지 수신을 처리하는 리스너 컨테이너. @Bean public RedisMessageListenerContainer redisMessageListenerContainer( @Qualifier("chatPubSub") RedisConnectionFactory connectionFactory, MessageListenerAdapter listenerAdapter, PatternTopic chatTopic ) { RedisMessageListenerContainer container = new RedisMessageListenerContainer(); container.setConnectionFactory(connectionFactory); container.addMessageListener(listenerAdapter, chatTopic); return container; } // (선택) Object 기반 Redis 템플릿 @Bean public RedisTemplate<String, Object> redisTemplate(@Qualifier("chatPubSub") RedisConnectionFactory connectionFactory) { RedisTemplate<String, Object> template = new RedisTemplate<>(); template.setConnectionFactory(connectionFactory); template.setKeySerializer(new StringRedisSerializer()); template.setValueSerializer(new GenericJackson2JsonRedisSerializer()); return template; } } ElastiCache로 접속은 굉장히 잘 됩니다. (EC2 내부에서는) 문제는 이게 UI 에 왜 안 뜨는지 모르겠습니다.
개발자라면 알아야 할 redis 기본 개발자라면 알아야 할 리눅스(linux-ubuntu) 기본 이거 두강좌 굉장히 저렴했던걸로 기억하는데 지금은 많이 올라갔네요 기존 수강생에게 이전 저렴한 가격으로 구입할수있는 할인쿠폰을 발행해주실수없으신지요? 괜히 가격인상됬다하니 구입하고싶은마음이 생겨났습니다. DB를 MYSQL을 사용하기에 선생님 강의 MYSQL둘다 구입했는데 그래서 REDIS가 뭔지 몰라서 관심밖이였었거든요 그리고 새로 개강한 eks를 활용한 spring 운영서버 배포(feat. devops의 모든것) 이강좌는 제가 구입해도될런지 잘모르겠습니다. 스프링부트로 게시판한번 만들어봤는데... 그단계의 연장선상인가요??? 빠르게 배우는 Spring Cloud 기초(MSA) 이강좌를 들어야만 새로개강한강좌를 이해할수있는건지요??? 스프링부트로 웹사이트만들어서 창업목적을 두고있는 사람입니다. 취업목적이아니라...
안녕하세요 강의 잘 듣고 있습니다. "5. websocket, stomp 비교" 강의 6:07초에 STOMP 흐름에서 궁금한 점이 있습니다! 방법1 "(클라이언트 1-> 서버)로 연결 요청 및 특정 room 구독"으로 되어 있어서 서버를 통해 브로커를 거쳐 구독하는 방법 방법2 "(클라이언트 2 -> 브로커)로 연결 요청 및 특정 room 구독"에서는 클라이언트 2는 웹 소켓 서버로 다이렉트로 전달하지 않고, 바로 브로커에게 요청을 보내는 방법 질문 두 가지 방법으로 요청을 할 수 있는건가요? 두 방법의 차이는 무엇인가요??
안녕하세요. 강의 잘 듣고 있습니다! http,websocket의 비교 강의를 듣다가 갑자기 궁금한게 생겼는데요. http 통신의 경우 요청을 보내는 쪽은 클라이언트로 정해져 있는 것 같은데, 웹 소켓 통신을 위해 클라이언트와 서버가 연결을 맺을 땐 연결 요청 주체 는 항상 클라이언트라고 보면 될까요? 반대로 서버가 클라이언트에게 연결 요청을 보낸 후 클라이언트와 웹 소켓 연결이 될 수도 있나요??
새로 개강한 강좌에 수강전문의게시판이없어서 여기에 질문을 남기게되었습니다. eks를 활용한 spring 운영서버 배포(feat. devops의 모든것) 이강좌가 새로 오픈했던데 빠르게 배우는 Spring Cloud 기초(MSA) 이강좌가 선수과정인가요??? 아니면 이걸안해도 새로 개강한강좌만 신청해도 되는관계인지 궁금합니다.
안녕하세요:) 도메인구성 강의 중 ddl-auto : update로 처음 설정 한 뒤 서버를 돌렸을 때 member 테이블 생성이 안되어서 ddl-auto: create로 바꾸신걸까요?? 제가 헷갈리는 부분이 처음 member테이블을 생성할 때는 ddl-auto: update로 설정했을 때에도 테이블생성이 되는 걸로 알고 있어서요. 혼자서 스프링부트 jpa프로젝트 만들엇을때도 ddl-auto:udpate로 엔티티테이블 잘 생성되었던 것 같은데 update를 꼭 create로 처음에 (지금처럼 member 테이블을 처음생성할 때) 바꿔줘야하는 건가요??
안녕하세요 ! 개발환경세팅에서 선생님이 하시는 과정을 그대로 따라 갔는데 npm run serve 명령어 입력 시 밑에 오류가 뜹니다. 검색해보니 pnpm을 설치하라고 하는데 선생님과 똑같이 진행 했는데 오류가 뜨는 이유가 뭔지 궁금해서 질문글 남깁니다. 오류 내용: ERROR Error: The project seems to require pnpm but it's not installed. Error: The project seems to require pnpm but it's not installed.
안녕하세요 선생님 ! 섹션 6. 이전 메시지 조회 강의에서 질문 있습니다. 1. 선생님 코드의 getChatHistory() 메서드에서 chatParticipants를 chatRoom으로 조회해서 for문을 돌면서 현재 로그인한 멤버가 chatParticipants에 있는지 확인하고 있는데, findByChatRoomAndMember 메서드를 통해 참여자인지 확인해도 로직에 상관이 없을까요 ? 2. 현재 로직은 getHistory 메서드가 실행되면 해당 참여자가 채팅방에 참여하기 전의 메시지들까지 전부 표시될것 같은데, 참여자가 채팅방에 참여한 후 부터의 메시지만 보여주고 싶으면 참여시간을 따로 저장한 다음 findByChatRoomAndCreatedTimeGreaterThanEqualOrderByCreatedTimeAsc( ChatRoom chatRoom, LocalDateTime participatedTime); 이런식으로 코드를 짜면 될까요 ? 좋은 방법인지 모르겠습니다. 선생님 코드 public List<ChatMessageDto> getChatHistory(Long roomId){ // 내가 해당 채팅방의 참여자가 아닐경우 에러 ChatRoom chatRoom = chatRoomRepository.findById(roomId).orElseThrow(()-> new EntityNotFoundException("room cannot be found")); Member member = memberRepository.findByEmail(SecurityContextHolder.getContext().getAuthentication().getName()).orElseThrow(()->new EntityNotFoundException("member cannot be found")); List<ChatParticipant> chatParticipants = chatParticipantRepository.findByChatRoom(chatRoom); boolean check = false; for(ChatParticipant c : chatParticipants){ if(c.getMember().equals(member)){ check = true; } } if(!check)throw new IllegalArgumentException("본인이 속하지 않은 채팅방입니다."); // 특정 room에 대한 message조회 List<ChatMessage> chatMessages = chatMessageRepository.findByChatRoomOrderByCreatedTimeAsc(chatRoom); List<ChatMessageDto> chatMessageDtos = new ArrayList<>(); for(ChatMessage c : chatMessages){ ChatMessageDto chatMessageDto = ChatMessageDto.builder() .message(c.getContent()) .senderEmail(c.getMember().getEmail()) .build(); chatMessageDtos.add(chatMessageDto); } return chatMessageDtos; }
안녕하세요 선생님, 섹션6. 메시지 읽음처리, 채팅방 나가기, 11분 25초 쯤에 findByChatRoomAndMember(chatRoom, member); 코드로 모든 readStatus를 가져와서 읽음 처리를 하는 것으로 보입니다. 읽지 않음 상태의 readStatus만 가져와서 읽음 상태로 바꿔주어도 상관이 없을까요 ? 좋은 강의 정말 감사합니다.
안녕하세요 ㅎㅎ 채팅 시스템이 있는 프로젝트를 구현하고 싶어서 강의 수강중에 있습니다 :) 현재 진행중인 프로젝트 구조는 msa 구조로 프런트-게이트웨이-유레카-백엔드 이런식으로 있고 프런트쪽은 html+js+spring feignClient로 이루어져 있습니다. 다른 프레임워크를 사용하지 않고 html과 js로만 구현이 가능케 해야하는데 vue를 사용하지 않고도 강의에서 다루고 있는 채팅 시스템을 문제없이 구현이 가능한지 궁금해서 여쭤봅니다!!
Event Loop에서 질문이 있습니다. 저는 await task()을 하면 작업 큐에 등록된 후 차례가 되면 실행이 되는 것으로 이해를 하였는데 그러면 다음과 같은 코드에서, import asyncio async def inner_task(): print(f"inner_task 시작") await asyncio.sleep(1) print(f"inner_task 완료") return "inner 결과" async def outer_task(n): print(f"outer_task {n} 시작") results = await inner_task() print(f"outer_task {n} 완료") return results async def main(): await asyncio.gather(outer_task(1), outer_task(2), outer_task(3)) asyncio.run(main()) 먼저 큐에 등록된 outer_task들이 먼저 동작을 하는 것으로 예상을 하였지만 먼저 들어간 outer_task보다 내부의 inner_task가 먼저 동작을 하였습니다. 하지만 outer_task에 gather을 사용하여 여러 task를 await하면, 모든 outer_task 이후에 작동을 하는 데 혹시 작동원리가 다를까요?
프로젝트에서 IOT와 결합한 게임을 만들 예정인데, 실시간 통신인 Websocket이 필요하다고 해서 강의를 급하게 결제하였습니다. 여기에서는 실시간 채팅을 구현하는 걸로 보이는데 혹시 여기 내용을 공부하면 프로젝트에 실시간 통신 방식에서도 활용할 수 있을까요? 실시간 방 생성이나 IOT 현물과의 통신에서 WebSocket을 이용할거 같습니다.
안녕하세요. jwt 11 버전 설명해주시면서 생성과 검증에 대한 궁금증이 생기는데요. 생성을 위해서는 base64로 인코딩 된 값을 디코더 하신 뒤 SecretKeySpec을 이용한 뒤 HS512로 암호화 하셨는데요. 검증 부분에서는 base64로 인코딩 된 값(디코더와 SecretKeySpec가 이루어지지 않은 채로)으로 토큰 검증 및 claims를 추출하는데 이 부분에서 문제가 없는 걸까요?