Skip to main content Link Search Menu Expand Document (external link)

23.03.30

  • 클라이언트와 서버간의 통신을 상태를 유지해야하는가?
  • socketio로 채팅 서비스를 구현하고 있다.
  • 채팅 서비스에서 필요한 유저와 채널의 상태를 서버에 저장하고 있다.
  • 클라이언트의 상태를 서버에 보존하는 방식은 문제점을 가지고 있다.
  • 해당 서버가 멈추거나 이상이 생겨서 다른 서버를 사용해야할 때 문제가 발생한다.
  • 새로운 서버에는 이전 서버에서 가지고 있는 상태값이 없기 때문이다.
  • 그래서 이러한 클라이언트의 상태 데이터를 캐시 서버에 저장한다. (Redis)
  • 서버에서 클라이언트의 상태를 저장하지 않으면 서버는 단순히 요청에 응답하는 역할을 수행한다.
  • 이로 인해 다른 서버를 사용하여도 같은 응답을 받게되어 서버 확장을 수월하게 한다.

  • 서비스가 커질수록 테스트하기 어려워진다.
  • 채팅 서비스를 구현하다가 메세지 보내기를 테스트하려면 여러 과정을 거쳐야한다.
  • 사용자 인증, 토큰 발급, 유저 생성, 채팅방 생성, 다른 유저 초대하는 과정을 거쳐야 메세지를 보낼 수 있다.
  • 그리고 추가로 서버를 빌드하는 시간까지 포함한다면 개발 시간이 비약적으로 늘어난다.
  • 비효율적으로 사용되는 시간을 줄이기 위해서 테스트 케이스를 작성해야 한다.

23.03.28

user 재접속시 socket 관리

  • 어떤 유저가 나가는 경우
    • 로그아웃
    • 채팅방 나가기
    • 브라우저 종료
    • 회원 탈퇴
  • 어느 채팅방에 유저가 있는지 알아야 한다.
  • 유저가 재접속하면 다른 socket으로 통신한다.
  • user객체에서 socket만 변경하여 사용한다.

user 식별 방법

  • REST API를 구현할 떄는 유저를 db 상의 id로 구별했다.
  • socket에는 id가 주어진다.
  • rest와 socket을 함께 사용하고 있으므로 한가지로 통일해야 한다.
  • db의 userId와 username을 socket에서도 사용해야 한다.

23.03.27

gateway와 service의 의존성 문제

  • gateway에서 로직을 service로 분리하려한다.
  • 채널에 메세지를 보내려면 Server객체가 필요하다.
  • @WebSocketServer 데코레이터는 @WebSocketGateway 로 정의된 클래스 안에서 정의된다.
  • service에서 server를 사용하려면 gateway를 연결해야 한다.
  • 이미 gateway가 service를 의존하고 있으므로 service가 gateway를 의존하면 에러가 발생된다.
  • gateway에는 afterInit()이 있다.
  • this.chatService.server = this.server;를 추가하여 해결

Socket 객체

  • socket event 부분을 gateway와 service로 나눈다.
  • chatGateway, chatService
  • chat.controller를 만들어서 chatService를 주입한다.
  • REST API에서는 Socket 객체를 받을 수 없다.
  • chatService에서 Socket 객체를 관리한다.
  • user의 nickname으로 Socket 객체를 가져온다.

23.03.05

NestJS 데코레이터

데코레이터를 사용하면 횡단 관심사를 분리하여 관점 지향 프로그래밍을 적용한 코드를 작성할 수 있다. 데코레이터는 클래스, 메서드, 접근자, 프로퍼티, 매개변수에 적용가능하다. 각 요소의 선언부 앞에 데코레이터를 선언하면 데코레이터로 구현된 코드를 함께 실행한다.

데코레이터는 @expression 형식으로 사용한다. expression은 데코레이팅 된 선언에 대한 정보와 함께 런타임에 호출되는 함수이어야 한다.

데코레이터 함수에는 세가지 인자가 전달된다.

  1. target : 현재 인스턴스 객체의 클래스
  2. propertyKey : 데코레이터를 적용할 속성 이름
  3. descriptor : 해당 속성의 설명자 객체
1
2
3
4
5
6
7
8
9
10
11
12
13
function deco(target: any, propertyKey: string, descriptor: PropertyDescriptor) {
  console.log('데코레이터가 평가됨');
}

class TestClass {
  @deco
  test() {
    console.log('함수 호출됨');
  }
}

const t = new TestClass();
t.test();

데코레이터 팩토리

만약에 데코레이터에 인자를 넘겨서 동작을 변경하고 싶다면 데코레이터 팩토리를 만들면 된다. 아래의 예시는 value라는 인자를 받는다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
function deco(value: string) {
  console.log('데코레이터가 평가됨');
  return function (target: any, propertykey: string, descriptor: PropertyDescriptor) {
   console.log(value); 
  }
}

class TestClass {
  @deco('HELLO')
  test() {
    console.log('함수 호출됨');
  }
}

/*

데코레이터가 평가됨
HELLO
함수 호출됨

*/

데코레이터 합성

여러개의 데코레이터를 사용한다면 다음 단계가 수행된다.

  1. 각 데코레이터의 표현은 위에서 아래로 평가
  2. 그 다음에 결과는 아래에서 위로 호출
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
function first() {
  console.log("first(): factory evaluated");
  return function (target: any, propertyKey: string, descriptor: PropertyDescriptor) {
    console.log("first(): called");
  }
}

function second() {
  console.log("second(): factory evaluated");
  return function (target: any, propertyKey: string, descriptor: PropertyDescriptor) {
    console.log("second(): called");
  }
}

class ExampleClass {
  @first()
  @second()
  method() {
    console.log('method is called');
  }
}

/*

first(): factory evaluated
second(): factory evaluated
second(): called
first(): called
method is called

*/

Class 데코레이터

클래스 데코레이터는 클래스의 생성자에 적용되어 클래스 정의를 읽거나 수정할 수 있다. 선언 파일이나 선언 클래스내에서는 사용할 수 없다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
// 클래스 데코레이터는 생성자를 반환하는 함수이어야 한다.
functionn reportableClass<T extends { new ( ... args: any[]): {} }>(constructor: T) {
  return class extends constructor {
    reportingURL = 'http://www.example.com';
  }
}

@reportableClass
class BugReport {
  type = 'report';
  title: string;
  contructor(t: string) {
    this.title = t;
  }
}

const bug = new BugReport('Needs dark mode');
console.log(bug);

/*

BugReport {
  type: 'report',
  title: 'Needs dark mode',
  reportingURL: 'http://www.example.com'
}

*/

Method 데코레이터

메서드 바로 앞에 선언되어 속성 디스크립터에 적용되고 메서드의 정의를 읽거나 수정할 수 있다. 선언 파일, 오버로드 메서드, 선언 클래스에 사용할 수 없다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
function HandleError() {
  return function (target: any, propertyKey: string, descriptor: PropertyDescriptor) {
    const method = descriptor.value;
    descriptor.value = function() {
      try {
        method();
      } catch(e) {
        console.log(e);
      }
    }
  }
}

class Greeter {
  @HandleError()
  hello() {
    throw new Error('텍스트 에러');
  }
}

const methodDeco = new Greeter();
methodDeco.hello();

/*

{}
hello
{
  value: function,
  writable: true,
  ...
}
Error: 테스트 에러

*/

NestJS - module

App module은 root module과 같다. provider와 controller를 추가하여 module를 구성하고 App module에 import하여 사용한다.

Controller나 Provider에 배열을 입력할 때 순서를 신경써야한다. 예를 들어, /todos/todos/name URL가 있는 경우에 /todos/name을 담당하는 Controller를 먼저 작성해야 한다. 클라이언트의 요청은 배열에 작성된 순서대로 컨트롤러가 처리된다.

NestJS - controller

express의 라우터처럼 URL을 가져오고 함수를 실행한다. URL을 매핑하고, 요청을 받고, 쿼리를 넘기는 역할을 한다. 비즈니스 로직은 provider에서 구현된다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
@Controller('movies')
export class MoviesController {
  @Get()
  getAll() {
    return 'movies';
  }
  @Get('/:id')
  getOne(@Param('id') movieId: string) {
    return `one movie id: ${movieId}`;
  }
  @Post()
  create() {
    return 'This will create a movie';
  }
  @Delete('/:id')
  remove(@Param('id') movieId: string) {
    return `this will delete a movie with the id : ${movieId}`;
  }
  @Patch('/:id')
  path(@Param('id') movieId: string) {
    return `this will patch a movie with the id : ${movieId}`;
  }
}

NestJS - Provider

종속성으로 주입할 수 있다. controller가 HTTP 요청을 처리하고 더 복잡한 적업을 Provider를 위임해야 한다. HTTP요청을 처리하여 필요한 Service를 호출한다.

모든 Provider들 위에 반드시 선언되어야하는 데코레이터이다. 이 데코레이터가 있어야 Modele을 통해 Inject된다.

23.03.04

passport-oauth2-refresh

https://github.com/fiznool/passport-oauth2-refresh

23.03.03

Refreshing Access Tokens

https://www.oauth.com/oauth2-servers/access-tokens/refreshing-access-tokens/

  • Request Parameters
    • grant_type(required) : “refresh_token”
    • refresh_token(required) : 이전에 받은 refresh token
    • scope (optional) : 원래 액세스 토큰에서 발급되지 않은 추가 범위를 추가하지 말아야 한다. 생략된 경우에는 이전에 발급된 것과 동일한 범위의 액세스 토큰이 발급된다.
    • Client Authenticaiton (만약에 secret을 발급했다면 required이다)

OAuth와 JWT

OAuth와 JWT는 둘 다 인증 및 권한 부여를 위한 프로토콜 및 토큰이지만 목적과 동작 방식에 차이가 있다.

  • OAuth : 사용자가 서드파티 애플리케이션에 대한 접근 권한을 제어하는 프로토콜
  • JWT : JSON Web Token의 약자로, 서비스 간에 정보를 안전하게 전송하기 위한 토큰

따라서, OAuth는 사용자가 서드파티 애플리케이션에 대한 접근 권한을 관리하는 반면에, JWT는 서비스 간에 정보를 안전하게 전송하고 인증 및 권한 부여를 처리한다.

23.03.02

OAuth

OAuth는 인터넷 사용자들이 비밀번호를 제공하지 않고 다른 웹사이트 상의 자신들의 정보에 대해 웹사이트나 애플리케이션의 접근 권한을 부여할 수 있는 공통적인 수단으로서 사용되는 개방형 표준이다. 예를 들어, 앱에서 사용자 인증을 위해 Google이나 Facebook에서 제공하는 사용자 인증 방식을 사용할 수 있다. 그리고 OAuth를 바탕으로 앱은 외부 서비스(Google, Facebook 등)의 특정 자원에 접근하고 사용할 수 있는 권한을 인가받는다.

OAuth 참여자

  • Resource Server : Client가 제어하고자 하는 자원을 보유하고 있는 서버
  • Resource Owner : 자원의 소유자
  • Client : Resource Server에 접속해서 정보를 가져오고자 하는 클라이언트

OAuth Flow

  1. Client 등록

Client가 Resource Server를 이용하기 위해서는 자신의 서비스를 등록하여 승인을 받아야 한다. 등록 절차를 통해 세가지 정보를 부여받는다.

  • Client ID : 클라이언트 웹 앱을 구별할 수 있는 식별자
  • Client Secret : ClientID에 대한 비밀키
  • Authorized redirect URL : Authorization Code를 전달받을 리다이렉트 주소

Client가 인증을 마치면 명시된 url로 리다이렉트시킨다. 이때 Query String으로 Code가 함께 전달된다. Client는 이 Code와 Client ID, Client Secret을 Resource Server에 보내서 자원을 사용할 수 있는 Access Token을 발급받는다.

  1. Resource Owner의 승인

Resource Owner는 로그인을 위해 특정 주소로 연결되는 소셜 로그인 버튼을 클릭한다. Resource Owner는 Resource Server에 접속하여 로그인을 수행한다. 로그인이 완료되면 Resource Server는 Query String으로 넘어온 파라미터들을 통해 Client를 검사한다. 검증이 마무리되면 권한 부여를 승인받은 후에 Resource Server는 Resource Owner에게 Client의 접근을 승인하게 된다.

  1. Resource Server의 승인

Resource Owner의 승인이 마무리 되면 명시된 Redirect URL로 클라이언트를 리다이렉트 시킨다. 이 때 Resource Server는 Client가 자신의 자원을 사용할 수 있는 Access Token을 발급하기 전에, 임시 암호인 Authorization Code를 함께 발급한다.

Query String으로 들어온 code가 바로 Authorization Code이다.

Client는 ID와 비밀키 및 code를 Resource Owner를 거치지 않고 Resource Server에 직접 전달한다. Resource Server는 정보를 검사한 다음, 유효한 요청이라면 Access Token을 발급하게 된다.

Client는 해당 토큰을 서버에 저장해두고, Resource Server의 자원을 사용하기 위한 API 호출시 해당 토큰을 헤더에 담아 보낸다.

  1. API 호출

23.03.01

Signalling Server

피어를 다음과 같은 순서로 연결할 수 있다.

  1. 브라우저에서 미디어 스트림을 받는다.
  2. 스트림을 넣어준다.
  3. local sdp를 설정한다.
  4. 연결할 Peer에 Offer을 제안한다.

연결할 Peer에서는 offer을 받으면

  1. remote sdp를 설정한다
  2. 브라우저 미디어 스트림을 받는다.
  3. 스트림을 넣어준다.
  4. local sdp 설정
  5. create answer 후 answer을 보낸다.
  6. 받는 Peer에서는 remote sdp를 설정한다.

create-answer 과정이 끝나면 icecandidate로 네트워크 정보를 교환한다.

  1. 요청자에서 candidate를 보낸다.
  2. 연결할 peer에서 받은 정보를 저장하고 자신의 candidate를 보내고
  3. 받는 쪽에서 해당 candidate를 저장한다.