반응형

 

MongoDB는 mysql과는 다르게 Database를 만들고, User를 생성한다.

 

DB생성

use chat

 

user생성
db.createUser({
  user: "root",
  pwd: "root",
  roles: [ "readWrite", "dbAdmin" ]
})

- 유저와 채팅하기 (view - 도서 donation) "채팅하기 버튼"을 눌렀을 시
1. [be] 채팅방 생성
2. [be] 웹소켓 연결
3. [be] 채팅방 구독
4. [fe] 채팅방으로 이동

- 하단 "Chat" 누르면
1. [be] 웹소켓 연결
2. [fe] 채팅방 리스트 이동
3. [be] 채팅방 리스트 조회 내려주기

- "유저와 채팅"을 눌렀을 시
1. [be] 채팅방 구독
2. [be] 채팅 기록 가져오기

- 채팅방 나갔을 시 작업
1. [be] 채팅방 구독 해제
- 하단 다른 component 로 이동 시
1. [be] 웹소켓 해제
반응형
반응형

현재 중고책 기부 프로젝트를 진행하고 있습니다.
유저들끼리 중고책 기부를 하기 위해 채팅이 필요하며,
다수가 참여하는 유저 채팅보다는 일대일 채팅만 필요한 상황입니다.

 

일대일 채팅만 구현해야 한다고 가정

 

user1이 user2 에게 채팅 신청을 한다.

user2는 user1이 채팅 신청한 것을 알아야 한다.

 

user1 user2의 채팅방이 데이터 베이스에 저장된다.

user1, user2는 각각 해당 채팅방을 검색할 수 있어야 한다.

어떻게 저장을 하는 것이 효율적일까?

 

해당 채팅방에 채팅을 하면, 채팅을 한 사람이 누군지만 기록하면 된다.

채팅 기록은 채팅방 id 와 연관관계를 형성하고 있기 때문이다.

 

 

기본적인 스키마 디자인

 

ChatRoom: 
chatRoomId (PK)
participant1 (user1의 ID)
participant2 (user2의 ID)


Message: 
messageId (PK)
chatRoomId (FK, ChatRoom 참조)
senderId (메시지를 보낸 사용자의 ID)

content (메시지 내용)
timestamp (메시지가 보내진 시간)

 

고민했던 것

participant1 (user1의 ID)
participant2 (user2의 ID)

순서를 어떻게 처리하는게 효율적일까? 중복을 방지해야 할 수도 있다.

 

1. nickname 정렬을 한다.

nickname을 바로 알 수 있지만, member-id를 통해 nickname을 받아와야 한다.

 

2. member-id 정렬을 한다.

member-id 가 RequestHeader를 통해 들어오기 때문에 바로 조회할 수 있다.

닉네임은 알 수 없다.

 

중복방지는 필요가 없었다. 유저끼리 여러 책을 교환하는 상황이 나올 수 있다.

 

 

해결방법

nickname, member-id 둘다 message에 저장하기로 했다.

message 데이터를 구별하기 위해 member 서버에 다시 요청을 계속 보내는 것이 부담스럽다.

DB저장 공간을 희생시키며 요청 횟수를 줄이기로 했다.

 

Message: 
messageId (PK)
chatRoomId (FK, ChatRoom 참조)
senderId (메시지를 보낸 사용자의 ID)

senderNickname (메시지를 보낸 사용자의 닉네임) 추가

content (메시지 내용)
timestamp (메시지가 보내진 시간)

 

 

User Flow

사용자가 자신이 참여하고 있는 채팅방을 본다.

ChatRoom - participant1 (user1의 ID), participant2 (user2의 ID) 둘중 하나라도 매칭되면 해당 채팅방을 보여준다.

 

사용자가 채팅방 하나를 누를경우 해당 채팅방 채팅기록을 보여준다.

채팅 기록들은 senderId가 자신이라면 오른쪽으로 배치, 아니라면 왼쪽으로 배치

 

 

추가적인 고려 상황

채팅 기록은 무조건 실시간으로 구현해야 한다.

하지만, 채팅방 리스트는 실시간으로 구현해야 할까?
알림이 오면 채팅방 리스트를 볼텐데, 그때 http통신을 다시 하지 않을까?

사용자 편의성을 위해 실시간으로 채팅방 리스트까지도 실시간으로 구현하는게 좋을 것 같다.

그리고 채팅방마다 마지막 채팅 내역 또한 실시간 기능이다.

읽지 않은 채팅 개수 까지는 필요하지 않을 것 같다. (추후 고려 사항)

 

 

WebSocket을 사용할까? SSE를 사용할까? 이 부분도 기술 스택을 무엇을 이용해야 할지 생각해봐야 한다.

> WebSocket을 사용하기로 했다.


채팅방 리스트까지 실시간으로 구현하려면, WebSocket을 채팅방을 클릭하면 WebSocket 통신을 열어야 하고,
채팅 기록만 실시간으로 구현한다면, 채팅방을 누른 이후부터 WebSocket 통신을 열어야 한다.

> 사용자 편의성을 위해 채팅방 리스트까지 실시간으로 구현하기로 했다.
   채팅 위젯을 열면, ws 연결

 

구현해야 할 실시간 기능들

> 채팅 방 추가, 채팅 방의 마지막 채팅, 채팅 내부 기록

반응형
반응형

Data

Docker에서 볼륨을 지정할 때, -v 옵션 뒤에 경로가 아닌 단순한 문자열(예: myvolume)을 지정하면, 

Docker는 이를 네임드 볼륨(named volume)으로 간주합니다. 반면에 슬래시(/)를 포함한 경로 형태(예: /path/on/host:/path/in/container)를 지정하면 바인드 마운트(bind mount)로 간주합니다.

예를 들면:

네임드 볼륨:
docker run -v myvolume:/path/in/container my_image
위의 명령어는 myvolume이라는 네임드 볼륨을 컨테이너의 /path/in/container 위치에 마운트합니다.

바인드 마운트:
docker run -v /path/on/host:/path/in/container my_image
위의 명령어는 호스트의 /path/on/host 디렉토리를 컨테이너의 /path/in/container 위치에 마운트합니다.

결론적으로, 슬래시(/)가 없는 단순한 문자열을 지정하면 Docker는 이를 네임드 볼륨으로 인식합니다.

 

바인드 마운트


호스트의 특정 디렉토리를 컨테이너에 마운트합니다.
-v 왼쪽경로디렉토리(로컬):오른쪽 경로디렉토리(컨테이너)
이 명령어를 사용하여 호스트의 디렉토리를 컨테이너에 마운트합니다.
주의점으로 bind mount가 컨테이너 내부의 폴더에 우선시 되므로, 기존에 컨테이너 안에 있던 데이터가 덮어쓰일 수 있습니다.
로컬 디렉토리가 우선되며, 로컬을 변경하면 컨테이너에 즉시 반영

 

도커 파일 캐싱

Docker는 이미지를 빌드할 때 효율적으로 처리하기 위해 캐싱 메커니즘이 있습니다.

Dockerfile의 각 줄은 독립적인 레이어로 생성되며, 이전에 빌드했을 때 변경되지 않은 레이어는 캐싱되어 재사용됩니다.

이 Dockerfile을 처음 빌드할 때, 모든 단계가 실행되어 새 이미지가 생성됩니다. 

하지만 COPY 단계에서 소스 코드에 변경사항이 발생하면, 이후의 모든 단계는 캐시를 사용하지 않고 다시 실행됩니다.

캐싱 전략
변경 빈도가 높은 단계는 Dockerfile의 하단에 위치시키는 것이 좋습니다. 

그렇게 하면 변경 빈도가 낮은 단계는 최대한 캐싱을 활용할 수 있습니다.

예를 들어, 애플리케이션의 종속성을 설치하는 단계와 소스 코드를 복사하는 단계가 있다면, 

종속성 설치 단계를 먼저 위치시키고 소스 코드 복사 단계를 그 이후에 위치시키는 것이 좋습니다. 

그러면 종속성 변경이 없는 경우에는 캐싱을 효율적으로 활용할 수 있습니다.

 

도커 컴포즈의 목적

다중 컨테이너 관리: docker-compose는 여러 서비스로 구성된 애플리케이션을

각 서비스의 이미지, 환경 변수, 포트, 볼륨 등을 설정

의존성 관리: 서비스 간의 의존 관계를 정의

예를 들어, 백엔드 서비스가 데이터베이스 서비스를 필요로 할 경우,

docker-compose를 사용하여 백엔드가 데이터베이스가 준비된 후에 시작

일관된 환경: 개발, 테스팅, 스테이징, 프로덕션 등 다양한 환경에서

동일한 docker-compose.yml 파일을 사용하여 애플리케이션을 실행


한 번의 명령으로 서비스 관리: 

docker-compose up 명령을 사용하여 모든 서비스를 시작하거나 

docker-compose down 명령을 사용하여 모든 서비스를 종료

이미지 다운로드: docker-compose를 사용하면 docker-compose.yml에 정의된 모든 이미지를 

한 번의 명령으로 다운로드 (docker-compose pull). 

 

배포 순서: ci.yml build -> docker-compose.yml -> cd.yml deploy

 

 

반응형

' > docker' 카테고리의 다른 글

[Docker] 도커로 스프링 프로젝트 배포하기  (0) 2022.10.18
[Docker] 도커 기초  (0) 2022.08.13
반응형

 

Blocking / Non Blocking Synchronous / Asynchronous

 

 

Blocking / Non Blocking

  • Blocking / Non Blocking은 호출되는 함수가 바로 제어권을 리턴 하느냐 마느냐 가 관심사
    • 제어권을 리턴하지 않으면 Blocking
    • 제어권을 리턴하면 NonBlocking
    제어권이 누구한테 있는지?

Synchronous / Asynchronous

  • Synchronous / Asynchronous는 호출되는 함수의 작업 완료 여부를 누가 신경 쓰냐 가 관심사
    • 호출되는 함수의 작업 완료를 호출한 함수가 확인하면 Synchronous (동기)
    • 호출되는 함수의 작업 완료를 호출된 함수가 확인하면 Asynchronous (비동기)
    호출한 함수의 완료 여부를 누가 확인하는지?
  • 비동기 방식은 멀티 스레드 , 단일 스레드?요점은 비동기 방식은 스레드를 효율적으로 운용하여 반응성을 향상시키는 것비동기 방식 DB I/O가 발생하면 DB I/O는 다른 스레드에게 넘기고, 나머지 작업을 수행
  • 기존 방식 DB I/O가 발생하면 다른 작업을 수행하지 않고, DB데이터가 올 때까지 기다림.
  • 단일 스레드에서도 가능하다고는 하는데, 사실은 다른 프로세스의 스레드를 사용한다.
    다른 스레드 또는 다른 프로세스의 스레드를 사용하므로 결국, 멀티 스레드가 맞다.
  • 동기?
    **작업의 완료 확인 방식에서의 동기(synchronous):** 
    작업을 호출한 쪽이 결과가 반환될 때까지 기다린다는 의미입니다. 
    비동기(asynchronous)는 작업의 완료를 기다리지 않고 다른 작업을 수행하며, 
    작업의 결과는 나중에 돌려받습니다.(콜백, 이벤트, 프로미스 등을 통해).
    
    **쓰레딩과 Race Condition 문맥에서의 동기(synchronization)**: 
    쓰레드 간의 실행 순서나 자원 접근을 조절하는 방식을 의미합니다. 
    즉, 어떤 일련의 작업이 '동기화' 되어 있다면, 그 작업들은 미리 정의된 어떠한 
    순서에 따라 실행되어야 하며, 이를 통해 레이스 컨디션을 방지할 수 있습니다.
    
    **Synchronous (동기):** 시간에 대한 것
    동기 방식은 하나의 작업이 완료되어야 다음 작업이 시작됩니다. 
    여기서는 `시간의 흐름`에 집중이 되어 있습니다.
    **-> 시간**
    
    **Synchronization (동기화):** 상태나 데이터의 일관성 
    여러 프로세스나 쓰레드가 동시에 어떠한 자원을 접근할 때 그 순서와 방식을 
    조절하여 `데이터의 일관성`을 유지하는 것을 의미합니다.
    **-> 상태**
    
  • → 동기가 헷갈리는 이유

 

 

결론

  1. Blocking + Synchronous: 요청을 보내고 응답을 받을 때까지 아무 것도 하지 않는 상태로 대기.이렇게 되면 시스템의 효율성이 떨어짐 긴 대기 시간 동안 다른 중요한 작업을 처리할 수 없게 된다.
  2. Non-Blocking + Asynchronous: 요청을 보내고 바로 다른 작업을 계속 진행. 응답이 오면 해당 응답을 처리하는 콜백 함수나 이벤트 리스너를 통해 처리함 이 방식은 시스템의 효율성, 반응성을 크게 향상 시킬 수 있다.

→ 결국, 작업 시간이 긴 여러 DB I/O, 네트워크 처리는 비동기 방식이 좋다는 내용 👍

 

 


 

예시

프론트

왜 Javascript에서 axios 호출을 비동기식이라고 하는지?

Web APIs Ajax 요청, setTimeout(), 이벤트 핸들러의 등록과 같이 웹 브라우저에서 제공하는 기능들을 말한다.

이러한 요청들의 처리가 JavaScript 엔진의 쓰레드와는 다른 쓰레드들에서 이뤄진다는 점이다.

JavaScript 엔진의 스택에서 실행된 비동기 함수가 요청하는 비동기 작업에 대한 정보와 콜백 함수를 웹 API를 통해

브라우저에게 넘기면, 브라우저는 이러한 요청들을 별도의 쓰레드에 위임 해당 요청이 완료되는 순간 콜백 함수를 JavaScript 엔진의 태스크 큐라는 곳에 집어넣는다.

→ JS가 비동기 함수를 실행하면, JS는 이 함수들에 대해 신경 쓰지 않는다. (비동기)

호출된 함수는 브라우저가 신경 쓰며, 작업을 완료하면 JS에게 Call Back 을 준다.

async function fetchExternalData() {
  const response = await fetch('<http://external-service/data>');
  const data = await response.json();

  // 위 요청-응답을 비동기로 브라우저에게 넘겨 처리하고, 아래 작업을 바로 실행함
  otherWork();

  return data;
}

 

 

백엔드

**WebFlux**는 Spring 5에서 도입된 반응형 프로그래밍 모델로, Non-Blocking + Asynchronous 방식 WebFlux는 Project Reactor를 기반으로 하며, **Mono**와 **Flux**라는 반응형 스트림 타입을 제공

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import reactor.core.publisher.Mono;

@Service
public class ExternalService {

    @Autowired
    private WebClient webClient;

    public Mono<String> fetchExternalData() {
        String data = webClient.get()
                        .uri("/data")
                        .retrieve()
                        .bodyToMono(String.class);

				// 위 요청-응답을 비동기로 처리하고, 아래 작업을 바로 실행함
				otherWork();
    }
}

 

 


 

 

! 질문

비동기 방식이 무엇인가?

→ DB I/O, 네트워크 작업 등의 시간 소요가 큰 작업의 완료를 기다리지 않고, 해당 작업 완료 시 콜백을 통해 결과를 받아오면서 동시에 다른 작업을 계속 수행하는 방식을 의미합니다.

WebFlux, axios를 추가로 설명하면 👍

 

(네트워크에서) 동기 방식이 무엇인가?

→ 여러 개의 요청-응답을 순차적으로 처리하여, 실행하는 것입니다.

반응형

'CS > 네트워크' 카테고리의 다른 글

[TCP/IP MODEL]  (0) 2022.08.31
반응형

JPA

https://jsonobject.tistory.com/605

 

Jackson 라이브러리는 기본적으로 엔티티의 Getter 메서드를 사용하여 JSON을 생성합니다.

따라서 Getter 메서드가 있고, 그 메서드가 연관 엔티티에 대한 접근을 포함하고 있다면,

Jackson은 해당 메서드를 호출하게 됩니다.

 

FetchType.LAZY로 설정된 연관 엔티티가 있을 경우,

Jackson이 해당 연관 엔티티의 Getter 메서드를 호출하면, JPA는 연관된 엔티티를 로드하려고 시도합니다.

만약 이 시점에서 JPA 세션이 닫혀 있으면 **LazyInitializationException**이 발생하게 됩니다.

 

해결방법

→ 연관엔티티가 있다면 Jackson 라이브러리가 Getter를 동작시키기 때문에 DTO를 사용해줘야 함

 

Jackson의 @JsonIgnore 어노테이션 사용: LAZY로 설정된 연관 엔티티의 Getter 메서드에

@JsonIgnore 어노테이션을 추가하여 Jackson이 해당 메서드를 호출하는 것을 방지합니다.

근데 결국, 연관 엔티티를 보내야 되는 경우가 있을 것이고, 그냥 DTO를 사용하는 방법이 제일 좋다.

반응형
반응형

React

렌더링 방식

  1. 내부 상태값(state)이나 중앙 상태값(redux store 등)이 변경되는 경우
  2. 부모 컴포넌트가 재렌더링되는 경우, 자식 컴포넌트도 재렌더링.

React 컴포넌트에서 return nul*을 사용하면 해당 컴포넌트는 아무것도 렌더링되지 않습니다.
이 방식을 사용하면 조건에 따라 특정 컴포넌트를 출력하지 않도록 만들 수 있습니다.

useEffect(() => {
        // 데이터를 비동기적으로 가져옵니다.
        fetchDataFromAPI().then(responseData => {
            setData(responseData);
        });
    }, []);

    // 데이터가 없을 경우 아무것도 렌더링하지 않습니다.
    if (!data) {
        return null;
    }

 

렌더링 순서

  1. 컴포넌트 함수 호출
  2. 컴포넌트 본문 호출
  3. useEffect

→ 컴포넌트 함수가 가장 먼저 호출되므로 if(!data) return loading 같은 걸로 처리

if (!data) {
    return <div>Loading...</div>;
}

 

useState

상태를 업데이트하는 함수(set)는 상태값을 변경하고 컴포넌트를 재렌더링합니다.

useEffect 첫째, 컴포넌트가 마운트된 직후에 코드를 실행하고, 둘째, 특정 상태가 변경될 때 코드를 실행합니다.

useState → 컴포넌트 렌더링 → useEffect

 

 


 

useEffect

 

  • 의존성 배열이 없는 경우 (useEffect에 두 번째 매개 변수가 전혀 주어지지 않은 경우),
    useEffect 내의 코드는 컴포넌트가 렌더링 될 때마다 실행됩니다. (무조건 실행)
  • 빈 의존성 배열이 있는 경우 (useEffect(fn, [])), useEffect 내의 코드는 컴포넌트가 처음 마운트될 때
    한 번만 실행되고, 그 후에는 실행되지 않습니다. (딱 한번)
  • 의존성 배열이 있는 경우 (useEffect(fn, [dep1, dep2, ...])), useEffect 내의 코드는 컴포넌트가
    처음 마운트될 때와 의존성 배열의 어떤 값이 변경될 때마다 실행됩니다. (감지 대상)
import React, { useState, useEffect } from 'react';

function Example() {
  const [count, setCount] = useState(0);

  useEffect(() => {
    document.title = `You clicked ${count} times`;

    // Cleanup function
    return () => {
      document.title = 'React App';
    };
  }, [count]); // 의존성 배열에 "count"를 추가합니다.

  return (
    <div>
      <p>You clicked {count} times</p>
      <button onClick={() => setCount(count + 1)}>
        Click me
      </button>
    </div>
  );
}

사용자 액션 → count useState변경 → 컴포넌트 리로딩

 

→ count 때문에 리로딩되어서 useEffect 동작

의존성 배열에 추가된 state에 의해 리로딩 된게 아니라면 useEffect가 동작하지 않는다.
의존성 배열이 없다면 첫 마운트(사이트 진입) 때에만 useEffect가 동작한다.

 

예를 들어, 서버에서 데이터를 비동기적으로 가져오는 경우에는 useEffect를 사용하여 데이터를 가져올 수 있습니다. 이 데이터를 컴포넌트의 상태에 저장하려면 useState를 사용해야 합니다.
그러면 상태가 업데이트되고 컴포넌트가 재렌더링되므로, 사용자는 최신의 데이터를 볼 수 있게 됩니다.

 

 

useEffect는 다음과 같은 다양한 경우에 사용될 수 있습니다:

  1. 비동기 작업: 서버로부터 데이터를 가져오는 등의 비동기 작업을 수행할 때 useEffect를 사용할 수 있습니다.
  2. 이벤트 리스너 설정: window의 resize 이벤트나 키보드 keydown 이벤트 등을 감지하려면 이벤트 리스너를 설정해야 하는데, 이런 작업도 useEffect에서 수행합니다.
  3. DOM 조작: React는 일반적으로 DOM을 직접 조작하지 않고, 상태 업데이트를 통해 간접적으로 DOM을 업데이트하는 것을 선호합니다. 그러나 특정 라이브러리를 사용하거나, 특정 DOM API를 직접적으로 사용해야 하는 경우에는 useEffect를 사용하여 이를 수행할 수 있습니다.
  4. 정리(cleanup) 작업: 컴포넌트가 언마운트되거나, 의존성이 변경될 때 정리 작업을 수행해야 하는 경우에도 useEffect를 사용할 수 있습니다. 예를 들어, 컴포넌트가 언마운트되면 이벤트 리스너를 제거해야 하는데, 이런 작업은 useEffect의 반환 함수에서 수행합니다.

따라서 useEffect는 비동기 작업뿐만 아니라 여러 가지 다른 사이드 이펙트를 처리하는 데 사용됩니다. 비동기 작업은 useEffect를 사용하는 주요한 경우 중 하나일 뿐입니다.

사이드 이펙트(side effect)는 프로그래밍 용어로, 함수나 컴포넌트의 주요 기능 외에 발생하는 부수적인 효과를 의미합니다. 이는 전역 상태를 변경하거나, 외부 네트워크와 통신하거나, DOM을 직접 조작하는 것과 같은 작업을 포함할 수 있습니다.

React에서 사이드 이펙트는 아래와 같은 상황들을 가리킵니다:

  1. 네트워크 요청: 서버에 데이터를 요청하거나, 서버로 데이터를 보내는 등의 네트워크 요청이 사이드 이펙트입니다. 이러한 요청은 종종 비동기적으로 수행됩니다.
  2. 구독(subscriptions): 외부 데이터 소스를 구독하는 것도 사이드 이펙트입니다. 예를 들어, WebSocket 연결을 통해 서버에서 실시간 업데이트를 받거나, 이벤트 리스너를 설정하여 사용자 입력을 감지하는 것이 이에 해당합니다.
  3. 타이머: setTimeout이나 setInterval과 같은 타이머를 설정하는 것도 사이드 이펙트입니다.
  4. 직접적인 DOM 조작: React는 일반적으로 DOM을 직접 조작하지 않고 상태 업데이트를 통해 간접적으로 DOM을 업데이트하는 것을 선호합니다. 그러나 특정 라이브러리를 사용하거나, 특정 DOM API를 직접적으로 사용해야 하는 경우에는 DOM을 직접 조작해야 할 수 있습니다.

 


 

REDUX

Store

state → 상태(데이터)

reducer → 액션(Action)을 수신, 상태(State)를 업데이트

getState → state를 가져와서 rendering

action → 상태를 갖고있는 그냥 메시지같은거

dispatch [dispatch(action)] → action으로 state변경, pub 작업을 한다.

subscribe[useSelector] → state가 변경되면, sub되어 state를 사용하는 컴포넌트들도 갱신 (화면)

      → useSelector를 사용하면 자동 subscribe함. 그래서 안씀

state는 reducers를 통해 변하고,

reducers에 action을 전달한다. action은 변화할 값을 갖고 있으며, dispatch가 action을 사용한다.

컴포넌트쪽에서는 노출된 것은 action이며, dispatch로 action을 전달한다. 한쪽에서 dispatch를 사용하면, 해당 state를 useSelector로 구독하는 모든 컴포넌트들은 상태변화를 감지한다.

// init

import React from 'react';
import ReactDOM from 'react-dom';
import { Provider } from 'react-redux';
import App from './App';
import store from './store';

ReactDOM.render(
  <Provider store={store}>
    <App />
  </Provider>,
  document.getElementById('root')
);
  1. 액션 함수를 만든다. (액션은 update data만 갖고 있음)
  2. dispatch()로 액션 함수를 전달한다.
  3. reducer에서 state, 액션함수를 인자로 받아서 변경하고 return
  4. 화면에 rendering 된다

 

ReduxTookit

reducer, action 함수를 같이 만듬

state도 slice안에서 관리함

들어온 param은 전부 payload라는 명칭을 사용

import { createSlice } from '@reduxjs/toolkit';

const chatSlice = createSlice({
  name: 'chat',
  initialState: { messages: [] },
  reducers: {
    addMessage: (state, action) => {
      state.messages.push(action.payload);
    },
    getMessage: (state, action) => {
      return state.messages.find(msg => msg.id === action.payload);
    },
    updateMessage: (state, action) => {
      const message = state.messages.find(msg => msg.id === action.payload.id);
      if (message) message.text = action.payload.text;
    },
    deleteMessage: (state, action) => {
      const index = state.messages.findIndex(msg => msg.id === action.payload);
      if (index !== -1) state.messages.splice(index, 1);
    },
  },
});

export const { addMessage, getMessage, updateMessage, deleteMessage } = chatSlice.actions;

export default chatSlice.reducer;

 

비동기 thunk

thunk를 만들고, slice → extraReducers에서 사용

pending, fulfilled, rejected 3가지 상태가 있다.

// chatSlice.js
import { createSlice, createAsyncThunk } from '@reduxjs/toolkit';
import axios from 'axios';

// 비동기 액션을 위한 Thunk
export const fetchMessages = createAsyncThunk(
  'chat/fetchMessages',
  async () => {
    const response = await axios.get('/api/messages');
    return response.data; // 이 값이 액션의 payload로 들어갑니다.
  }
);

const chatSlice = createSlice({
  name: 'chat',
  initialState: { messages: [] },
  reducers: {
    addMessage: (state, action) => {
      state.messages.push(action.payload);
    },
  },
  extraReducers: (builder) => {
    builder.addCase(fetchMessages.pending, (state) => {
        state.status = 'loading';
      })
      .addCase(fetchMessages.fulfilled, (state, action) => {
        state.status = 'succeeded';
        state.messages = action.payload;
      })
      .addCase(fetchMessages.rejected, (state, action) => {
        state.status = 'failed';
        state.error = action.error.message;
      });
  },
});

export const { addMessage } = chatSlice.actions;

export default chatSlice.reducer;

사용

import React, { useEffect } from 'react';
import { useSelector, useDispatch } from 'react-redux';
import { fetchMessages } from './chatSlice';

function Chat() {
  const dispatch = useDispatch();
  const messages = useSelector(state => state.chat.messages);

  useEffect(() => {
    dispatch(fetchMessages());
  }, [dispatch]);

  return (
    <div>
      {messages.map((message, index) => (
        <p key={index}>{message}</p>
      ))}
    </div>
  );
}

export default Chat;

 


설정

import { configureStore, combineReducers } from "@reduxjs/toolkit";
import webSocketSlice from "redux/webSocketSlice";
import chatSlice from "redux/chatSlice";
import chatRoomSlice from "redux/chatRoomSlice";

// rootReducer 생성
const rootReducer = combineReducers({
  webSocket: webSocketSlice.reducer,
  chat: chatSlice.reducer,
  chatRoom: chatRoomSlice.reducer,
});

export const store = configureStore({
  reducer: rootReducer,
});

보통 reducer만 내보낸다고함.

비동기 요청 createAsynThunk는 같은 slice 파일 내에 위치시키고

따로 export해서 사용하는 것 같다.

extraReducers는 사용하지말고, 그냥 createAsyncThunk로 모든것을 처리하자.

반응형
반응형

 

백엔드는 spring security, 프론트엔드는 Vue로 구성해서

로그인, 로그아웃, Post 요청 처리를 하는 도중

계속해서 cookie가 전달되지 않는 문제가 있었다.

 

요청마다 withCredential을 true로 설정해주면 되었는데,

또 다른 문제는 get 요청은 동작하는데 post 요청은 동작하지 않았다.

 

 

 

전역으로 설정하니 post 요청도 해결되었다.

 


 

 

 

알고보니 axios post 메서드에는 매개인자 순서가 있다.

 

post(url, data, config) 순서로 넣어주면 동작한다.

post(url, config) 으로 넣어서 동작하지 않았던 것

 

Axios api가 컴파일 에러, 런타임 에러가 나지않고 동작하여 이걸 눈치채지 못했다.

 

 

 

 


도움이 된 곳

 

https://yamoo9.github.io/axios/guide/api.html#http-%EB%A9%94%EC%84%9C%EB%93%9C-%EB%B3%84%EC%B9%AD

 

반응형

'프로그래밍 > Vue' 카테고리의 다른 글

[Vue] Vue 구조 파악해보기  (0) 2022.10.18
반응형

 


요청 시

 

토큰은 여러 종류가 있고, 헤더에 토큰 종류를 명시한다.

Authorization : Basic xxxx    - id, pass를 base64 인코딩

Authorization : Bearer xxxxx    - 보통 JWT를 사용할 경우

 

 

 

 

 


보안 설정

 

 

 

 - 서버 설정

Access-Control-Allow-Credentials : true

로 설정하게 되면,

Access-Control-Allow-Origin

를 "*" 로 할 수 없다.

 

모든 Origin의 Credentials를 허가하는 것은 위험하기 때문

Pattern, whiteList을 이용하거나, 클라이언트 Origin을 명시해서 적어야한다.

 

 

 - 브라우저 클라이언트 설정

withCredential : true

 

 

 

 

 

 

 

 

 

 

 

 

 

 

반응형
반응형

 

 

서버에서 쿠키를 만든 후, Vue에서 응답 쿠키를 확인하려고 했지만

Vue를 사용하는 브라우저에 쿠키가 들어오지 않았다.

 

 

 

이유는 서버에서 쿠키는 HttpOnlytrue로 브라우저에게 전송한다.

HttpOnly로 설정된 쿠키는 브라우저에서 실행하는 JS로 "직접 접근"할 수가 없다.

 

 

XSS공격으로 자바스크립트를 이용한 쿠키 탈취를 할 수 있기 때문에 막아둔 것.

 - 브라우저의 저장된 쿠키를 사용자가 모르게 실행시켜서, 해커 자신의 서버로 전송할 수 없도록 한다.

 

 

쿠키에는 Secure 설정도 해줄 수 있는데,

Secure true 쿠키는 https를 이용해야만 서버로 전달해줄 수 있다.

응답으로 브라우저에 저장은 가능하다.

 

 


 

해결

 

 

{ withCredentials: true } 로 설정하면 브라우저에 쿠키가 전달된다.

서버에서도 Credentials 설정을 true로 해줘야 한다.

브라우저가 쿠키 저장, 전송 처리를 허가해준다.

 

 

withCredentials

Same Origin일 경우 Request, Response 에 쿠키가 전달되지만,

Cross Origin일 경우 쿠키가 전달되지 않는다. Cross Origin의 기본은 쿠키 전송이 false이다.

true 값을 주어 전달되도록 설정해주어야한다.

인증과 인가에 사용되는 쿠키와 토큰 등을 공유하겠다는 의미

 

추가적으로, 헤더 authorization 요청을 보낼때도 허가해줘야 한다.

 

 

 

 

처리 이후 브라우저에 쿠키가 브라우저에 저장된 것을 볼 수 있었다.

 

 

 

 

 

 

 

 

 


 

 

 

 

 

 

반응형

'프로그래밍 > Security' 카테고리의 다른 글

웹 요청 정리  (0) 2022.10.20
[Web 보안] XSS, CSRF  (0) 2022.08.25
[Spring Security] 로그인 구현  (0) 2022.08.24
[인증과 인가] 쿠키와 세션, JWT  (0) 2022.06.23
반응형

Vue 설치

 

npm install -g vue

npm install -g @vue/cli

 

-g global

cli 를 사용해서 환경 설정을 통일화.

 

 

Vue 프로젝트 생성

vue create 프로젝트명

 

 

Vue 프로젝트 실행

cd 프로젝트 내부 폴더로 이동

npm run serve

 

 


 

package.json 

의존 모듈 관리

 

node_modules 

적용된 모듈들을 볼 수 있다.

 


초기 설정 살펴보기

 

 

main.js

Vue를 실행하면 가장 먼저 main.js 가 실행된다.

App.vue에 index.html 에 있는 div id="app"을 마운트 시킨다.

 

전역 설정은 이곳에 추가한다.

 


 

App.vue

라우터가 포함되어있다.

 

전역으로 사용되는 template, style을 이곳에서 관리한다.

 

 


 

 

#app은 최상위 div가 된다.

 

 


ROUTER

 

SPA는 라우터를 통해 single page로 하위 부분만 교체하므로, 사용자 경험을 향상시킨다.

 

 

<router-view/> 부분이 교체된다.

 

 

 

 

router/index.js 에서 라우터 설정을 해줄 수 있다.

path경로와 view.vue 를 매핑

 

import를 상단에 할 경우, 바로 loading,

component에서 import할 경우, 해당 path에 접근해야 import되는 lazy loading 방식으로 동작한다.

 

 

 


VIEWS

 

UI

 

화면에 보일 라우터에 의해 교체되는 template 

사용할 components를 지정해서 사용한다.

해당 view에서만 사용되는 script를 직접 작성할 수도 있다.

 

 

 


Components

 

UI + 재사용할 기능

 

화면에 보일 template + 재사용할 js  + 재사용할 css 

화면뿐만 아니라 js, css 를 여러 VIEW에서 사용할 수 있도록 component화한 것이다.

 

 

 

 

 


 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

반응형

'프로그래밍 > Vue' 카테고리의 다른 글

[Vue] Axios post cookie null  (0) 2022.10.21

+ Recent posts